@vantageos/mcp-boilerplate 0.1.1 → 0.2.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.
Files changed (50) hide show
  1. package/.claude-plugin/plugin.json +29 -8
  2. package/.mcp.json +8 -0
  3. package/CHANGELOG.md +71 -1
  4. package/README.fr.md +177 -142
  5. package/README.md +170 -133
  6. package/dist/auth.d.ts +20 -10
  7. package/dist/auth.js +9 -8
  8. package/dist/config.d.ts +10 -3
  9. package/dist/config.js +31 -3
  10. package/dist/exampleItems.d.ts +5 -4
  11. package/dist/exampleItems.js +3 -0
  12. package/dist/http.js +11 -1
  13. package/dist/httpApp.d.ts +23 -30
  14. package/dist/httpApp.js +73 -74
  15. package/dist/i18n.d.ts +6 -0
  16. package/dist/i18n.js +9 -0
  17. package/dist/locales/en.json +9 -0
  18. package/dist/locales/fr.json +9 -0
  19. package/dist/mcpServerFactory.d.ts +42 -41
  20. package/dist/mcpServerFactory.js +114 -146
  21. package/dist/oauth/authorizationServer.d.ts +56 -0
  22. package/dist/oauth/authorizationServer.js +423 -0
  23. package/dist/oauth/clerk.d.ts +15 -0
  24. package/dist/oauth/clerk.js +59 -0
  25. package/dist/oauth/clientRegistry.d.ts +50 -0
  26. package/dist/oauth/clientRegistry.js +203 -0
  27. package/dist/oauth/codeStore.d.ts +11 -0
  28. package/dist/oauth/codeStore.js +28 -0
  29. package/dist/oauth/picker.d.ts +11 -0
  30. package/dist/oauth/picker.js +67 -0
  31. package/dist/oauth/settings.d.ts +48 -0
  32. package/dist/oauth/settings.js +100 -0
  33. package/dist/oauth/tokenIssuer.d.ts +22 -0
  34. package/dist/oauth/tokenIssuer.js +61 -0
  35. package/dist/stdio.d.ts +7 -6
  36. package/dist/stdio.js +20 -19
  37. package/dist/tenancy.d.ts +16 -21
  38. package/dist/tenancy.js +12 -26
  39. package/dist/ui/uiResource.d.ts +36 -3
  40. package/dist/ui/uiResource.js +40 -6
  41. package/dist/version.d.ts +2 -0
  42. package/dist/version.js +8 -0
  43. package/docs/architecture.md +82 -0
  44. package/docs/oauth-stores.md +60 -0
  45. package/examples/oauth.env.example +25 -0
  46. package/mcp.json +19 -0
  47. package/package.json +23 -14
  48. package/plugin.json +43 -0
  49. package/scripts/plugin-manifest.source.json +35 -0
  50. package/skills/connect-mcp-server/SKILL.md +40 -0
