From eb7d9319a35796d4dd96f71f013dea59b4208a97 Mon Sep 17 00:00:00 2001 From: suguanYang Date: Wed, 8 Jul 2026 11:34:51 +0800 Subject: [PATCH] fix(mcp): remove blocking parse tools --- .changeset/remove-sync-mcp-parse-tools.md | 5 ++ packages/mcp/README.md | 17 ++---- packages/mcp/src/__tests__/mcp.test.ts | 74 +++++------------------ packages/mcp/src/index.ts | 54 ----------------- packages/mcp/src/tool-result-formatter.ts | 59 ------------------ 5 files changed, 27 insertions(+), 182 deletions(-) create mode 100644 .changeset/remove-sync-mcp-parse-tools.md diff --git a/.changeset/remove-sync-mcp-parse-tools.md b/.changeset/remove-sync-mcp-parse-tools.md new file mode 100644 index 0000000..10fe199 --- /dev/null +++ b/.changeset/remove-sync-mcp-parse-tools.md @@ -0,0 +1,5 @@ +--- +"@ontos-ai/knowhere-mcp": major +--- + +Remove the blocking `knowhere_parse_url` and `knowhere_parse_file` tools from the MCP server. Agents should use the async parse tools and poll `knowhere_async_get_job_status` before reading parsed results. diff --git a/packages/mcp/README.md b/packages/mcp/README.md index b0a5b69..aeb8094 100644 --- a/packages/mcp/README.md +++ b/packages/mcp/README.md @@ -30,8 +30,8 @@ During login, the dashboard asks for a Permission: - Read only: query Knowhere and read existing parsed documents. Parse and delete tools are not exposed to the MCP host. -- Full access: query, read, parse URLs/files, cache completed parse jobs, and - archive documents. +- Full access: query, read, start async URL/file parse jobs, cache completed + parse jobs, and archive documents. Useful auth commands: @@ -178,12 +178,6 @@ When logged in with Read only permission, the MCP server exposes only `knowhere_get_document_outline`, `knowhere_read_chunks`, `knowhere_grep_chunks`, and `knowhere_async_get_job_status`. -- `knowhere_parse_url`: blocking parse for a remote URL; waits for completion - and makes the parsed document available to outline/read/grep/search tools. -- `knowhere_parse_file`: blocking parse for a file path available to the MCP - process; waits for completion and makes the parsed document available to - outline/read/grep/search tools. The path is resolved on the machine running - this stdio MCP server. - `knowhere_async_parse_url`: start parsing a remote URL and return the job immediately. When checking status, use exponential backoff. - `knowhere_async_parse_file`: start parsing a local file path, upload it if @@ -248,9 +242,10 @@ The following `` tells callers to open or fetch the listed `assetUrl` before relying on preview text. If a page asset exists without an `assetUrl`, the text says that the asset is not directly readable. -Blocking parse responses summarize document/job identifiers, chunk counts, and -asset URL mappings. They intentionally do not dump the full parse result, ZIP -bytes, or every chunk. +Async parse responses return the job identifier and source metadata +immediately. Use `knowhere_async_get_job_status` to poll the job until it +completes, then pass the returned `localDocumentId`, `documentId`, or `jobId` +to outline, read, or grep tools. ## Package Boundary diff --git a/packages/mcp/src/__tests__/mcp.test.ts b/packages/mcp/src/__tests__/mcp.test.ts index fb323d4..565c08d 100644 --- a/packages/mcp/src/__tests__/mcp.test.ts +++ b/packages/mcp/src/__tests__/mcp.test.ts @@ -17,7 +17,6 @@ describe('knowhere MCP wrapper', () => { const toolNames = tools.tools.map((tool) => tool.name).sort(); const statusTool = tools.tools.find((tool) => tool.name === 'knowhere_async_get_job_status'); const listTool = tools.tools.find((tool) => tool.name === 'knowhere_list_documents'); - const parseFileTool = tools.tools.find((tool) => tool.name === 'knowhere_parse_file'); const asyncParseFileTool = tools.tools.find( (tool) => tool.name === 'knowhere_async_parse_file', ); @@ -32,8 +31,6 @@ describe('knowhere MCP wrapper', () => { 'knowhere_get_document_outline', 'knowhere_grep_chunks', 'knowhere_list_documents', - 'knowhere_parse_file', - 'knowhere_parse_url', 'knowhere_read_chunks', 'knowhere_search', ]); @@ -43,9 +40,6 @@ describe('knowhere MCP wrapper', () => { expect(statusTool?.description).toContain('5s, 10s, 20s, 40s, 80s'); expect(statusTool?.description).toContain('Large PDFs or OCR-heavy files can take 10+ minutes'); expect(listTool?.description).toContain('remote API'); - expect(parseFileTool?.description).toContain( - 'resolved on the machine running this stdio MCP server', - ); expect(asyncParseFileTool?.description).toContain( 'resolved on the machine running this stdio MCP server', ); @@ -104,12 +98,12 @@ describe('knowhere MCP wrapper', () => { await server.close(); }); - it('should delegate parse and grep calls to the SDK knowledge module', async () => { + it('should delegate async parse and grep calls to the SDK knowledge module', async () => { const knowhereClient = createClient(); const { client, server } = await connectTestClient(knowhereClient); const parseResponse = await client.callTool({ - name: 'knowhere_parse_file', + name: 'knowhere_async_parse_file', arguments: { file: './report.md', localDocumentId: 'local-report', @@ -127,7 +121,7 @@ describe('knowhere MCP wrapper', () => { }, }); - expect(knowhereClient.knowledge.parseToLocalCache).toHaveBeenCalledWith({ + expect(knowhereClient.knowledge.startParse).toHaveBeenCalledWith({ file: './report.md', fileName: undefined, namespace: undefined, @@ -149,16 +143,8 @@ describe('knowhere MCP wrapper', () => { }); expectToolText( parseResponse, - ` - - - - - - - - - + ` + `, ); expectToolText( @@ -807,44 +793,18 @@ function createClient(): Knowhere & { knowledge: KnowledgeWithMocks; } { const knowledge: KnowledgeWithMocks = { - parseToLocalCache: vi.fn().mockResolvedValue({ - document: createLocalDocument(), - result: { - jobId: 'job-1', - documentId: 'doc-1', - namespace: 'support-center', - manifest: { - jobId: 'job-1', - sourceFileName: 'report.md', - statistics: { - totalChunks: 3, - textChunks: 1, - imageChunks: 0, - tableChunks: 0, - pageChunks: 2, - }, + startParse: vi.fn().mockImplementation((params: Parameters[0]) => + Promise.resolve({ + job: { + jobId: 'job-async', + status: 'running', + sourceType: 'file' in params ? 'file' : 'url', + documentId: 'doc-async', + namespace: 'support-center', }, - rawZip: Buffer.from('not rendered'), - chunks: [ - { - chunkId: 'not-rendered', - }, - ], - }, - assetUrlsByFilePath: { - 'page_citation_assets/page-1.png': 'https://assets.example/page-1.png', - }, - }), - startParse: vi.fn().mockResolvedValue({ - job: { - jobId: 'job-async', - status: 'running', - sourceType: 'url', - documentId: 'doc-async', - namespace: 'support-center', - }, - localDocumentId: 'local-report', - }), + localDocumentId: 'local-report', + }), + ), getJobStatus: vi.fn().mockResolvedValue({ job: { jobId: 'job-async', @@ -996,7 +956,6 @@ type ToolCallResponse = Awaited>; type KnowledgeWithMocks = Pick< Knowledge, - | 'parseToLocalCache' | 'startParse' | 'getJobStatus' | 'importJobResult' @@ -1009,7 +968,6 @@ type KnowledgeWithMocks = Pick< | 'withCacheDirectory' | 'withParsedStorage' > & { - parseToLocalCache: Mock; startParse: Mock; getJobStatus: Mock; importJobResult: Mock; diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts index 76c4326..95a543a 100644 --- a/packages/mcp/src/index.ts +++ b/packages/mcp/src/index.ts @@ -62,60 +62,6 @@ export async function createKnowhereMcpServer( const hasWritePermission = permission === 'full_access'; if (hasWritePermission) { - server.registerTool( - 'knowhere_parse_url', - { - description: - 'Blocking parse: submit a remote URL to Knowhere, wait for completion, then make the parsed document available to outline/read/grep/search tools.', - inputSchema: { - url: z.string().url(), - namespace: z.string().optional(), - localDocumentId: z.string().optional(), - dataId: z.string().optional(), - parsingParams: parsingParamsSchema, - }, - }, - async (input) => - createToolResult( - 'parseUrl', - await knowledge.parseToLocalCache({ - url: input.url, - namespace: input.namespace, - localDocumentId: input.localDocumentId, - dataId: input.dataId, - ...toFlatParsingParams(input.parsingParams), - }), - ), - ); - - server.registerTool( - 'knowhere_parse_file', - { - description: - 'Blocking parse: submit a local file path available to this MCP process, wait for completion, then make the parsed document available to outline/read/grep/search tools. The file path is resolved on the machine running this stdio MCP server, not on a remote chat client.', - inputSchema: { - file: z.string().describe('Local file path available to this MCP server process.'), - fileName: z.string().optional(), - namespace: z.string().optional(), - localDocumentId: z.string().optional(), - dataId: z.string().optional(), - parsingParams: parsingParamsSchema, - }, - }, - async (input) => - createToolResult( - 'parseFile', - await knowledge.parseToLocalCache({ - file: input.file, - fileName: input.fileName, - namespace: input.namespace, - localDocumentId: input.localDocumentId, - dataId: input.dataId, - ...toFlatParsingParams(input.parsingParams), - }), - ), - ); - server.registerTool( 'knowhere_async_parse_url', { diff --git a/packages/mcp/src/tool-result-formatter.ts b/packages/mcp/src/tool-result-formatter.ts index 365f3e0..f05f542 100644 --- a/packages/mcp/src/tool-result-formatter.ts +++ b/packages/mcp/src/tool-result-formatter.ts @@ -51,10 +51,6 @@ function formatOperationResult(operation: string, result: unknown): string { const lines: string[] = [``]; switch (operation) { - case 'parseUrl': - case 'parseFile': - appendBlockingParseResult(lines, result); - break; case 'asyncParseUrl': case 'asyncParseFile': appendAsyncParseResult(lines, result); @@ -92,13 +88,6 @@ function formatOperationResult(operation: string, result: unknown): string { return lines.join('\n'); } -function appendBlockingParseResult(lines: string[], result: unknown): void { - const response: UnknownRecord | undefined = toRecord(result); - appendDocument(lines, readRecord(response, 'document'), 1); - appendParseSummary(lines, readRecord(response, 'result'), 1); - appendAssetUrls(lines, readRecord(response, 'assetUrlsByFilePath'), 1); -} - function appendAsyncParseResult(lines: string[], result: unknown): void { const response: UnknownRecord | undefined = toRecord(result); const job: UnknownRecord | undefined = readRecord(response, 'job'); @@ -256,54 +245,6 @@ function appendSearchResult(lines: string[], result: unknown): void { lines.push(`${indent(1)}`); } -function appendParseSummary( - lines: string[], - result: UnknownRecord | undefined, - depth: number, -): void { - const manifest: UnknownRecord | undefined = readRecord(result, 'manifest'); - const statistics: UnknownRecord | undefined = readRecord(manifest, 'statistics'); - lines.push( - `${indent(depth)}`, - ); - appendChunkCounts(lines, statistics, depth + 1, readNumber(statistics, 'totalChunks')); - lines.push(`${indent(depth)}`); -} - -function appendAssetUrls( - lines: string[], - assetUrlsByFilePath: UnknownRecord | undefined, - depth: number, -): void { - if (!assetUrlsByFilePath) { - return; - } - - const entries: readonly [string, string][] = Object.entries(assetUrlsByFilePath).filter( - (entry): entry is [string, string] => typeof entry[1] === 'string', - ); - if (entries.length === 0) { - return; - } - - lines.push(`${indent(depth)}`); - for (const [filePath, assetUrl] of entries) { - appendSelfClosingTag(lines, depth + 1, { - name: 'assetUrl', - attributes: { - filePath, - assetUrl, - }, - }); - } - lines.push(`${indent(depth)}`); -} - function appendDocument(lines: string[], document: UnknownRecord | undefined, depth: number): void { if (!document) { appendSelfClosingTag(lines, depth, { name: 'document' });