vantage-peers-mcp 2.14.1 → 2.15.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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.14.2 — 2026-06-30
4
+
5
+ - fix(oauth): DCR `/register` rejects empty/missing/malformed `redirect_uris` (RFC 7591 §3.2.2 `invalid_redirect_uri`). Closes zombie-client class — clients with empty `redirectUris` arrays can no longer be created. Defense-in-depth: same guard at Convex `registerPublicClient`. TDD-strict RED-then-GREEN.
6
+
3
7
  ## [Unreleased]
4
8
 
5
9
  ### Added
package/README.md CHANGED
@@ -171,7 +171,7 @@ VantagePeers ships a built-in OAuth 2.1 authorization server so Claude.ai web ca
171
171
  |--------|------|-------------|
172
172
  | `GET` | `/.well-known/oauth-authorization-server` | Authorization Server Metadata (RFC 8414) — advertises supported grant types, endpoints, and capabilities |
173
173
  | `GET` | `/.well-known/oauth-protected-resource` | Protected Resource Metadata (RFC 9728) — links back to the authorization server |
174
- | `POST` | `/register` | Dynamic Client Registration (RFC 7591) — Claude.ai registers itself automatically on first connect |
174
+ | `POST` | `/register` | Dynamic Client Registration (RFC 7591) — Claude.ai registers itself automatically on first connect. `redirect_uris` MUST be a non-empty array of valid `https://` URIs (or `http://localhost` / `http://127.0.0.1` for dev); absent, empty, non-string, unparseable, non-https, or fragment-bearing entries are rejected with `invalid_redirect_uri` (RFC 7591 §3.2.2, commit `2f3e653`). |
175
175
  | `GET` | `/authorize` | Authorization endpoint — redirects the user to grant access |
176
176
  | `POST` | `/token` | Token endpoint — issues access tokens per OAuth 2.1 |
177
177
 
@@ -373,6 +373,12 @@ Same two advisory VP-Sources doctrine paragraphs appended after the existing des
373
373
  - `search_components_by_keyword` (alias `search_components`) — keyword search over components
374
374
  - `hybrid_search` — RRF-fused vector + BM25 search; VP-Sources doctrine applies
375
375
 
376
+ Knowledge Base document upload is a two-step flow (see `docs/cloud/kb-ingest.md`):
377
+ 1. `generate_upload_url` — mints a signed Convex storage upload URL (Convex mutation `kbMutations:generateUploadUrl`, requires a Clerk JWT with `org_id`). `POST` the binary to that URL to obtain a `storageId`.
378
+ 2. `store_document_chunked` — with the `storageId`, extracts text, chunks (~512 tokens), and schedules RAG embedding per chunk at write time.
379
+
380
+ Ingested documents are then fully retrievable through `recall`, `text_search`, and `hybrid_search` like any other memory (Day 122 indexing fix + Day 123 `generate_upload_url` — the previously-missing upload entrypoint that made the KB end-to-end usable).
381
+
376
382
  #### `recall` — VP-Sources doctrine (PR-H)
377
383
 
378
384
  Alias of `search_memories_by_semantic`. Exports `RECALL_TOOL_DESCRIPTION` from `mcp-server/src/tools.ts`.
@@ -27,11 +27,11 @@
27
27
  import { readFileSync } from "node:fs";
