vantage-peers-mcp 2.12.0 → 2.13.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/LICENSE ADDED
@@ -0,0 +1,110 @@
1
+ Functional Source License, Version 1.1, Apache 2.0 Future License
2
+
3
+ Abbreviation
4
+
5
+ FSL-1.1-Apache-2.0
6
+
7
+ Notice
8
+
9
+ Copyright 2026 VantageOS (ElPi Corp)
10
+
11
+ Terms and Conditions
12
+
13
+ Licensor: VantageOS (ElPi Corp)
14
+
15
+ Licensed Work: VantagePeers
16
+ The Licensed Work is (c) 2026 VantageOS (ElPi Corp)
17
+
18
+ Additional Use Grant: You may make production use of the Licensed Work,
19
+ provided Your use does not include offering the Licensed
20
+ Work to third parties as a hosted or managed service
21
+ where the service provides users with access to any
22
+ substantial set of the features or functionality of the
23
+ Licensed Work.
24
+
25
+ Change Date: 2028-04-03 (two years from publication)
26
+
27
+ Change License: Apache License, Version 2.0
28
+
29
+ For information about alternative licensing arrangements for the Licensed Work,
30
+ please contact: contact@vantageos.com
31
+
32
+ License text below is provided for informational purposes only.
33
+
34
+ ---
35
+
36
+ Functional Source License, Version 1.1, Apache 2.0 Future License
37
+
38
+ 1. Purpose
39
+
40
+ This license gives you broad permission to use, modify, and share this
41
+ software, with one important condition: you may not use it to compete with us.
42
+
43
+ 2. Acceptance
44
+
45
+ To use the software, you must agree to these terms. If you do not agree, you
46
+ may not use the software.
47
+
48
+ 3. Copyright License
49
+
50
+ The licensor grants you a non-exclusive, royalty-free, worldwide, non-
51
+ sublicensable, non-transferable license to use, copy, distribute, make
52
+ available, and prepare derivative works of the software, in each case subject
53
+ to the limitations below.
54
+
55
+ 4. Limitations
56
+
57
+ You may not make the functionality of the software available to third parties
58
+ as a service, or otherwise use the software to provide a service to third
59
+ parties that competes with the licensor.
60
+
61
+ 5. Patents
62
+
63
+ The licensor grants you a license, under any patent claims the licensor can
64
+ license or becomes able to license, to make, have made, use, sell, offer for
65
+ sale, import, and have imported the software, in each case subject to the
66
+ limitations and conditions in this license.
67
+
68
+ 6. Fair Use
69
+
70
+ This license is not intended to limit any rights you may have under applicable
71
+ fair use or other laws.
72
+
73
+ 7. No Other Rights
74
+
75
+ These terms do not grant any other rights. The licensor reserves all rights not
76
+ expressly granted.
77
+
78
+ 8. Termination
79
+
80
+ Your license is automatically terminated if you violate the terms. However, if
81
+ you cure the violation within 30 days of discovering it, your license is
82
+ reinstated retroactively.
83
+
84
+ 9. Disclaimer
85
+
86
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
87
+ IMPLIED.
88
+
89
+ 10. Limitation of Liability
90
+
91
+ TO THE EXTENT PERMITTED BY LAW, THE LICENSOR IS NOT LIABLE FOR ANY DAMAGES
92
+ ARISING FROM THE USE OF THE SOFTWARE.
93
+
94
+ 11. Change Date and License
95
+
96
+ After the Change Date, the licensor grants you the rights under the Change
97
+ License.
98
+
99
+ 12. Definitions
100
+
101
+ "Compete" means providing a product or service that is substantially similar
102
+ to, or a replacement for, all or a significant portion of the Licensed Work.
103
+
104
+ ---
105
+
106
+ Apache License, Version 2.0
107
+
108
+ After the Change Date (2028-04-03), this software will be available under the
109
+ Apache License, Version 2.0. See https://www.apache.org/licenses/LICENSE-2.0
110
+ for full text.
package/README.md CHANGED
@@ -25,6 +25,7 @@ Day 92 VP MCP quality overhaul (mission `k57a36y8w5t085bqr23dsmvb2d882506`, PR #
25
25
  - **C1 — 87 Zod `outputSchema` exports** following the per-family envelope standard (`create_*` → `{id,...}`, `list_*` → `{items,cursor}`, `delete_*` → `{id,deleted:true}`, etc.) based on the `whoamiOutputSchema` precedent (commit `5231811`).
26
26
  - **C2 — Unicode NFC normalization + case-insensitive orchestrator-ID matching** applied at all write paths and filter comparisons; closes the NFD/NFC silent mismatch class discovered in the Hélios/helios production regression.
27
27
  - **C3 — 97 tool descriptions standardized** (1-line summary + WHEN clause + concrete EXAMPLE, 80–500 chars) + 10 canonical aliases aligned to the `verb_noun_snake` whitelist.
28
+ - **PR-J (Day 113) — canonical 114-tool snapshot quality gate** (`mcp-server/src/__tests__/tools-descriptions-canonical.test.ts`): inventory floor ≥100, length floor ≥60 chars, placeholder ban, category contracts (every `list_*` mentions `limit` + `cap`/`default 20`/`default 100`; every recall-class tool carries the PR-H VP-Sources doctrine verbatim). 15 `list_*` descriptions amended in T-GREEN `41944dc` to add the paging qualifier `Default limit N. cap M.` aligned with PR-A/B/C/E precedent.
28
29
  - **C4 — `claude-peers` legacy references removed** from source and docs + grep-gate CI check to prevent reintroduction.
29
30
  - **A3 — `whoami` LECTURE tool** (PR #661, commit `5231811`) — returns `suggested_orchestrator_id`, `scope_profile`, and `namespace_read_prefixes` so skills auto-resolve identity without prompting the user.
30
31
  - **F1 — `validate_task_payload` validator tool** (commit `cf6c961`) — client-side payload validation before any write reaches Convex.
@@ -96,8 +97,87 @@ The server also reads `CONVEX_URL` from `.env.local` in the parent directory if
96
97
  ### Profiles (3)
97
98
  `get_profile`, `update_profile`, `list_peers`
98
99
 
99
- ### Tasks (10)
100
- `create_task`, `list_tasks`, `list_tasks_by_mission`, `update_task`, `start_task`, `complete_task`, `checkout_task`, `delete_task`, `block_task`, `add_task_dependency`
100
+ ### Tasks (11)
101
+ `create_task`, `list_tasks`, `list_tasks_by_mission`, `update_task`, `start_task`, `complete_task`, `checkout_task`, `delete_task`, `block_task`, `add_task_dependency`, `bulk_complete_tasks`
102
+
103
+ #### `list_tasks` — args schema + `excludeAutoGenerated` filter (PR-E)
104
+
105
+ ```
106
+ list_tasks(assignedTo?, status?, missionId?, createdBy?, updatedSince?, createdBefore?, limit?, cursor?, fields?, excludeAutoGenerated?)
107
+ ```
108
+
109
+ | Arg | Type | Default | Notes |
110
+ |-----|------|---------|-------|
111
+ | `assignedTo` | string | — | Filter by assignee (e.g. `"pi"`). |
112
+ | `status` | string \| string[] \| alias | — | Single status, array, or alias (`"open"`, `"active"`, `"all"`). |
113
+ | `missionId` | string | — | Filter to tasks in a specific mission. |
114
+ | `createdBy` | string | — | Filter by creator (e.g. `"sigma"`). |
115
+ | `updatedSince` | number | — | Epoch ms. Returns tasks with `updatedAt >= this`. |
116
+ | `createdBefore` | number | — | Epoch ms. Pagination anchor (legacy; prefer `cursor`). |
117
+ | `limit` | number 1–200 | `50` | Page size. |
118
+ | `cursor` | string | — | Opaque token from prior `nextCursor`. |
119
+ | `fields` | `"lite"\|"full"` | `"full"` | `"lite"` returns `{_id, _creationTime, title, status, priority, assignedTo, missionId}`. |
120
+ | `excludeAutoGenerated` | boolean | `false` | When `true`, filters tasks where `createdBy ~ /^cron-/i` OR `title ~ /^\/?check-messages$/i`. Default `false` — backward-compatible. |
121
+
122
+ **`excludeAutoGenerated` cron contract:**
123
+ - `createdBy` matches `/^cron-/i` (dash mandatory): `cron-bot` is filtered, `cronus` is **not** filtered.
124
+ - `title` matches `/^\/?check-messages$/i` (whole-string, optional leading slash, case-insensitive).
125
+ - Filter applied in-memory after existing query filters, before envelope assembly.
126
+ - **Post-filter pages may be smaller than `limit`** — filtered rows do not count toward limit. Acceptable for cron-spam catalog (small, narrowly targeted).
127
+
128
+ Example — Pi queue cleaned of cron-spam:
129
+ ```json
130
+ {
131
+ "tool": "list_tasks",
132
+ "arguments": { "assignedTo": "pi", "status": "open", "excludeAutoGenerated": true, "limit": 50 }
133
+ }
134
+ ```
135
+
136
+ Returns `{ items: Task[], nextCursor: string | null }`. `nextCursor` is `null` on the last page.
137
+
138
+ #### `bulk_complete_tasks` — args schema + dry-run-default safety (PR-F)
139
+
140
+ ```
141
+ bulk_complete_tasks(filter, dryRun?, completionNoteTemplate?, callerOrchestrator?)
142
+ ```
143
+
144
+ | Arg | Type | Default | Notes |
145
+ |-----|------|---------|-------|
146
+ | `filter` | object | (required) | Filter object. Currently: `{ autoGeneratedOnly?: boolean }`. |
147
+ | `filter.autoGeneratedOnly` | boolean | `false` | When `true`, matches tasks where `createdBy ~ /^cron-/i` OR `title ~ /^\/?check-messages$/i`. |
148
+ | `dryRun` | boolean | `true` | **Safety default.** When `true`, returns a preview `{count, sampleIds, bulkRunId}` without mutating. Pass `false` explicitly to commit. |
149
+ | `completionNoteTemplate` | string | (see below) | Template string for the `completionNote` written to each closed task. Supports `{{day}}`, `{{bulkRunId}}`, `{{executedAt}}` interpolation. Default: `"bulk-cleanup: cron-spam day {{day}} runId={{bulkRunId}} executedAt={{executedAt}}"`. |
150
+ | `callerOrchestrator` | string | — | Caller identity for RBAC. When provided and not `"system"`, every matched task must have `createdBy` or `assignedTo` equal to the caller — otherwise throws `RBAC_DENIED`. |
151
+
152
+ **`dryRun` safety note:** `bulk_complete_tasks` always defaults `dryRun` to `true`. Calling the tool without `dryRun=false` never mutates the database. This mirrors the two-step pattern required for all destructive bulk operations: preview first, then commit.
153
+
154
+ **`excludeAutoGenerated` cron contract** (same as `list_tasks`):
155
+ - `createdBy` matches `/^cron-/i` (dash mandatory): `cron-bot` is filtered, `cronus` is **not** filtered.
156
+ - `title` matches `/^\/?check-messages$/i` (whole-string, optional leading slash, case-insensitive).
157
+ - Filter applied in-memory against all non-done tasks.
158
+ - **Post-filter count may be smaller than expected** — same trade-off as `list_tasks excludeAutoGenerated`.
159
+
160
+ Examples:
161
+
162
+ ```json
163
+ // Step 1 — dry-run preview (default dryRun=true)
164
+ {
165
+ "tool": "bulk_complete_tasks",
166
+ "arguments": { "filter": { "autoGeneratedOnly": true }, "callerOrchestrator": "system" }
167
+ }
168
+ // → { "count": 152, "sampleIds": ["k17...", "k18..."], "bulkRunId": "bulk-1782050000000-a3f2" }
169
+
170
+ // Step 2 — commit (explicit dryRun=false)
171
+ {
172
+ "tool": "bulk_complete_tasks",
173
+ "arguments": { "filter": { "autoGeneratedOnly": true }, "dryRun": false, "callerOrchestrator": "system" }
174
+ }
175
+ // → { "count": 152, "sampleIds": ["k17...", "k18..."], "bulkRunId": "bulk-1782050000000-a3f2", "executedAt": 1782050000000 }
176
+ ```
177
+
178
+ Returns `{ count, sampleIds, bulkRunId, executedAt? }`:
179
+ - `dryRun=true` — `{ count, sampleIds, bulkRunId }` (no `executedAt`).
180
+ - `dryRun=false` — `{ count, sampleIds, bulkRunId, executedAt }` — `bulkRunId` is the Day-76 evidence token; `executedAt` is the mutation epoch ms.
101
181
 
102
182
  ### Missions (6)
103
183
  `create_mission`, `list_missions`, `update_mission`, `update_mission_status`, `get_mission_template`, `get_mission`
@@ -114,9 +194,45 @@ The server also reads `CONVEX_URL` from `.env.local` in the parent directory if
114
194
  ### Briefing Notes (2)
115
195
  `create_briefing_note`, `list_briefing_notes`
116
196
 
197
+ #### `list_briefing_notes` — VP-Sources doctrine (PR-H)
198
+
199
+ Exports `LIST_BRIEFING_NOTES_TOOL_DESCRIPTION` from `mcp-server/src/tools.ts`.
200
+
201
+ Same two advisory VP-Sources doctrine paragraphs appended after the existing description (identical strings, see `recall` in Search / RAG above).
202
+
203
+ #### `search_briefing_notes_by_keyword` — VP-Sources doctrine (PR-H)
204
+
205
+ Exports `SEARCH_BRIEFING_NOTES_BY_KEYWORD_TOOL_DESCRIPTION` from `mcp-server/src/tools.ts`.
206
+
207
+ Same two advisory VP-Sources doctrine paragraphs appended after the existing description (identical strings, see `recall` in Search / RAG above).
208
+
117
209
  ### Search / RAG (3)
118
210
  `search_fix_patterns_by_semantic` (alias `search_fix_patterns`), `search_memories_by_keyword` (alias `text_search`), `hybrid_search`
119
211
 
212
+ #### `recall` — VP-Sources doctrine (PR-H)
213
+
214
+ Alias of `search_memories_by_semantic`. Exports `RECALL_TOOL_DESCRIPTION` from `mcp-server/src/tools.ts`.
215
+
216
+ The description now embeds two advisory VP-Sources doctrine paragraphs appended after the existing text:
217
+
218
+ > VP-Sources doctrine: MUST be called before any factual claim about fleet state, audits, dette tooling, mission/task/client status, incident history, doctrine references.
219
+ >
220
+ > Cite returned ids in the answer footer as 'VP-Sources: recall("\<q\>")→[ids] | none-needed:\<reason\>'.
221
+
222
+ Doctrine is advisory-only — no hook blocks on absence. Client LLMs read the doctrine at tool-list time.
223
+
224
+ #### `text_search` — VP-Sources doctrine (PR-H)
225
+
226
+ Alias of `search_memories_by_keyword`. Exports `TEXT_SEARCH_TOOL_DESCRIPTION` from `mcp-server/src/tools.ts`.
227
+
228
+ Same two advisory VP-Sources doctrine paragraphs appended after the existing description (identical strings, see `recall` above).
229
+
230
+ #### `hybrid_search` — VP-Sources doctrine (PR-H)
231
+
232
+ Exports `HYBRID_SEARCH_TOOL_DESCRIPTION` from `mcp-server/src/tools.ts`.
233
+
234
+ Same two advisory VP-Sources doctrine paragraphs appended after the existing description (identical strings, see `recall` above).
235
+
120
236
  ### Issues (6)
121
237
  `get_issue`, `list_issues`, `update_issue_status`, `verify_issue`, `issue_stats`, `link_commit_to_issue`
122
238
 
@@ -227,12 +343,58 @@ Example:
227
343
  ### Deployments & Repos (5)
228
344
  `add_deployment`, `remove_deployment`, `list_repo_mappings`, `add_repo_mapping`, `remove_repo_mapping`
229
345
 
346
+ #### `list_repo_mappings` — args schema + defaults (PR-C)
347
+
348
+ ```
349
+ list_repo_mappings(limit?, cursor?, fields?)
350
+ ```
351
+
352
+ | Arg | Type | Default | Notes |
353
+ |-----|------|---------|-------|
354
+ | `limit` | number 1–200 | `20` | Page size. Capped at `200` server-side. |
355
+ | `cursor` | string | — | Opaque token from prior `nextCursor`. |
356
+ | `fields` | `"lite"\|"full"` | `"full"` | `"lite"` returns `{_id, _creationTime, repo, orchestrator, project}`. `"full"` returns complete mapping object (including `active`, `lastDeployedSHA`, `lastDeployedAt`). |
357
+
358
+ Returns `{ items: RepoMapping[], nextCursor: string | null }`. `nextCursor` is `null` on the last page.
359
+
230
360
  ### Business Units (5)
231
361
  `create_bu`, `list_bus`, `get_bu`, `update_bu`, `delete_bu`
232
362
 
363
+ #### `list_bus` — args schema + defaults (PR-A)
364
+
365
+ ```
366
+ list_bus(orchestratorId?, status?, limit?, cursor?, fields?)
367
+ ```
368
+
369
+ | Arg | Type | Default | Notes |
370
+ |-----|------|---------|-------|
371
+ | `orchestratorId` | string | — | Filter by lead orchestrator (e.g. `"sigma"`). |
372
+ | `status` | `"idea"\|"building"\|"live"\|"revenue"` | — | Filter by lifecycle status. |
373
+ | `limit` | number 1–200 | `20` | Page size. Capped at `200` server-side. |
374
+ | `cursor` | string | — | Opaque token from prior `nextCursor`. |
375
+ | `fields` | `"lite"\|"full"` | `"full"` | `"lite"` returns `{_id, name, status, orchestratorId, _creationTime}`. `"full"` returns complete BU object (18+ keys). |
376
+
377
+ Returns `{ items: BusinessUnit[], nextCursor: string | null }`. `nextCursor` is `null` on the last page.
378
+
233
379
  ### Components (6)
234
380
  `register_component`, `list_components`, `get_component`, `update_component`, `delete_component`, `search_components_by_keyword` (alias `search_components`)
235
381
 
382
+ #### `list_components` — args schema + defaults (PR-B)
383
+
384
+ ```
385
+ list_components(type?, team?, limit?, cursor?, fields?)
386
+ ```
387
+
388
+ | Arg | Type | Default | Notes |
389
+ |-----|------|---------|-------|
390
+ | `type` | `"agent"\|"skill"\|"hook"\|"plugin"` | — | Filter by component type. |
391
+ | `team` | string | — | Filter by team (e.g. `"development"`). |
392
+ | `limit` | number 1–200 | `20` | Page size. Capped at `200` server-side. |
393
+ | `cursor` | string | — | Opaque token from prior `nextCursor`. |
394
+ | `fields` | `"lite"\|"full"` | `"full"` | `"lite"` returns `{_id, _creationTime, name, type, team}`. `"full"` returns complete component object. |
395
+
396
+ Returns `{ items: Component[], nextCursor: string | null }`. `nextCursor` is `null` on the last page.
397
+
236
398
  ### Mandates (6)
237
399
  `create_mandate`, `list_mandates`, `accept_mandate`, `update_mandate`, `validate_mandate_spending`, `settle_mandate`
238
400
 
@@ -242,11 +404,61 @@ Example:
242
404
  ### Session (1)
243
405
  `set_summary`
244
406
 
245
- ## Compact payloads and status aliases (v2.11.0 — feature since v2.3.0)
407
+ ### Observability (1)
408
+ `improvisation_digest`
409
+
410
+ #### `improvisation_digest` — weekly advisory digest (PR-I)
411
+
412
+ Scans a rolling time window of VP tasks, messages, and memories for records carrying fleet/state tokens (commit SHA, PR#, VP id, decisive verb) with **no VP-Sources footer** — the Eta heuristic proxy for "made a fleet-state claim without a prior `recall` upstream".
413
+
414
+ ```
415
+ improvisation_digest(windowDays?, orchestrators?)
416
+ ```
417
+
418
+ | Arg | Type | Default | Notes |
419
+ |-----|------|---------|-------|
420
+ | `windowDays` | number | `7` | Days to look back. |
421
+ | `orchestrators` | string[] | — | Scope to these roles only (e.g. `["sigma","pi"]`). Omit for all orchestrators. |
422
+
423
+ **Returns:**
424
+
425
+ ```ts
426
+ {
427
+ countsByOrch: Record<string, number>, // hit count per orchestrator
428
+ countsByCategory: Record<string, number>, // hit count per record type (task/message/memory)
429
+ samples: Array<{
430
+ id: string,
431
+ category: string,
432
+ orchestrator: string,
433
+ snippet: string
434
+ }> // up to 50 representative snippets
435
+ }
436
+ ```
437
+
438
+ **ADVISORY-only.** Pure read query — never blocks any action. Results are informational: a high improvisation rate suggests a team should increase VP-Sources citation hygiene, but the tool itself takes no automated action.
439
+
440
+ **V1 scope (Option C):** scans VP records (tasks + messages + memories) only. Per Pi Day-113 arbitration (msg `k97a0pp6kq1axkj6cmc4pecpy989ce1w`), fallback if V1 misses too many = **Option B** (new dedicated `sessions` Convex table), not Option A (JSONL replay).
441
+
442
+ **Detection heuristic (Eta A5 scope filter):**
443
+ - Flag condition 1: record body contains a durable-artifact token (7–40 hex SHA, `#NNN`, Convex ID prefix, or decisive verb `merged/deployed/approved/shipped/released/fixed`).
444
+ - Flag condition 2: record body does NOT contain the `VP-Sources:` footer substring.
445
+ - A5 scope: excludes `system`, `cron-*`, and webhook-sourced entries.
446
+
447
+ Examples:
448
+
449
+ ```json
450
+ // Default 7-day window, all orchestrators
451
+ { "tool": "improvisation_digest", "arguments": { "windowDays": 7 } }
452
+
453
+ // Scoped to one orchestrator
454
+ { "tool": "improvisation_digest", "arguments": { "windowDays": 14, "orchestrators": ["sigma"] } }
455
+ ```
456
+
457
+ ## Compact payloads and status aliases (v2.12.0 — feature since v2.3.0)
246
458
 
247
459
  ### `fields=lite` — reduced token payloads
248
460
 
249
- `list_tasks`, `list_tasks_by_mission`, `list_missions`, and `list_briefing_notes` accept an optional `fields` parameter:
461
+ `list_tasks`, `list_tasks_by_mission`, `list_missions`, `list_briefing_notes`, `list_bus`, `list_components`, and `list_repo_mappings` accept an optional `fields` parameter:
250
462
 
251
463
  | Value | Behaviour |
252
464
  |-------|-----------|
@@ -260,6 +472,9 @@ Lite projections per entity:
260
472
  | `list_tasks` / `list_tasks_by_mission` | `_id`, `_creationTime`, `title`, `status`, `priority`, `assignedTo`, `missionId` |
261
473
  | `list_missions` | `_id`, `_creationTime`, `name`, `status`, `pilot`, `priority`, `project` |
262
474
  | `list_briefing_notes` | `_id`, `_creationTime`, `topic`, `title`, `participants`, `createdBy` |
475
+ | `list_bus` | `_id`, `_creationTime`, `name`, `status`, `orchestratorId` — PR-A activated actual projection (was no-op since v2.4.12) |
476
+ | `list_components` | `_id`, `_creationTime`, `name`, `type`, `team` — PR-B activated actual projection (was no-op — returned full row) |
477
+ | `list_repo_mappings` | `_id`, `_creationTime`, `repo`, `orchestrator`, `project` — PR-C activated actual projection (excludes `active`, `lastDeployedSHA`, `lastDeployedAt`) |
263
478
 
264
479
  Example (tasks lite):
265
480
  ```json
@@ -46,8 +46,13 @@ catch {
46
46
  // ─────────────────────────────────────────────────────────────────────────────
47
47
  // Constants
48
48
  // ─────────────────────────────────────────────────────────────────────────────
49
- const PUBLIC_BASE_URL_FALLBACK = process.env.PUBLIC_BASE_URL ??
50
- "https://vantage-peers-production.up.railway.app";
49
+ // Day 107 Cédric BLOCKER root cause: previously hardcoded a fallback to the
50
+ // VantagePeers Cloud production URL, which meant Self-host deploys forgetting
51
+ // PUBLIC_BASE_URL silently advertised Sigma's URL in OAuth metadata and broke
52
+ // every Self-host customer's DCR chain with `invalid_client`. Fix: env-only
53
+ // fallback, no implicit cross-tenant default. `resolveIssuer` throws clear
54
+ // error if both Host header AND env are absent.
55
+ const PUBLIC_BASE_URL_FALLBACK = process.env.PUBLIC_BASE_URL ?? null;
51
56
  const ACCESS_TOKEN_TTL_SECONDS = 3600; // 1 hour
52
57
  const REFRESH_TOKEN_TTL_SECONDS = 30 * 24 * 3600; // 30 days
53
58
  const AUTH_CODE_TTL_SECONDS = 600; // 10 minutes
@@ -77,7 +82,10 @@ function resolveIssuer(req) {
77
82
  : "https");
78
83
  return `${proto}://${host}`;
79
84
  }
80
- return PUBLIC_BASE_URL_FALLBACK;
85
+ if (PUBLIC_BASE_URL_FALLBACK) {
86
+ return PUBLIC_BASE_URL_FALLBACK;
87
+ }
88
+ throw new Error("Server misconfigured: cannot determine public base URL (no Host header and PUBLIC_BASE_URL env var unset). Self-host deploys MUST set PUBLIC_BASE_URL.");
81
89
  }
82
90
  function randomOpaqueToken() {
83
91
  // 256-bit entropy via getRandomValues (32 bytes → 64 hex chars).
package/dist/server.js CHANGED
File without changes
package/dist/src/auth.js CHANGED
@@ -163,8 +163,28 @@ export function bearerAuthMiddleware() {
163
163
  // the param MUST be `resource_metadata=` (not `resource=`). Claude.ai's OAuth
164
164
  // connector looks for `resource_metadata=` to bootstrap PRM discovery; with
165
165
  // `resource=` the entire DCR chain breaks before any token is issued.
166
- const publicBaseUrl = process.env.PUBLIC_BASE_URL ??
167
- "https://vantage-peers-production.up.railway.app";
166
+ //
167
+ // Day 107 Cédric BLOCKER root cause: a hardcoded fallback to the
168
+ // VantagePeers Cloud production URL ("vantage-peers-production.up.railway.app")
169
+ // meant Self-host deploys that forgot PUBLIC_BASE_URL silently advertised
170
+ // Sigma's PRM endpoint, breaking every Self-host customer's DCR chain with
171
+ // `invalid_client`. Fix: derive from the incoming request (RFC 8414 §2 —
172
+ // issuer MUST be the URL the client used). Fall back to PUBLIC_BASE_URL env
173
+ // only if Host header is absent (curl smoke). Fail closed if neither is set.
174
+ const host = c.req.header("host");
175
+ const xfProto = c.req.header("x-forwarded-proto");
176
+ const proto = xfProto ??
177
+ (host?.startsWith("localhost") || host?.startsWith("127.")
178
+ ? "http"
179
+ : "https");
180
+ const publicBaseUrl = host
181
+ ? `${proto}://${host}`
182
+ : (process.env.PUBLIC_BASE_URL ?? null);
183
+ if (!publicBaseUrl) {
184
+ return c.json({
185
+ error: "Server misconfigured: cannot determine public base URL (no Host header and PUBLIC_BASE_URL env var unset).",
186
+ }, 500);
187
+ }
168
188
  const wwwAuthHeader = `Bearer resource_metadata="${publicBaseUrl}/.well-known/oauth-protected-resource"`;
169
189
  const authHeader = c.req.header("Authorization");
170
190
  if (!authHeader?.startsWith("Bearer ")) {
@@ -1,3 +1,24 @@
1
+ import { z } from "zod";
2
+ export declare const pagingArgsSchema: z.ZodObject<{
3
+ limit: z.ZodOptional<z.ZodNumber>;
4
+ cursor: z.ZodOptional<z.ZodString>;
5
+ fields: z.ZodOptional<z.ZodEnum<{
6
+ lite: "lite";
7
+ full: "full";
8
+ }>>;
9
+ }, z.core.$strip>;
10
+ export type PagingArgs = z.infer<typeof pagingArgsSchema>;
11
+ export interface PagingDefaults {
12
+ limit: number;
13
+ cap: number;
14
+ fields: "lite" | "full";
15
+ }
16
+ export declare const DEFAULT_PAGING: PagingDefaults;
17
+ export declare function applyPagingDefaults(args: PagingArgs, defaults?: PagingDefaults): {
18
+ limit: number;
19
+ cursor: string | undefined;
20
+ fields: "lite" | "full";
21
+ };
1
22
  /**
2
23
  * Shared paging utility for VP MCP `list_*` tools (S3.3 B8).
3
24
  *
@@ -1,3 +1,29 @@
1
+ import { z } from "zod";
2
+ // ─────────────────────────────────────────────────────────────────────────────
3
+ // PR-A envelope safety — shared schema + applyPagingDefaults helper
4
+ // Reusable by list_bus, list_tasks (PR-B), list_memories (PR-C) etc.
5
+ // ─────────────────────────────────────────────────────────────────────────────
6
+ export const pagingArgsSchema = z.object({
7
+ limit: z.number().int().min(1).max(200).optional(),
8
+ cursor: z.string().optional(),
9
+ fields: z.enum(["lite", "full"]).optional(),
10
+ });
11
+ export const DEFAULT_PAGING = {
12
+ limit: 20,
13
+ cap: 200,
14
+ fields: "full",
15
+ };
16
+ export function applyPagingDefaults(args, defaults = DEFAULT_PAGING) {
17
+ const requested = args.limit ?? defaults.limit;
18
+ const clamped = Math.min(requested, defaults.cap);
19
+ const limit = Math.max(1, clamped);
20
+ return {
21
+ limit,
22
+ cursor: args.cursor,
23
+ fields: args.fields ?? defaults.fields,
24
+ };
25
+ }
26
+ // ─────────────────────────────────────────────────────────────────────────────
1
27
  /**
2
28
  * Shared paging utility for VP MCP `list_*` tools (S3.3 B8).
3
29
  *
@@ -0,0 +1,54 @@
1
+ /**
2
+ * MCP tool: export_okf_bundle (Phase 1 — T3).
3
+ *
4
+ * Thin proxy around the Convex `okfBundle:exportOkfBundle` action. The MCP
5
+ * tool wrapper exposes the export to any MCP client (Claude.ai, ChatGPT,
6
+ * Claude Code, Codex, IDE…) via the public VantagePeers Cloud surface.
7
+ *
8
+ * **VantagePeers Cloud, multi-tenant**: this is the Cloud product (NOT
9
+ * Self-host). The Convex action enforces auth — caller must match the
10
+ * namespace tail when an identity is attached (cross-tenant export
11
+ * forbidden). The Phase 1 hard lock to `project/elpi-corp` was relaxed by
12
+ * B3 (mission k5779qbxh, task k17f3407) so any `team/<orgId>/*` tenant can
13
+ * export their own bundle.
14
+ *
15
+ * RFC parent: decisions/okf-bridge-phase-1-rfc-2026-06-18.md (commit 6613610).
16
+ * ADR: decisions/adr-okf-exporter-arch.md (commit 2cd357e).
17
+ *
18
+ * Orchestrator: Sigma — VantagePeers | 2026-06-19
19
+ */
20
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
21
+ import type { ConvexHttpClient } from "convex/browser";
22
+ import { z } from "zod";
23
+ export interface ExportOkfBundleResult {
24
+ bundleUrl: string;
25
+ storageId: string;
26
+ size: number;
27
+ fileCount: number;
28
+ manifest: {
29
+ types: {
30
+ memoryCount: number;
31
+ briefingCount: number;
32
+ taskCount: number;
33
+ };
34
+ truncated: boolean;
35
+ urlExpiresAt: string;
36
+ };
37
+ }
38
+ export declare const exportOkfBundleArgsSchema: {
39
+ namespace: z.ZodString;
40
+ types: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString>>>;
41
+ format: z.ZodEnum<{
42
+ tarball: "tarball";
43
+ tree: "tree";
44
+ }>;
45
+ since: z.ZodOptional<z.ZodNullable<z.ZodString>>;
46
+ urlTtl: z.ZodOptional<z.ZodNumber>;
47
+ };
48
+ /**
49
+ * Register the `export_okf_bundle` MCP tool against an McpServer instance.
50
+ *
51
+ * Call this from `tools.ts` (or directly from `server.ts`) alongside the other
52
+ * `server.tool(...)` registrations.
53
+ */
54
+ export declare function registerExportOkfBundle(server: McpServer, convex: ConvexHttpClient): void;
@@ -0,0 +1,107 @@
1
+ /**
2
+ * MCP tool: export_okf_bundle (Phase 1 — T3).
3
+ *
4
+ * Thin proxy around the Convex `okfBundle:exportOkfBundle` action. The MCP
5
+ * tool wrapper exposes the export to any MCP client (Claude.ai, ChatGPT,
6
+ * Claude Code, Codex, IDE…) via the public VantagePeers Cloud surface.
7
+ *
8
+ * **VantagePeers Cloud, multi-tenant**: this is the Cloud product (NOT
9
+ * Self-host). The Convex action enforces auth — caller must match the
10
+ * namespace tail when an identity is attached (cross-tenant export
11
+ * forbidden). The Phase 1 hard lock to `project/elpi-corp` was relaxed by
12
+ * B3 (mission k5779qbxh, task k17f3407) so any `team/<orgId>/*` tenant can
13
+ * export their own bundle.
14
+ *
15
+ * RFC parent: decisions/okf-bridge-phase-1-rfc-2026-06-18.md (commit 6613610).
16
+ * ADR: decisions/adr-okf-exporter-arch.md (commit 2cd357e).
17
+ *
18
+ * Orchestrator: Sigma — VantagePeers | 2026-06-19
19
+ */
20
+ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
21
+ import { z } from "zod";
22
+ // ─────────────────────────────────────────────────────────────────────────────
23
+ // Zod input schema (RFC §3.1)
24
+ // ─────────────────────────────────────────────────────────────────────────────
25
+ export const exportOkfBundleArgsSchema = {
26
+ namespace: z
27
+ .string()
28
+ .describe("OKF export namespace prefix. Any prefix the caller has write scope on " +
29
+ "is accepted (e.g. 'project/elpi-corp', 'team/<orgId>', 'org/<slug>'). " +
30
+ "Identity-attached callers must match the namespace tail (cross-tenant " +
31
+ "export forbidden). The 'project/elpi-corp' Phase 1 hard lock was " +
32
+ "removed by B3 (mission k5779qbxh) for multi-tenant Cloud dashboards."),
33
+ types: z
34
+ .array(z.string())
35
+ .nullable()
36
+ .optional()
37
+ .describe("Optional type filter — null/omitted exports all 3 families. " +
38
+ "Tokens: 'memory-*' (all subtypes), 'memory-<sub>' (literal), " +
39
+ "'briefing-note', 'task'."),
40
+ format: z
41
+ .enum(["tarball", "tree"])
42
+ .describe("Bundle format. Phase 1 supports 'tarball' only; 'tree' is reserved " +
43
+ "for Phase 2."),
44
+ since: z
45
+ .string()
46
+ .nullable()
47
+ .optional()
48
+ .describe("Optional ISO 8601 timestamp — only entries updated on/after are " +
49
+ "included. Anticipation Phase 2."),
50
+ urlTtl: z
51
+ .number()
52
+ .int()
53
+ .positive()
54
+ .optional()
55
+ .describe("Optional signed URL TTL in seconds (default 3600 = 1 hour). The " +
56
+ "storage object is purged at TTL expiry."),
57
+ };
58
+ // ─────────────────────────────────────────────────────────────────────────────
59
+ // Registration
60
+ // ─────────────────────────────────────────────────────────────────────────────
61
+ /**
62
+ * Register the `export_okf_bundle` MCP tool against an McpServer instance.
63
+ *
64
+ * Call this from `tools.ts` (or directly from `server.ts`) alongside the other
65
+ * `server.tool(...)` registrations.
66
+ */
67
+ export function registerExportOkfBundle(server, convex) {
68
+ server.tool("export_okf_bundle", "Export a VantagePeers namespace as an OKF v0.1 bundle (tarball). " +
69
+ "WHEN: use to ship a snapshot to Knowledge Catalog / RAG bridge / audit. " +
70
+ "EXAMPLE: export_okf_bundle namespace='project/elpi-corp' format='tarball'.", exportOkfBundleArgsSchema, {
71
+ readOnlyHint: true,
72
+ openWorldHint: false,
73
+ destructiveHint: false,
74
+ title: "Export OKF bundle",
75
+ }, async ({ namespace, types, format, since, urlTtl }) => {
76
+ try {
77
+ const result = (await convex.action("okfBundleNode:exportOkfBundle", {
78
+ namespace,
79
+ types: types ?? null,
80
+ format,
81
+ since: since ?? null,
82
+ urlTtl,
83
+ }));
84
+ return {
85
+ content: [
86
+ {
87
+ type: "text",
88
+ text: JSON.stringify(result, null, 2),
89
+ },
90
+ ],
91
+ };
92
+ }
93
+ catch (error) {
94
+ if (error instanceof McpError)
95
+ throw error;
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ // Surface structured OKF_* error codes verbatim — the action emits
98
+ // them as prefix tokens in the Error message.
99
+ console.error("[export_okf_bundle] action failed", {
100
+ namespace,
101
+ format,
102
+ errorMessage: message,
103
+ });
104
+ throw new McpError(ErrorCode.InternalError, message);
105
+ }
106
+ });
107
+ }