mcp-hosted-tools-list.test.mjs file-level

at sha256:6 · 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 * Regression guard: hosted MCP tools/list must succeed for every role.
3 * A single bad Zod → JSON Schema export (e.g. z.record(z.unknown())) fails the entire list.
4 */
5
6 import { describe, it } from 'node:test';
7 import assert from 'node:assert/strict';
8 import { Client } from '@modelcontextprotocol/sdk/client/index.js';
9 import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
10 import { createHostedMcpServer } from '../hub/gateway/mcp-hosted-server.mjs';
11
12 const CANISTER_URL = 'http://canister.test:4322';
13 const BRIDGE_URL = 'http://bridge.test:4321';
14
15 /** Golden sets: update when adding/removing tools in mcp-hosted-server.mjs */
16 const TOOLS_VIEWER = [
17 'backlinks',
18 'cluster',
19 'enrich',
20 'extract_tasks',
21 'get_document_tree',
22 'get_metadata_facets',
23 'get_note',
24 'get_note_outline',
25 'get_section_source',
26 'list_notes',
27 'relate',
28 'search',
29 'summarize',
30 'tag_suggest',
31 ];
32 const TOOLS_EDITOR = [
33 'backlinks',
34 'capture',
35 'cluster',
36 'enrich',
37 'extract_tasks',
38 'get_document_tree',
39 'get_metadata_facets',
40 'get_note',
41 'get_note_outline',
42 'get_section_source',
43 'hub_create_proposal',
44 'list_notes',
45 'relate',
46 'search',
47 'summarize',
48 'tag_suggest',
49 'transcribe',
50 'vault_sync',
51 'write',
52 ];
53 const TOOLS_ADMIN = [
54 'backlinks',
55 'capture',
56 'cluster',
57 'enrich',
58 'export',
59 'extract_tasks',
60 'get_document_tree',
61 'get_metadata_facets',
62 'get_note',
63 'get_note_outline',
64 'get_section_source',
65 'hub_create_proposal',
66 'import',
67 'import_url',
68 'index',
69 'list_notes',
70 'relate',
71 'search',
72 'summarize',
73 'tag_suggest',
74 'transcribe',
75 'vault_sync',
76 'write',
77 ];
78
79 function sortNames(names) {
80 return [...names].sort();
81 }
82
83 async function listToolNamesForRole(role) {
84 const mcpServer = createHostedMcpServer({
85 userId: 'u-test',
86 vaultId: 'v-test',
87 role,
88 token: 'tok-test',
89 canisterUrl: CANISTER_URL,
90 bridgeUrl: BRIDGE_URL,
91 gatewayApiBaseUrl: 'http://gateway.test:5555',
92 });
93 const client = new Client({ name: 'tools-list-test', version: '0.0.1' });
94 const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
95 await mcpServer.connect(serverTransport);
96 await client.connect(clientTransport);
97 try {
98 const { tools } = await client.listTools();
99 assert.ok(Array.isArray(tools), 'tools/list must return an array');
100 assert.ok(tools.length > 0, `${role}: at least one tool must be listed`);
101 for (const t of tools) {
102 assert.ok(t.name, 'each tool has a name');
103 assert.ok(
104 t.inputSchema != null && typeof t.inputSchema === 'object',
105 `tool ${t.name} must have inputSchema object (tools/list serialization)`
106 );
107 }
108 return tools.map((t) => t.name);
109 } finally {
110 try {
111 await client.close();
112 } catch (_) {}
113 }
114 }
115
116 describe('hosted MCP tools/list (JSON Schema export)', () => {
117 it('viewer role lists expected tools without throw', async () => {
118 const names = sortNames(await listToolNamesForRole('viewer'));
119 assert.deepEqual(names, TOOLS_VIEWER);
120 });
121
122 it('editor role lists expected tools without throw', async () => {
123 const names = sortNames(await listToolNamesForRole('editor'));
124 assert.deepEqual(names, TOOLS_EDITOR);
125 });
126
127 it('admin role lists expected tools without throw', async () => {
128 const names = sortNames(await listToolNamesForRole('admin'));
129 assert.deepEqual(names, TOOLS_ADMIN);
130 });
131
132 it('evaluator role lists same tools as editor (incl. hub_create_proposal)', async () => {
133 const names = sortNames(await listToolNamesForRole('evaluator'));
134 assert.deepEqual(names, TOOLS_EDITOR);
135 });
136 });