mcp-document-tree.test.mjs file-level

at sha256:8 · View file ↗ · Intel ↗

History
1 files
1 commits
0 hotspots
0 🧊 dead
0 💥 blast risk
sha256:9 feat(calendar): hosted bridge/gateway route parity and timeline noteRec… · aaronrene · Jun 19, 2026
1 /**
2 * Self-hosted MCP tests for get_document_tree.
3 *
4 * Phase 1D mirrors the CLI DocumentTree contract over self-hosted MCP only.
5 * It must not introduce hosted behavior, MCP resources, persistence, search,
6 * vectors, summaries, PageIndex, OCR, labels, metadata facets, or note body output.
7 */
8 import { describe, it } from 'node:test';
9 import assert from 'node:assert/strict';
10 import path from 'path';
11 import { fileURLToPath } from 'url';
12 import { Client } from '@modelcontextprotocol/sdk/client/index.js';
13 import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
14
15 const __dirname = path.dirname(fileURLToPath(import.meta.url));
16 const fixtureVault = path.join(__dirname, 'fixtures', 'vault-fs');
17 process.env.KNOWTATION_VAULT_PATH = fixtureVault;
18
19 const { createKnowtationMcpServer } = await import('../mcp/create-server.mjs');
20
21 async function connectPair() {
22 process.env.KNOWTATION_VAULT_PATH = fixtureVault;
23 const mcpServer = createKnowtationMcpServer();
24 const client = new Client({ name: 'document-tree-local', version: '0.0.1' });
25 const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
26 await mcpServer.connect(serverTransport);
27 await client.connect(clientTransport);
28 return { client };
29 }
30
31 function parseToolResult(result) {
32 const text = result.content?.[0]?.text;
33 assert.equal(typeof text, 'string');
34 return JSON.parse(text);
35 }
36
37 describe('MCP get_document_tree', () => {
38 it('returns the DocumentTree v0 JSON contract without body or frontmatter', async () => {
39 const { client } = await connectPair();
40 try {
41 const result = await client.callTool({
42 name: 'get_document_tree',
43 arguments: { path: 'inbox/one.md' },
44 });
45 const data = parseToolResult(result);
46 const serialized = JSON.stringify(data);
47
48 assert.equal(result.isError, undefined);
49 assert.equal(data.schema, 'knowtation.document_tree/v0');
50 assert.equal(data.path, 'inbox/one.md');
51 assert.equal(data.title, 'one');
52 assert.deepEqual(data.root, {
53 children: [
54 {
55 id: 'h1-inbox-one-0001',
56 level: 1,
57 text: 'Inbox one',
58 children: [],
59 },
60 ],
61 });
62 assert.equal(data.truncated, false);
63 assert.equal(Object.hasOwn(data, 'body'), false);
64 assert.equal(Object.hasOwn(data, 'frontmatter'), false);
65 assert.equal(Object.hasOwn(data, 'snippet'), false);
66 assert.equal(Object.hasOwn(data, 'summary'), false);
67 assert.equal(serialized.includes('Body of inbox one'), false);
68 assert.equal(serialized.includes('/Users/'), false);
69 } finally {
70 await client.close();
71 }
72 });
73
74 it('does not expose a document tree MCP resource URI', async () => {
75 const { client } = await connectPair();
76 try {
77 const result = await client.callTool({
78 name: 'get_document_tree',
79 arguments: { path: 'inbox/one.md' },
80 });
81 const serialized = JSON.stringify(parseToolResult(result));
82
83 assert.equal(serialized.includes('knowtation://document-tree'), false);
84 assert.equal(serialized.includes('knowtation://tree'), false);
85 } finally {
86 await client.close();
87 }
88 });
89
90 it('returns an MCP JSON error for missing notes', async () => {
91 const { client } = await connectPair();
92 try {
93 const result = await client.callTool({
94 name: 'get_document_tree',
95 arguments: { path: 'inbox/missing.md' },
96 });
97 const data = parseToolResult(result);
98
99 assert.equal(result.isError, true);
100 assert.equal(data.code, 'RUNTIME_ERROR');
101 assert.match(data.error, /Note not found/);
102 } finally {
103 await client.close();
104 }
105 });
106
107 it('returns an MCP JSON error for traversal paths', async () => {
108 const { client } = await connectPair();
109 try {
110 const result = await client.callTool({
111 name: 'get_document_tree',
112 arguments: { path: '../../../etc/passwd' },
113 });
114 const data = parseToolResult(result);
115
116 assert.equal(result.isError, true);
117 assert.equal(data.code, 'RUNTIME_ERROR');
118 assert.match(data.error, /Invalid path|escape/);
119 } finally {
120 await client.close();
121 }
122 });
123 });