knowledge_graph
Manage an onboard knowledge graph to organize, connect, and analyze webset results through entities, relations, and observations for structured data representation.
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)Main handler function that dispatches tool operations to KnowledgeGraphService methods, handles logging, error catching, and formats responses as MCP tool outputs.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 for validating input parameters to the knowledge_graph tool, defining the operation enum and structures 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)Registration of the knowledge_graph tool in the toolRegistry, specifying 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 };
- Core KnowledgeGraphService class implementing all graph operations (create, add, delete, read, search, open) with JSON file-based persistence, used by the tool handler.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 }; } }