@vantageos/mcp-boilerplate 0.2.0 → 0.2.2

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.
@@ -5,6 +5,17 @@ in-memory adapters ship for tests and single-process development; a production d
5
5
  more than one instance (or one that restarts) MUST inject durable adapters through
6
6
  `AuthorizationServerDeps` (`buildHttpApp({ authMode: "oauth", authorizationServer: { codeStore, clientRegistry } })`).
7
7
 
8
+ ## Production guard
9
+
10
+ The in-memory adapters lose every code and client on restart and cannot be shared by a second
11
+ instance. With `NODE_ENV=production`, `createAuthorizationServer` therefore THROWS at boot unless
12
+ both `codeStore` and `clientRegistry` are injected, or `MCP_ALLOW_IN_MEMORY_STORES=1` is set
13
+ (exactly `1`; the override logs a warning on every start). Use the override only for a
14
+ single-instance deployment that accepts forgetting codes and DCR clients on restart (for example
15
+ a verification instance). The in-memory adapters are bounded: codes expire out of memory and the
16
+ used set is pruned past each code's expiry; registered clients expire after 30 days and the oldest
17
+ is evicted at the cap.
18
+
8
19
  ## `AuthorizationCodeStore` (cloud-identity type, `src/oauth/codeStore.ts`)
9
20
 
10
21
  ```ts
@@ -53,7 +64,7 @@ in-process).
53
64
  ## CIMD fetch and DNS rebinding
54
65
 
55
66
  `resolveCimdClient` accepts an https URL with a path only, refuses any host that resolves to a
56
- non-public address (loopback, private, link-local, CGNAT, multicast, and the IPv4-mapped, IPv4-compatible and NAT64 spellings of those, in any textual form), follows no
67
+ non-public address (loopback, private, link-local, CGNAT, multicast, and the IPv4-mapped, IPv4-compatible, NAT64, IPv4-translated (`::ffff:0:0/96`) and 6to4 (`2002::/16`) spellings of those, in any textual form; Teredo `2001::/32` is refused whole), follows no
57
68
  redirect, reads at most 16 KiB (streamed, the read is cancelled once the cap is crossed) and gives up after 5 s. It resolves the host and then `fetch`
58
69
  resolves it again, so a DNS-rebinding window exists. A deployment on a hostile network injects a
59
70
  `fetchImpl` that connects to the validated address (for example an undici dispatcher with a
@@ -23,3 +23,13 @@ MCP_OAUTH_SIGNING_JWK=
23
23
  # AUTHORIZE_AUTHORIZED_PARTIES=
24
24
  # Optional: dedicated sandbox origin of the MCP Apps widget (host-dependent).
25
25
  # MCP_UI_DOMAIN=
26
+
27
+ # Number of trusted reverse proxies in front of this server (default 0): the /register rate limit
28
+ # keys on the socket address, or on the X-Forwarded-For entry the N-th proxy appended.
29
+ # MCP_TRUSTED_PROXY_COUNT=1
30
+ # NODE_ENV=production refuses the in-memory stores; exactly "1" accepts them (single instance).
31
+ # MCP_ALLOW_IN_MEMORY_STORES=1
32
+ # Largest request body the Node entry accepts, in bytes (default 1048576); over it answers 413.
33
+ # MCP_MAX_BODY_BYTES=1048576
34
+ # Browser origins allowed to call /register cross-origin (comma-separated; default none).
35
+ # MCP_REGISTER_CORS_ORIGINS=https://inspector.example.com
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vantageos/mcp-boilerplate",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Clonable MCP server skeleton on the official @modelcontextprotocol/server SDK v2 (protocol 2026-07-28 + 2025 legacy, stdio + stateless Streamable HTTP), with its own OAuth 2.1 authorization server (Clerk person + organisation, cloud-identity), MCP Apps UI, tenant scoping, FR/EN i18n and a declarative eval harness.",
5
5
  "description_fr": "Squelette de serveur MCP clonable sur le SDK officiel @modelcontextprotocol/server v2 (protocole 2026-07-28 + héritage 2025, stdio + Streamable HTTP sans état), avec son propre serveur d'autorisation OAuth 2.1 (personne + organisation Clerk, cloud-identity), UI MCP Apps, scoping tenant, i18n FR/EN et un harnais d'évaluations déclaratif.",
6
6
  "type": "module",
@@ -28,6 +28,7 @@
28
28
  "mcp.json",
29
29
  "skills",
30
30
  "scripts/plugin-manifest.source.json",
31
+ "scripts/generate-signing-key.mjs",
31
32
  "docs",
32
33
  "examples/claude-desktop.json",
33
34
  "examples/oauth.env.example"
@@ -74,9 +75,9 @@
74
75
  "dependencies": {
75
76
  "@modelcontextprotocol/ext-apps": "^2.0.3",
76
77
  "@modelcontextprotocol/server": "^2.3.1",
77
- "@vantageos/cloud-identity": "^0.15.0",
78
+ "@vantageos/cloud-identity": "^0.17.0",
78
79
  "@vantageos/mosaic-i18n": "^0.1.1",
79
- "hono": "^4.6.0",
80
+ "hono": "^4.13.13",
80
81
  "jose": "^6.2.7",
81
82
  "zod": "^4.4.3"
82
83
  },
@@ -90,8 +91,7 @@
90
91
  "react": "^19.0.0",
91
92
  "react-dom": "^19.0.0",
92
93
  "typescript": "^5.7.2",
93
- "vite": "^5.4.0",
94
- "vite-plugin-singlefile": "^2.0.0",
95
- "vitest": "^2.1.8"
94
+ "vite": "^8.3.4",
95
+ "vitest": "^5.0.3"
96
96
  }
97
97
  }
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "vantage-mcp-boilerplate",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "Clonable MCP server template: SDK v2 on protocol 2026-07-28 (stdio + stateless Streamable HTTP), its own OAuth 2.1 authorization server (Clerk person + organisation), MCP Apps UI, FR/EN i18n and a declarative eval harness. Scaffold, extend, publish.",
6
6
  "author": {
7
7
  "name": "ElPi Corp / Laurent Perello"
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Prints a fresh RS256 private JWK on stdout, ready to be stored as the MCP_OAUTH_SIGNING_JWK
4
+ * secret of the authorization server. The key id is the RFC 7638 thumbprint, so it is derived,
5
+ * never typed. Nothing is written to disk; pipe it straight into your secret manager.
6
+ */
7
+ import { calculateJwkThumbprint, exportJWK, generateKeyPair } from "jose";
8
+
9
+ const { privateKey } = await generateKeyPair("RS256", { modulusLength: 3072, extractable: true });
10
+ const jwk = await exportJWK(privateKey);
11
+ jwk.kid = await calculateJwkThumbprint(jwk);
12
+ jwk.alg = "RS256";
13
+ jwk.use = "sig";
14
+ process.stdout.write(`${JSON.stringify(jwk)}\n`);