knowledge_graph
Manage an onboard knowledge graph to organize, connect, and analyze webset results for research and content management.
Instructions
Maintain an onboard knowledge graph of webset results.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| operation | Yes | ||
| entities | No | ||
| relations | No | ||
| observations | No | ||
| deletions | No | ||
| names | No | ||
| query | No |
Implementation Reference
- src/tools/knowledgeGraph.ts:48-83 (handler)The handler function for the 'knowledge_graph' tool. It destructures the input arguments, handles logging, and uses a switch statement to call the appropriate method on the KnowledgeGraphService instance based on the 'operation' parameter, returning formatted responses.handler: async (args) => { const { operation, entities, relations, observations, deletions, names, query } = args; const requestId = `knowledge_graph-${Date.now()}-${Math.random().toString(36).slice(2,7)}`; const logger = createRequestLogger(requestId, 'knowledge_graph'); logger.start(operation); try { switch (operation) { case 'create_entities': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.createEntities(entities || []), null, 2) }] }; case 'create_relations': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.createRelations(relations || []), null, 2) }] }; case 'add_observations': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.addObservations(observations || []), null, 2) }] }; case 'delete_entities': await knowledgeGraph.deleteEntities(names || []); return { content: [{ type: 'text', text: 'Entities deleted successfully' }] }; case 'delete_observations': await knowledgeGraph.deleteObservations(deletions || []); return { content: [{ type: 'text', text: 'Observations deleted successfully' }] }; case 'delete_relations': await knowledgeGraph.deleteRelations(relations || []); return { content: [{ type: 'text', text: 'Relations deleted successfully' }] }; case 'read_graph': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.readGraph(), null, 2) }] }; case 'search_nodes': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.searchNodes(query || ''), null, 2) }] }; case 'open_nodes': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.openNodes(names || []), null, 2) }] }; default: throw new Error(`Unknown operation: ${operation}`); } } catch (error: any) { logger.error(error); return { content: [{ type: 'text', text: `Knowledge graph error: ${error.message}` }], isError: true }; } },
- src/tools/knowledgeGraph.ts:8-40 (schema)Zod schema defining the input structure for the knowledge_graph tool, including the required 'operation' enum and optional parameters for entities, relations, observations, deletions, names, and query.const KnowledgeGraphSchema = z.object({ operation: z.enum([ 'create_entities', 'create_relations', 'add_observations', 'delete_entities', 'delete_observations', 'delete_relations', 'read_graph', 'search_nodes', 'open_nodes' ]), entities: z.array(z.object({ name: z.string(), entityType: z.string(), observations: z.array(z.string()).default([]) })).optional(), relations: z.array(z.object({ from: z.string(), to: z.string(), relationType: z.string() })).optional(), observations: z.array(z.object({ entityName: z.string(), contents: z.array(z.string()) })).optional(), deletions: z.array(z.object({ entityName: z.string(), observations: z.array(z.string()) })).optional(), names: z.array(z.string()).optional(), query: z.string().optional() });
- src/tools/knowledgeGraph.ts:42-85 (registration)Registers the 'knowledge_graph' tool in the toolRegistry object, including name, description, schema, category, service type, handler, and enabled status.toolRegistry['knowledge_graph'] = { name: 'knowledge_graph', description: 'Maintain an onboard knowledge graph of webset results.', schema: KnowledgeGraphSchema.shape, category: ToolCategory.WEBSETS, service: ServiceType.WEBSETS, handler: async (args) => { const { operation, entities, relations, observations, deletions, names, query } = args; const requestId = `knowledge_graph-${Date.now()}-${Math.random().toString(36).slice(2,7)}`; const logger = createRequestLogger(requestId, 'knowledge_graph'); logger.start(operation); try { switch (operation) { case 'create_entities': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.createEntities(entities || []), null, 2) }] }; case 'create_relations': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.createRelations(relations || []), null, 2) }] }; case 'add_observations': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.addObservations(observations || []), null, 2) }] }; case 'delete_entities': await knowledgeGraph.deleteEntities(names || []); return { content: [{ type: 'text', text: 'Entities deleted successfully' }] }; case 'delete_observations': await knowledgeGraph.deleteObservations(deletions || []); return { content: [{ type: 'text', text: 'Observations deleted successfully' }] }; case 'delete_relations': await knowledgeGraph.deleteRelations(relations || []); return { content: [{ type: 'text', text: 'Relations deleted successfully' }] }; case 'read_graph': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.readGraph(), null, 2) }] }; case 'search_nodes': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.searchNodes(query || ''), null, 2) }] }; case 'open_nodes': return { content: [{ type: 'text', text: JSON.stringify(await knowledgeGraph.openNodes(names || []), null, 2) }] }; default: throw new Error(`Unknown operation: ${operation}`); } } catch (error: any) { logger.error(error); return { content: [{ type: 'text', text: `Knowledge graph error: ${error.message}` }], isError: true }; } }, enabled: true };
- src/index.ts:133-142 (registration)Registers tools from the simplifiedRegistry (which includes knowledge_graph from toolRegistry) with the MCP server using this.server.tool().// Register our tools Object.values(simplifiedRegistry).forEach(tool => { if (tool) { this.server.tool( tool.name, tool.description, tool.schema, tool.handler ); }
- The KnowledgeGraphService class implements all the core graph operations (create, delete, read, search) used by the knowledge_graph tool handler. It persists data in a JSON file.export class KnowledgeGraphService { private filePath: string; constructor(filePath?: string) { const defaultPath = path.join(process.cwd(), 'knowledge-graph.json'); const resolved = filePath || process.env.KG_FILE_PATH || defaultPath; this.filePath = path.isAbsolute(resolved) ? resolved : path.join(process.cwd(), resolved); } private async loadGraph(): Promise<KnowledgeGraph> { try { const data = await fs.readFile(this.filePath, 'utf-8'); const lines = data.split('\n').filter(line => line.trim() !== ''); return lines.reduce<KnowledgeGraph>((graph, line) => { const item = JSON.parse(line); if (item.type === 'entity') graph.entities.push(item as GraphEntity); if (item.type === 'relation') graph.relations.push(item as GraphRelation); return graph; }, { entities: [], relations: [] }); } catch (err: any) { if (err && err.code === 'ENOENT') { return { entities: [], relations: [] }; } throw err; } } private async saveGraph(graph: KnowledgeGraph): Promise<void> { const lines = [ ...graph.entities.map(e => JSON.stringify({ type: 'entity', ...e })), ...graph.relations.map(r => JSON.stringify({ type: 'relation', ...r })), ]; await fs.writeFile(this.filePath, lines.join('\n')); } async createEntities(entities: GraphEntity[]): Promise<GraphEntity[]> { const graph = await this.loadGraph(); const newEntities = entities.filter(e => !graph.entities.some(ex => ex.name === e.name)); graph.entities.push(...newEntities); await this.saveGraph(graph); return newEntities; } async createRelations(relations: GraphRelation[]): Promise<GraphRelation[]> { const graph = await this.loadGraph(); const newRelations = relations.filter(r => !graph.relations.some(ex => ex.from === r.from && ex.to === r.to && ex.relationType === r.relationType )); graph.relations.push(...newRelations); await this.saveGraph(graph); return newRelations; } async addObservations(observations: { entityName: string; contents: string[] }[]) : Promise<{ entityName: string; addedObservations: string[] }[]> { const graph = await this.loadGraph(); const results = observations.map(o => { const entity = graph.entities.find(e => e.name === o.entityName); if (!entity) { throw new Error(`Entity with name ${o.entityName} not found`); } const newObs = o.contents.filter(c => !entity.observations.includes(c)); entity.observations.push(...newObs); return { entityName: o.entityName, addedObservations: newObs }; }); await this.saveGraph(graph); return results; } async deleteEntities(names: string[]): Promise<void> { const graph = await this.loadGraph(); graph.entities = graph.entities.filter(e => !names.includes(e.name)); graph.relations = graph.relations.filter(r => !names.includes(r.from) && !names.includes(r.to)); await this.saveGraph(graph); } async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> { const graph = await this.loadGraph(); deletions.forEach(d => { const entity = graph.entities.find(e => e.name === d.entityName); if (entity) { entity.observations = entity.observations.filter(o => !d.observations.includes(o)); } }); await this.saveGraph(graph); } async deleteRelations(relations: GraphRelation[]): Promise<void> { const graph = await this.loadGraph(); graph.relations = graph.relations.filter(r => !relations.some(del => r.from === del.from && r.to === del.to && r.relationType === del.relationType )); await this.saveGraph(graph); } async readGraph(): Promise<KnowledgeGraph> { return this.loadGraph(); } async searchNodes(query: string): Promise<KnowledgeGraph> { const graph = await this.loadGraph(); const filteredEntities = graph.entities.filter(e => e.name.toLowerCase().includes(query.toLowerCase()) || e.entityType.toLowerCase().includes(query.toLowerCase()) || e.observations.some(o => o.toLowerCase().includes(query.toLowerCase())) ); const names = new Set(filteredEntities.map(e => e.name)); const filteredRelations = graph.relations.filter(r => names.has(r.from) && names.has(r.to) ); return { entities: filteredEntities, relations: filteredRelations }; } async openNodes(names: string[]): Promise<KnowledgeGraph> { const graph = await this.loadGraph(); const filteredEntities = graph.entities.filter(e => names.includes(e.name)); const nameSet = new Set(filteredEntities.map(e => e.name)); const filteredRelations = graph.relations.filter(r => nameSet.has(r.from) && nameSet.has(r.to) ); return { entities: filteredEntities, relations: filteredRelations }; } }