fix(memory): convert to modern TypeScript SDK APIs (#3015)
* fix(memory): convert to modern TypeScript SDK APIs Convert the memory server to use the modern McpServer API instead of the low-level Server API. Key changes: - Replace Server with McpServer from @modelcontextprotocol/sdk/server/mcp.js - Convert all 9 tools to use registerTool() instead of manual request handlers - Create reusable Zod schemas for Entity and Relation types - Use Zod schemas directly in inputSchema/outputSchema - Add structuredContent to all tool responses - Fix type literals to use 'as const' assertions The modern API provides: - Less boilerplate code (removed ~200 lines of schema definitions) - Better type safety with Zod - More declarative tool registration - Cleaner, more maintainable code * fix: exclude test files from TypeScript build Add exclude for test files and vitest.config.ts to tsconfig
This commit is contained in:
parent
88a2ac4360
commit
b84637314f
2 changed files with 232 additions and 240 deletions
|
|
@ -1,11 +1,8 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
import {
|
import { z } from "zod";
|
||||||
CallToolRequestSchema,
|
|
||||||
ListToolsRequestSchema,
|
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
|
||||||
import { promises as fs } from 'fs';
|
import { promises as fs } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
@ -226,243 +223,235 @@ export class KnowledgeGraphManager {
|
||||||
|
|
||||||
let knowledgeGraphManager: KnowledgeGraphManager;
|
let knowledgeGraphManager: KnowledgeGraphManager;
|
||||||
|
|
||||||
|
// Zod schemas for entities and relations
|
||||||
|
const EntitySchema = z.object({
|
||||||
|
name: z.string().describe("The name of the entity"),
|
||||||
|
entityType: z.string().describe("The type of the entity"),
|
||||||
|
observations: z.array(z.string()).describe("An array of observation contents associated with the entity")
|
||||||
|
});
|
||||||
|
|
||||||
|
const RelationSchema = z.object({
|
||||||
|
from: z.string().describe("The name of the entity where the relation starts"),
|
||||||
|
to: z.string().describe("The name of the entity where the relation ends"),
|
||||||
|
relationType: z.string().describe("The type of the relation")
|
||||||
|
});
|
||||||
|
|
||||||
// The server instance and tools exposed to Claude
|
// The server instance and tools exposed to Claude
|
||||||
const server = new Server({
|
const server = new McpServer({
|
||||||
name: "memory-server",
|
name: "memory-server",
|
||||||
version: "0.6.3",
|
version: "0.6.3",
|
||||||
}, {
|
});
|
||||||
capabilities: {
|
|
||||||
tools: {},
|
// Register create_entities tool
|
||||||
|
server.registerTool(
|
||||||
|
"create_entities",
|
||||||
|
{
|
||||||
|
title: "Create Entities",
|
||||||
|
description: "Create multiple new entities in the knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
entities: z.array(EntitySchema)
|
||||||
},
|
},
|
||||||
},);
|
outputSchema: {
|
||||||
|
entities: z.array(EntitySchema)
|
||||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
}
|
||||||
return {
|
},
|
||||||
tools: [
|
async ({ entities }) => {
|
||||||
{
|
const result = await knowledgeGraphManager.createEntities(entities);
|
||||||
name: "create_entities",
|
return {
|
||||||
description: "Create multiple new entities in the knowledge graph",
|
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
||||||
inputSchema: {
|
structuredContent: { entities: result }
|
||||||
type: "object",
|
};
|
||||||
properties: {
|
|
||||||
entities: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
name: { type: "string", description: "The name of the entity" },
|
|
||||||
entityType: { type: "string", description: "The type of the entity" },
|
|
||||||
observations: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" },
|
|
||||||
description: "An array of observation contents associated with the entity"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["name", "entityType", "observations"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["entities"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "create_relations",
|
|
||||||
description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
relations: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
from: { type: "string", description: "The name of the entity where the relation starts" },
|
|
||||||
to: { type: "string", description: "The name of the entity where the relation ends" },
|
|
||||||
relationType: { type: "string", description: "The type of the relation" },
|
|
||||||
},
|
|
||||||
required: ["from", "to", "relationType"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["relations"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "add_observations",
|
|
||||||
description: "Add new observations to existing entities in the knowledge graph",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
observations: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
entityName: { type: "string", description: "The name of the entity to add the observations to" },
|
|
||||||
contents: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" },
|
|
||||||
description: "An array of observation contents to add"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["entityName", "contents"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["observations"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "delete_entities",
|
|
||||||
description: "Delete multiple entities and their associated relations from the knowledge graph",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
entityNames: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" },
|
|
||||||
description: "An array of entity names to delete"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["entityNames"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "delete_observations",
|
|
||||||
description: "Delete specific observations from entities in the knowledge graph",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
deletions: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
entityName: { type: "string", description: "The name of the entity containing the observations" },
|
|
||||||
observations: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" },
|
|
||||||
description: "An array of observations to delete"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["entityName", "observations"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["deletions"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "delete_relations",
|
|
||||||
description: "Delete multiple relations from the knowledge graph",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
relations: {
|
|
||||||
type: "array",
|
|
||||||
items: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
from: { type: "string", description: "The name of the entity where the relation starts" },
|
|
||||||
to: { type: "string", description: "The name of the entity where the relation ends" },
|
|
||||||
relationType: { type: "string", description: "The type of the relation" },
|
|
||||||
},
|
|
||||||
required: ["from", "to", "relationType"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
description: "An array of relations to delete"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["relations"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "read_graph",
|
|
||||||
description: "Read the entire knowledge graph",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {},
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "search_nodes",
|
|
||||||
description: "Search for nodes in the knowledge graph based on a query",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
query: { type: "string", description: "The search query to match against entity names, types, and observation content" },
|
|
||||||
},
|
|
||||||
required: ["query"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "open_nodes",
|
|
||||||
description: "Open specific nodes in the knowledge graph by their names",
|
|
||||||
inputSchema: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
names: {
|
|
||||||
type: "array",
|
|
||||||
items: { type: "string" },
|
|
||||||
description: "An array of entity names to retrieve",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["names"],
|
|
||||||
additionalProperties: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
||||||
const { name, arguments: args } = request.params;
|
|
||||||
|
|
||||||
if (name === "read_graph") {
|
|
||||||
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.readGraph(), null, 2) }] };
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
if (!args) {
|
// Register create_relations tool
|
||||||
throw new Error(`No arguments provided for tool: ${name}`);
|
server.registerTool(
|
||||||
|
"create_relations",
|
||||||
|
{
|
||||||
|
title: "Create Relations",
|
||||||
|
description: "Create multiple new relations between entities in the knowledge graph. Relations should be in active voice",
|
||||||
|
inputSchema: {
|
||||||
|
relations: z.array(RelationSchema)
|
||||||
|
},
|
||||||
|
outputSchema: {
|
||||||
|
relations: z.array(RelationSchema)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ relations }) => {
|
||||||
|
const result = await knowledgeGraphManager.createRelations(relations);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
||||||
|
structuredContent: { relations: result }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
switch (name) {
|
// Register add_observations tool
|
||||||
case "create_entities":
|
server.registerTool(
|
||||||
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createEntities(args.entities as Entity[]), null, 2) }] };
|
"add_observations",
|
||||||
case "create_relations":
|
{
|
||||||
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createRelations(args.relations as Relation[]), null, 2) }] };
|
title: "Add Observations",
|
||||||
case "add_observations":
|
description: "Add new observations to existing entities in the knowledge graph",
|
||||||
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.addObservations(args.observations as { entityName: string; contents: string[] }[]), null, 2) }] };
|
inputSchema: {
|
||||||
case "delete_entities":
|
observations: z.array(z.object({
|
||||||
await knowledgeGraphManager.deleteEntities(args.entityNames as string[]);
|
entityName: z.string().describe("The name of the entity to add the observations to"),
|
||||||
return { content: [{ type: "text", text: "Entities deleted successfully" }] };
|
contents: z.array(z.string()).describe("An array of observation contents to add")
|
||||||
case "delete_observations":
|
}))
|
||||||
await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]);
|
},
|
||||||
return { content: [{ type: "text", text: "Observations deleted successfully" }] };
|
outputSchema: {
|
||||||
case "delete_relations":
|
results: z.array(z.object({
|
||||||
await knowledgeGraphManager.deleteRelations(args.relations as Relation[]);
|
entityName: z.string(),
|
||||||
return { content: [{ type: "text", text: "Relations deleted successfully" }] };
|
addedObservations: z.array(z.string())
|
||||||
case "search_nodes":
|
}))
|
||||||
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.searchNodes(args.query as string), null, 2) }] };
|
}
|
||||||
case "open_nodes":
|
},
|
||||||
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.openNodes(args.names as string[]), null, 2) }] };
|
async ({ observations }) => {
|
||||||
default:
|
const result = await knowledgeGraphManager.addObservations(observations);
|
||||||
throw new Error(`Unknown tool: ${name}`);
|
return {
|
||||||
|
content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
|
||||||
|
structuredContent: { results: result }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
|
// Register delete_entities tool
|
||||||
|
server.registerTool(
|
||||||
|
"delete_entities",
|
||||||
|
{
|
||||||
|
title: "Delete Entities",
|
||||||
|
description: "Delete multiple entities and their associated relations from the knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
entityNames: z.array(z.string()).describe("An array of entity names to delete")
|
||||||
|
},
|
||||||
|
outputSchema: {
|
||||||
|
success: z.boolean(),
|
||||||
|
message: z.string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ entityNames }) => {
|
||||||
|
await knowledgeGraphManager.deleteEntities(entityNames);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text" as const, text: "Entities deleted successfully" }],
|
||||||
|
structuredContent: { success: true, message: "Entities deleted successfully" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register delete_observations tool
|
||||||
|
server.registerTool(
|
||||||
|
"delete_observations",
|
||||||
|
{
|
||||||
|
title: "Delete Observations",
|
||||||
|
description: "Delete specific observations from entities in the knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
deletions: z.array(z.object({
|
||||||
|
entityName: z.string().describe("The name of the entity containing the observations"),
|
||||||
|
observations: z.array(z.string()).describe("An array of observations to delete")
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
outputSchema: {
|
||||||
|
success: z.boolean(),
|
||||||
|
message: z.string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ deletions }) => {
|
||||||
|
await knowledgeGraphManager.deleteObservations(deletions);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text" as const, text: "Observations deleted successfully" }],
|
||||||
|
structuredContent: { success: true, message: "Observations deleted successfully" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register delete_relations tool
|
||||||
|
server.registerTool(
|
||||||
|
"delete_relations",
|
||||||
|
{
|
||||||
|
title: "Delete Relations",
|
||||||
|
description: "Delete multiple relations from the knowledge graph",
|
||||||
|
inputSchema: {
|
||||||
|
relations: z.array(RelationSchema).describe("An array of relations to delete")
|
||||||
|
},
|
||||||
|
outputSchema: {
|
||||||
|
success: z.boolean(),
|
||||||
|
message: z.string()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ relations }) => {
|
||||||
|
await knowledgeGraphManager.deleteRelations(relations);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text" as const, text: "Relations deleted successfully" }],
|
||||||
|
structuredContent: { success: true, message: "Relations deleted successfully" }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register read_graph tool
|
||||||
|
server.registerTool(
|
||||||
|
"read_graph",
|
||||||
|
{
|
||||||
|
title: "Read Graph",
|
||||||
|
description: "Read the entire knowledge graph",
|
||||||
|
inputSchema: {},
|
||||||
|
outputSchema: {
|
||||||
|
entities: z.array(EntitySchema),
|
||||||
|
relations: z.array(RelationSchema)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async () => {
|
||||||
|
const graph = await knowledgeGraphManager.readGraph();
|
||||||
|
return {
|
||||||
|
content: [{ type: "text" as const, text: JSON.stringify(graph, null, 2) }],
|
||||||
|
structuredContent: { ...graph }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register search_nodes tool
|
||||||
|
server.registerTool(
|
||||||
|
"search_nodes",
|
||||||
|
{
|
||||||
|
title: "Search Nodes",
|
||||||
|
description: "Search for nodes in the knowledge graph based on a query",
|
||||||
|
inputSchema: {
|
||||||
|
query: z.string().describe("The search query to match against entity names, types, and observation content")
|
||||||
|
},
|
||||||
|
outputSchema: {
|
||||||
|
entities: z.array(EntitySchema),
|
||||||
|
relations: z.array(RelationSchema)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ query }) => {
|
||||||
|
const graph = await knowledgeGraphManager.searchNodes(query);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text" as const, text: JSON.stringify(graph, null, 2) }],
|
||||||
|
structuredContent: { ...graph }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Register open_nodes tool
|
||||||
|
server.registerTool(
|
||||||
|
"open_nodes",
|
||||||
|
{
|
||||||
|
title: "Open Nodes",
|
||||||
|
description: "Open specific nodes in the knowledge graph by their names",
|
||||||
|
inputSchema: {
|
||||||
|
names: z.array(z.string()).describe("An array of entity names to retrieve")
|
||||||
|
},
|
||||||
|
outputSchema: {
|
||||||
|
entities: z.array(EntitySchema),
|
||||||
|
relations: z.array(RelationSchema)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async ({ names }) => {
|
||||||
|
const graph = await knowledgeGraphManager.openNodes(names);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text" as const, text: JSON.stringify(graph, null, 2) }],
|
||||||
|
structuredContent: { ...graph }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
// Initialize memory file path with backward compatibility
|
// Initialize memory file path with backward compatibility
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,14 @@
|
||||||
{
|
{
|
||||||
"extends": "../../tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "./dist",
|
"outDir": "./dist",
|
||||||
"rootDir": "."
|
"rootDir": "."
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./**/*.ts"
|
"./**/*.ts"
|
||||||
]
|
],
|
||||||
}
|
"exclude": [
|
||||||
|
"**/*.test.ts",
|
||||||
|
"vitest.config.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue