mcp-note-outline.test.mjs
97 lines 3.3 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Self-hosted MCP tests for get_note_outline.
3 *
4 * Phase 1C mirrors the CLI NoteOutline contract over MCP only. It must not
5 * introduce hosted behavior, persistence, search, memory, or note body output.
6 */
7 import { describe, it } from 'node:test';
8 import assert from 'node:assert/strict';
9 import path from 'path';
10 import { fileURLToPath } from 'url';
11 import { Client } from '@modelcontextprotocol/sdk/client/index.js';
12 import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
13
14 const __dirname = path.dirname(fileURLToPath(import.meta.url));
15 const fixtureVault = path.join(__dirname, 'fixtures', 'vault-fs');
16 process.env.KNOWTATION_VAULT_PATH = fixtureVault;
17
18 const { createKnowtationMcpServer } = await import('../mcp/create-server.mjs');
19
20 async function connectPair() {
21 process.env.KNOWTATION_VAULT_PATH = fixtureVault;
22 const mcpServer = createKnowtationMcpServer();
23 const client = new Client({ name: 'note-outline-local', version: '0.0.1' });
24 const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
25 await mcpServer.connect(serverTransport);
26 await client.connect(clientTransport);
27 return { client };
28 }
29
30 function parseToolResult(result) {
31 const text = result.content?.[0]?.text;
32 assert.equal(typeof text, 'string');
33 return JSON.parse(text);
34 }
35
36 describe('MCP get_note_outline', () => {
37 it('returns the NoteOutline JSON contract without body or frontmatter', async () => {
38 const { client } = await connectPair();
39 try {
40 const result = await client.callTool({
41 name: 'get_note_outline',
42 arguments: { path: 'inbox/one.md' },
43 });
44 const data = parseToolResult(result);
45
46 assert.equal(result.isError, undefined);
47 assert.equal(data.schema, 'knowtation.note_outline/v1');
48 assert.equal(data.path, 'inbox/one.md');
49 assert.equal(data.title, 'one');
50 assert.deepEqual(data.headings, [
51 { level: 1, text: 'Inbox one', id: 'h1-inbox-one-0001' },
52 ]);
53 assert.equal(data.truncated, false);
54 assert.equal(Object.hasOwn(data, 'body'), false);
55 assert.equal(Object.hasOwn(data, 'frontmatter'), false);
56 assert.equal(Object.hasOwn(data, 'snippet'), false);
57 assert.equal(JSON.stringify(data).includes('Body of inbox one'), false);
58 assert.equal(JSON.stringify(data).includes('/Users/'), false);
59 } finally {
60 await client.close();
61 }
62 });
63
64 it('returns an MCP JSON error for missing notes', async () => {
65 const { client } = await connectPair();
66 try {
67 const result = await client.callTool({
68 name: 'get_note_outline',
69 arguments: { path: 'inbox/missing.md' },
70 });
71 const data = parseToolResult(result);
72
73 assert.equal(result.isError, true);
74 assert.equal(data.code, 'RUNTIME_ERROR');
75 assert.match(data.error, /Note not found/);
76 } finally {
77 await client.close();
78 }
79 });
80
81 it('returns an MCP JSON error for traversal paths', async () => {
82 const { client } = await connectPair();
83 try {
84 const result = await client.callTool({
85 name: 'get_note_outline',
86 arguments: { path: '../../../etc/passwd' },
87 });
88 const data = parseToolResult(result);
89
90 assert.equal(result.isError, true);
91 assert.equal(data.code, 'RUNTIME_ERROR');
92 assert.match(data.error, /Invalid path|escape/);
93 } finally {
94 await client.close();
95 }
96 });
97 });
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 1 day ago