mcp-hosted-extract-tasks.test.mjs
146 lines 4.1 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Hosted MCP `extract_tasks` — canister list + bodies + checkbox scan.
3 */
4
5 import { describe, it, afterEach } from 'node:test';
6 import assert from 'node:assert/strict';
7 import { Client } from '@modelcontextprotocol/sdk/client/index.js';
8 import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
9 import { createHostedMcpServer } from '../hub/gateway/mcp-hosted-server.mjs';
10
11 const CANISTER_URL = 'http://canister.test:4322';
12 const BRIDGE_URL = 'http://bridge.test:4321';
13
14 function makeCtx(overrides = {}) {
15 return {
16 userId: 'u-1',
17 vaultId: 'v-1',
18 role: 'viewer',
19 token: 'tok-test',
20 canisterUrl: CANISTER_URL,
21 bridgeUrl: BRIDGE_URL,
22 ...overrides,
23 };
24 }
25
26 function installExtractTasksFetchMock({ emptyListBody = false } = {}) {
27 const calls = [];
28 const origFetch = globalThis.fetch;
29 globalThis.fetch = async (url) => {
30 const u = String(url);
31 calls.push({ url: u });
32 if (u.includes(`${CANISTER_URL}/api/v1/notes?`)) {
33 const bodyField = emptyListBody ? '' : '- [ ] One task\n';
34 return {
35 ok: true,
36 status: 200,
37 json: async () => ({
38 notes: [
39 {
40 path: 'inbox/a.md',
41 frontmatter: '{"date":"2026-04-01","tags":["alpha"]}',
42 body: bodyField,
43 },
44 {
45 path: 'other/b.md',
46 frontmatter: '{}',
47 body: '- [x] Done thing\n',
48 },
49 ],
50 total: 2,
51 }),
52 text: async () => '{}',
53 };
54 }
55 if (u.includes(`${CANISTER_URL}/api/v1/notes/inbox%2Fa.md`) || u.includes('/notes/inbox/a.md')) {
56 return {
57 ok: true,
58 status: 200,
59 json: async () => ({
60 path: 'inbox/a.md',
61 frontmatter: '{"date":"2026-04-01","tags":["alpha"]}',
62 body: '- [ ] One task\n',
63 }),
64 text: async () => '{}',
65 };
66 }
67 return {
68 ok: false,
69 status: 404,
70 json: async () => ({}),
71 text: async () => 'not found',
72 };
73 };
74 return {
75 calls,
76 restore() {
77 globalThis.fetch = origFetch;
78 },
79 };
80 }
81
82 async function connectPair(ctx) {
83 const mcpServer = createHostedMcpServer(ctx ?? makeCtx());
84 const client = new Client({ name: 'extract-tasks-test', version: '0.0.1' });
85 const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
86 await mcpServer.connect(serverTransport);
87 await client.connect(clientTransport);
88 return { client, mcpServer };
89 }
90
91 describe('hosted MCP extract_tasks', () => {
92 let mock = { restore() {} };
93
94 afterEach(() => {
95 mock.restore();
96 });
97
98 it('returns open checkbox tasks from list row bodies', async () => {
99 mock = installExtractTasksFetchMock();
100 const { client } = await connectPair();
101
102 const result = await client.callTool({
103 name: 'extract_tasks',
104 arguments: { status: 'open' },
105 });
106
107 assert.ok(!result.isError);
108 const out = JSON.parse(result.content[0].text);
109 assert.equal(out.tasks.length, 1);
110 assert.equal(out.tasks[0].text, 'One task');
111 assert.equal(out.tasks[0].path, 'inbox/a.md');
112 assert.equal(out.tasks[0].status, 'open');
113 assert.equal(out.extract_tasks_truncated, false);
114 assert.equal(out.extract_tasks_notes_scanned, 2);
115 });
116
117 it('filters by folder client-side', async () => {
118 mock = installExtractTasksFetchMock();
119 const { client } = await connectPair();
120
121 const result = await client.callTool({
122 name: 'extract_tasks',
123 arguments: { folder: 'inbox' },
124 });
125
126 assert.ok(!result.isError);
127 const out = JSON.parse(result.content[0].text);
128 assert.equal(out.tasks.length, 1);
129 assert.equal(out.tasks[0].path, 'inbox/a.md');
130 });
131
132 it('fetches note when list body is empty', async () => {
133 mock = installExtractTasksFetchMock({ emptyListBody: true });
134 const { client } = await connectPair();
135
136 const result = await client.callTool({
137 name: 'extract_tasks',
138 arguments: {},
139 });
140
141 assert.ok(!result.isError);
142 const out = JSON.parse(result.content[0].text);
143 assert.ok(out.tasks.some((t) => t.text === 'One task'));
144 assert.ok(mock.calls.some((c) => c.url.includes('/notes/inbox')));
145 });
146 });
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