index.ts•34.5 kB
#!/usr/bin/env node
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 { promises as fs } from 'fs';
import { existsSync } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import minimist from 'minimist';
import { isAbsolute } from 'path';
// Parse args and handle paths safely
const argv = minimist(process.argv.slice(2));
let memoryPath = argv['memory-path'];
// If a custom path is provided, ensure it's absolute
if (memoryPath && !isAbsolute(memoryPath)) {
memoryPath = path.resolve(process.cwd(), memoryPath);
}
// Define the base directory for memory files
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Handle memory path - could be a file or directory
let baseMemoryPath: string;
if (memoryPath) {
// If memory-path points to a .jsonl file, use its directory as the base
if (memoryPath.endsWith('.jsonl')) {
baseMemoryPath = path.dirname(memoryPath);
} else {
// Otherwise treat it as a directory
baseMemoryPath = memoryPath;
}
} else {
baseMemoryPath = __dirname;
}
// Simple marker to identify our files - prevents writing to unrelated JSONL files
const FILE_MARKER = {
type: "_aim",
source: "mcp-knowledge-graph"
};
// Project detection - look for common project markers
function findProjectRoot(startDir: string = process.cwd()): string | null {
const projectMarkers = ['.git', 'package.json', 'pyproject.toml', 'Cargo.toml', 'go.mod'];
let currentDir = startDir;
const maxDepth = 5;
for (let i = 0; i < maxDepth; i++) {
// Check for project markers
for (const marker of projectMarkers) {
if (existsSync(path.join(currentDir, marker))) {
return currentDir;
}
}
// Move up one directory
const parentDir = path.dirname(currentDir);
if (parentDir === currentDir) {
// Reached root directory
break;
}
currentDir = parentDir;
}
return null;
}
// Function to get memory file path based on context and optional location override
function getMemoryFilePath(context?: string, location?: 'project' | 'global'): string {
const filename = context ? `memory-${context}.jsonl` : 'memory.jsonl';
// If location is explicitly specified, use it
if (location === 'global') {
return path.join(baseMemoryPath, filename);
}
if (location === 'project') {
const projectRoot = findProjectRoot();
if (projectRoot) {
const aimDir = path.join(projectRoot, '.aim');
return path.join(aimDir, filename); // Will create .aim if it doesn't exist
} else {
throw new Error('No project detected - cannot use project location');
}
}
// Auto-detect logic (existing behavior)
const projectRoot = findProjectRoot();
if (projectRoot) {
const aimDir = path.join(projectRoot, '.aim');
if (existsSync(aimDir)) {
return path.join(aimDir, filename);
}
}
// Fallback to configured base directory
return path.join(baseMemoryPath, filename);
}
// We are storing our memory using entities, relations, and observations in a graph structure
interface Entity {
name: string;
entityType: string;
observations: string[];
}
interface Relation {
from: string;
to: string;
relationType: string;
}
interface KnowledgeGraph {
entities: Entity[];
relations: Relation[];
}
// The KnowledgeGraphManager class contains all operations to interact with the knowledge graph
class KnowledgeGraphManager {
private async loadGraph(context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> {
const filePath = getMemoryFilePath(context, location);
try {
const data = await fs.readFile(filePath, "utf-8");
const lines = data.split("\n").filter(line => line.trim() !== "");
if (lines.length === 0) {
return { entities: [], relations: [] };
}
// Check first line for our file marker
const firstLine = JSON.parse(lines[0]!);
if (firstLine.type !== "_aim" || firstLine.source !== "mcp-knowledge-graph") {
throw new Error(`File ${filePath} does not contain required _aim safety marker. This file may not belong to the knowledge graph system. Expected first line: {"type":"_aim","source":"mcp-knowledge-graph"}`);
}
// Process remaining lines (skip metadata)
return lines.slice(1).reduce((graph: KnowledgeGraph, line) => {
const item = JSON.parse(line);
if (item.type === "entity") graph.entities.push(item as Entity);
if (item.type === "relation") graph.relations.push(item as Relation);
return graph;
}, { entities: [], relations: [] });
} catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === "ENOENT") {
// File doesn't exist - we'll create it with metadata on first save
return { entities: [], relations: [] };
}
throw error;
}
}
private async saveGraph(graph: KnowledgeGraph, context?: string, location?: 'project' | 'global'): Promise<void> {
const filePath = getMemoryFilePath(context, location);
// Write our simple file marker
const lines = [
JSON.stringify(FILE_MARKER),
...graph.entities.map(e => JSON.stringify({ type: "entity", ...e })),
...graph.relations.map(r => JSON.stringify({ type: "relation", ...r })),
];
// Ensure directory exists
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, lines.join("\n"));
}
async createEntities(entities: Entity[], context?: string, location?: 'project' | 'global'): Promise<Entity[]> {
const graph = await this.loadGraph(context, location);
const newEntities = entities.filter(e => !graph.entities.some(existingEntity => existingEntity.name === e.name));
graph.entities.push(...newEntities);
await this.saveGraph(graph, context, location);
return newEntities;
}
async createRelations(relations: Relation[], context?: string, location?: 'project' | 'global'): Promise<Relation[]> {
const graph = await this.loadGraph(context, location);
const newRelations = relations.filter(r => !graph.relations.some(existingRelation =>
existingRelation.from === r.from &&
existingRelation.to === r.to &&
existingRelation.relationType === r.relationType
));
graph.relations.push(...newRelations);
await this.saveGraph(graph, context, location);
return newRelations;
}
async addObservations(observations: { entityName: string; contents: string[] }[], context?: string, location?: 'project' | 'global'): Promise<{ entityName: string; addedObservations: string[] }[]> {
const graph = await this.loadGraph(context, location);
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 newObservations = o.contents.filter(content => !entity.observations.includes(content));
entity.observations.push(...newObservations);
return { entityName: o.entityName, addedObservations: newObservations };
});
await this.saveGraph(graph, context, location);
return results;
}
async deleteEntities(entityNames: string[], context?: string, location?: 'project' | 'global'): Promise<void> {
const graph = await this.loadGraph(context, location);
graph.entities = graph.entities.filter(e => !entityNames.includes(e.name));
graph.relations = graph.relations.filter(r => !entityNames.includes(r.from) && !entityNames.includes(r.to));
await this.saveGraph(graph, context, location);
}
async deleteObservations(deletions: { entityName: string; observations: string[] }[], context?: string, location?: 'project' | 'global'): Promise<void> {
const graph = await this.loadGraph(context, location);
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, context, location);
}
async deleteRelations(relations: Relation[], context?: string, location?: 'project' | 'global'): Promise<void> {
const graph = await this.loadGraph(context, location);
graph.relations = graph.relations.filter(r => !relations.some(delRelation =>
r.from === delRelation.from &&
r.to === delRelation.to &&
r.relationType === delRelation.relationType
));
await this.saveGraph(graph, context, location);
}
async readGraph(context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> {
return this.loadGraph(context, location);
}
// Very basic search function
async searchNodes(query: string, context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> {
const graph = await this.loadGraph(context, location);
// Filter entities
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()))
);
// Create a Set of filtered entity names for quick lookup
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
// Filter relations to only include those between filtered entities
const filteredRelations = graph.relations.filter(r =>
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
);
const filteredGraph: KnowledgeGraph = {
entities: filteredEntities,
relations: filteredRelations,
};
return filteredGraph;
}
async openNodes(names: string[], context?: string, location?: 'project' | 'global'): Promise<KnowledgeGraph> {
const graph = await this.loadGraph(context, location);
// Filter entities
const filteredEntities = graph.entities.filter(e => names.includes(e.name));
// Create a Set of filtered entity names for quick lookup
const filteredEntityNames = new Set(filteredEntities.map(e => e.name));
// Filter relations to only include those between filtered entities
const filteredRelations = graph.relations.filter(r =>
filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)
);
const filteredGraph: KnowledgeGraph = {
entities: filteredEntities,
relations: filteredRelations,
};
return filteredGraph;
}
async listDatabases(): Promise<{ project_databases: string[], global_databases: string[], current_location: string }> {
const result = {
project_databases: [] as string[],
global_databases: [] as string[],
current_location: ""
};
// Check project-local .aim directory
const projectRoot = findProjectRoot();
if (projectRoot) {
const aimDir = path.join(projectRoot, '.aim');
if (existsSync(aimDir)) {
result.current_location = "project (.aim directory detected)";
try {
const files = await fs.readdir(aimDir);
result.project_databases = files
.filter(file => file.endsWith('.jsonl'))
.map(file => file === 'memory.jsonl' ? 'default' : file.replace('memory-', '').replace('.jsonl', ''))
.sort();
} catch (error) {
// Directory exists but can't read - ignore
}
} else {
result.current_location = "global (no .aim directory in project)";
}
} else {
result.current_location = "global (no project detected)";
}
// Check global directory
try {
const files = await fs.readdir(baseMemoryPath);
result.global_databases = files
.filter(file => file.endsWith('.jsonl'))
.map(file => file === 'memory.jsonl' ? 'default' : file.replace('memory-', '').replace('.jsonl', ''))
.sort();
} catch (error) {
// Directory doesn't exist or can't read
result.global_databases = [];
}
return result;
}
}
const knowledgeGraphManager = new KnowledgeGraphManager();
// The server instance and tools exposed to AI models
const server = new Server({
name: "mcp-knowledge-graph",
version: "1.0.1",
}, {
capabilities: {
tools: {},
},
},);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "aim_create_entities",
description: `Create multiple new entities in the knowledge graph.
DATABASE SELECTION: By default, all memories are stored in the master database. Use the 'context' parameter to organize information into separate knowledge graphs for different areas of life or work.
STORAGE LOCATION: Files are stored in the user's configured directory, or project-local .aim directory if one exists. Each database creates its own file (e.g., memory-work.jsonl, memory-personal.jsonl).
LOCATION OVERRIDE: Use the 'location' parameter to force storage in a specific location:
- 'project': Always use project-local .aim directory (creates if needed)
- 'global': Always use global configured directory
- Leave blank: Auto-detect (project if .aim exists, otherwise global)
WHEN TO USE DATABASES:
- Any descriptive name: 'work', 'personal', 'health', 'research', 'basket-weaving', 'book-club', etc.
- New databases are created automatically - no setup required
- IMPORTANT: Use consistent, simple names - prefer 'work' over 'work-stuff' or 'job-related'
- Common examples: 'work' (professional), 'personal' (private), 'health' (medical), 'research' (academic)
- Leave blank: General information or when unsure (uses master database)
EXAMPLES:
- Master database (default): aim_create_entities({entities: [{name: "John", entityType: "person", observations: ["Met at conference"]}]})
- Work database: aim_create_entities({context: "work", entities: [{name: "Q4_Project", entityType: "project", observations: ["Due December 2024"]}]})
- Master database in global location: aim_create_entities({location: "global", entities: [{name: "John", entityType: "person", observations: ["Met at conference"]}]})
- Work database in project location: aim_create_entities({context: "work", location: "project", entities: [{name: "Q4_Project", entityType: "project", observations: ["Due December 2024"]}]})`,
inputSchema: {
type: "object",
properties: {
context: {
type: "string",
description: "Optional memory context. Defaults to master database if not specified. Use any descriptive name ('work', 'personal', 'health', 'basket-weaving', etc.) - new contexts created automatically."
},
location: {
type: "string",
enum: ["project", "global"],
description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
},
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"],
},
},
},
required: ["entities"],
},
},
{
name: "aim_create_relations",
description: `Create multiple new relations between entities in the knowledge graph. Relations should be in active voice.
DATABASE SELECTION: Relations are created within the specified database's knowledge graph. Entities must exist in the same database.
LOCATION OVERRIDE: Use the 'location' parameter to force storage in 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
EXAMPLES:
- Master database (default): aim_create_relations({relations: [{from: "John", to: "TechConf2024", relationType: "attended"}]})
- Work database: aim_create_relations({context: "work", relations: [{from: "Alice", to: "Q4_Project", relationType: "manages"}]})
- Master database in global location: aim_create_relations({location: "global", relations: [{from: "John", to: "TechConf2024", relationType: "attended"}]})
- Personal database in project location: aim_create_relations({context: "personal", location: "project", relations: [{from: "Mom", to: "Gardening", relationType: "enjoys"}]})`,
inputSchema: {
type: "object",
properties: {
context: {
type: "string",
description: "Optional memory context. Relations will be created in the specified context's knowledge graph."
},
location: {
type: "string",
enum: ["project", "global"],
description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
},
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"],
},
},
},
required: ["relations"],
},
},
{
name: "aim_add_observations",
description: `Add new observations to existing entities in the knowledge graph.
DATABASE SELECTION: Observations are added to entities within the specified database's knowledge graph.
LOCATION OVERRIDE: Use the 'location' parameter to force storage in 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
EXAMPLES:
- Master database (default): aim_add_observations({observations: [{entityName: "John", contents: ["Lives in Seattle", "Works in tech"]}]})
- Work database: aim_add_observations({context: "work", observations: [{entityName: "Q4_Project", contents: ["Behind schedule", "Need more resources"]}]})
- Master database in global location: aim_add_observations({location: "global", observations: [{entityName: "John", contents: ["Lives in Seattle", "Works in tech"]}]})
- Health database in project location: aim_add_observations({context: "health", location: "project", observations: [{entityName: "Daily_Routine", contents: ["30min morning walk", "8 glasses water"]}]})`,
inputSchema: {
type: "object",
properties: {
context: {
type: "string",
description: "Optional memory context. Observations will be added to entities in the specified context's knowledge graph."
},
location: {
type: "string",
enum: ["project", "global"],
description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
},
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"],
},
},
},
required: ["observations"],
},
},
{
name: "aim_delete_entities",
description: `Delete multiple entities and their associated relations from the knowledge graph.
DATABASE SELECTION: Entities are deleted from the specified database's knowledge graph.
LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
EXAMPLES:
- Master database (default): aim_delete_entities({entityNames: ["OldProject"]})
- Work database: aim_delete_entities({context: "work", entityNames: ["CompletedTask", "CancelledMeeting"]})
- Master database in global location: aim_delete_entities({location: "global", entityNames: ["OldProject"]})
- Personal database in project location: aim_delete_entities({context: "personal", location: "project", entityNames: ["ExpiredReminder"]})`,
inputSchema: {
type: "object",
properties: {
context: {
type: "string",
description: "Optional memory context. Entities will be deleted from the specified context's knowledge graph."
},
location: {
type: "string",
enum: ["project", "global"],
description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
},
entityNames: {
type: "array",
items: { type: "string" },
description: "An array of entity names to delete"
},
},
required: ["entityNames"],
},
},
{
name: "aim_delete_observations",
description: `Delete specific observations from entities in the knowledge graph.
DATABASE SELECTION: Observations are deleted from entities within the specified database's knowledge graph.
LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
EXAMPLES:
- Master database (default): aim_delete_observations({deletions: [{entityName: "John", observations: ["Outdated info"]}]})
- Work database: aim_delete_observations({context: "work", deletions: [{entityName: "Project", observations: ["Old deadline"]}]})
- Master database in global location: aim_delete_observations({location: "global", deletions: [{entityName: "John", observations: ["Outdated info"]}]})
- Health database in project location: aim_delete_observations({context: "health", location: "project", deletions: [{entityName: "Exercise", observations: ["Injured knee"]}]})`,
inputSchema: {
type: "object",
properties: {
context: {
type: "string",
description: "Optional memory context. Observations will be deleted from entities in the specified context's knowledge graph."
},
location: {
type: "string",
enum: ["project", "global"],
description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
},
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"],
},
},
},
required: ["deletions"],
},
},
{
name: "aim_delete_relations",
description: `Delete multiple relations from the knowledge graph.
DATABASE SELECTION: Relations are deleted from the specified database's knowledge graph.
LOCATION OVERRIDE: Use the 'location' parameter to force deletion from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
EXAMPLES:
- Master database (default): aim_delete_relations({relations: [{from: "John", to: "OldCompany", relationType: "worked_at"}]})
- Work database: aim_delete_relations({context: "work", relations: [{from: "Alice", to: "CancelledProject", relationType: "manages"}]})
- Master database in global location: aim_delete_relations({location: "global", relations: [{from: "John", to: "OldCompany", relationType: "worked_at"}]})
- Personal database in project location: aim_delete_relations({context: "personal", location: "project", relations: [{from: "Me", to: "OldHobby", relationType: "enjoys"}]})`,
inputSchema: {
type: "object",
properties: {
context: {
type: "string",
description: "Optional memory context. Relations will be deleted from the specified context's knowledge graph."
},
location: {
type: "string",
enum: ["project", "global"],
description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
},
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"],
},
description: "An array of relations to delete"
},
},
required: ["relations"],
},
},
{
name: "aim_read_graph",
description: `Read the entire knowledge graph.
DATABASE SELECTION: Reads from the specified database or master database if no database is specified.
LOCATION OVERRIDE: Use the 'location' parameter to force reading from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
EXAMPLES:
- Master database (default): aim_read_graph({})
- Work database: aim_read_graph({context: "work"})
- Master database in global location: aim_read_graph({location: "global"})
- Personal database in project location: aim_read_graph({context: "personal", location: "project"})`,
inputSchema: {
type: "object",
properties: {
context: {
type: "string",
description: "Optional memory context. Reads from the specified context's knowledge graph or master database if not specified."
},
location: {
type: "string",
enum: ["project", "global"],
description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
}
},
},
},
{
name: "aim_search_nodes",
description: `Search for nodes in the knowledge graph based on a query.
DATABASE SELECTION: Searches within the specified database or master database if no database is specified.
LOCATION OVERRIDE: Use the 'location' parameter to force searching in 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
EXAMPLES:
- Master database (default): aim_search_nodes({query: "John"})
- Work database: aim_search_nodes({context: "work", query: "project"})
- Master database in global location: aim_search_nodes({location: "global", query: "John"})
- Personal database in project location: aim_search_nodes({context: "personal", location: "project", query: "family"})`,
inputSchema: {
type: "object",
properties: {
context: {
type: "string",
description: "Optional memory context. Searches within the specified context's knowledge graph or master database if not specified."
},
location: {
type: "string",
enum: ["project", "global"],
description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
},
query: { type: "string", description: "The search query to match against entity names, types, and observation content" },
},
required: ["query"],
},
},
{
name: "aim_open_nodes",
description: `Open specific nodes in the knowledge graph by their names.
DATABASE SELECTION: Retrieves entities from the specified database or master database if no database is specified.
LOCATION OVERRIDE: Use the 'location' parameter to force retrieval from 'project' (.aim directory) or 'global' (configured directory). Leave blank for auto-detection.
EXAMPLES:
- Master database (default): aim_open_nodes({names: ["John", "TechConf2024"]})
- Work database: aim_open_nodes({context: "work", names: ["Q4_Project", "Alice"]})
- Master database in global location: aim_open_nodes({location: "global", names: ["John", "TechConf2024"]})
- Personal database in project location: aim_open_nodes({context: "personal", location: "project", names: ["Mom", "Birthday_Plans"]})`,
inputSchema: {
type: "object",
properties: {
context: {
type: "string",
description: "Optional memory context. Retrieves entities from the specified context's knowledge graph or master database if not specified."
},
location: {
type: "string",
enum: ["project", "global"],
description: "Optional storage location override. 'project' forces project-local .aim directory, 'global' forces global directory. If not specified, uses automatic detection."
},
names: {
type: "array",
items: { type: "string" },
description: "An array of entity names to retrieve",
},
},
required: ["names"],
},
},
{
name: "aim_list_databases",
description: `List all available memory databases in both project and global locations.
DISCOVERY: Shows which databases exist, where they're stored, and which location is currently active.
EXAMPLES:
- aim_list_databases() - Shows all available databases and current storage location`,
inputSchema: {
type: "object",
properties: {},
},
},
],
};
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (!args) {
throw new Error(`No arguments provided for tool: ${name}`);
}
switch (name) {
case "aim_create_entities":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createEntities(args.entities as Entity[], args.context as string, args.location as 'project' | 'global'), null, 2) }] };
case "aim_create_relations":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.createRelations(args.relations as Relation[], args.context as string, args.location as 'project' | 'global'), null, 2) }] };
case "aim_add_observations":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.addObservations(args.observations as { entityName: string; contents: string[] }[], args.context as string, args.location as 'project' | 'global'), null, 2) }] };
case "aim_delete_entities":
await knowledgeGraphManager.deleteEntities(args.entityNames as string[], args.context as string, args.location as 'project' | 'global');
return { content: [{ type: "text", text: "Entities deleted successfully" }] };
case "aim_delete_observations":
await knowledgeGraphManager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[], args.context as string, args.location as 'project' | 'global');
return { content: [{ type: "text", text: "Observations deleted successfully" }] };
case "aim_delete_relations":
await knowledgeGraphManager.deleteRelations(args.relations as Relation[], args.context as string, args.location as 'project' | 'global');
return { content: [{ type: "text", text: "Relations deleted successfully" }] };
case "aim_read_graph":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.readGraph(args.context as string, args.location as 'project' | 'global'), null, 2) }] };
case "aim_search_nodes":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.searchNodes(args.query as string, args.context as string, args.location as 'project' | 'global'), null, 2) }] };
case "aim_open_nodes":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.openNodes(args.names as string[], args.context as string, args.location as 'project' | 'global'), null, 2) }] };
case "aim_list_databases":
return { content: [{ type: "text", text: JSON.stringify(await knowledgeGraphManager.listDatabases(), null, 2) }] };
default:
throw new Error(`Unknown tool: ${name}`);
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Knowledge Graph MCP Server running on stdio");
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});