package/dist/httpApp.d.ts CHANGED
@@ -1,47 +1,40 @@
1
1
  /**
2
- * Hono HTTP app: stateless MCP transport for the boilerplate skeleton.
2
+ * Hono HTTP app: the MCP endpoint plus, on the OAuth tier, this server's own authorization
3
+ * server (ruling R1).
3
4
  *
4
- * The MCP wire protocol itself is served via the official
5
- * `@modelcontextprotocol/sdk` `WebStandardStreamableHTTPServerTransport`
6
- * (stateless mode: `sessionIdGenerator: undefined`) — a standard MCP client
7
- * (Claude Code, ChatGPT, Cursor) speaks JSON-RPC over this endpoint exactly
8
- * as it would over any other MCP HTTP server. No ad-hoc REST routes stand
9
- * in front of tool calls.
5
+ * The MCP wire protocol is served by the SDK v2 `createMcpHandler` — ONE handler serves both
6
+ * protocol eras on `/mcp`: a 2026-07-28 client opens with `server/discover`, a 2025-era client
7
+ * with `initialize` (served through the SDK's stateless legacy leg). No ad-hoc REST routes
8
+ * stand in front of tool calls.
10
9
  *
11
- * True statelessness: a FRESH `McpServer` + a FRESH stateless transport are
12
- * created for every single POST to /mcp, connected, used to handle exactly
13
- * one request, and never retained. Nothing survives past the request/
14
- * response cycle, so the endpoint is horizontally load-balanceable and two
15
- * concurrent requests never share any in-memory object.
16
- *
17
- * `server/discover` is NOT a separate REST route: it is a JSON-RPC method
18
- * on this same `/mcp` endpoint (registered on the low-level `Server` in
19
- * mcpServerFactory.ts via `@vantageos/mcp-protocol-kit`'s
20
- * `handleServerDiscover`), exactly like `tools/list` and `tools/call` —
21
- * a standard MCP client dispatches all three the same way.
10
+ * True statelessness: a FRESH `McpServer` is built for every request by the handler's factory
11
+ * and never retained, so the endpoint is horizontally load-balanceable.
22
12
  *
23
13
  * Auth tiers (mcp-standard.md "Authentication & Authorization"):
24
14
  *
25
- * - `authMode: "oauth"` — OAuth 2.1 resource server via
26
- * @vantageos/mcp-auth-kit. Every /mcp call requires a valid Bearer JWT
27
- * (RS256, audience/issuer-bound); the RFC 9728 Protected Resource
28
- * Metadata document is served at `/.well-known/oauth-protected-resource`;
29
- * `/health` stays token-free. Requires an `AuthConfig` (deps.auth) — see
30
- * config.ts `loadAuthConfig()`.
31
- * - `authMode: "public"` — no gate at all. For local/self-host use where
32
- * the caller is already trusted, mirroring the stdio transport's trust
33
- * model (stdio.ts never had a gate to begin with). This is the DEFAULT
34
- * when `buildHttpApp()` is called with no deps, so the transport-only
35
- * tests (httpHandshake.test.ts, protocolKitWiring.test.ts) exercise the
36
- * skeleton without needing OAuth configuration.
15
+ * - `authMode: "oauth"` with `authorizationServer` — the default: this app is BOTH the OAuth
16
+ * 2.1 authorization server (src/oauth/, every decision delegated to @vantageos/cloud-identity)
17
+ * and the resource server (@vantageos/cloud-identity resource-server). Every /mcp call needs a valid Bearer
18
+ * JWT bound to `<base>/mcp` (RFC 8707) and to this issuer. Missing configuration answers
19
+ * 503 (fail-closed), never an open endpoint.
20
+ * - `authMode: "oauth"` with `auth` — resource server only, for a deployment that keeps an
21
+ * EXTERNAL issuer (config.ts `loadAuthConfig`).
22
+ * - `authMode: "public"` — no gate at all, for local/self-host use where the caller is already
23
+ * trusted (mirrors stdio's trust model). The DEFAULT when `buildHttpApp()` gets no deps.
37
24
  */
38
25
  import { Hono } from "hono";
39
26
  import { type AuthConfig } from "./auth.js";
40
27
  import type { AuthMode } from "./config.js";
28
+ import { type AuthorizationServerDeps } from "./oauth/authorizationServer.js";
41
29
  import { type ResolvedTenant } from "./tenancy.js";
42
30
  export interface AppDeps {
43
31
  authMode?: AuthMode;
32
+ /** External-issuer resource-server configuration (no authorization server mounted). */
44
33
  auth?: AuthConfig;
34
+ /** Own authorization server (R1). Takes precedence over `auth`. */
35
+ authorizationServer?: AuthorizationServerDeps;
36
+ /** Dedicated sandbox origin for the MCP Apps widget (`_meta.ui.domain`). */
37
+ uiDomain?: string;
45
38
  }