28
28
  import { McpServer, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp.js";
29
29
  import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
30
+ import { timingSafeEqual } from "@vantageos/cloud-identity";
30
31
  import { ConvexHttpClient } from "convex/browser";
31
32
  import { Hono } from "hono";
32
33
  import { cors } from "hono/cors";
33
34
  import { bearerAuthMiddleware, internalClient, masterOnlyMiddleware, sha256Base64Url, sha256Hex, } from "./src/auth.js";
34
- import { timingSafeEqual } from "@vantageos/cloud-identity";
35
35
  import { registerTools } from "./src/tools.js";
36
36
  import { listUiResources, readUiResource } from "./src/ui-resources/index.js";
37
37
  let pkg;
@@ -262,9 +262,54 @@ app.post("/register", async (c) => {
262
262
  catch {
263
263
  // allow empty body — Claude sometimes posts nothing
264
264
  }
265
- const redirectUris = Array.isArray(body.redirect_uris)
266
- ? body.redirect_uris
267
- : [];
265
+ // RFC 7591 §2: redirect_uris is REQUIRED for authorization_code grant.
266
+ // RFC 7591 §3.2.2: invalid_redirect_uri is the canonical error code for
267
+ // bad, missing, or empty redirect_uris. Reject here so zombie clients
268
+ // (e.g. prod 87abdf5c-616b-4767-8a96-5ca04db88d9f) can never be created.
269
+ if (!Array.isArray(body.redirect_uris) ||
270
+ body.redirect_uris.length === 0) {
271
+ return c.json({
272
+ error: "invalid_redirect_uri",
273
+ error_description: "redirect_uris is required and must be a non-empty array of valid HTTPS URIs",
274
+ }, 400);
275
+ }
276
+ // Validate each URI: must be parseable and https: scheme (or http://localhost
277
+ // for dev). Reject file://, javascript:, data:, fragments, etc.
278
+ for (const uri of body.redirect_uris) {
279
+ if (typeof uri !== "string") {
280
+ return c.json({
281
+ error: "invalid_redirect_uri",
282
+ error_description: "redirect_uris is required and must be a non-empty array of valid HTTPS URIs",
283
+ }, 400);
284
+ }
285
+ let parsed;
286
+ try {
287
+ parsed = new URL(uri);
288
+ }
289
+ catch {
290
+ return c.json({
291
+ error: "invalid_redirect_uri",
292
+ error_description: "redirect_uris is required and must be a non-empty array of valid HTTPS URIs",
293
+ }, 400);
294
+ }
295
+ const isHttpsScheme = parsed.protocol === "https:";
296
+ const isLocalhostHttp = parsed.protocol === "http:" &&
297
+ (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1");
298
+ if (!isHttpsScheme && !isLocalhostHttp) {
299
+ return c.json({
300
+ error: "invalid_redirect_uri",
301
+ error_description: "redirect_uris is required and must be a non-empty array of valid HTTPS URIs",
302
+ }, 400);
303
+ }
304
+ // Reject URIs with fragments (RFC 6749 §3.1.2)
305
+ if (parsed.hash) {
306
+ return c.json({
307
+ error: "invalid_redirect_uri",
308
+ error_description: "redirect_uris is required and must be a non-empty array of valid HTTPS URIs",
309
+ }, 400);
310
+ }
311
+ }
312
+ const redirectUris = body.redirect_uris;
268
313
  const clientId = crypto.randomUUID();
269
314
  const clientSecret = randomOpaqueToken();
270
315
  const clientSecretHash = await sha256Hex(clientSecret);
@@ -58,6 +58,8 @@ export declare const SOFT_DELETE_DOCUMENT_TOOL_DESCRIPTION: string;
58
58
  export declare const softDeleteDocumentArgsSchema: z.ZodObject<{
59
59
  docId: z.ZodString;
60
60
  }, z.core.$strip>;
61
+ export declare const GENERATE_UPLOAD_URL_TOOL_DESCRIPTION: string;
62
+ export declare const generateUploadUrlArgsSchema: z.ZodObject<{}, z.core.$strip>;
61
63
  /**
62
64
  * Register store_document_chunked + soft_delete_document MCP tools.
63
65
  *
@@ -76,6 +76,15 @@ export const softDeleteDocumentArgsSchema = z.object({
76
76
  .describe("Document ID returned by store_document_chunked. " +
77
77
  "All chunks at namespace team/<orgId>/<docId> will be soft-deleted."),
78
78
  });
79
+ export const GENERATE_UPLOAD_URL_TOOL_DESCRIPTION = "Mint a Convex storage upload URL for the Knowledge Base ingest flow. " +
80
+ "The caller's org (from the Clerk JWT) is bound implicitly — no client args required. " +
81
+ "POST the file binary to the returned URL to obtain a storageId, then call " +
82
+ "store_document_chunked with that storageId. " +
83
+ "Requires Clerk JWT with org_id — no-org bearers are rejected. " +
84
+ "Default limit: 1 URL per call. cap: 1 URL. " +
85
+ "Returns the upload URL as plain text. " +
86
+ "EXAMPLE: generate_upload_url (no args) → 'https://…convex.cloud/api/storage/upload?...'.";
87
+ export const generateUploadUrlArgsSchema = z.object({});
79
88
  // ─────────────────────────────────────────────────────────────────────────────
80
89
  // Registration
81
90
  // ─────────────────────────────────────────────────────────────────────────────
@@ -177,4 +186,33 @@ export function registerKbIngestTools(server, convex, oauthCtx) {
177
186
  throw new McpError(ErrorCode.InternalError, message);
178
187
  }
179
188
  });
189
+ // ── generate_upload_url ─────────────────────────────────────────────────────
190
+ server.tool("generate_upload_url", GENERATE_UPLOAD_URL_TOOL_DESCRIPTION, generateUploadUrlArgsSchema.shape, {
191
+ readOnlyHint: false,
192
+ openWorldHint: false,
193
+ destructiveHint: false,
194
+ title: "Generate Knowledge Base upload URL",
195
+ }, async () => {
196
+ try {
197
+ const { orgId, namespacePrefix } = resolveOrgContext();
198
+ const url = (await convex.mutation("kbMutations:generateUploadUrl", { orgId, namespace: namespacePrefix }));
199
+ return {
200
+ content: [
201
+ {
202
+ type: "text",
203
+ text: url,
204
+ },
205
+ ],
206
+ };
207
+ }
208
+ catch (error) {
209
+ if (error instanceof McpError)
210
+ throw error;
211
+ const message = error instanceof Error ? error.message : String(error);
212
+ console.error("[generate_upload_url] mutation failed", {
213
+ errorMessage: message,
214
+ });
215
+ throw new McpError(ErrorCode.InternalError, message);
216
+ }
217
+ });
180
218
  }
package/package.json CHANGED
@@ -1,95 +1,95 @@
1
1
  {
2
- "name": "vantage-peers-mcp",
3
- "version": "2.14.1",
4
- "description": "MCP server for VantagePeers — shared memory, messaging, and task coordination for AI agent teams",
5
- "type": "module",
6
- "main": "./dist/server.js",
7
- "exports": {
8
- ".": "./dist/server.js",
9
- "./api": {
10
- "types": "./dist/api.d.ts",
11
- "default": "./dist/api.js"
12
- }
13
- },
14
- "bin": {
15
- "vantage-peers-mcp": "./dist/server.js"
16
- },
17
- "files": [
18
- "dist/",
19
- "README.md",
20
- "CHANGELOG.md"
21
- ],
22
- "scripts": {
23
- "generate:api": "cd .. && npx convex-helpers ts-api-spec --prod --output-file mcp-server/api.ts",
24
- "build": "tsc --project tsconfig.json",
25
- "prepublishOnly": "npm run build",
26
- "dev": "bun run server.ts",
27
- "start": "bun run dist/server-http.js",
28
- "dev:http": "bun run server-http.ts",
29
- "test": "vitest run",
30
- "test:watch": "vitest",
31
- "test:coverage": "vitest run --coverage"
32
- },
33
- "repository": {
34
- "type": "git",
35
- "url": "https://github.com/vantageos-agency/vantage-peers.git",
36
- "directory": "mcp-server"
37
- },
38
- "homepage": "https://vantagepeers.com/docs",
39
- "bugs": {
40
- "url": "https://github.com/vantageos-agency/vantage-peers/issues"
41
- },
42
- "keywords": [
43
- "mcp",
44
- "claude",
45
- "convex",
46
- "ai-agents",
47
- "memory",
48
- "messaging",
49
- "tasks",
50
- "multi-agent",
51
- "orchestration",
52
- "vantagepeers",
53
- "diary",
54
- "episodes",
55
- "mandates",
56
- "knowledge-base",
57
- "rag",
58
- "vector-search"
59
- ],
60
- "author": "VantageOS AI Orchestrator Team",
61
- "contributors": [
62
- "Pi (π) — Lead orchestrator",
63
- "Laurent Perello — Founder, supervisor",
64
- "ElPi Corp"
65
- ],
66
- "license": "FSL-1.1-Apache-2.0",
67
- "dependencies": {
68
- "@mcp-ui/server": "^6.1.0",
69
- "@modelcontextprotocol/sdk": "^1.29.0",
70
- "@vantageos/cloud-identity": "^0.1.0",
71
- "@vantageos/mosaic": "^0.1.2",
72
- "convex": ">=1.0.0",
73
- "dotenv": "^17.4.2",
74
- "zod": "^4.3.6"
75
- },
76
- "peerDependencies": {
77
- "convex": ">=1.0.0"
78
- },
79
- "devDependencies": {
80
- "@types/node": "^24.12.2",
81
- "@vitest/coverage-v8": "^4.1.8",
82
- "typescript": "^5.9.3",
83
- "vitest": "^4.1.8"
84
- },
85
- "overrides": {
86
- "path-to-regexp": "^8.4.0"
87
- },
88
- "engines": {
89
- "node": ">=20"
90
- },
91
- "publishConfig": {
92
- "access": "public",
93
- "provenance": true
94
- }
2
+ "name": "vantage-peers-mcp",
3
+ "version": "2.15.0",
4
+ "description": "MCP server for VantagePeers — shared memory, messaging, and task coordination for AI agent teams",
5
+ "type": "module",
6
+ "main": "./dist/server.js",
7
+ "exports": {
8
+ ".": "./dist/server.js",
9
+ "./api": {
10
+ "types": "./dist/api.d.ts",
11
+ "default": "./dist/api.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "vantage-peers-mcp": "./dist/server.js"
16
+ },
17
+ "files": [
18
+ "dist/",
19
+ "README.md",
20
+ "CHANGELOG.md"
21
+ ],
22
+ "scripts": {
23
+ "generate:api": "cd .. && npx convex-helpers ts-api-spec --prod --output-file mcp-server/api.ts",
24
+ "build": "tsc --project tsconfig.json",
25
+ "prepublishOnly": "npm run build",
26
+ "dev": "bun run server.ts",
27
+ "start": "bun run dist/server-http.js",
28
+ "dev:http": "bun run server-http.ts",
29
+ "test": "vitest run",
30
+ "test:watch": "vitest",
31
+ "test:coverage": "vitest run --coverage"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/vantageos-agency/vantage-peers.git",
36
+ "directory": "mcp-server"
37
+ },
38
+ "homepage": "https://vantagepeers.com/docs",
39
+ "bugs": {
40
+ "url": "https://github.com/vantageos-agency/vantage-peers/issues"
41
+ },
42
+ "keywords": [
43
+ "mcp",
44
+ "claude",
45
+ "convex",
46
+ "ai-agents",
47
+ "memory",
48
+ "messaging",
49
+ "tasks",
50
+ "multi-agent",
51
+ "orchestration",
52
+ "vantagepeers",
53
+ "diary",
54
+ "episodes",
55
+ "mandates",
56
+ "knowledge-base",
57
+ "rag",
58
+ "vector-search"
59
+ ],
60
+ "author": "VantageOS AI Orchestrator Team",
61
+ "contributors": [
62
+ "Pi (π) — Lead orchestrator",
63
+ "Laurent Perello — Founder, supervisor",
64
+ "ElPi Corp"
65
+ ],
66
+ "license": "FSL-1.1-Apache-2.0",
67
+ "dependencies": {
68
+ "@mcp-ui/server": "^6.1.0",
69
+ "@modelcontextprotocol/sdk": "^1.29.0",
70
+ "@vantageos/cloud-identity": "^0.1.0",
71
+ "@vantageos/mosaic": "^0.1.2",
72
+ "convex": ">=1.0.0",
73
+ "dotenv": "^17.4.2",
74
+ "zod": "^4.3.6"
75
+ },
76
+ "peerDependencies": {
77
+ "convex": ">=1.0.0"
78
+ },
79
+ "devDependencies": {
80
+ "@types/node": "^24.12.2",
81
+ "@vitest/coverage-v8": "^4.1.8",
82
+ "typescript": "^5.9.3",
83
+ "vitest": "^4.1.8"
84
+ },
85
+ "overrides": {
86
+ "path-to-regexp": "^8.4.0"
87
+ },
88
+ "engines": {
89
+ "node": ">=20"
90
+ },
91
+ "publishConfig": {
92
+ "access": "public",
93
+ "provenance": true
94
+ }
95
95
  }