mcp-metadata-facets.test.mjs
129 lines 4.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_metadata_facets.
3 *
4 * Phase 1C mirrors the approved CLI MetadataFacets v0 contract over MCP only.
5 * It must not introduce hosted behavior, persistence, search, memory, indexing,
6 * label text, OCR, PageIndex, media metadata, 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: 'metadata-facets-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_metadata_facets', () => {
38 it('returns the MetadataFacets v0 JSON contract without body or frontmatter', async () => {
39 const { client } = await connectPair();
40 try {
41 const result = await client.callTool({
42 name: 'get_metadata_facets',
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.deepEqual(data, {
50 schema: 'knowtation.metadata_facets/v0',
51 path: 'inbox/one.md',
52 facets: {
53 project: 'foo',
54 tags: ['a', 'b'],
55 date: '2025-03-01T00:00:00.000Z',
56 updated: null,
57 causal_chain_id: null,
58 entity: [],
59 episode_id: null,
60 },
61 inferred: {
62 folder: 'inbox',
63 source_type: null,
64 },
65 truncated: false,
66 });
67 assert.equal(Object.hasOwn(data, 'body'), false);
68 assert.equal(Object.hasOwn(data, 'frontmatter'), false);
69 assert.equal(Object.hasOwn(data, 'snippet'), false);
70 assert.equal(Object.hasOwn(data, 'summary'), false);
71 assert.equal(Object.hasOwn(data, 'labels'), false);
72 assert.equal(Object.hasOwn(data, 'metadata_facets'), false);
73 assert.equal(serialized.includes('Body of inbox one'), false);
74 assert.equal(serialized.includes('/Users/'), false);
75 } finally {
76 await client.close();
77 }
78 });
79
80 it('does not expose a metadata facets MCP resource URI', async () => {
81 const { client } = await connectPair();
82 try {
83 const result = await client.callTool({
84 name: 'get_metadata_facets',
85 arguments: { path: 'inbox/one.md' },
86 });
87 const serialized = JSON.stringify(parseToolResult(result));
88
89 assert.equal(serialized.includes('knowtation://metadata-facets'), false);
90 assert.equal(serialized.includes('knowtation://facets'), false);
91 } finally {
92 await client.close();
93 }
94 });
95
96 it('returns an MCP JSON error for missing notes', async () => {
97 const { client } = await connectPair();
98 try {
99 const result = await client.callTool({
100 name: 'get_metadata_facets',
101 arguments: { path: 'inbox/missing.md' },
102 });
103 const data = parseToolResult(result);
104
105 assert.equal(result.isError, true);
106 assert.equal(data.code, 'RUNTIME_ERROR');
107 assert.match(data.error, /Note not found/);
108 } finally {
109 await client.close();
110 }
111 });
112
113 it('returns an MCP JSON error for traversal paths', async () => {
114 const { client } = await connectPair();
115 try {
116 const result = await client.callTool({
117 name: 'get_metadata_facets',
118 arguments: { path: '../../../etc/passwd' },
119 });
120 const data = parseToolResult(result);
121
122 assert.equal(result.isError, true);
123 assert.equal(data.code, 'RUNTIME_ERROR');
124 assert.match(data.error, /Invalid path|escape/);
125 } finally {
126 await client.close();
127 }
128 });
129 });
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