#!/usr/bin/env node
/**
* Oceanir Memory MCP Server
*
* Provides persistent memory for AI agents via MCP.
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { getStore } from './store.js';
const store = getStore();
const server = new Server(
{
name: 'oceanir-memory',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Define tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'remember',
description: 'Store something in memory. Use for user preferences, project patterns, solutions, concepts.',
inputSchema: {
type: 'object',
properties: {
name: { type: 'string', description: 'Short name for the memory' },
type: {
type: 'string',
enum: ['person', 'project', 'file', 'concept', 'preference', 'pattern', 'error', 'solution'],
description: 'Type of memory',
},
content: { type: 'string', description: 'The content to remember' },
},
required: ['name', 'type', 'content'],
},
},
{
name: 'recall',
description: 'Search memories. Returns relevant stored information.',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'What to search for' },
limit: { type: 'number', description: 'Max results (default: 5)' },
},
required: ['query'],
},
},
{
name: 'observe',
description: 'Add an observation to an existing memory entity.',
inputSchema: {
type: 'object',
properties: {
entityId: { type: 'string', description: 'ID of the entity' },
observation: { type: 'string', description: 'The observation to add' },
},
required: ['entityId', 'observation'],
},
},
{
name: 'relate',
description: 'Create a relationship between two memory entities.',
inputSchema: {
type: 'object',
properties: {
fromId: { type: 'string', description: 'Source entity ID' },
toId: { type: 'string', description: 'Target entity ID' },
type: { type: 'string', description: 'Relationship type (e.g., uses, prefers, solved, related_to)' },
},
required: ['fromId', 'toId', 'type'],
},
},
{
name: 'get_preferences',
description: 'Get all stored user preferences.',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'get_patterns',
description: 'Get all stored code patterns and solutions.',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'memory_stats',
description: 'Get memory statistics.',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'forget',
description: 'Delete a memory entity.',
inputSchema: {
type: 'object',
properties: {
entityId: { type: 'string', description: 'ID of the entity to delete' },
},
required: ['entityId'],
},
},
],
};
});
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'remember': {
const { name: entityName, type, content } = args as { name: string; type: any; content: string };
const entity = store.createEntity({ name: entityName, type, content });
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
message: `Remembered "${entityName}" as ${type}`,
id: entity.id,
}, null, 2),
}],
};
}
case 'recall': {
const { query, limit = 5 } = args as { query: string; limit?: number };
const results = store.search(query, limit);
return {
content: [{
type: 'text',
text: JSON.stringify({
query,
count: results.length,
results: results.map(r => ({
id: r.entity.id,
name: r.entity.name,
type: r.entity.type,
content: r.entity.content,
score: r.score,
observations: r.observations.map(o => o.content),
})),
}, null, 2),
}],
};
}
case 'observe': {
const { entityId, observation } = args as { entityId: string; observation: string };
const obs = store.addObservation(entityId, observation);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
message: 'Observation added',
id: obs.id,
}, null, 2),
}],
};
}
case 'relate': {
const { fromId, toId, type } = args as { fromId: string; toId: string; type: string };
const relation = store.createRelation(fromId, toId, type);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
message: `Created ${type} relation`,
id: relation.id,
}, null, 2),
}],
};
}
case 'get_preferences': {
const prefs = store.getByType('preference');
return {
content: [{
type: 'text',
text: JSON.stringify({
count: prefs.length,
preferences: prefs.map(p => ({
id: p.id,
name: p.name,
content: p.content,
})),
}, null, 2),
}],
};
}
case 'get_patterns': {
const patterns = store.getByType('pattern');
const solutions = store.getByType('solution');
return {
content: [{
type: 'text',
text: JSON.stringify({
patterns: patterns.map(p => ({ id: p.id, name: p.name, content: p.content })),
solutions: solutions.map(s => ({ id: s.id, name: s.name, content: s.content })),
}, null, 2),
}],
};
}
case 'memory_stats': {
const stats = store.stats();
return {
content: [{
type: 'text',
text: JSON.stringify({
...stats,
message: `Memory contains ${stats.entities} entities, ${stats.relations} relations, ${stats.observations} observations`,
}, null, 2),
}],
};
}
case 'forget': {
const { entityId } = args as { entityId: string };
const deleted = store.deleteEntity(entityId);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: deleted,
message: deleted ? 'Entity forgotten' : 'Entity not found',
}, null, 2),
}],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
content: [{
type: 'text',
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
}],
isError: true,
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Oceanir Memory MCP server running');
}
main().catch(console.error);