46
39
  type Variables = {
47
40
  tenant?: ResolvedTenant;
package/dist/httpApp.js CHANGED
@@ -1,113 +1,112 @@
1
1
  /**
2
- * Hono HTTP app: stateless MCP transport for the boilerplate skeleton.
2
+ * Hono HTTP app: the MCP endpoint plus, on the OAuth tier, this server's own authorization
3
+ * server (ruling R1).
3
4
  *
4
- * The MCP wire protocol itself is served via the official
5
- * `@modelcontextprotocol/sdk` `WebStandardStreamableHTTPServerTransport`
6
- * (stateless mode: `sessionIdGenerator: undefined`) — a standard MCP client
7
- * (Claude Code, ChatGPT, Cursor) speaks JSON-RPC over this endpoint exactly
8
- * as it would over any other MCP HTTP server. No ad-hoc REST routes stand
9
- * in front of tool calls.
5
+ * The MCP wire protocol is served by the SDK v2 `createMcpHandler` — ONE handler serves both
6
+ * protocol eras on `/mcp`: a 2026-07-28 client opens with `server/discover`, a 2025-era client
7
+ * with `initialize` (served through the SDK's stateless legacy leg). No ad-hoc REST routes
8
+ * stand in front of tool calls.
10
9
  *
11
- * True statelessness: a FRESH `McpServer` + a FRESH stateless transport are
12
- * created for every single POST to /mcp, connected, used to handle exactly
13
- * one request, and never retained. Nothing survives past the request/
14
- * response cycle, so the endpoint is horizontally load-balanceable and two
15
- * concurrent requests never share any in-memory object.
16
- *
17
- * `server/discover` is NOT a separate REST route: it is a JSON-RPC method
18
- * on this same `/mcp` endpoint (registered on the low-level `Server` in
19
- * mcpServerFactory.ts via `@vantageos/mcp-protocol-kit`'s
20
- * `handleServerDiscover`), exactly like `tools/list` and `tools/call` —
21
- * a standard MCP client dispatches all three the same way.
10
+ * True statelessness: a FRESH `McpServer` is built for every request by the handler's factory
11
+ * and never retained, so the endpoint is horizontally load-balanceable.
22
12
  *
23
13
  * Auth tiers (mcp-standard.md "Authentication & Authorization"):
24
14
  *
25
- * - `authMode: "oauth"` — OAuth 2.1 resource server via
26
- * @vantageos/mcp-auth-kit. Every /mcp call requires a valid Bearer JWT
27
- * (RS256, audience/issuer-bound); the RFC 9728 Protected Resource
28
- * Metadata document is served at `/.well-known/oauth-protected-resource`;
29
- * `/health` stays token-free. Requires an `AuthConfig` (deps.auth) — see
30
- * config.ts `loadAuthConfig()`.
31
- * - `authMode: "public"` — no gate at all. For local/self-host use where
32
- * the caller is already trusted, mirroring the stdio transport's trust
33
- * model (stdio.ts never had a gate to begin with). This is the DEFAULT
34
- * when `buildHttpApp()` is called with no deps, so the transport-only
35
- * tests (httpHandshake.test.ts, protocolKitWiring.test.ts) exercise the
36
- * skeleton without needing OAuth configuration.
15
+ * - `authMode: "oauth"` with `authorizationServer` — the default: this app is BOTH the OAuth
16
+ * 2.1 authorization server (src/oauth/, every decision delegated to @vantageos/cloud-identity)
17
+ * and the resource server (@vantageos/cloud-identity resource-server). Every /mcp call needs a valid Bearer
18
+ * JWT bound to `<base>/mcp` (RFC 8707) and to this issuer. Missing configuration answers
19
+ * 503 (fail-closed), never an open endpoint.
20
+ * - `authMode: "oauth"` with `auth` — resource server only, for a deployment that keeps an
21
+ * EXTERNAL issuer (config.ts `loadAuthConfig`).
22
+ * - `authMode: "public"` — no gate at all, for local/self-host use where the caller is already
23
+ * trusted (mirrors stdio's trust model). The DEFAULT when `buildHttpApp()` gets no deps.
37
24
  */
38
- import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
25
+ import { createMcpHandler } from "@modelcontextprotocol/server";
39
26
  import { Hono } from "hono";
40
27
  import { authenticateRequest, protectedResourceMetadataDocument } from "./auth.js";
41
28
  import { detectLocale, t } from "./i18n.js";
42
29
  import { createBoilerplateMcpServer } from "./mcpServerFactory.js";
30
+ import { createAuthorizationServer, } from "./oauth/authorizationServer.js";
43
31
  import { resolveTenantFromJwtPayload } from "./tenancy.js";
44
- const WELL_KNOWN_PATHS = ["/.well-known/oauth-protected-resource", "/health"];
32
+ const PRM_PATHS = [
33
+ "/.well-known/oauth-protected-resource",
34
+ "/.well-known/oauth-protected-resource/mcp",
35
+ ];
45
36
  export function buildHttpApp(deps = {}) {
46
37
  const app = new Hono();
47
38
  const authMode = deps.authMode ?? "public";
48
39
  app.get("/health", (c) => c.json({ status: "ok" }));
49
40
  if (authMode === "oauth") {
50
- if (!deps.auth) {
51
- throw new Error("authMode 'oauth' requires an AuthConfig (deps.auth) — build one via " +
52
- "config.ts loadAuthConfig(), or pass authMode: 'public' for the no-auth tier.");
41
+ // Resolves the resource-server configuration per request: the own-AS tier reads its
42
+ // environment lazily, so a missing value is a 503 on the request, not a crash at boot.
43
+ let resolve;
44
+ if (deps.authorizationServer) {
45
+ const as = createAuthorizationServer(deps.authorizationServer);
46
+ as.mount(app);
47
+ resolve = () => as.resourceServer();
53
48
  }
54
- const auth = deps.auth;
55
- app.get("/.well-known/oauth-protected-resource", (c) => {
56
- return c.json(protectedResourceMetadataDocument(auth));
57
- });
58
- // OAuth 2.1 resource-server gate — every route below this line requires
59
- // a valid Bearer JWT, verified entirely by @vantageos/mcp-auth-kit.
60
- app.use("*", async (c, next) => {
61
- if (WELL_KNOWN_PATHS.includes(c.req.path)) {
62
- await next();
63
- return;
49
+ else if (deps.auth) {
50
+ const auth = deps.auth;
51
+ resolve = async () => ({ ok: true, auth });
52
+ }
53
+ else {
54
+ throw new Error("authMode 'oauth' requires an AuthConfig (deps.auth, external issuer) or an " +
55
+ "authorizationServer (own issuer, the default) — see config.ts and src/http.ts, or pass " +
56
+ "authMode: 'public' for the no-auth tier.");
57
+ }
58
+ for (const path of PRM_PATHS) {
59
+ app.get(path, async (c) => {
60
+ const rs = await resolve();
61
+ if (!rs.ok)
62
+ return c.json({ error: "temporarily_unavailable" }, 503);
63
+ return c.json(protectedResourceMetadataDocument(rs.auth));
64
+ });
65
+ }
66
+ // OAuth 2.1 resource-server gate — /mcp requires a valid Bearer JWT, verified entirely by
67
+ // @vantageos/cloud-identity (`verifyMcpAccessToken`).
68
+ app.use("/mcp", async (c, next) => {
69
+ const rs = await resolve();
70
+ if (!rs.ok) {
71
+ console.error(`[oauth] /mcp refused: missing configuration ${rs.missing.join(", ")}`);
72
+ return c.json({ error: "temporarily_unavailable" }, 503);
64
73
  }
65
- const authHeader = c.req.header("Authorization");
66
- const result = await authenticateRequest(authHeader, auth);
74
+ const result = await authenticateRequest(c.req.header("Authorization"), rs.auth);
67
75
  if (!result.ok) {
76
+ if (result.status === 503) {
77
+ // Key set unreachable: an outage, never a bad credential — no challenge, no 401.
78
+ console.error("[oauth] /mcp refused: signing keys unavailable");
79
+ return c.json({ error: "temporarily_unavailable" }, 503);
80
+ }
68
81
  const locale = detectLocale(c.req.header("X-MCP-Locale"));
69
82
  c.header("WWW-Authenticate", result.headers["WWW-Authenticate"]);
70
83
  return c.json({ error: t(locale, "auth.unauthorized") }, result.status);
71
84
  }
72
- // Tenant resolution (tenancy.ts) is attempted here but is NOT
73
- // request-fatal: `initialize`/`tools/list` and any tenant-agnostic
74
- // tool (e.g. `ping`) must keep working for a verified caller that has
75
- // no organization on their token. Tenant-scoped tools (`list_items`)
76
- // enforce the requirement themselves, at the tool-call level (see
77
- // mcpServerFactory.ts) — a missing tenant there is refused as an MCP
78
- // tool error, never silently treated as an unscoped/master caller.
85
+ // Tenant resolution (tenancy.ts) is attempted here but is NOT request-fatal:
86
+ // `initialize`/`tools/list` and any tenant-agnostic tool (e.g. `ping`) must keep working
87
+ // for a verified caller with no organization on their token. Tenant-scoped tools enforce
88
+ // the requirement themselves, at the tool-call level (see mcpServerFactory.ts) — a
89
+ // missing tenant there is refused as an MCP tool error, never treated as unscoped.
79
90
  try {
80
- c.set("tenant", resolveTenantFromJwtPayload(result.payload));
91
+ c.set("tenant", resolveTenantFromJwtPayload(result.session.claims));
81
92
  }
82
93
  catch {
83
- // No organization on this token — tenant stays unset. Tenant-scoped
84
- // tools refuse explicitly when they see no tenant under oauth mode.
94
+ // No organization on this token — tenant stays unset.
85
95
  }
86
96
  await next();
87
97
  });
88
98
  }
89
- // The one real MCP endpoint: standard JSON-RPC over Streamable HTTP,
90
- // stateless mode. GET/POST/DELETE are all delegated to the SDK transport,
91
- // which handles the protocol's method dispatch itself.
92
- app.all("/mcp", async (c) => {
99
+ // The one real MCP endpoint, both protocol eras (see header comment).
100
+ app.all("/mcp", (c) => {
93
101
  const tenant = authMode === "oauth" ? c.get("tenant") : undefined;
94
102
  const locale = detectLocale(c.req.header("X-MCP-Locale"));
95
- const server = createBoilerplateMcpServer({
103
+ const handler = createMcpHandler(() => createBoilerplateMcpServer({
96
104
  tenant,
97
105
  requireTenant: authMode === "oauth",
98
106
  locale,
99
- });
100
- const transport = new WebStandardStreamableHTTPServerTransport({
101
- sessionIdGenerator: undefined, // stateless mode — no session ID anywhere
102
- enableJsonResponse: true,
103
- });
104
- await server.connect(transport);
105
- try {
106
- return await transport.handleRequest(c.req.raw);
107
- }
108
- finally {
109
- await transport.close();
110
- }
107
+ uiDomain: deps.uiDomain,
108
+ }));
109
+ return handler.fetch(c.req.raw);
111
110
  });
112
111
  return app;
113
112
  }
package/dist/i18n.d.ts CHANGED
@@ -24,6 +24,12 @@ export type McpLocale = "en" | "fr";
24
24
  * Defaults to "en" for a missing header or any value other than "fr".
25
25
  */
26
26
  export declare function detectLocale(headerValue: string | null | undefined): McpLocale;
27
+ /**
28
+ * Resolves the locale of a browser navigation (the OAuth picker page) from `Accept-Language`,
29
+ * where no `X-MCP-Locale` header exists. The first listed language decides; "fr" selects
30
+ * French, anything else English.
31
+ */
32
+ export declare function detectLocaleFromAcceptLanguage(headerValue: string | null | undefined): McpLocale;
27
33
  /**
28
34
  * Looks up a translated MCP message. `key` is a dot path under the `Mcp`
29
35
  * namespace, e.g. `"auth.unauthorized"` or `"tenant.permissionDenied"`.
package/dist/i18n.js CHANGED
@@ -43,6 +43,15 @@ const RESOURCES = {
43
43
  export function detectLocale(headerValue) {
44
44
  return headerValue?.toLowerCase() === "fr" ? "fr" : "en";
45
45
  }
46
+ /**
47
+ * Resolves the locale of a browser navigation (the OAuth picker page) from `Accept-Language`,
48
+ * where no `X-MCP-Locale` header exists. The first listed language decides; "fr" selects
49
+ * French, anything else English.
50
+ */
51
+ export function detectLocaleFromAcceptLanguage(headerValue) {
52
+ const first = headerValue?.split(",")[0]?.trim().toLowerCase() ?? "";
53
+ return first === "fr" || first.startsWith("fr-") || first.startsWith("fr;") ? "fr" : "en";
54
+ }
46
55
  /**
47
56
  * Looks up a translated MCP message. `key` is a dot path under the `Mcp`
48
57
  * namespace, e.g. `"auth.unauthorized"` or `"tenant.permissionDenied"`.
@@ -8,6 +8,15 @@
8
8
  },
9
9
  "validation": {
10
10
  "messageTooLong": "message exceeds the maximum length of 200 characters"
11
+ },
12
+ "oauth": {
13
+ "pickerTitle": "Connect {client}",
14
+ "pickerIntro": "{client} asks to act on your behalf. Choose the organisation it may reach, then approve or deny.",
15
+ "pickerOrganisation": "Organisation",
16
+ "approve": "Approve",
17
+ "deny": "Deny",
18
+ "denied": "The request was denied; you can close this window.",
19
+ "unavailable": "Authorization is not configured on this server."
11
20
  }
12
21
  }
13
22
  }
@@ -8,6 +8,15 @@
8
8
  },
9
9
  "validation": {
10
10
  "messageTooLong": "le message dépasse la longueur maximale de 200 caractères"
11
+ },
12
+ "oauth": {
13
+ "pickerTitle": "Connecter {client}",
14
+ "pickerIntro": "{client} demande à agir en votre nom. Choisissez l'organisation à laquelle il peut accéder, puis approuvez ou refusez.",
15
+ "pickerOrganisation": "Organisation",
16
+ "approve": "Approuver",
17
+ "deny": "Refuser",
18
+ "denied": "La demande a été refusée ; vous pouvez fermer cette fenêtre.",
19
+ "unavailable": "L'autorisation n'est pas configurée sur ce serveur."
11
20
  }
12
21
  }
13
22
  }
@@ -1,60 +1,61 @@
1
1
  /**
2
- * Single MCP server definition shared by both transports (stdio and HTTP).
3
- * The example tool is registered here, once, on the SDK's `McpServer` —
4
- * never on ad-hoc REST routes. Each call to `createBoilerplateMcpServer`
5
- * builds a fresh server instance with no shared mutable state; the HTTP
6
- * transport creates one per request (see httpApp.ts), which is what makes
7
- * the Streamable HTTP stateless mode genuinely load-balanceable: nothing
8
- * survives past the single request/response cycle.
2
+ * Single MCP server definition shared by both transports. It is registered once, on the SDK
3
+ * v2 `McpServer`, and served by `createMcpHandler` (HTTP, src/httpApp.ts) and `serveStdio`
4
+ * (src/stdio.ts) — each owns the protocol-era decision, so ONE factory serves a 2026-07-28
5
+ * client (opens with `server/discover`) and a 2025-era client (opens with `initialize`).
6
+ * Each call builds a fresh instance with no shared mutable state; the HTTP transport builds
7
+ * one per request, which is what keeps the endpoint load-balanceable.
9
8
  *
10
- * This is a SKELETON: `ping` is the only tool, kept trivial on purpose to
11
- * prove the MCP handshake on both transports. Later layers replace or
12
- * extend it (auth-gated tools, tenant-scoped tools, ...).
9
+ * Wire posture:
10
+ * - the 2025-era floor is 2025-06-18: `supportedProtocolVersions` lists only 2025-11-25 and
11
+ * 2025-06-18 (the SDK option on `ProtocolOptions`); older revisions are never counter-offered;
12
+ * - `ttlMs` / `cacheScope` on 2026-07-28 results come from the SDK's `cacheHints` — no
13
+ * handler override, no private-field access, no hand-written discover schema;
14
+ * - the advertised version is `SERVER_VERSION`, derived from package.json.
13
15
  *
14
- * Protocol-kit wiring (`@vantageos/mcp-protocol-kit` 0.2.0+): `tools/list`
15
- * is re-shaped through `buildToolsListResult` so every response carries
16
- * `resultType: "complete"` + a CacheableResult (`ttlMs`/`cacheScope`) on top
17
- * of the `tools` key the official SDK's `ListToolsResultSchema` requires. The
18
- * kit emits that key directly now — no hand alias.
16
+ * Tool records (mcp-standard C-4, C-15): every tool declares `title`, the four behavioural
17
+ * annotations as explicit booleans, a typed input schema AND a typed output schema (Zod 4),
18
+ * and returns `structuredContent` plus a text block carrying the same JSON.
19
+ * tests/toolRecordComplete.test.ts walks `tools/list` and fails on any gap.
19
20
  *
20
- * IMPORTANT — tools/call posture: `ping`'s tool-call result is NOT run
21
- * through the cache builders. The official `CallToolResultSchema` is the
22
- * fixed `{ content, isError? }` shape with no room for extra top-level fields
23
- * without breaking client-side schema validation, so `resultType`/
24
- * `CacheableResult` never apply to a tool-call result. The kit's
25
- * `buildToolCallResult` (0.2.0+) is the conformant builder for that case; the
26
- * inline `toolResult` helper here predates it and stays for the skeleton's
27
- * trivial `{ content }` returns.
28
- *
29
- * `list_items` is the multi-tenant example tool (tenancy.ts): when a
30
- * resolved tenant is supplied (OAuth tier — see httpApp.ts), results are
31
- * scoped via `scopeRowsForTenant` (@vantageos/cloud-identity, reused
32
- * unmodified); when no tenant is supplied (public tier — no auth gate at
33
- * all, mirrors stdio's trust model), results are returned unscoped.
21
+ * `list_items` is the multi-tenant example (tenancy.ts): with a resolved tenant, rows are
22
+ * filtered by stored `orgId` (cloud-identity `isRowInTenant`); on the public tier there is no
23
+ * tenant gate at all, by design (it mirrors stdio's local-trust model).
34
24
  */
35
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
25
+ import { McpServer } from "@modelcontextprotocol/server";
36
26
  import { type McpLocale } from "./i18n.js";
37
27
  import { type ResolvedTenant } from "./tenancy.js";
28
+ /** Oldest 2025-era protocol revision served; the legacy `initialize` handshake never offers older. */
29
+ export declare const SUPPORTED_LEGACY_PROTOCOL_VERSIONS: string[];
30
+ /** The four behavioural annotations, explicit on every tool (all three tools are pure reads). */
31
+ export declare const TOOL_ANNOTATIONS: {
32
+ readonly readOnlyHint: true;
33
+ readonly destructiveHint: false;
34
+ readonly idempotentHint: true;
35
+ readonly openWorldHint: false;
36
+ };
38
37
  export interface McpServerDeps {
39
38
  /**
40
- * The caller's resolved tenant (tenancy.ts), when the OAuth tier verified
41
- * a JWT carrying an organization. Undefined on the public tier (no gate)
42
- * or when the verified token itself carries no organization.
39
+ * The caller's resolved tenant (tenancy.ts), when the OAuth tier verified a JWT carrying an
40
+ * organization. Undefined on the public tier (no gate) or when the verified token itself
41
+ * carries no organization.
43
42
  */
44
43
  tenant?: ResolvedTenant;
45
44
  /**
46
- * True when this deployment's tenant-scoped tools MUST see a resolved
47
- * tenant (the OAuth tier). A verified-but-org-less caller then gets an
48
- * explicit MCP tool-call refusal from `list_items` rather than silently
49
- * falling through to an unscoped/master view. False on the public tier,
50
- * where there is no tenant gate at all by design.
45
+ * True when this deployment's tenant-scoped tools MUST see a resolved tenant (the OAuth
46
+ * tier). A verified-but-org-less caller then gets an explicit MCP tool-call refusal from
47
+ * `list_items` rather than silently falling through to an unscoped/master view.
51
48
  */
52
49
  requireTenant?: boolean;
53
50
  /**
54
- * The caller's locale, resolved from the `X-MCP-Locale` request header
55
- * (see i18n.ts + httpApp.ts). Defaults to "en" when not supplied — the
56
- * stdio transport (no HTTP headers) always uses the default.
51
+ * The caller's locale, resolved from the `X-MCP-Locale` request header (see i18n.ts +
52
+ * httpApp.ts). Defaults to "en"; the stdio transport (no HTTP headers) uses the default.
57
53
  */
58
54
  locale?: McpLocale;
55
+ /**
56
+ * Dedicated sandbox origin for the MCP Apps widget (`_meta.ui.domain`). Host-dependent; set
57
+ * only when the deployment has one (MCP_UI_DOMAIN). Omitted from the resource when unset.
58
+ */
59
+ uiDomain?: string;
59
60
  }
60
61
  export declare function createBoilerplateMcpServer(deps?: McpServerDeps): McpServer;