Skip to content

Commit a57fe09

Browse files
authored
Merge pull request #1233 from getlarge/issue-1231-mcp-app-followup
chore(mcp): typed callTool + e2e api-client fix + MCP apps docs/skill (follow-up to #1229)
2 parents aced830 + 2f2886e commit a57fe09

15 files changed

Lines changed: 475 additions & 108 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
name: rendered-pack-bba8dca4
3+
description: Use when writing or reviewing MoltNet e2e tests (apps/*/e2e, libs/mcp-test-harness): use @moltnet/api-client (createClient + typed fns like createDiaryEntry) not raw fetch for in-spec endpoints. Raw fetch only for /health, /oauth2/token, /auth/register.
4+
moltnet:
5+
rendered_pack_id: bba8dca4-c029-42ad-a43d-5d28fec0fdc3
6+
rendered_pack_cid: bafyreig6xgdiw6hsehuafjv3vr54gprbqvqedmm36ey3rp3pthksduzati
7+
source_pack_id: 141fdb3d-eec9-427d-89f8-6171fbd3e0cc
8+
bundled_at: 2026-05-24T17:36:03Z
9+
---
10+
11+
# Context Pack 141fdb3d-eec9-427d-89f8-6171fbd3e0cc
12+
13+
- Created: 2026-05-24T17:31:34.460Z
14+
- Entries: 4
15+
16+
### Decision: enforce api-client usage in e2e dedup tests
17+
18+
- Entry ID: `7b0a6488-6609-4a1f-8a97-8a117cd7969d`
19+
- CID: `bafkreihztuh4jkjl2t55bfl5surlotmvrveyw5ujuznqwgxeebkfy4w4ri`
20+
- Compression: `full`
21+
- Tokens: 131/131
22+
23+
Decision: Add explicit eval criteria requiring `@moltnet/api-client` helpers (not raw `fetch`) for authenticated duplicate/invalidation e2e coverage.
24+
Alternatives considered: Keep tests as raw HTTP `fetch` calls for simplicity.
25+
Reason chosen: API-client usage keeps tests aligned with repo conventions, typed request/response shapes, and auth helper reuse. It also avoids criterion ambiguity around transport details.
26+
Trade-offs: Slightly more ceremony in concurrent status assertions (`response.status` fields), but higher consistency and maintainability.
27+
Context: User review explicitly requested criteria entries for avoiding fetch in these e2e tests.
28+
29+
<metadata>
30+
operator: edouard
31+
tool: codex
32+
refs: apps/rest-api/e2e/diary-distill.e2e.test.ts, evals/add-dbos-dedup-queues/criteria.json, libs/api-client/
33+
timestamp: 2026-03-09T00:00:00Z
34+
branch: feat/issue-378-eval-runner
35+
scope: scope:evals, scope:rest-api
36+
</metadata>
37+
38+
### Incident: raw fetch in e2e tests despite api-client having the endpoints
39+
40+
- Entry ID: `24e3532a-753f-4767-989d-c49bb1c0b16c`
41+
- CID: `bafkreih3bnpgudnk6l4rjrs3ov6iwhis2v7ts42mm5p34g5sgribyihb64`
42+
- Compression: `full`
43+
- Tokens: 80/80
44+
45+
Wrote team-governance.e2e.test.ts using raw fetch() for all governance endpoints instead of @moltnet/api-client. Test was written before OpenAPI regen, not updated after. All endpoints (initiateTransfer, acceptTransfer, rejectTransfer, listPendingTransfers, acceptTeamFounding, createTeam) were in sdk.gen.ts after regeneration. Fix: rewrite to use client functions. Rule: e2e tests must use api-client for any endpoint in the generated spec; raw fetch only for /health, /oauth2/token, /auth/register.
46+
47+
### Recurrence: raw fetch in mcp-host-e2e + mcp-test-harness instead of api-client
48+
49+
- Entry ID: `cbdd365c-b319-4390-8f64-9a59920af2ab`
50+
- CID: `bafkreicwgdd7scrllup2hqjh3aj43rrmpzsw754kbv6jq4brqqsrfl6bi4`
51+
- Compression: `full`
52+
- Tokens: 347/347
53+
54+
What happened: apps/mcp-host-e2e/src/entry-map.spec.ts (added in PR #1229) seeds diary entries with a raw fetch() to POST /diaries/:id/entries instead of the generated @moltnet/api-client (createClient + createDiaryEntry). The shared libs/mcp-test-harness/src/harness.ts does the same for POST /diaries (createDiary). This re-introduces the exact anti-pattern previously documented and ruled against.
55+
56+
Root cause: the new spec followed the harness's existing raw-fetch seeding style rather than the repo convention. The sibling suites apps/rest-api/e2e/_.e2e.test.ts and apps/mcp-server/e2e/_.e2e.test.ts already do this correctly (import createClient + createDiaryEntry from @moltnet/api-client). POST /diaries/:id/entries and POST /diaries ARE in the generated SDK, so per the established rule they must NOT use raw fetch.
57+
58+
The rule (from incident 24e3532a): e2e tests must use api-client for any endpoint in the generated spec; raw fetch is allowed ONLY for /health, /oauth2/token, /auth/register. Decision 7b0a6488 enforces api-client usage in e2e coverage. Both endpoints here are in-spec, so both violate it.
59+
60+
Fix: NOT done in PR #1229 (user explicitly deferred to avoid scope creep). Tracked for the next PR: refactor entry-map.spec.ts seeding to createDiaryEntry, and the mcp-test-harness public-diary creation to createDiary, both via createClient({ baseUrl: restApiUrl }) with the bearer + x-moltnet-team-id headers. Note the harness fix benefits every MCP e2e suite, so it is the higher-leverage one.
61+
62+
Watch for: when writing or copying e2e setup/seeding, use @moltnet/api-client for any in-spec endpoint; only /health, /oauth2/token, /auth/register may use raw fetch. The mcp-test-harness raw-fetch seeding is a tempting template that propagates the anti-pattern — fix it at the source.
63+
64+
<metadata>
65+
operator: edouard | tool: claude | timestamp: 2026-05-24T17:28:10Z
66+
branch: issue-1194-diary-map-app | scope: testing,mcp-apps | refs: apps/mcp-host-e2e/src/entry-map.spec.ts, libs/mcp-test-harness/src/harness.ts, @moltnet/api-client, createDiaryEntry
67+
</metadata>
68+
69+
### Accountable commit: Replace raw fetch() calls in e2e test with @moltnet/api-client functions.
70+
71+
- Entry ID: `73ac8b20-f36b-46ef-803f-8204fba12fc4`
72+
- CID: `bafkreigebb2uu65tkt43detaiolbjkqj2riivtxxwlncnvlo6wtxgu6jhm`
73+
- Compression: `full`
74+
- Tokens: 110/110
75+
76+
<content>
77+
Replace raw fetch() calls in e2e test with @moltnet/api-client functions. All governance endpoints (createTeam with foundingMembers, acceptTeamFounding, initiateTransfer, listPendingTransfers, acceptTransfer, rejectTransfer) were already in the generated SDK after OpenAPI regen. Two fetch calls remain for GET /diaries/:id to read teamId — the generated getDiary response type does not expose teamId, making raw fetch the correct choice for those assertions.
78+
</content>
79+
<metadata>
80+
signer: 1671-B080-99BF-4270
81+
operator: edouard
82+
tool: claude
83+
risk-level: low
84+
files-changed: 1
85+
refs: apps/rest-api/e2e/team-governance.e2e.test.ts
86+
timestamp: 2026-04-07T17:50:30Z
87+
branch: feat/team-governance-workflows
88+
scope: test, governance
89+
</metadata>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
name: rendered-pack-bba8dca4
3+
description: Use when writing or reviewing MoltNet e2e tests (apps/*/e2e, libs/mcp-test-harness): use @moltnet/api-client (createClient + typed fns like createDiaryEntry) not raw fetch for in-spec endpoints. Raw fetch only for /health, /oauth2/token, /auth/register.
4+
moltnet:
5+
rendered_pack_id: bba8dca4-c029-42ad-a43d-5d28fec0fdc3
6+
rendered_pack_cid: bafyreig6xgdiw6hsehuafjv3vr54gprbqvqedmm36ey3rp3pthksduzati
7+
source_pack_id: 141fdb3d-eec9-427d-89f8-6171fbd3e0cc
8+
bundled_at: 2026-05-24T17:36:03Z
9+
---
10+
11+
# Context Pack 141fdb3d-eec9-427d-89f8-6171fbd3e0cc
12+
13+
- Created: 2026-05-24T17:31:34.460Z
14+
- Entries: 4
15+
16+
### Decision: enforce api-client usage in e2e dedup tests
17+
18+
- Entry ID: `7b0a6488-6609-4a1f-8a97-8a117cd7969d`
19+
- CID: `bafkreihztuh4jkjl2t55bfl5surlotmvrveyw5ujuznqwgxeebkfy4w4ri`
20+
- Compression: `full`
21+
- Tokens: 131/131
22+
23+
Decision: Add explicit eval criteria requiring `@moltnet/api-client` helpers (not raw `fetch`) for authenticated duplicate/invalidation e2e coverage.
24+
Alternatives considered: Keep tests as raw HTTP `fetch` calls for simplicity.
25+
Reason chosen: API-client usage keeps tests aligned with repo conventions, typed request/response shapes, and auth helper reuse. It also avoids criterion ambiguity around transport details.
26+
Trade-offs: Slightly more ceremony in concurrent status assertions (`response.status` fields), but higher consistency and maintainability.
27+
Context: User review explicitly requested criteria entries for avoiding fetch in these e2e tests.
28+
29+
<metadata>
30+
operator: edouard
31+
tool: codex
32+
refs: apps/rest-api/e2e/diary-distill.e2e.test.ts, evals/add-dbos-dedup-queues/criteria.json, libs/api-client/
33+
timestamp: 2026-03-09T00:00:00Z
34+
branch: feat/issue-378-eval-runner
35+
scope: scope:evals, scope:rest-api
36+
</metadata>
37+
38+
### Incident: raw fetch in e2e tests despite api-client having the endpoints
39+
40+
- Entry ID: `24e3532a-753f-4767-989d-c49bb1c0b16c`
41+
- CID: `bafkreih3bnpgudnk6l4rjrs3ov6iwhis2v7ts42mm5p34g5sgribyihb64`
42+
- Compression: `full`
43+
- Tokens: 80/80
44+
45+
Wrote team-governance.e2e.test.ts using raw fetch() for all governance endpoints instead of @moltnet/api-client. Test was written before OpenAPI regen, not updated after. All endpoints (initiateTransfer, acceptTransfer, rejectTransfer, listPendingTransfers, acceptTeamFounding, createTeam) were in sdk.gen.ts after regeneration. Fix: rewrite to use client functions. Rule: e2e tests must use api-client for any endpoint in the generated spec; raw fetch only for /health, /oauth2/token, /auth/register.
46+
47+
### Recurrence: raw fetch in mcp-host-e2e + mcp-test-harness instead of api-client
48+
49+
- Entry ID: `cbdd365c-b319-4390-8f64-9a59920af2ab`
50+
- CID: `bafkreicwgdd7scrllup2hqjh3aj43rrmpzsw754kbv6jq4brqqsrfl6bi4`
51+
- Compression: `full`
52+
- Tokens: 347/347
53+
54+
What happened: apps/mcp-host-e2e/src/entry-map.spec.ts (added in PR #1229) seeds diary entries with a raw fetch() to POST /diaries/:id/entries instead of the generated @moltnet/api-client (createClient + createDiaryEntry). The shared libs/mcp-test-harness/src/harness.ts does the same for POST /diaries (createDiary). This re-introduces the exact anti-pattern previously documented and ruled against.
55+
56+
Root cause: the new spec followed the harness's existing raw-fetch seeding style rather than the repo convention. The sibling suites apps/rest-api/e2e/_.e2e.test.ts and apps/mcp-server/e2e/_.e2e.test.ts already do this correctly (import createClient + createDiaryEntry from @moltnet/api-client). POST /diaries/:id/entries and POST /diaries ARE in the generated SDK, so per the established rule they must NOT use raw fetch.
57+
58+
The rule (from incident 24e3532a): e2e tests must use api-client for any endpoint in the generated spec; raw fetch is allowed ONLY for /health, /oauth2/token, /auth/register. Decision 7b0a6488 enforces api-client usage in e2e coverage. Both endpoints here are in-spec, so both violate it.
59+
60+
Fix: NOT done in PR #1229 (user explicitly deferred to avoid scope creep). Tracked for the next PR: refactor entry-map.spec.ts seeding to createDiaryEntry, and the mcp-test-harness public-diary creation to createDiary, both via createClient({ baseUrl: restApiUrl }) with the bearer + x-moltnet-team-id headers. Note the harness fix benefits every MCP e2e suite, so it is the higher-leverage one.
61+
62+
Watch for: when writing or copying e2e setup/seeding, use @moltnet/api-client for any in-spec endpoint; only /health, /oauth2/token, /auth/register may use raw fetch. The mcp-test-harness raw-fetch seeding is a tempting template that propagates the anti-pattern — fix it at the source.
63+
64+
<metadata>
65+
operator: edouard | tool: claude | timestamp: 2026-05-24T17:28:10Z
66+
branch: issue-1194-diary-map-app | scope: testing,mcp-apps | refs: apps/mcp-host-e2e/src/entry-map.spec.ts, libs/mcp-test-harness/src/harness.ts, @moltnet/api-client, createDiaryEntry
67+
</metadata>
68+
69+
### Accountable commit: Replace raw fetch() calls in e2e test with @moltnet/api-client functions.
70+
71+
- Entry ID: `73ac8b20-f36b-46ef-803f-8204fba12fc4`
72+
- CID: `bafkreigebb2uu65tkt43detaiolbjkqj2riivtxxwlncnvlo6wtxgu6jhm`
73+
- Compression: `full`
74+
- Tokens: 110/110
75+
76+
<content>
77+
Replace raw fetch() calls in e2e test with @moltnet/api-client functions. All governance endpoints (createTeam with foundingMembers, acceptTeamFounding, initiateTransfer, listPendingTransfers, acceptTransfer, rejectTransfer) were already in the generated SDK after OpenAPI regen. Two fetch calls remain for GET /diaries/:id to read teamId — the generated getDiary response type does not expose teamId, making raw fetch the correct choice for those assertions.
78+
</content>
79+
<metadata>
80+
signer: 1671-B080-99BF-4270
81+
operator: edouard
82+
tool: claude
83+
risk-level: low
84+
files-changed: 1
85+
refs: apps/rest-api/e2e/team-governance.e2e.test.ts
86+
timestamp: 2026-04-07T17:50:30Z
87+
branch: feat/team-governance-workflows
88+
scope: test, governance
89+
</metadata>

apps/mcp-host-e2e/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"private": true,
66
"type": "module",
77
"dependencies": {
8+
"@moltnet/api-client": "workspace:*",
89
"@moltnet/mcp-test-harness": "workspace:*"
910
},
1011
"devDependencies": {

apps/mcp-host-e2e/src/entry-map.spec.ts

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
1+
import {
2+
type Client,
3+
createClient,
4+
createDiaryEntry,
5+
} from '@moltnet/api-client';
16
import {
27
createMcpTestHarness,
38
type McpTestHarness,
49
} from '@moltnet/mcp-test-harness';
510
import { expect, type Page, test } from '@playwright/test';
611

712
let harness: McpTestHarness;
13+
let client: Client;
814
const seededEntryIds: string[] = [];
915

1016
/** Seed a handful of namespaced-tag entries so a zone resolves a real mosaic. */
1117
async function seedEntry(content: string, tags: string[]): Promise<string> {
12-
const response = await fetch(
13-
`${harness.restApiUrl}/diaries/${harness.privateDiaryId}/entries`,
14-
{
15-
method: 'POST',
16-
headers: {
17-
'content-type': 'application/json',
18-
authorization: `Bearer ${harness.agent.accessToken}`,
19-
'x-moltnet-team-id': harness.personalTeamId,
20-
},
21-
body: JSON.stringify({ content, tags, entryType: 'semantic' }),
22-
},
23-
);
24-
if (!response.ok) {
25-
throw new Error(`seed failed: ${response.status} ${await response.text()}`);
18+
const { data, error } = await createDiaryEntry({
19+
client,
20+
auth: () => harness.agent.accessToken,
21+
headers: { 'x-moltnet-team-id': harness.personalTeamId },
22+
path: { diaryId: harness.privateDiaryId },
23+
body: { content, tags, entryType: 'semantic' },
24+
});
25+
if (error || !data) {
26+
throw new Error(`seed failed: ${JSON.stringify(error)}`);
2627
}
27-
const body = (await response.json()) as { id: string };
28-
return body.id;
28+
return data.id;
2929
}
3030

3131
test.beforeAll(async () => {
3232
harness = await createMcpTestHarness();
33+
client = createClient({ baseUrl: harness.restApiUrl });
3334
seededEntryIds.push(
3435
await seedEntry('Chose Drizzle migrations over raw SQL.', [
3536
'scope:infra',

apps/mcp-host-e2e/tsconfig.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
"src/**/*.d.ts"
1818
],
1919
"references": [
20+
{
21+
"path": "../../libs/api-client"
22+
},
2023
{
2124
"path": "../../libs/mcp-test-harness"
2225
}

docs/reference/mcp-server.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,27 @@ See [DIARY_ENTRY_STATE_MODEL § Signing reference](./diary-entry-state-model#sig
7171
- `tasks_create` — create and enqueue a task. Validates `input` against the registered task-type schema (TypeBox via `@moltnet/tasks`) before posting. Same operation as `moltnet task create` and `agent.tasks.create(...)`.
7272
- `tasks_get`, `tasks_list` — fetch by ID or list with filters.
7373
- `tasks_attempts_list`, `tasks_messages_list` — read attempt envelopes and per-attempt streaming events.
74-
- `tasks_console_link`, `tasks_app_open` — render a console URL or open the task in the web app.
74+
- `tasks_console_link` — render a console URL for a task. `tasks_app_open`open the interactive **Tasks MCP App** (see [MCP Apps](#mcp-apps) below).
7575

7676
See [Tasks](../use/tasks.md) for the three-tab CLI / MCP / SDK examples and [Task Reference § Create envelope](./tasks#create-envelope) for the field-by-field mapping. The MCP tool argument names use snake_case (`task_type`, `team_id`, `correlation_id`, …) and map 1:1 to the CLI's kebab-case flags.
7777

78+
## MCP Apps
79+
80+
Some tools open an **interactive UI** that renders inline in MCP hosts which support [MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview) (Claude Desktop, claude.ai, ChatGPT). Instead of returning text, the tool mounts a small web app in a sandboxed iframe in the chat. You don't call these directly — ask the assistant in plain language ("show me my tasks", "help me make sense of this diary") and it opens the matching app.
81+
82+
| Tool | App | What it's for |
83+
| ------------------ | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
84+
| `tasks_app_open` | MoltNet Tasks | Inspect a team's task queue, drill into a task's attempts and messages, and jump to the console. Read-only. |
85+
| `entries_map_open` | Diary Map | Human-first sense-making for a large diary: the assistant interprets it into labeled **knowledge zones**; you browse zones, see representative entries, and **save a zone** as a draft context pack to revisit or pin. |
86+
87+
**How they work** (so the behavior isn't surprising):
88+
89+
- **The assistant drives the data.** These tools are deterministic openers — they mount the app and declare which read tools it may call (`entries_list`, `entries_search`, `diary_tags`, `packs_*`). All interpretation (which zones exist, their labels) is done by the assistant in your session, not the server. The server stays retrieval-only; there is no server-side LLM.
90+
- **Diary Map zones are draft context packs.** "Save this zone" materializes the selection as an _unpinned_ [context pack](../understand/knowledge-factory.md) carrying the search that produced it; validating it pins the pack. Nothing is written to your diary.
91+
- **Host display limits.** Inline app height is capped by the host (Claude inline ≈ 500px, no nested scroll; ChatGPT grows with content). Where the host allows it, an app can request fullscreen for a roomier view. On hosts without MCP Apps support the opener tool still returns its structured result as text.
92+
93+
To exercise an app locally against the e2e stack, see [`apps/mcp-host/README.md`](https://github.com/getlarge/themoltnet/blob/main/apps/mcp-host/README.md).
94+
7895
## Prompts
7996

8097
Three MCP prompts shape common agent workflows:

0 commit comments

Comments
 (0)