lint: fix code format

This commit is contained in:
kangfenmao 2025-04-19 18:58:26 +08:00
parent 4663794ba6
commit 55aac1cb9b
3 changed files with 328 additions and 277 deletions

View File

@ -1,9 +1,9 @@
import { getConfigDir } from '@main/utils/file' import { getConfigDir } from '@main/utils/file'
import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js' import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError } from '@modelcontextprotocol/sdk/types.js'
import { Mutex } from 'async-mutex' // 引入 Mutex
import { promises as fs } from 'fs' import { promises as fs } from 'fs'
import path from 'path' import path from 'path'
import { Mutex } from 'async-mutex' // 引入 Mutex
// Define memory file path // Define memory file path
const defaultMemoryPath = path.join(getConfigDir(), 'memory.json') const defaultMemoryPath = path.join(getConfigDir(), 'memory.json')
@ -62,7 +62,10 @@ class KnowledgeGraphManager {
} catch (error) { } catch (error) {
console.error('Failed to ensure memory path exists:', error) console.error('Failed to ensure memory path exists:', error)
// Propagate the error or handle it more gracefully depending on requirements // Propagate the error or handle it more gracefully depending on requirements
throw new McpError(ErrorCode.InternalError, `Failed to ensure memory path: ${error instanceof Error ? error.message : String(error)}`) throw new McpError(
ErrorCode.InternalError,
`Failed to ensure memory path: ${error instanceof Error ? error.message : String(error)}`
)
} }
} }
@ -81,8 +84,8 @@ class KnowledgeGraphManager {
const graph: KnowledgeGraph = JSON.parse(data) const graph: KnowledgeGraph = JSON.parse(data)
this.entities.clear() this.entities.clear()
this.relations.clear() this.relations.clear()
graph.entities.forEach(entity => this.entities.set(entity.name, entity)) graph.entities.forEach((entity) => this.entities.set(entity.name, entity))
graph.relations.forEach(relation => this.relations.add(this._serializeRelation(relation))) graph.relations.forEach((relation) => this.relations.add(this._serializeRelation(relation)))
} catch (error) { } catch (error) {
if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') { if (error instanceof Error && 'code' in error && (error as any).code === 'ENOENT') {
// File doesn't exist (should have been created by _ensureMemoryPathExists, but handle defensively) // File doesn't exist (should have been created by _ensureMemoryPathExists, but handle defensively)
@ -90,14 +93,17 @@ class KnowledgeGraphManager {
this.relations = new Set() this.relations = new Set()
await this._persistGraph() // Create the file with empty structure await this._persistGraph() // Create the file with empty structure
} else if (error instanceof SyntaxError) { } else if (error instanceof SyntaxError) {
console.error('Failed to parse memory.json, initializing with empty graph:', error) console.error('Failed to parse memory.json, initializing with empty graph:', error)
// If JSON is invalid, start fresh and overwrite the corrupted file // If JSON is invalid, start fresh and overwrite the corrupted file
this.entities = new Map() this.entities = new Map()
this.relations = new Set() this.relations = new Set()
await this._persistGraph() await this._persistGraph()
} else { } else {
console.error('Failed to load knowledge graph from disk:', error) console.error('Failed to load knowledge graph from disk:', error)
throw new McpError(ErrorCode.InternalError, `Failed to load graph: ${error instanceof Error ? error.message : String(error)}`) throw new McpError(
ErrorCode.InternalError,
`Failed to load graph: ${error instanceof Error ? error.message : String(error)}`
)
} }
} }
} }
@ -108,13 +114,16 @@ class KnowledgeGraphManager {
try { try {
const graphData: KnowledgeGraph = { const graphData: KnowledgeGraph = {
entities: Array.from(this.entities.values()), entities: Array.from(this.entities.values()),
relations: Array.from(this.relations).map(rStr => this._deserializeRelation(rStr)) relations: Array.from(this.relations).map((rStr) => this._deserializeRelation(rStr))
} }
await fs.writeFile(this.memoryPath, JSON.stringify(graphData, null, 2)) await fs.writeFile(this.memoryPath, JSON.stringify(graphData, null, 2))
} catch (error) { } catch (error) {
console.error('Failed to save knowledge graph:', error) console.error('Failed to save knowledge graph:', error)
// Decide how to handle write errors - potentially retry or notify // Decide how to handle write errors - potentially retry or notify
throw new McpError(ErrorCode.InternalError, `Failed to save graph: ${error instanceof Error ? error.message : String(error)}`) throw new McpError(
ErrorCode.InternalError,
`Failed to save graph: ${error instanceof Error ? error.message : String(error)}`
)
} finally { } finally {
release() release()
} }
@ -133,10 +142,10 @@ class KnowledgeGraphManager {
async createEntities(entities: Entity[]): Promise<Entity[]> { async createEntities(entities: Entity[]): Promise<Entity[]> {
const newEntities: Entity[] = [] const newEntities: Entity[] = []
entities.forEach(entity => { entities.forEach((entity) => {
if (!this.entities.has(entity.name)) { if (!this.entities.has(entity.name)) {
// Ensure observations is always an array // Ensure observations is always an array
const newEntity = { ...entity, observations: Array.isArray(entity.observations) ? entity.observations : [] }; const newEntity = { ...entity, observations: Array.isArray(entity.observations) ? entity.observations : [] }
this.entities.set(entity.name, newEntity) this.entities.set(entity.name, newEntity)
newEntities.push(newEntity) newEntities.push(newEntity)
} }
@ -149,11 +158,11 @@ class KnowledgeGraphManager {
async createRelations(relations: Relation[]): Promise<Relation[]> { async createRelations(relations: Relation[]): Promise<Relation[]> {
const newRelations: Relation[] = [] const newRelations: Relation[] = []
relations.forEach(relation => { relations.forEach((relation) => {
// Ensure related entities exist before creating a relation // Ensure related entities exist before creating a relation
if (!this.entities.has(relation.from) || !this.entities.has(relation.to)) { if (!this.entities.has(relation.from) || !this.entities.has(relation.to)) {
console.warn(`Skipping relation creation: Entity not found for relation ${relation.from} -> ${relation.to}`) console.warn(`Skipping relation creation: Entity not found for relation ${relation.from} -> ${relation.to}`)
return; // Skip this relation return // Skip this relation
} }
const relationStr = this._serializeRelation(relation) const relationStr = this._serializeRelation(relation)
if (!this.relations.has(relationStr)) { if (!this.relations.has(relationStr)) {
@ -172,20 +181,20 @@ class KnowledgeGraphManager {
): Promise<{ entityName: string; addedObservations: string[] }[]> { ): Promise<{ entityName: string; addedObservations: string[] }[]> {
const results: { entityName: string; addedObservations: string[] }[] = [] const results: { entityName: string; addedObservations: string[] }[] = []
let changed = false let changed = false
observations.forEach(o => { observations.forEach((o) => {
const entity = this.entities.get(o.entityName) const entity = this.entities.get(o.entityName)
if (!entity) { if (!entity) {
// Option 1: Throw error // Option 1: Throw error
throw new McpError(ErrorCode.InvalidParams, `Entity with name ${o.entityName} not found`) throw new McpError(ErrorCode.InvalidParams, `Entity with name ${o.entityName} not found`)
// Option 2: Skip and warn // Option 2: Skip and warn
// console.warn(`Entity with name ${o.entityName} not found when adding observations. Skipping.`); // console.warn(`Entity with name ${o.entityName} not found when adding observations. Skipping.`);
// return; // return;
} }
// Ensure observations array exists // Ensure observations array exists
if (!Array.isArray(entity.observations)) { if (!Array.isArray(entity.observations)) {
entity.observations = []; entity.observations = []
} }
const newObservations = o.contents.filter(content => !entity.observations.includes(content)) const newObservations = o.contents.filter((content) => !entity.observations.includes(content))
if (newObservations.length > 0) { if (newObservations.length > 0) {
entity.observations.push(...newObservations) entity.observations.push(...newObservations)
results.push({ entityName: o.entityName, addedObservations: newObservations }) results.push({ entityName: o.entityName, addedObservations: newObservations })
@ -206,7 +215,7 @@ class KnowledgeGraphManager {
const namesToDelete = new Set(entityNames) const namesToDelete = new Set(entityNames)
// Delete entities // Delete entities
namesToDelete.forEach(name => { namesToDelete.forEach((name) => {
if (this.entities.delete(name)) { if (this.entities.delete(name)) {
changed = true changed = true
} }
@ -214,14 +223,14 @@ class KnowledgeGraphManager {
// Delete relations involving deleted entities // Delete relations involving deleted entities
const relationsToDelete = new Set<string>() const relationsToDelete = new Set<string>()
this.relations.forEach(relStr => { this.relations.forEach((relStr) => {
const rel = this._deserializeRelation(relStr) const rel = this._deserializeRelation(relStr)
if (namesToDelete.has(rel.from) || namesToDelete.has(rel.to)) { if (namesToDelete.has(rel.from) || namesToDelete.has(rel.to)) {
relationsToDelete.add(relStr) relationsToDelete.add(relStr)
} }
}) })
relationsToDelete.forEach(relStr => { relationsToDelete.forEach((relStr) => {
if (this.relations.delete(relStr)) { if (this.relations.delete(relStr)) {
changed = true changed = true
} }
@ -234,12 +243,12 @@ class KnowledgeGraphManager {
async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> { async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise<void> {
let changed = false let changed = false
deletions.forEach(d => { deletions.forEach((d) => {
const entity = this.entities.get(d.entityName) const entity = this.entities.get(d.entityName)
if (entity && Array.isArray(entity.observations)) { if (entity && Array.isArray(entity.observations)) {
const initialLength = entity.observations.length const initialLength = entity.observations.length
const observationsToDelete = new Set(d.observations) const observationsToDelete = new Set(d.observations)
entity.observations = entity.observations.filter(o => !observationsToDelete.has(o)) entity.observations = entity.observations.filter((o) => !observationsToDelete.has(o))
if (entity.observations.length !== initialLength) { if (entity.observations.length !== initialLength) {
changed = true changed = true
} }
@ -252,7 +261,7 @@ class KnowledgeGraphManager {
async deleteRelations(relations: Relation[]): Promise<void> { async deleteRelations(relations: Relation[]): Promise<void> {
let changed = false let changed = false
relations.forEach(rel => { relations.forEach((rel) => {
const relStr = this._serializeRelation(rel) const relStr = this._serializeRelation(rel)
if (this.relations.delete(relStr)) { if (this.relations.delete(relStr)) {
changed = true changed = true
@ -266,27 +275,29 @@ class KnowledgeGraphManager {
// Read the current state from memory // Read the current state from memory
async readGraph(): Promise<KnowledgeGraph> { async readGraph(): Promise<KnowledgeGraph> {
// Return a deep copy to prevent external modification of the internal state // Return a deep copy to prevent external modification of the internal state
return JSON.parse(JSON.stringify({ return JSON.parse(
JSON.stringify({
entities: Array.from(this.entities.values()), entities: Array.from(this.entities.values()),
relations: Array.from(this.relations).map(rStr => this._deserializeRelation(rStr)) relations: Array.from(this.relations).map((rStr) => this._deserializeRelation(rStr))
})); })
)
} }
// Search operates on the in-memory graph // Search operates on the in-memory graph
async searchNodes(query: string): Promise<KnowledgeGraph> { async searchNodes(query: string): Promise<KnowledgeGraph> {
const lowerCaseQuery = query.toLowerCase() const lowerCaseQuery = query.toLowerCase()
const filteredEntities = Array.from(this.entities.values()).filter( const filteredEntities = Array.from(this.entities.values()).filter(
e => (e) =>
e.name.toLowerCase().includes(lowerCaseQuery) || e.name.toLowerCase().includes(lowerCaseQuery) ||
e.entityType.toLowerCase().includes(lowerCaseQuery) || e.entityType.toLowerCase().includes(lowerCaseQuery) ||
(Array.isArray(e.observations) && e.observations.some(o => o.toLowerCase().includes(lowerCaseQuery))) (Array.isArray(e.observations) && e.observations.some((o) => o.toLowerCase().includes(lowerCaseQuery)))
) )
const filteredEntityNames = new Set(filteredEntities.map(e => e.name)) const filteredEntityNames = new Set(filteredEntities.map((e) => e.name))
const filteredRelations = Array.from(this.relations) const filteredRelations = Array.from(this.relations)
.map(rStr => this._deserializeRelation(rStr)) .map((rStr) => this._deserializeRelation(rStr))
.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)) .filter((r) => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to))
return { return {
entities: filteredEntities, entities: filteredEntities,
@ -296,26 +307,26 @@ class KnowledgeGraphManager {
// Open operates on the in-memory graph // Open operates on the in-memory graph
async openNodes(names: string[]): Promise<KnowledgeGraph> { async openNodes(names: string[]): Promise<KnowledgeGraph> {
const nameSet = new Set(names); const nameSet = new Set(names)
const filteredEntities = Array.from(this.entities.values()).filter(e => nameSet.has(e.name)); const filteredEntities = Array.from(this.entities.values()).filter((e) => nameSet.has(e.name))
const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); const filteredEntityNames = new Set(filteredEntities.map((e) => e.name))
const filteredRelations = Array.from(this.relations) const filteredRelations = Array.from(this.relations)
.map(rStr => this._deserializeRelation(rStr)) .map((rStr) => this._deserializeRelation(rStr))
.filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)); .filter((r) => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to))
return { return {
entities: filteredEntities, entities: filteredEntities,
relations: filteredRelations relations: filteredRelations
}; }
} }
} }
class MemoryServer { class MemoryServer {
public server: Server public server: Server
// Hold the manager instance, initialized asynchronously // Hold the manager instance, initialized asynchronously
private knowledgeGraphManager: KnowledgeGraphManager | null = null; private knowledgeGraphManager: KnowledgeGraphManager | null = null
private initializationPromise: Promise<void>; // To track initialization private initializationPromise: Promise<void> // To track initialization
constructor(envPath: string = '') { constructor(envPath: string = '') {
const memoryPath = envPath const memoryPath = envPath
@ -336,33 +347,32 @@ class MemoryServer {
} }
) )
// Start initialization, but don't block constructor // Start initialization, but don't block constructor
this.initializationPromise = this._initializeManager(memoryPath); this.initializationPromise = this._initializeManager(memoryPath)
this.setupRequestHandlers(); // Setup handlers immediately this.setupRequestHandlers() // Setup handlers immediately
} }
// Private async method to handle manager initialization // Private async method to handle manager initialization
private async _initializeManager(memoryPath: string): Promise<void> { private async _initializeManager(memoryPath: string): Promise<void> {
try { try {
this.knowledgeGraphManager = await KnowledgeGraphManager.create(memoryPath); this.knowledgeGraphManager = await KnowledgeGraphManager.create(memoryPath)
console.log("KnowledgeGraphManager initialized successfully."); console.log('KnowledgeGraphManager initialized successfully.')
} catch (error) { } catch (error) {
console.error("Failed to initialize KnowledgeGraphManager:", error); console.error('Failed to initialize KnowledgeGraphManager:', error)
// Server might be unusable, consider how to handle this state // Server might be unusable, consider how to handle this state
// Maybe set a flag and return errors for all tool calls? // Maybe set a flag and return errors for all tool calls?
this.knowledgeGraphManager = null; // Ensure it's null if init fails this.knowledgeGraphManager = null // Ensure it's null if init fails
} }
} }
// Ensures the manager is initialized before handling tool calls // Ensures the manager is initialized before handling tool calls
private async _getManager(): Promise<KnowledgeGraphManager> { private async _getManager(): Promise<KnowledgeGraphManager> {
await this.initializationPromise; // Wait for initialization to complete await this.initializationPromise // Wait for initialization to complete
if (!this.knowledgeGraphManager) { if (!this.knowledgeGraphManager) {
throw new McpError(ErrorCode.InternalError, "Memory server failed to initialize. Cannot process requests."); throw new McpError(ErrorCode.InternalError, 'Memory server failed to initialize. Cannot process requests.')
} }
return this.knowledgeGraphManager; return this.knowledgeGraphManager
} }
// Setup handlers (can be called from constructor) // Setup handlers (can be called from constructor)
setupRequestHandlers() { setupRequestHandlers() {
// ListTools remains largely the same, descriptions might be updated if needed // ListTools remains largely the same, descriptions might be updated if needed
@ -371,196 +381,197 @@ class MemoryServer {
// Although ListTools itself doesn't *call* the manager, it implies the // Although ListTools itself doesn't *call* the manager, it implies the
// manager is ready to handle calls for those tools. // manager is ready to handle calls for those tools.
try { try {
await this._getManager(); // Wait for initialization before confirming tools are available await this._getManager() // Wait for initialization before confirming tools are available
} catch (error) { } catch (error) {
// If manager failed to init, maybe return an empty tool list or throw? // If manager failed to init, maybe return an empty tool list or throw?
console.error("Cannot list tools, manager initialization failed:", error); console.error('Cannot list tools, manager initialization failed:', error)
return { tools: [] }; // Return empty list if server is not ready return { tools: [] } // Return empty list if server is not ready
} }
return { return {
tools: [ tools: [
{ {
name: 'create_entities', name: 'create_entities',
description: 'Create multiple new entities in the knowledge graph. Skips existing entities.', description: 'Create multiple new entities in the knowledge graph. Skips existing entities.',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
properties: { properties: {
entities: { entities: {
type: 'array', type: 'array',
items: { items: {
type: 'object', type: 'object',
properties: { properties: {
name: { type: 'string', description: 'The name of the entity' }, name: { type: 'string', description: 'The name of the entity' },
entityType: { type: 'string', description: 'The type of the entity' }, entityType: { type: 'string', description: 'The type of the entity' },
observations: { observations: {
type: 'array', type: 'array',
items: { type: 'string' }, items: { type: 'string' },
description: 'An array of observation contents associated with the entity', description: 'An array of observation contents associated with the entity',
default: [] // Add default empty array default: [] // Add default empty array
} }
},
required: ['name', 'entityType'] // Observations are optional now on creation
}
}
},
required: ['entities']
}
},
{
name: 'create_relations',
description: 'Create multiple new relations between EXISTING entities. Skips existing relations or relations with non-existent entities.',
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']
}
}
},
required: ['relations']
}
},
{
name: 'add_observations',
description: 'Add new observations to existing entities. Skips duplicate observations.',
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']
}
}
},
required: ['observations']
}
},
{
name: 'delete_entities',
description: 'Delete multiple entities and their associated relations.',
inputSchema: {
type: 'object',
properties: {
entityNames: {
type: 'array',
items: { type: 'string' },
description: 'An array of entity names to delete'
}
},
required: ['entityNames']
}
},
{
name: 'delete_observations',
description: 'Delete specific observations from entities.',
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']
}
}
},
required: ['deletions']
}
},
{
name: 'delete_relations',
description: 'Delete multiple specific relations.',
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']
}, },
description: 'An array of relations to delete' required: ['name', 'entityType'] // Observations are optional now on creation
} }
}, }
required: ['relations'] },
} required: ['entities']
}, }
{ },
name: 'read_graph', {
description: 'Read the entire knowledge graph from memory.', name: 'create_relations',
inputSchema: { description:
type: 'object', 'Create multiple new relations between EXISTING entities. Skips existing relations or relations with non-existent entities.',
properties: {} inputSchema: {
} type: 'object',
}, properties: {
{ relations: {
name: 'search_nodes', type: 'array',
description: 'Search nodes (entities and relations) in memory based on a query.', items: {
inputSchema: { type: 'object',
type: 'object', properties: {
properties: { from: { type: 'string', description: 'The name of the entity where the relation starts' },
query: { to: { type: 'string', description: 'The name of the entity where the relation ends' },
type: 'string', relationType: { type: 'string', description: 'The type of the relation' }
description: 'The search query to match against entity names, types, and observation content' },
required: ['from', 'to', 'relationType']
} }
}, }
required: ['query'] },
} required: ['relations']
}, }
{ },
name: 'open_nodes', {
description: 'Retrieve specific entities and their connecting relations from memory by name.', name: 'add_observations',
inputSchema: { description: 'Add new observations to existing entities. Skips duplicate observations.',
type: 'object', inputSchema: {
properties: { type: 'object',
names: { properties: {
type: 'array', observations: {
items: { type: 'string' }, type: 'array',
description: 'An array of entity names to retrieve' 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: ['names'] },
} required: ['observations']
} }
] },
{
name: 'delete_entities',
description: 'Delete multiple entities and their associated relations.',
inputSchema: {
type: 'object',
properties: {
entityNames: {
type: 'array',
items: { type: 'string' },
description: 'An array of entity names to delete'
}
},
required: ['entityNames']
}
},
{
name: 'delete_observations',
description: 'Delete specific observations from entities.',
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']
}
}
},
required: ['deletions']
}
},
{
name: 'delete_relations',
description: 'Delete multiple specific relations.',
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']
},
description: 'An array of relations to delete'
}
},
required: ['relations']
}
},
{
name: 'read_graph',
description: 'Read the entire knowledge graph from memory.',
inputSchema: {
type: 'object',
properties: {}
}
},
{
name: 'search_nodes',
description: 'Search nodes (entities and relations) in memory 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']
}
},
{
name: 'open_nodes',
description: 'Retrieve specific entities and their connecting relations from memory by name.',
inputSchema: {
type: 'object',
properties: {
names: {
type: 'array',
items: { type: 'string' },
description: 'An array of entity names to retrieve'
}
},
required: ['names']
}
}
]
} }
}) })
// CallTool handler needs to await the manager and the async methods // CallTool handler needs to await the manager and the async methods
this.server.setRequestHandler(CallToolRequestSchema, async (request) => { this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const manager = await this._getManager(); // Ensure manager is ready const manager = await this._getManager() // Ensure manager is ready
const { name, arguments: args } = request.params const { name, arguments: args } = request.params
if (!args) { if (!args) {
@ -573,41 +584,75 @@ class MemoryServer {
case 'create_entities': case 'create_entities':
// Validate args structure if necessary, though SDK might do basic validation // Validate args structure if necessary, though SDK might do basic validation
if (!args.entities || !Array.isArray(args.entities)) { if (!args.entities || !Array.isArray(args.entities)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'entities' array is required.`); throw new McpError(
ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'entities' array is required.`
)
} }
return { return {
content: [{ type: 'text', text: JSON.stringify(await manager.createEntities(args.entities as Entity[]), null, 2) }] content: [
{ type: 'text', text: JSON.stringify(await manager.createEntities(args.entities as Entity[]), null, 2) }
]
} }
case 'create_relations': case 'create_relations':
if (!args.relations || !Array.isArray(args.relations)) { if (!args.relations || !Array.isArray(args.relations)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'relations' array is required.`); throw new McpError(
} ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'relations' array is required.`
)
}
return { return {
content: [{ type: 'text', text: JSON.stringify(await manager.createRelations(args.relations as Relation[]), null, 2) }] content: [
{
type: 'text',
text: JSON.stringify(await manager.createRelations(args.relations as Relation[]), null, 2)
}
]
} }
case 'add_observations': case 'add_observations':
if (!args.observations || !Array.isArray(args.observations)) { if (!args.observations || !Array.isArray(args.observations)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'observations' array is required.`); throw new McpError(
} ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'observations' array is required.`
)
}
return { return {
content: [{ type: 'text', text: JSON.stringify(await manager.addObservations(args.observations as { entityName: string; contents: string[] }[]), null, 2) }] content: [
{
type: 'text',
text: JSON.stringify(
await manager.addObservations(args.observations as { entityName: string; contents: string[] }[]),
null,
2
)
}
]
} }
case 'delete_entities': case 'delete_entities':
if (!args.entityNames || !Array.isArray(args.entityNames)) { if (!args.entityNames || !Array.isArray(args.entityNames)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'entityNames' array is required.`); throw new McpError(
} ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'entityNames' array is required.`
)
}
await manager.deleteEntities(args.entityNames as string[]) await manager.deleteEntities(args.entityNames as string[])
return { content: [{ type: 'text', text: 'Entities deleted successfully' }] } return { content: [{ type: 'text', text: 'Entities deleted successfully' }] }
case 'delete_observations': case 'delete_observations':
if (!args.deletions || !Array.isArray(args.deletions)) { if (!args.deletions || !Array.isArray(args.deletions)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'deletions' array is required.`); throw new McpError(
} ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'deletions' array is required.`
)
}
await manager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]) await manager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[])
return { content: [{ type: 'text', text: 'Observations deleted successfully' }] } return { content: [{ type: 'text', text: 'Observations deleted successfully' }] }
case 'delete_relations': case 'delete_relations':
if (!args.relations || !Array.isArray(args.relations)) { if (!args.relations || !Array.isArray(args.relations)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'relations' array is required.`); throw new McpError(
} ErrorCode.InvalidParams,
`Invalid arguments for ${name}: 'relations' array is required.`
)
}
await manager.deleteRelations(args.relations as Relation[]) await manager.deleteRelations(args.relations as Relation[])
return { content: [{ type: 'text', text: 'Relations deleted successfully' }] } return { content: [{ type: 'text', text: 'Relations deleted successfully' }] }
case 'read_graph': case 'read_graph':
@ -616,30 +661,37 @@ class MemoryServer {
content: [{ type: 'text', text: JSON.stringify(await manager.readGraph(), null, 2) }] content: [{ type: 'text', text: JSON.stringify(await manager.readGraph(), null, 2) }]
} }
case 'search_nodes': case 'search_nodes':
if (typeof args.query !== 'string') { if (typeof args.query !== 'string') {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'query' string is required.`); throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'query' string is required.`)
} }
return { return {
content: [{ type: 'text', text: JSON.stringify(await manager.searchNodes(args.query as string), null, 2) }] content: [
{ type: 'text', text: JSON.stringify(await manager.searchNodes(args.query as string), null, 2) }
]
} }
case 'open_nodes': case 'open_nodes':
if (!args.names || !Array.isArray(args.names)) { if (!args.names || !Array.isArray(args.names)) {
throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'names' array is required.`); throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'names' array is required.`)
} }
return { return {
content: [{ type: 'text', text: JSON.stringify(await manager.openNodes(args.names as string[]), null, 2) }] content: [
{ type: 'text', text: JSON.stringify(await manager.openNodes(args.names as string[]), null, 2) }
]
} }
default: default:
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`) throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`)
} }
} catch (error) { } catch (error) {
// Catch errors from manager methods (like entity not found) or other issues // Catch errors from manager methods (like entity not found) or other issues
if (error instanceof McpError) { if (error instanceof McpError) {
throw error; // Re-throw McpErrors directly throw error // Re-throw McpErrors directly
} }
console.error(`Error executing tool ${name}:`, error); console.error(`Error executing tool ${name}:`, error)
// Throw a generic internal error for unexpected issues // Throw a generic internal error for unexpected issues
throw new McpError(ErrorCode.InternalError, `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}`); throw new McpError(
ErrorCode.InternalError,
`Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}`
)
} }
}) })
} }

View File

@ -324,7 +324,6 @@ const McpSettings: React.FC<Props> = ({ server }) => {
} }
const onToggleActive = async (active: boolean) => { const onToggleActive = async (active: boolean) => {
if (isFormChanged && active) { if (isFormChanged && active) {
await onSave() await onSave()
return return

View File

@ -2,8 +2,8 @@ import '@renderer/databases'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import store, { persistor, useAppDispatch } from '@renderer/store' import store, { persistor, useAppDispatch } from '@renderer/store'
import { message } from 'antd'
import { setCustomCss } from '@renderer/store/settings' import { setCustomCss } from '@renderer/store/settings'
import { message } from 'antd'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react' import { PersistGate } from 'redux-persist/integration/react'