diff --git a/src/main/mcpServers/memory.ts b/src/main/mcpServers/memory.ts index 9b4d2d4c..211b5f22 100644 --- a/src/main/mcpServers/memory.ts +++ b/src/main/mcpServers/memory.ts @@ -1,9 +1,9 @@ import { getConfigDir } from '@main/utils/file' 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 path from 'path' -import { Mutex } from 'async-mutex' // 引入 Mutex // Define memory file path const defaultMemoryPath = path.join(getConfigDir(), 'memory.json') @@ -62,7 +62,10 @@ class KnowledgeGraphManager { } catch (error) { console.error('Failed to ensure memory path exists:', error) // 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) this.entities.clear() this.relations.clear() - graph.entities.forEach(entity => this.entities.set(entity.name, entity)) - graph.relations.forEach(relation => this.relations.add(this._serializeRelation(relation))) + graph.entities.forEach((entity) => this.entities.set(entity.name, entity)) + graph.relations.forEach((relation) => this.relations.add(this._serializeRelation(relation))) } catch (error) { 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) @@ -90,14 +93,17 @@ class KnowledgeGraphManager { this.relations = new Set() await this._persistGraph() // Create the file with empty structure } else if (error instanceof SyntaxError) { - console.error('Failed to parse memory.json, initializing with empty graph:', error) - // If JSON is invalid, start fresh and overwrite the corrupted file - this.entities = new Map() - this.relations = new Set() - await this._persistGraph() + console.error('Failed to parse memory.json, initializing with empty graph:', error) + // If JSON is invalid, start fresh and overwrite the corrupted file + this.entities = new Map() + this.relations = new Set() + await this._persistGraph() } else { 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 { const graphData: KnowledgeGraph = { 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)) } catch (error) { console.error('Failed to save knowledge graph:', error) // 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 { release() } @@ -133,10 +142,10 @@ class KnowledgeGraphManager { async createEntities(entities: Entity[]): Promise { const newEntities: Entity[] = [] - entities.forEach(entity => { + entities.forEach((entity) => { if (!this.entities.has(entity.name)) { // 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) newEntities.push(newEntity) } @@ -149,11 +158,11 @@ class KnowledgeGraphManager { async createRelations(relations: Relation[]): Promise { const newRelations: Relation[] = [] - relations.forEach(relation => { + relations.forEach((relation) => { // Ensure related entities exist before creating a relation 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}`) - return; // Skip this relation + console.warn(`Skipping relation creation: Entity not found for relation ${relation.from} -> ${relation.to}`) + return // Skip this relation } const relationStr = this._serializeRelation(relation) if (!this.relations.has(relationStr)) { @@ -172,20 +181,20 @@ class KnowledgeGraphManager { ): Promise<{ entityName: string; addedObservations: string[] }[]> { const results: { entityName: string; addedObservations: string[] }[] = [] let changed = false - observations.forEach(o => { + observations.forEach((o) => { const entity = this.entities.get(o.entityName) if (!entity) { // 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 // console.warn(`Entity with name ${o.entityName} not found when adding observations. Skipping.`); // return; } // Ensure observations array exists 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) { entity.observations.push(...newObservations) results.push({ entityName: o.entityName, addedObservations: newObservations }) @@ -206,7 +215,7 @@ class KnowledgeGraphManager { const namesToDelete = new Set(entityNames) // Delete entities - namesToDelete.forEach(name => { + namesToDelete.forEach((name) => { if (this.entities.delete(name)) { changed = true } @@ -214,14 +223,14 @@ class KnowledgeGraphManager { // Delete relations involving deleted entities const relationsToDelete = new Set() - this.relations.forEach(relStr => { + this.relations.forEach((relStr) => { const rel = this._deserializeRelation(relStr) if (namesToDelete.has(rel.from) || namesToDelete.has(rel.to)) { relationsToDelete.add(relStr) } }) - relationsToDelete.forEach(relStr => { + relationsToDelete.forEach((relStr) => { if (this.relations.delete(relStr)) { changed = true } @@ -234,12 +243,12 @@ class KnowledgeGraphManager { async deleteObservations(deletions: { entityName: string; observations: string[] }[]): Promise { let changed = false - deletions.forEach(d => { + deletions.forEach((d) => { const entity = this.entities.get(d.entityName) if (entity && Array.isArray(entity.observations)) { const initialLength = entity.observations.length 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) { changed = true } @@ -252,7 +261,7 @@ class KnowledgeGraphManager { async deleteRelations(relations: Relation[]): Promise { let changed = false - relations.forEach(rel => { + relations.forEach((rel) => { const relStr = this._serializeRelation(rel) if (this.relations.delete(relStr)) { changed = true @@ -266,27 +275,29 @@ class KnowledgeGraphManager { // Read the current state from memory async readGraph(): Promise { // 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()), - 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 async searchNodes(query: string): Promise { const lowerCaseQuery = query.toLowerCase() const filteredEntities = Array.from(this.entities.values()).filter( - e => + (e) => e.name.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) - .map(rStr => this._deserializeRelation(rStr)) - .filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)) + .map((rStr) => this._deserializeRelation(rStr)) + .filter((r) => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)) return { entities: filteredEntities, @@ -296,26 +307,26 @@ class KnowledgeGraphManager { // Open operates on the in-memory graph async openNodes(names: string[]): Promise { - const nameSet = new Set(names); - const filteredEntities = Array.from(this.entities.values()).filter(e => nameSet.has(e.name)); - const filteredEntityNames = new Set(filteredEntities.map(e => e.name)); + const nameSet = new Set(names) + const filteredEntities = Array.from(this.entities.values()).filter((e) => nameSet.has(e.name)) + const filteredEntityNames = new Set(filteredEntities.map((e) => e.name)) - const filteredRelations = Array.from(this.relations) - .map(rStr => this._deserializeRelation(rStr)) - .filter(r => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)); + const filteredRelations = Array.from(this.relations) + .map((rStr) => this._deserializeRelation(rStr)) + .filter((r) => filteredEntityNames.has(r.from) && filteredEntityNames.has(r.to)) - return { - entities: filteredEntities, - relations: filteredRelations - }; + return { + entities: filteredEntities, + relations: filteredRelations + } } } class MemoryServer { public server: Server // Hold the manager instance, initialized asynchronously - private knowledgeGraphManager: KnowledgeGraphManager | null = null; - private initializationPromise: Promise; // To track initialization + private knowledgeGraphManager: KnowledgeGraphManager | null = null + private initializationPromise: Promise // To track initialization constructor(envPath: string = '') { const memoryPath = envPath @@ -336,33 +347,32 @@ class MemoryServer { } ) // Start initialization, but don't block constructor - this.initializationPromise = this._initializeManager(memoryPath); - this.setupRequestHandlers(); // Setup handlers immediately + this.initializationPromise = this._initializeManager(memoryPath) + this.setupRequestHandlers() // Setup handlers immediately } // Private async method to handle manager initialization private async _initializeManager(memoryPath: string): Promise { - try { - this.knowledgeGraphManager = await KnowledgeGraphManager.create(memoryPath); - console.log("KnowledgeGraphManager initialized successfully."); - } catch (error) { - console.error("Failed to initialize KnowledgeGraphManager:", error); - // Server might be unusable, consider how to handle this state - // Maybe set a flag and return errors for all tool calls? - this.knowledgeGraphManager = null; // Ensure it's null if init fails - } + try { + this.knowledgeGraphManager = await KnowledgeGraphManager.create(memoryPath) + console.log('KnowledgeGraphManager initialized successfully.') + } catch (error) { + console.error('Failed to initialize KnowledgeGraphManager:', error) + // Server might be unusable, consider how to handle this state + // Maybe set a flag and return errors for all tool calls? + this.knowledgeGraphManager = null // Ensure it's null if init fails + } } // Ensures the manager is initialized before handling tool calls private async _getManager(): Promise { - await this.initializationPromise; // Wait for initialization to complete - if (!this.knowledgeGraphManager) { - throw new McpError(ErrorCode.InternalError, "Memory server failed to initialize. Cannot process requests."); - } - return this.knowledgeGraphManager; + await this.initializationPromise // Wait for initialization to complete + if (!this.knowledgeGraphManager) { + throw new McpError(ErrorCode.InternalError, 'Memory server failed to initialize. Cannot process requests.') + } + return this.knowledgeGraphManager } - // Setup handlers (can be called from constructor) setupRequestHandlers() { // 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 // manager is ready to handle calls for those tools. 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) { - // If manager failed to init, maybe return an empty tool list or throw? - console.error("Cannot list tools, manager initialization failed:", error); - return { tools: [] }; // Return empty list if server is not ready + // If manager failed to init, maybe return an empty tool list or throw? + console.error('Cannot list tools, manager initialization failed:', error) + return { tools: [] } // Return empty list if server is not ready } return { tools: [ - { - name: 'create_entities', - description: 'Create multiple new entities in the knowledge graph. Skips existing entities.', - inputSchema: { - 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', - 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'] + { + name: 'create_entities', + description: 'Create multiple new entities in the knowledge graph. Skips existing entities.', + inputSchema: { + 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', + default: [] // Add default empty array + } }, - description: 'An array of relations to delete' + required: ['name', 'entityType'] // Observations are optional now on creation } - }, - 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: ['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: ['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: ['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: ['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 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 if (!args) { @@ -573,41 +584,75 @@ class MemoryServer { case 'create_entities': // Validate args structure if necessary, though SDK might do basic validation 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 { - 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': - if (!args.relations || !Array.isArray(args.relations)) { - throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'relations' array is required.`); - } + if (!args.relations || !Array.isArray(args.relations)) { + throw new McpError( + ErrorCode.InvalidParams, + `Invalid arguments for ${name}: 'relations' array is required.` + ) + } 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': - if (!args.observations || !Array.isArray(args.observations)) { - throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'observations' array is required.`); - } + if (!args.observations || !Array.isArray(args.observations)) { + throw new McpError( + ErrorCode.InvalidParams, + `Invalid arguments for ${name}: 'observations' array is required.` + ) + } 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': - if (!args.entityNames || !Array.isArray(args.entityNames)) { - throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'entityNames' array is required.`); - } + if (!args.entityNames || !Array.isArray(args.entityNames)) { + throw new McpError( + ErrorCode.InvalidParams, + `Invalid arguments for ${name}: 'entityNames' array is required.` + ) + } await manager.deleteEntities(args.entityNames as string[]) return { content: [{ type: 'text', text: 'Entities deleted successfully' }] } case 'delete_observations': - if (!args.deletions || !Array.isArray(args.deletions)) { - throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'deletions' array is required.`); - } + if (!args.deletions || !Array.isArray(args.deletions)) { + throw new McpError( + ErrorCode.InvalidParams, + `Invalid arguments for ${name}: 'deletions' array is required.` + ) + } await manager.deleteObservations(args.deletions as { entityName: string; observations: string[] }[]) return { content: [{ type: 'text', text: 'Observations deleted successfully' }] } case 'delete_relations': - if (!args.relations || !Array.isArray(args.relations)) { - throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'relations' array is required.`); - } + if (!args.relations || !Array.isArray(args.relations)) { + throw new McpError( + ErrorCode.InvalidParams, + `Invalid arguments for ${name}: 'relations' array is required.` + ) + } await manager.deleteRelations(args.relations as Relation[]) return { content: [{ type: 'text', text: 'Relations deleted successfully' }] } case 'read_graph': @@ -616,30 +661,37 @@ class MemoryServer { content: [{ type: 'text', text: JSON.stringify(await manager.readGraph(), null, 2) }] } case 'search_nodes': - if (typeof args.query !== 'string') { - throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'query' string is required.`); - } + if (typeof args.query !== 'string') { + throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'query' string is required.`) + } 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': - if (!args.names || !Array.isArray(args.names)) { - throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'names' array is required.`); - } + if (!args.names || !Array.isArray(args.names)) { + throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${name}: 'names' array is required.`) + } 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: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`) } } catch (error) { - // Catch errors from manager methods (like entity not found) or other issues - if (error instanceof McpError) { - throw error; // Re-throw McpErrors directly - } - console.error(`Error executing tool ${name}:`, error); - // Throw a generic internal error for unexpected issues - throw new McpError(ErrorCode.InternalError, `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}`); + // Catch errors from manager methods (like entity not found) or other issues + if (error instanceof McpError) { + throw error // Re-throw McpErrors directly + } + console.error(`Error executing tool ${name}:`, error) + // Throw a generic internal error for unexpected issues + throw new McpError( + ErrorCode.InternalError, + `Error executing tool ${name}: ${error instanceof Error ? error.message : String(error)}` + ) } }) } diff --git a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx index 251b2ba5..c48dbf48 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpSettings.tsx @@ -324,7 +324,6 @@ const McpSettings: React.FC = ({ server }) => { } const onToggleActive = async (active: boolean) => { - if (isFormChanged && active) { await onSave() return diff --git a/src/renderer/src/windows/mini/App.tsx b/src/renderer/src/windows/mini/App.tsx index 416ba11b..986c6f16 100644 --- a/src/renderer/src/windows/mini/App.tsx +++ b/src/renderer/src/windows/mini/App.tsx @@ -2,8 +2,8 @@ import '@renderer/databases' import { useSettings } from '@renderer/hooks/useSettings' import store, { persistor, useAppDispatch } from '@renderer/store' -import { message } from 'antd' import { setCustomCss } from '@renderer/store/settings' +import { message } from 'antd' import { useEffect, useState } from 'react' import { Provider } from 'react-redux' import { PersistGate } from 'redux-persist/integration/react'