vantage-peers-mcp 2.3.4 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +142 -0
- package/dist/server-http.js +27 -1
- package/dist/src/tools.d.ts +1 -0
- package/dist/src/tools.js +167 -54
- package/dist/src/ui-resources/index.d.ts +36 -0
- package/dist/src/ui-resources/index.js +100 -0
- package/dist/src/ui-resources/primitives/briefing-note.d.ts +18 -0
- package/dist/src/ui-resources/primitives/briefing-note.js +126 -0
- package/dist/src/ui-resources/primitives/diary-entry.d.ts +18 -0
- package/dist/src/ui-resources/primitives/diary-entry.js +127 -0
- package/dist/src/ui-resources/primitives/memory-quote.d.ts +16 -0
- package/dist/src/ui-resources/primitives/memory-quote.js +109 -0
- package/dist/src/ui-resources/primitives/messages-feed.d.ts +16 -0
- package/dist/src/ui-resources/primitives/messages-feed.js +117 -0
- package/dist/src/ui-resources/primitives/mission-timeline.d.ts +17 -0
- package/dist/src/ui-resources/primitives/mission-timeline.js +157 -0
- package/dist/src/ui-resources/primitives/tasks-table.d.ts +17 -0
- package/dist/src/ui-resources/primitives/tasks-table.js +130 -0
- package/dist/src/ui-resources/schemas.d.ts +124 -0
- package/dist/src/ui-resources/schemas.js +88 -0
- package/dist/src/ui-resources/stream-marker.d.ts +33 -0
- package/dist/src/ui-resources/stream-marker.js +67 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,125 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.4.0] — 2026-05-28 — M3 iframeEmbedSessions + __VP_TOOL_RESULT__ stream marker + ack-checklist
|
|
4
|
+
|
|
5
|
+
**Mission instance** : `sigma-vantage-peers-mcp-gui-iframe-embed-v1` (k5730xct6rvrwkvxhy5t5js12d87jwfw).
|
|
6
|
+
**Pi sign-off** : PI_AUTHORIZED_TASK_ID=`k1793m1qgn0zaay6r87dhvsh7187kwya` (PROD-DEPLOY-AUTHORIZED).
|
|
7
|
+
**Eta sign-off** : ETA_APPROVED_TASK_ID=`k171ep964sxabbrgmb21fk9axd87ka1n` at commit `338a7b9e6130ce69dc5fe7f3e2e9ecc4648b4f6a` (Day 79 SHA-pinned).
|
|
8
|
+
**Merge** : PR #545 squash `f509c8d92f0b142bc063a0e9dd070e1993cc729b`.
|
|
9
|
+
|
|
10
|
+
M3 delivers the session registry and stream-marker protocol that connects the VP MCP server
|
|
11
|
+
to the Gen UI iframe bridge. All marker emission is gated behind `VP_EMIT_UI_MARKERS=1`
|
|
12
|
+
so production behaviour is unchanged until the bridge is deployed.
|
|
13
|
+
|
|
14
|
+
### Convex schema — `iframeEmbedSessions` table
|
|
15
|
+
|
|
16
|
+
NEW table `iframeEmbedSessions` in `convex/schema.ts` :
|
|
17
|
+
- Fields : `sessionId` (string), `tenantId` (optional string), `origin` (string),
|
|
18
|
+
`userId` (optional string), `createdAt` (number), `lastSeenAt` (number),
|
|
19
|
+
`expiresAt` (number), `revoked` (boolean).
|
|
20
|
+
- Indexes : `by_session_id` on `["sessionId"]`, `by_origin_expires` on `["origin", "expiresAt"]`.
|
|
21
|
+
|
|
22
|
+
NEW `convex/iframeEmbedSessions.ts` — 4 operations :
|
|
23
|
+
- `createSession` mutation — inserts a new session row.
|
|
24
|
+
- `getSession` query — returns session or null (null for expired / revoked).
|
|
25
|
+
- `touchSession` mutation — bumps `lastSeenAt` to now; returns bool.
|
|
26
|
+
- `revokeSession` mutation — sets `revoked=true`; returns bool.
|
|
27
|
+
|
|
28
|
+
### Stream marker — `mcp-server/src/ui-resources/stream-marker.ts`
|
|
29
|
+
|
|
30
|
+
NEW `MARKER_START = "__VP_TOOL_RESULT__"`, `MARKER_END = "__END__"`.
|
|
31
|
+
|
|
32
|
+
NEW `wrapToolResult(payload: VpToolResult): string` :
|
|
33
|
+
- Validates via `VpToolResultSchema`, throws `TypeError` on schema failure.
|
|
34
|
+
- Returns `__VP_TOOL_RESULT__<json>__END__`.
|
|
35
|
+
|
|
36
|
+
NEW `parseToolResult(text: string): VpToolResult | null` :
|
|
37
|
+
- Extracts marker substring (handles bare, embedded, surrounding text).
|
|
38
|
+
- Returns validated `VpToolResult` or null on any failure (no-throw contract).
|
|
39
|
+
|
|
40
|
+
### MCP tools — marker emission gated by `VP_EMIT_UI_MARKERS=1`
|
|
41
|
+
|
|
42
|
+
`mcp-server/src/tools.ts` — 6 tools now append `wrapToolResult(...)` after the JSON payload
|
|
43
|
+
when `VP_EMIT_UI_MARKERS=1` (default OFF) :
|
|
44
|
+
|
|
45
|
+
| Tool | kind |
|
|
46
|
+
|-----------------------|--------------------|
|
|
47
|
+
| `list_tasks` | `tasks-table` |
|
|
48
|
+
| `list_messages` | `messages-feed` |
|
|
49
|
+
| `get_diary` | `diary-entry` |
|
|
50
|
+
| `list_missions` | `mission-timeline` |
|
|
51
|
+
| `list_briefing_notes` | `briefing-note` |
|
|
52
|
+
| `list_memories` | `memory-quote` |
|
|
53
|
+
|
|
54
|
+
Change is surgical — existing return shape is preserved; marker is appended as a new line.
|
|
55
|
+
|
|
56
|
+
### Ack checklist
|
|
57
|
+
|
|
58
|
+
NEW `docs/M3-ACK-CHECKLIST.md` — bilingual FR/EN post-deploy verification checklist
|
|
59
|
+
for Marie + Ismaël. Covers: package install, primitive reads, Shadow DOM scoping,
|
|
60
|
+
stream marker emit + parse, bilingual spot check, WCAG AA (contrast + role attrs),
|
|
61
|
+
default-OFF guard.
|
|
62
|
+
|
|
63
|
+
### Tests
|
|
64
|
+
|
|
65
|
+
15+ new vitest cases (≥264 total after M3, baseline 253 after M2) :
|
|
66
|
+
- `mcp-server/src/__tests__/m3-stream-marker.test.ts` — 14 cases:
|
|
67
|
+
`wrapToolResult` ×6 valid kinds, ×2 throws on invalid, `parseToolResult` roundtrip,
|
|
68
|
+
non-marker text ×2, embedded text, malformed JSON ×2, schema rejects unknown kind ×2.
|
|
69
|
+
- `convex/iframeEmbedSessions.test.ts` — 7 cases:
|
|
70
|
+
create+get, optional fields, getSession unknown, expired session null,
|
|
71
|
+
touchSession updates lastSeenAt, touchSession unknown false,
|
|
72
|
+
revokeSession marks revoked (getSession null), revokeSession unknown false.
|
|
73
|
+
|
|
74
|
+
0 regression on M1+M2 suites (253/253 baseline).
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## [Unreleased] — M1 SEP-1865 ui:// resources backend + M2 primitives + Zod schemas
|
|
79
|
+
|
|
80
|
+
**Mission instance** : `sigma-vantage-peers-mcp-gui-iframe-embed-v1` (k5730xct6rvrwkvxhy5t5js12d87jwfw).
|
|
81
|
+
**Template VR consumed** : `gui-iframe-embed-v1` v1.0.0 (jx7bzk0x1086tgwgj2zrssk2pn87k1ga).
|
|
82
|
+
|
|
83
|
+
M1 Foundation (adapted MCP-pure paradigm per Pi arbitrage Day 84) :
|
|
84
|
+
- NEW `mcp-server/src/ui-resources/index.ts` : URI parser `ui://vp/v1/<primitive>?<query>` + primitive registry + handler factory.
|
|
85
|
+
- NEW `mcp-server/src/ui-resources/primitives/tasks-table.ts` : M1 MVP primitive returning HTML inline (Shadow DOM scoped CSS) — WCAG AA + bilingual FR+EN.
|
|
86
|
+
- `mcp-server/server-http.ts` : wired `ListResourcesRequestSchema` + `ReadResourceRequestSchema` MCP handlers on the existing McpServer instance.
|
|
87
|
+
|
|
88
|
+
Tests : 14 new vitest cases (`src/__tests__/ui-resources-sep-1865.test.ts`) — URI parsing, primitive registry, render variants (empty, populated, FR), backend arg forwarding, XSS escape, error fallback, limit clamping, unknown primitive rejection. 0 regression on existing suites.
|
|
89
|
+
|
|
90
|
+
### M2 — Resolve 5 Gaps + Bearer sha256 hardening (adapted MCP-pure paradigm)
|
|
91
|
+
|
|
92
|
+
5 new ui:// primitives :
|
|
93
|
+
- `messages-feed` (`messages:listMessages` backend — channel filter applied client-side)
|
|
94
|
+
- `diary-entry` (`diary:get` single-entry + `diary:list` multi-entry backend)
|
|
95
|
+
- `mission-timeline` (`missions:list` backend with fields=lite)
|
|
96
|
+
- `briefing-note` (`briefingNotes:get` by noteId OR `briefingNotes:list` by topic backend)
|
|
97
|
+
- `memory-quote` (`memories:listMemories` backend — supports both plain-array and paginated result shapes)
|
|
98
|
+
|
|
99
|
+
Zod discriminated union schemas : `mcp-server/src/ui-resources/schemas.ts` exports `VpTaskPayloadSchema` + `VpMessagePayloadSchema` + `VpDiaryEntryPayloadSchema` + `VpMissionPayloadSchema` + `VpBriefingNotePayloadSchema` + `VpMemoryPayloadSchema` + `VpToolResultSchema` (discriminated union by `kind`). Cross-fleet ready for Mu vantage-bridge sidepanel S3 consumer.
|
|
100
|
+
|
|
101
|
+
Bearer sha256 validation : Already in place since v2.3.4 DCR security fix. `mcp-server/src/auth.ts` line 275 calls `sha256Hex(token)` before every Convex lookup (layers 2 and 4). Raw token never reaches Convex. No further hardening needed in M2.
|
|
102
|
+
|
|
103
|
+
Tests : 42 new vitest cases in `src/__tests__/ui-resources-m2-primitives.test.ts` (target was ≥22). Covers : PRIMITIVES registry (6 entries), each of 5 new primitives (empty + populated + FR labels + XSS escape + error fallback = 5 cases each), Zod schema roundtrip (VpToolResultSchema all 6 variants accepted, malformed rejected, individual payload schema validations). 0 regression on M1 17 cases + 194 other MCP tests (253/253 total).
|
|
104
|
+
|
|
105
|
+
M3 next : Registry json-render + `__VP_TOOL_RESULT__<json>` stream marker + smoke E2E + ack-checklist + PI-SIGNED Convex prod deploy + visual ack Marie/Ismaël.
|
|
106
|
+
|
|
107
|
+
## v2.3.5 — 2026-05-28
|
|
108
|
+
|
|
109
|
+
**Critical hotfix** — v2.3.3 (PR #539) shipped the backend filters `createdBy` + `updatedSince` and the Zod schema exports but did NOT wire those params into the 4 list MCP tool args blocks. Pi pull-cycle quickstart `list_tasks createdBy="pi" status="review" fields="lite"` was silently dropping `createdBy` at the MCP boundary and returning all visible tasks. Auto-clamp safeguard (Day 83) also could not trigger because Zod `.default(50)` / `.default(20)` on `limit` overrode the absent-value signal before it reached the backend.
|
|
110
|
+
|
|
111
|
+
Fixes:
|
|
112
|
+
- `mcp-server/src/tools.ts` : 4 list tools now expose `createdBy` (`list_tasks` + `list_tasks_by_mission` only — `list_missions` + `list_briefing_notes` do not accept it backend-side) and `updatedSince` (all 4).
|
|
113
|
+
- Removed `.default(50)` (3 tools) and `.default(20)` (1 tool) on `limit` so absent value reaches the backend, enabling the v2.3.3 auto-clamp safeguard.
|
|
114
|
+
|
|
115
|
+
Tests : 8 new boundary-forwarding cases (`src/__tests__/list-queries-v2.3.5-wire-createdby-updatedsince.test.ts`) — verify MCP layer actually forwards new params to `convex.query` instead of dropping them. 0 regression on existing suites.
|
|
116
|
+
|
|
117
|
+
Detection : Vantage-Bridge architecture review Sigma scope Day 84 — direct `grep`/`sed` inspection of `tools.ts` confirmed the gap. Backend already correct since v2.3.3 (`convex/tasks.ts:354-357`).
|
|
118
|
+
|
|
119
|
+
Fix-pattern (Day 84 capitalize) : when adding a new param across backend + MCP wrapper, the test suite MUST cover not only schema validation but also the tool-handler→convex.query forwarding boundary. Schema-only tests passed cleanly in v2.3.3 while the actual feature was broken in prod.
|
|
120
|
+
|
|
121
|
+
VP task : `k177tsvdxzase5sjy2qm9fdvp187kbwr`. Predecessor v2.3.3 PR #539 (`k1796s5j6jfkvkx0tn5n926ftd87jx9p`).
|
|
122
|
+
|
|
3
123
|
## v2.3.4 — 2026-05-28
|
|
4
124
|
|
|
5
125
|
**Security fix** — DCR (Dynamic Client Registration) self-registration now defaults to tenant-scope only. Master scope requires explicit admin authorization (`ADMIN_DCR_TOKEN` / `BEARER_SECRET_MASTER` env var). Closes beta blocker for Marie/Iris RH onboarding identified in VP Cloud audit Day 84.
|
|
@@ -13,6 +133,28 @@ Tests: 5 new Convex security tests (`convex/oauth-dcr-security.test.ts`) + 5 new
|
|
|
13
133
|
|
|
14
134
|
VP task: k17218rvqyncs1v6rwj3qdzfsn87jj4n. Beta unblock chain: DCR fix → 5 quick wins onboarding (seed-profiles + marie-iris-rh client + README VP Cloud + runbook + email).
|
|
15
135
|
|
|
136
|
+
## v2.3.3 — 2026-05-28
|
|
137
|
+
|
|
138
|
+
**Follow-up to v2.3.2 (Day 84 scope élargi)** — Extend list queries with `createdBy` + `updatedSince` filters + auto-clamp safeguard.
|
|
139
|
+
|
|
140
|
+
Backend (Convex) :
|
|
141
|
+
- `tasks.list` + `tasks.listByMission` : + `createdBy` (filter by task creator) + `updatedSince` (Unix ms window) + auto-clamp limit=30 when `fields="full"` and no explicit limit
|
|
142
|
+
- `missions.list` : + `updatedSince` + auto-clamp (30)
|
|
143
|
+
- `briefingNotes.list` : + `updatedSince` + auto-clamp (15 when fields=full)
|
|
144
|
+
|
|
145
|
+
MCP wrapper :
|
|
146
|
+
- 4 list tools forward the new params
|
|
147
|
+
- New export `updatedSinceSchema` (positive integer ms)
|
|
148
|
+
- `limit` `.default()` removed on the 4 list tools so absent limit flows to backend → enables auto-clamp
|
|
149
|
+
|
|
150
|
+
Tests : 15 new MCP schema cases (`src/__tests__/list-queries-v2.3.3-createdby-updatedsince.test.ts`) + 6 new Convex round-trip cases.
|
|
151
|
+
|
|
152
|
+
Pi pull cycle unblocked : `list_tasks createdBy="pi" status="review" fields="lite"` returns only Pi-dispatched tasks recently moved to review, payload 5-10× smaller.
|
|
153
|
+
|
|
154
|
+
Cap fleet : 0 overflow tolérance future (auto-clamp).
|
|
155
|
+
|
|
156
|
+
VP task: `k1796s5j6jfkvkx0tn5n926ftd87jx9p`. Successor of `k17e09ng1tf217n93z9m4tr0mx87hfe0` (v2.3.2 PR #537).
|
|
157
|
+
|
|
16
158
|
## v2.3.2 — 2026-05-28
|
|
17
159
|
|
|
18
160
|
**Hotfix** — Expose `fields="lite"` + `status` array/aliases in MCP tool schemas (Day 82 sprint gap).
|
package/dist/server-http.js
CHANGED
|
@@ -25,13 +25,14 @@
|
|
|
25
25
|
* NODE_ENV — set to "production" on Railway
|
|
26
26
|
*/
|
|
27
27
|
import { readFileSync } from "node:fs";
|
|
28
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
28
|
+
import { McpServer, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
29
29
|
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
|
|
30
30
|
import { ConvexHttpClient } from "convex/browser";
|
|
31
31
|
import { Hono } from "hono";
|
|
32
32
|
import { cors } from "hono/cors";
|
|
33
33
|
import { bearerAuthMiddleware, internalClient, masterOnlyMiddleware, sha256Base64Url, sha256Hex, } from "./src/auth.js";
|
|
34
34
|
import { registerTools } from "./src/tools.js";
|
|
35
|
+
import { listUiResources, readUiResource } from "./src/ui-resources/index.js";
|
|
35
36
|
let pkg;
|
|
36
37
|
try {
|
|
37
38
|
// Source mode: server-http.ts → ./package.json = mcp-server/package.json
|
|
@@ -563,6 +564,31 @@ app.all("/mcp", bearerAuthMiddleware(), async (c) => {
|
|
|
563
564
|
version: pkg.version,
|
|
564
565
|
});
|
|
565
566
|
registerTools(server, convex, oauthCtx);
|
|
567
|
+
// SEP-1865 ui:// resources for Generative UI primitives
|
|
568
|
+
// Uses McpServer.resource() high-level API with a ResourceTemplate so that
|
|
569
|
+
// resources/list (via listCallback) and resources/read both work.
|
|
570
|
+
// URI pattern: ui://vp/v1/{primitive} — query params read from the URL object.
|
|
571
|
+
const uiResourceTemplate = new ResourceTemplate("ui://vp/v1/{primitive}", {
|
|
572
|
+
list: async () => ({ resources: listUiResources() }),
|
|
573
|
+
});
|
|
574
|
+
server.resource("vp-ui", uiResourceTemplate, {
|
|
575
|
+
description: "SEP-1865 VantagePeers Generative UI primitives (HTML inline, Shadow DOM scoped)",
|
|
576
|
+
}, async (uri) => {
|
|
577
|
+
const fetchConvex = async (functionName, args) => {
|
|
578
|
+
// biome-ignore lint/suspicious/noExplicitAny: Convex string API
|
|
579
|
+
return convex.query(functionName, args);
|
|
580
|
+
};
|
|
581
|
+
const resource = await readUiResource(uri.toString(), fetchConvex);
|
|
582
|
+
return {
|
|
583
|
+
contents: [
|
|
584
|
+
{
|
|
585
|
+
uri: resource.uri,
|
|
586
|
+
mimeType: resource.mimeType,
|
|
587
|
+
text: resource.text,
|
|
588
|
+
},
|
|
589
|
+
],
|
|
590
|
+
};
|
|
591
|
+
});
|
|
566
592
|
const transport = new WebStandardStreamableHTTPServerTransport();
|
|
567
593
|
await server.connect(transport);
|
|
568
594
|
return transport.handleRequest(c.req.raw);
|
package/dist/src/tools.d.ts
CHANGED
package/dist/src/tools.js
CHANGED
|
@@ -10,6 +10,36 @@
|
|
|
10
10
|
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { checkFromAllowed, checkNamespaceRead, checkNamespaceWrite, isMasterScope, } from "./auth.js";
|
|
13
|
+
import { wrapToolResult } from "./ui-resources/stream-marker.js";
|
|
14
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
15
|
+
// VP_EMIT_UI_MARKERS gate
|
|
16
|
+
//
|
|
17
|
+
// When VP_EMIT_UI_MARKERS=1 the 6 list/get tools that have a matching
|
|
18
|
+
// ui:// primitive append a __VP_TOOL_RESULT__<json>__END__ marker after the
|
|
19
|
+
// existing JSON payload. The Gen UI iframe bridge detects this marker and
|
|
20
|
+
// renders the structured primitive inline. Default is OFF so prod behaviour
|
|
21
|
+
// is unchanged.
|
|
22
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
23
|
+
const UI_MARKERS_ENABLED = process.env.VP_EMIT_UI_MARKERS === "1" ||
|
|
24
|
+
process.env.VP_EMIT_UI_MARKERS === "true";
|
|
25
|
+
/**
|
|
26
|
+
* Append a stream marker to a text response when UI markers are enabled.
|
|
27
|
+
* `buildPayload` is called only when the flag is ON to avoid any overhead.
|
|
28
|
+
*/
|
|
29
|
+
function appendMarkerIfEnabled(text, buildPayload) {
|
|
30
|
+
if (!UI_MARKERS_ENABLED)
|
|
31
|
+
return text;
|
|
32
|
+
try {
|
|
33
|
+
const payload = buildPayload();
|
|
34
|
+
if (payload === null)
|
|
35
|
+
return text;
|
|
36
|
+
return `${text}\n${wrapToolResult(payload)}`;
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Never break the primary response — marker emission is best-effort.
|
|
40
|
+
return text;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
13
43
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
14
44
|
// Pre-flight content size guard
|
|
15
45
|
//
|
|
@@ -162,6 +192,14 @@ export const fieldsSchema = z
|
|
|
162
192
|
.describe('Field projection — "lite" returns compact fields only ' +
|
|
163
193
|
'(typical 5-10× smaller payload for large list scans), ' +
|
|
164
194
|
'"full" (default) returns the full document.');
|
|
195
|
+
// v2.3.3 — Unix timestamp ms filter for "updated since".
|
|
196
|
+
// Pass `Date.now() - 24*60*60*1000` for "last 24h" pattern.
|
|
197
|
+
export const updatedSinceSchema = z
|
|
198
|
+
.number()
|
|
199
|
+
.int()
|
|
200
|
+
.positive()
|
|
201
|
+
.describe("Unix timestamp (ms) — return only rows whose updatedAt >= this value. " +
|
|
202
|
+
"Typical usage: Date.now() - 24*60*60*1000 for last-24h window.");
|
|
165
203
|
const mandateStatusSchema = z
|
|
166
204
|
.enum(["requested", "accepted", "in_progress", "delivered", "settled"])
|
|
167
205
|
.describe("Mandate lifecycle status");
|
|
@@ -738,13 +776,24 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
738
776
|
type,
|
|
739
777
|
limit: limit ?? 20,
|
|
740
778
|
});
|
|
779
|
+
const rawList = Array.isArray(memories)
|
|
780
|
+
? memories
|
|
781
|
+
: Array.isArray(memories?.page)
|
|
782
|
+
? memories.page
|
|
783
|
+
: [];
|
|
784
|
+
const baseText = JSON.stringify(memories, null, 2);
|
|
785
|
+
const text = appendMarkerIfEnabled(baseText, () => ({
|
|
786
|
+
kind: "memory-quote",
|
|
787
|
+
items: rawList.map((m) => ({
|
|
788
|
+
_id: m._id,
|
|
789
|
+
namespace: m.namespace,
|
|
790
|
+
type: m.type,
|
|
791
|
+
content: m.content,
|
|
792
|
+
score: m.score,
|
|
793
|
+
})),
|
|
794
|
+
}));
|
|
741
795
|
return {
|
|
742
|
-
content: [
|
|
743
|
-
{
|
|
744
|
-
type: "text",
|
|
745
|
-
text: JSON.stringify(memories, null, 2),
|
|
746
|
-
},
|
|
747
|
-
],
|
|
796
|
+
content: [{ type: "text", text }],
|
|
748
797
|
};
|
|
749
798
|
}
|
|
750
799
|
catch (error) {
|
|
@@ -1017,13 +1066,21 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1017
1066
|
from,
|
|
1018
1067
|
limit: limit ?? 100,
|
|
1019
1068
|
});
|
|
1069
|
+
const baseText = JSON.stringify(messages, null, 2);
|
|
1070
|
+
const text = appendMarkerIfEnabled(baseText, () => ({
|
|
1071
|
+
kind: "messages-feed",
|
|
1072
|
+
items: Array.isArray(messages)
|
|
1073
|
+
? messages.map((m) => ({
|
|
1074
|
+
_id: m._id,
|
|
1075
|
+
from: m.from,
|
|
1076
|
+
channel: m.channel,
|
|
1077
|
+
content: m.content,
|
|
1078
|
+
createdAt: m.createdAt,
|
|
1079
|
+
}))
|
|
1080
|
+
: [],
|
|
1081
|
+
}));
|
|
1020
1082
|
return {
|
|
1021
|
-
content: [
|
|
1022
|
-
{
|
|
1023
|
-
type: "text",
|
|
1024
|
-
text: JSON.stringify(messages, null, 2),
|
|
1025
|
-
},
|
|
1026
|
-
],
|
|
1083
|
+
content: [{ type: "text", text }],
|
|
1027
1084
|
};
|
|
1028
1085
|
}
|
|
1029
1086
|
catch (error) {
|
|
@@ -1141,28 +1198,42 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1141
1198
|
.min(1)
|
|
1142
1199
|
.max(200)
|
|
1143
1200
|
.optional()
|
|
1144
|
-
.
|
|
1145
|
-
.describe("Maximum number of tasks to return (default 50)"),
|
|
1201
|
+
.describe("Maximum number of tasks to return. Default 50 with fields=lite, auto-clamped to 30 when fields=full and no explicit limit (overflow protection)."),
|
|
1146
1202
|
fields: fieldsSchema
|
|
1147
1203
|
.optional()
|
|
1148
1204
|
.describe('Field projection ("lite"|"full")'),
|
|
1149
|
-
|
|
1205
|
+
createdBy: assigneeSchema
|
|
1206
|
+
.optional()
|
|
1207
|
+
.describe("Filter by task creator (e.g. 'pi' to find Pi-dispatched tasks)"),
|
|
1208
|
+
updatedSince: updatedSinceSchema.optional(),
|
|
1209
|
+
}, async ({ assignedTo, assignedToInstance, status, project, limit, fields, createdBy, updatedSince, }) => {
|
|
1150
1210
|
try {
|
|
1151
1211
|
const tasks = await convex.query("tasks:list", {
|
|
1152
1212
|
assignedTo,
|
|
1153
1213
|
assignedToInstance,
|
|
1154
1214
|
status,
|
|
1155
1215
|
project,
|
|
1156
|
-
limit
|
|
1216
|
+
limit,
|
|
1157
1217
|
fields,
|
|
1158
|
-
|
|
1218
|
+
createdBy,
|
|
1219
|
+
updatedSince,
|
|
1220
|
+
});
|
|
1221
|
+
const baseText = JSON.stringify(tasks, null, 2);
|
|
1222
|
+
const text = appendMarkerIfEnabled(baseText, () => ({
|
|
1223
|
+
kind: "tasks-table",
|
|
1224
|
+
items: Array.isArray(tasks)
|
|
1225
|
+
? tasks.map((t) => ({
|
|
1226
|
+
_id: t._id,
|
|
1227
|
+
title: t.title,
|
|
1228
|
+
status: t.status,
|
|
1229
|
+
priority: t.priority,
|
|
1230
|
+
assignedTo: t.assignedTo,
|
|
1231
|
+
_creationTime: t._creationTime,
|
|
1232
|
+
}))
|
|
1233
|
+
: [],
|
|
1234
|
+
}));
|
|
1159
1235
|
return {
|
|
1160
|
-
content: [
|
|
1161
|
-
{
|
|
1162
|
-
type: "text",
|
|
1163
|
-
text: JSON.stringify(tasks, null, 2),
|
|
1164
|
-
},
|
|
1165
|
-
],
|
|
1236
|
+
content: [{ type: "text", text }],
|
|
1166
1237
|
};
|
|
1167
1238
|
}
|
|
1168
1239
|
catch (error) {
|
|
@@ -1470,18 +1541,23 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1470
1541
|
.min(1)
|
|
1471
1542
|
.max(200)
|
|
1472
1543
|
.optional()
|
|
1473
|
-
.
|
|
1474
|
-
.describe("Maximum number of tasks to return (default 50)"),
|
|
1544
|
+
.describe("Maximum number of tasks to return. Default 50 with fields=lite, auto-clamped to 30 when fields=full and no explicit limit."),
|
|
1475
1545
|
fields: fieldsSchema
|
|
1476
1546
|
.optional()
|
|
1477
1547
|
.describe('Field projection ("lite"|"full")'),
|
|
1478
|
-
|
|
1548
|
+
createdBy: assigneeSchema
|
|
1549
|
+
.optional()
|
|
1550
|
+
.describe("Filter by task creator"),
|
|
1551
|
+
updatedSince: updatedSinceSchema.optional(),
|
|
1552
|
+
}, async ({ missionId, status, limit, fields, createdBy, updatedSince }) => {
|
|
1479
1553
|
try {
|
|
1480
1554
|
const tasks = await convex.query("tasks:listByMission", {
|
|
1481
1555
|
missionId: missionId,
|
|
1482
1556
|
status,
|
|
1483
|
-
limit
|
|
1557
|
+
limit,
|
|
1484
1558
|
fields,
|
|
1559
|
+
createdBy,
|
|
1560
|
+
updatedSince,
|
|
1485
1561
|
});
|
|
1486
1562
|
return {
|
|
1487
1563
|
content: [
|
|
@@ -1565,27 +1641,38 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1565
1641
|
.min(1)
|
|
1566
1642
|
.max(200)
|
|
1567
1643
|
.optional()
|
|
1568
|
-
.
|
|
1569
|
-
.describe("Maximum number of missions to return (default 50)"),
|
|
1644
|
+
.describe("Maximum number of missions to return. Default 50 with fields=lite, auto-clamped to 30 when fields=full and no explicit limit."),
|
|
1570
1645
|
fields: fieldsSchema
|
|
1571
1646
|
.optional()
|
|
1572
1647
|
.describe('Field projection ("lite"|"full")'),
|
|
1573
|
-
|
|
1648
|
+
updatedSince: updatedSinceSchema.optional(),
|
|
1649
|
+
}, async ({ project, pilot, status, limit, fields, updatedSince }) => {
|
|
1574
1650
|
try {
|
|
1575
1651
|
const missions = await convex.query("missions:list", {
|
|
1576
1652
|
project,
|
|
1577
1653
|
pilot,
|
|
1578
1654
|
status,
|
|
1579
|
-
limit
|
|
1655
|
+
limit,
|
|
1580
1656
|
fields,
|
|
1581
|
-
|
|
1657
|
+
updatedSince,
|
|
1658
|
+
});
|
|
1659
|
+
const baseText = JSON.stringify(missions, null, 2);
|
|
1660
|
+
const text = appendMarkerIfEnabled(baseText, () => ({
|
|
1661
|
+
kind: "mission-timeline",
|
|
1662
|
+
items: Array.isArray(missions)
|
|
1663
|
+
? missions.map((m) => ({
|
|
1664
|
+
_id: m._id,
|
|
1665
|
+
name: m.name,
|
|
1666
|
+
project: m.project,
|
|
1667
|
+
status: m.status,
|
|
1668
|
+
pilot: m.pilot,
|
|
1669
|
+
priority: m.priority,
|
|
1670
|
+
progress: m.progress,
|
|
1671
|
+
}))
|
|
1672
|
+
: [],
|
|
1673
|
+
}));
|
|
1582
1674
|
return {
|
|
1583
|
-
content: [
|
|
1584
|
-
{
|
|
1585
|
-
type: "text",
|
|
1586
|
-
text: JSON.stringify(missions, null, 2),
|
|
1587
|
-
},
|
|
1588
|
-
],
|
|
1675
|
+
content: [{ type: "text", text }],
|
|
1589
1676
|
};
|
|
1590
1677
|
}
|
|
1591
1678
|
catch (error) {
|
|
@@ -1740,13 +1827,24 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1740
1827
|
date,
|
|
1741
1828
|
orchestrator,
|
|
1742
1829
|
});
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1830
|
+
const baseText = JSON.stringify(entry, null, 2);
|
|
1831
|
+
const text = appendMarkerIfEnabled(baseText, () => {
|
|
1832
|
+
if (!entry)
|
|
1833
|
+
return null;
|
|
1834
|
+
return {
|
|
1835
|
+
kind: "diary-entry",
|
|
1836
|
+
item: {
|
|
1837
|
+
_id: entry._id,
|
|
1838
|
+
date: entry.date,
|
|
1839
|
+
orchestrator: entry.orchestrator,
|
|
1840
|
+
content: entry.content,
|
|
1841
|
+
highlights: entry.highlights,
|
|
1842
|
+
blockers: entry.blockers,
|
|
1748
1843
|
},
|
|
1749
|
-
|
|
1844
|
+
};
|
|
1845
|
+
});
|
|
1846
|
+
return {
|
|
1847
|
+
content: [{ type: "text", text }],
|
|
1750
1848
|
};
|
|
1751
1849
|
}
|
|
1752
1850
|
catch (error) {
|
|
@@ -1899,25 +1997,40 @@ export function registerTools(server, convex, oauthCtx) {
|
|
|
1899
1997
|
.min(1)
|
|
1900
1998
|
.max(100)
|
|
1901
1999
|
.optional()
|
|
1902
|
-
.
|
|
1903
|
-
.describe("Maximum notes to return (default 20)"),
|
|
2000
|
+
.describe("Maximum notes to return. Default 20 with fields=lite, auto-clamped to 15 when fields=full and no explicit limit."),
|
|
1904
2001
|
fields: fieldsSchema
|
|
1905
2002
|
.optional()
|
|
1906
2003
|
.describe('Field projection ("lite"|"full")'),
|
|
1907
|
-
|
|
2004
|
+
updatedSince: updatedSinceSchema.optional(),
|
|
2005
|
+
}, async ({ topic, limit, fields, updatedSince }) => {
|
|
1908
2006
|
try {
|
|
1909
2007
|
const notes = await convex.query("briefingNotes:list", {
|
|
1910
2008
|
topic,
|
|
1911
|
-
limit
|
|
2009
|
+
limit,
|
|
1912
2010
|
fields,
|
|
2011
|
+
updatedSince,
|
|
2012
|
+
});
|
|
2013
|
+
const baseText = JSON.stringify(notes, null, 2);
|
|
2014
|
+
const text = appendMarkerIfEnabled(baseText, () => {
|
|
2015
|
+
const items = Array.isArray(notes) ? notes : [];
|
|
2016
|
+
if (items.length === 0)
|
|
2017
|
+
return null;
|
|
2018
|
+
// Emit the first note as a briefing-note item for the primitive renderer.
|
|
2019
|
+
const first = items[0];
|
|
2020
|
+
return {
|
|
2021
|
+
kind: "briefing-note",
|
|
2022
|
+
item: {
|
|
2023
|
+
_id: first._id,
|
|
2024
|
+
topic: first.topic,
|
|
2025
|
+
title: first.title,
|
|
2026
|
+
participants: first.participants,
|
|
2027
|
+
content: first.content,
|
|
2028
|
+
createdBy: first.createdBy,
|
|
2029
|
+
},
|
|
2030
|
+
};
|
|
1913
2031
|
});
|
|
1914
2032
|
return {
|
|
1915
|
-
content: [
|
|
1916
|
-
{
|
|
1917
|
-
type: "text",
|
|
1918
|
-
text: JSON.stringify(notes, null, 2),
|
|
1919
|
-
},
|
|
1920
|
-
],
|
|
2033
|
+
content: [{ type: "text", text }],
|
|
1921
2034
|
};
|
|
1922
2035
|
}
|
|
1923
2036
|
catch (error) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEP-1865 ui:// resources for VantagePeers Generative UI.
|
|
3
|
+
*
|
|
4
|
+
* URI pattern : ui://vp/v1/<primitive>?<query>
|
|
5
|
+
* Examples :
|
|
6
|
+
* ui://vp/v1/tasks-table?assignedTo=pi&status=review&fields=lite&limit=10
|
|
7
|
+
* ui://vp/v1/messages-feed?recipient=sigma&limit=20
|
|
8
|
+
*
|
|
9
|
+
* M1 scope : 1 primitive (tasks-table) — proves the pipeline.
|
|
10
|
+
* M2 scope : ≥6 primitives (tasks/messages/diary/missions/briefingNotes/memories).
|
|
11
|
+
*
|
|
12
|
+
* Pattern Hybrid 60% static lit-ui + 11% Gen UI + 27% Hybrid (cf vp-gui-views-research-2026-05-28.md).
|
|
13
|
+
* Returns HTML inline with embedded JS + CSS Shadow DOM scoped.
|
|
14
|
+
*
|
|
15
|
+
* Reference instance Theta : theta-vantage-crm-gui-iframe-embed-v1 (blissful-gopher-531).
|
|
16
|
+
* Mission Sigma : sigma-vantage-peers-mcp-gui-iframe-embed-v1 (k5730xct6rvrwkvxhy5t5js12d87jwfw).
|
|
17
|
+
*/
|
|
18
|
+
export type UiResourceParsed = {
|
|
19
|
+
primitive: string;
|
|
20
|
+
query: URLSearchParams;
|
|
21
|
+
};
|
|
22
|
+
export declare function parseUiUri(uri: string): UiResourceParsed | null;
|
|
23
|
+
export declare const PRIMITIVES: readonly ["tasks-table", "messages-feed", "diary-entry", "mission-timeline", "briefing-note", "memory-quote"];
|
|
24
|
+
export type Primitive = (typeof PRIMITIVES)[number];
|
|
25
|
+
export declare const PRIMITIVE_DESCRIPTIONS: Record<Primitive, string>;
|
|
26
|
+
export declare function listUiResources(): Array<{
|
|
27
|
+
uri: string;
|
|
28
|
+
name: string;
|
|
29
|
+
description: string;
|
|
30
|
+
mimeType: string;
|
|
31
|
+
}>;
|
|
32
|
+
export declare function readUiResource(uri: string, fetchConvex: (functionName: string, args: Record<string, unknown>) => Promise<unknown>): Promise<{
|
|
33
|
+
uri: string;
|
|
34
|
+
mimeType: string;
|
|
35
|
+
text: string;
|
|
36
|
+
}>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEP-1865 ui:// resources for VantagePeers Generative UI.
|
|
3
|
+
*
|
|
4
|
+
* URI pattern : ui://vp/v1/<primitive>?<query>
|
|
5
|
+
* Examples :
|
|
6
|
+
* ui://vp/v1/tasks-table?assignedTo=pi&status=review&fields=lite&limit=10
|
|
7
|
+
* ui://vp/v1/messages-feed?recipient=sigma&limit=20
|
|
8
|
+
*
|
|
9
|
+
* M1 scope : 1 primitive (tasks-table) — proves the pipeline.
|
|
10
|
+
* M2 scope : ≥6 primitives (tasks/messages/diary/missions/briefingNotes/memories).
|
|
11
|
+
*
|
|
12
|
+
* Pattern Hybrid 60% static lit-ui + 11% Gen UI + 27% Hybrid (cf vp-gui-views-research-2026-05-28.md).
|
|
13
|
+
* Returns HTML inline with embedded JS + CSS Shadow DOM scoped.
|
|
14
|
+
*
|
|
15
|
+
* Reference instance Theta : theta-vantage-crm-gui-iframe-embed-v1 (blissful-gopher-531).
|
|
16
|
+
* Mission Sigma : sigma-vantage-peers-mcp-gui-iframe-embed-v1 (k5730xct6rvrwkvxhy5t5js12d87jwfw).
|
|
17
|
+
*/
|
|
18
|
+
import { renderBriefingNote } from "./primitives/briefing-note.js";
|
|
19
|
+
import { renderDiaryEntry } from "./primitives/diary-entry.js";
|
|
20
|
+
import { renderMemoryQuote } from "./primitives/memory-quote.js";
|
|
21
|
+
import { renderMessagesFeed } from "./primitives/messages-feed.js";
|
|
22
|
+
import { renderMissionTimeline } from "./primitives/mission-timeline.js";
|
|
23
|
+
import { renderTasksTable } from "./primitives/tasks-table.js";
|
|
24
|
+
// URI parser : ui://vp/v1/<primitive>?<query>
|
|
25
|
+
const UI_URI_RE = /^ui:\/\/vp\/v1\/([a-z][a-z0-9-]*)(?:\?(.*))?$/;
|
|
26
|
+
export function parseUiUri(uri) {
|
|
27
|
+
const match = UI_URI_RE.exec(uri);
|
|
28
|
+
if (!match)
|
|
29
|
+
return null;
|
|
30
|
+
const primitive = match[1];
|
|
31
|
+
const queryString = match[2] ?? "";
|
|
32
|
+
return {
|
|
33
|
+
primitive,
|
|
34
|
+
query: new URLSearchParams(queryString),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
// Primitive registry — M1 ships 1 (tasks-table). M2 adds 5 more.
|
|
38
|
+
export const PRIMITIVES = [
|
|
39
|
+
"tasks-table",
|
|
40
|
+
"messages-feed",
|
|
41
|
+
"diary-entry",
|
|
42
|
+
"mission-timeline",
|
|
43
|
+
"briefing-note",
|
|
44
|
+
"memory-quote",
|
|
45
|
+
];
|
|
46
|
+
export const PRIMITIVE_DESCRIPTIONS = {
|
|
47
|
+
"tasks-table": "Render a compact table of tasks. Query params: assignedTo, status, fields=lite|full, limit. Mirrors list_tasks tool semantics.",
|
|
48
|
+
"messages-feed": "Render a chronological feed of VantagePeers messages. Query params: from, channel, limit, lang.",
|
|
49
|
+
"diary-entry": "Render a single diary entry or list of recent entries. Query params: date (YYYY-MM-DD), orchestrator, limit, lang.",
|
|
50
|
+
"mission-timeline": "Render a missions timeline. Query params: pilot, project, status, limit, lang.",
|
|
51
|
+
"briefing-note": "Render briefing note details. Query params: noteId or (topic + limit), lang.",
|
|
52
|
+
"memory-quote": "Render memory quotes from a namespace. Query params: namespace, type, limit, lang.",
|
|
53
|
+
};
|
|
54
|
+
// Resource list — returned by resources/list MCP handler
|
|
55
|
+
export function listUiResources() {
|
|
56
|
+
return PRIMITIVES.map((p) => ({
|
|
57
|
+
uri: `ui://vp/v1/${p}`,
|
|
58
|
+
name: p,
|
|
59
|
+
description: PRIMITIVE_DESCRIPTIONS[p],
|
|
60
|
+
mimeType: "text/html",
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
// Resource read — dispatched by primitive name. Returns HTML inline.
|
|
64
|
+
export async function readUiResource(uri, fetchConvex) {
|
|
65
|
+
const parsed = parseUiUri(uri);
|
|
66
|
+
if (!parsed) {
|
|
67
|
+
throw new Error(`[VP UI Resources] Invalid ui:// URI: ${uri}`);
|
|
68
|
+
}
|
|
69
|
+
if (!PRIMITIVES.includes(parsed.primitive)) {
|
|
70
|
+
throw new Error(`[VP UI Resources] Unknown primitive: ${parsed.primitive}. Available: ${PRIMITIVES.join(", ")}`);
|
|
71
|
+
}
|
|
72
|
+
let html;
|
|
73
|
+
switch (parsed.primitive) {
|
|
74
|
+
case "tasks-table":
|
|
75
|
+
html = await renderTasksTable(parsed.query, fetchConvex);
|
|
76
|
+
break;
|
|
77
|
+
case "messages-feed":
|
|
78
|
+
html = await renderMessagesFeed(parsed.query, fetchConvex);
|
|
79
|
+
break;
|
|
80
|
+
case "diary-entry":
|
|
81
|
+
html = await renderDiaryEntry(parsed.query, fetchConvex);
|
|
82
|
+
break;
|
|
83
|
+
case "mission-timeline":
|
|
84
|
+
html = await renderMissionTimeline(parsed.query, fetchConvex);
|
|
85
|
+
break;
|
|
86
|
+
case "briefing-note":
|
|
87
|
+
html = await renderBriefingNote(parsed.query, fetchConvex);
|
|
88
|
+
break;
|
|
89
|
+
case "memory-quote":
|
|
90
|
+
html = await renderMemoryQuote(parsed.query, fetchConvex);
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
throw new Error(`[VP UI Resources] Unimplemented primitive: ${parsed.primitive}`);
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
uri,
|
|
97
|
+
mimeType: "text/html",
|
|
98
|
+
text: html,
|
|
99
|
+
};
|
|
100
|
+
}
|