From c5ea82f6bfe60742835a8244777d672a2a5d5db9 Mon Sep 17 00:00:00 2001 From: cadenli Date: Tue, 18 Aug 2026 11:02:40 +0800 Subject: [PATCH] fix: accept standard tools list metadata --- scripts/memory-mcp.mjs | 12 +++++++-- tests/scripts/memory-mcp.test.js | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/scripts/memory-mcp.mjs b/scripts/memory-mcp.mjs index 5cde5e80e..7821efbfc 100755 --- a/scripts/memory-mcp.mjs +++ b/scripts/memory-mcp.mjs @@ -423,8 +423,16 @@ function createMemoryMcpService(options = {}) { return jsonRpcResult(message.id, {}); } if (message.method === 'tools/list') { - if (message.params && Object.keys(message.params).length > 0) { - return jsonRpcError(message.id, -32602, 'tools/list does not accept parameters.'); + const params = message.params || {}; + if ( + (Object.prototype.hasOwnProperty.call(params, '_meta') && !isRecord(params._meta)) + || ( + Object.prototype.hasOwnProperty.call(params, 'cursor') + && typeof params.cursor !== 'string' + ) + || Object.keys(params).some(key => !['cursor', '_meta'].includes(key)) + ) { + return jsonRpcError(message.id, -32602, 'Invalid tools/list parameters.'); } return jsonRpcResult(message.id, { tools: TOOL_DEFINITIONS.map(tool => ({ ...tool })), diff --git a/tests/scripts/memory-mcp.test.js b/tests/scripts/memory-mcp.test.js index 0a3a7a7a0..a234d28a6 100644 --- a/tests/scripts/memory-mcp.test.js +++ b/tests/scripts/memory-mcp.test.js @@ -132,6 +132,7 @@ async function withClient(fn, options = {}) { const client = { listTools: () => request('tools/list'), + listToolsRaw: params => request('tools/list', params), callTool: ({ name, arguments: toolArguments }) => request( 'tools/call', { name, arguments: toolArguments } @@ -181,6 +182,51 @@ async function main() { }); }); + await test('accepts reserved tools/list params and rejects malformed values', async () => { + await withClient(async client => { + const withMeta = await client.listToolsRaw({ + _meta: { progressToken: 'progress-123' }, + }); + assert.strictEqual(withMeta.tools.length, 4); + + const withCursor = await client.listToolsRaw({ cursor: 'next-page' }); + assert.strictEqual(withCursor.tools.length, 4); + + const withCursorAndMeta = await client.listToolsRaw({ + cursor: 'next-page', + _meta: { progressToken: 'progress-456' }, + }); + assert.strictEqual(withCursorAndMeta.tools.length, 4); + + const withoutMeta = await client.listTools(); + assert.deepStrictEqual( + withoutMeta.tools.map(tool => tool.name).sort(), + ['memory_doctor', 'memory_read', 'memory_save', 'memory_search'] + ); + + for (const badMeta of [null, ['not', 'an', 'object'], 'string', 42, true]) { + await assert.rejects( + client.listToolsRaw({ _meta: badMeta }), + /-32602/, + `expected _meta=${JSON.stringify(badMeta)} to be rejected` + ); + } + + for (const badCursor of [null, {}, [], 42, true]) { + await assert.rejects( + client.listToolsRaw({ cursor: badCursor }), + /-32602/, + `expected cursor=${JSON.stringify(badCursor)} to be rejected` + ); + } + + await assert.rejects( + client.listToolsRaw({ unexpected: true }), + /-32602/ + ); + }); + }); + await test('accepts the reserved _meta param on tools/call and rejects malformed values', async () => { await withClient(async client => { // A valid `_meta` object (e.g. progressToken) must not block the tool call.