@nyuchi/mzizi-mcp 0.14.0 → 0.16.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 (56) hide show
  1. package/README.md +111 -39
  2. package/dist/auth/cors.d.ts +33 -0
  3. package/dist/auth/cors.d.ts.map +1 -0
  4. package/dist/auth/cors.js +110 -0
  5. package/dist/auth/cors.js.map +1 -0
  6. package/dist/auth/front.d.ts +8 -0
  7. package/dist/auth/front.d.ts.map +1 -1
  8. package/dist/auth/front.js +10 -0
  9. package/dist/auth/front.js.map +1 -1
  10. package/dist/auth/mcp-api-handler.d.ts +1 -1
  11. package/dist/auth/mcp-api-handler.d.ts.map +1 -1
  12. package/dist/auth/mcp-api-handler.js +4 -3
  13. package/dist/auth/mcp-api-handler.js.map +1 -1
  14. package/dist/auth/provider-options.d.ts +16 -0
  15. package/dist/auth/provider-options.d.ts.map +1 -0
  16. package/dist/auth/provider-options.js +21 -0
  17. package/dist/auth/provider-options.js.map +1 -0
  18. package/dist/env.d.ts +1 -0
  19. package/dist/env.d.ts.map +1 -1
  20. package/dist/floors.d.ts +117 -0
  21. package/dist/floors.d.ts.map +1 -0
  22. package/dist/floors.js +103 -0
  23. package/dist/floors.js.map +1 -0
  24. package/dist/generated/registry-pin.d.ts +8 -1
  25. package/dist/generated/registry-pin.d.ts.map +1 -1
  26. package/dist/generated/registry-pin.js +3 -1
  27. package/dist/generated/registry-pin.js.map +1 -1
  28. package/dist/generated/registry.d.ts +2 -2
  29. package/dist/generated/registry.js +198 -198
  30. package/dist/generated/registry.js.map +1 -1
  31. package/dist/generated/skills.d.ts +1 -1
  32. package/dist/generated/skills.js +13 -13
  33. package/dist/generated/skills.js.map +1 -1
  34. package/dist/http.d.ts +14 -4
  35. package/dist/http.d.ts.map +1 -1
  36. package/dist/http.js +38 -54
  37. package/dist/http.js.map +1 -1
  38. package/dist/origin.d.ts +68 -0
  39. package/dist/origin.d.ts.map +1 -0
  40. package/dist/origin.js +141 -0
  41. package/dist/origin.js.map +1 -0
  42. package/dist/rate-limit.d.ts +1 -1
  43. package/dist/rate-limit.d.ts.map +1 -1
  44. package/dist/rate-limit.js +3 -2
  45. package/dist/rate-limit.js.map +1 -1
  46. package/dist/server.d.ts +47 -3
  47. package/dist/server.d.ts.map +1 -1
  48. package/dist/server.js +14 -8
  49. package/dist/server.js.map +1 -1
  50. package/dist/tools.d.ts.map +1 -1
  51. package/dist/tools.js +115 -48
  52. package/dist/tools.js.map +1 -1
  53. package/dist/worker.d.ts.map +1 -1
  54. package/dist/worker.js +25 -11
  55. package/dist/worker.js.map +1 -1
  56. package/package.json +2 -2
package/README.md CHANGED
@@ -12,7 +12,7 @@ Owned and operated by **Mzizi**.
12
12
  ---
13
13
 
14
14
  **Live at** `https://mcp.mzizi.dev/mcp` (Cloudflare Worker `mzizi-mcp`, version
15
- 0.14.0 — `MZIZI_MCP_VERSION` in `src/server.ts` is what `serverInfo.version`
15
+ 0.16.0 — `MZIZI_MCP_VERSION` in `src/server.ts` is what `serverInfo.version`
16
16
  reports, and it moves in lockstep with `package.json` and `server.json`).
17
17
 
18
18
  ## Free, with no gate except Fundi
@@ -72,10 +72,13 @@ registration and does not publish a client ID metadata document.
72
72
 
73
73
  ### Per client
74
74
 
75
- Each client writes the entry above in its own format. The full guide, checked against each
76
- vendor's documentation, is
77
- [docs.mzizi.dev/toolchain/connect](https://docs.mzizi.dev/toolchain/connect). The short
78
- version:
75
+ Each client writes the entry above in its own format. The full guide, with the vendor page
76
+ each line was read from, is
77
+ [docs.mzizi.dev/toolchain/connect](https://docs.mzizi.dev/toolchain/connect); the table in this
78
+ repository's [README](../README.md#connect-a-client) has the config path and key for Claude
79
+ Code, Claude.ai and Desktop, Codex, Cursor, VS Code, Antigravity, Gemini CLI, Devin Desktop
80
+ (formerly Windsurf), Zed, Cline, OpenCode, Kiro and JetBrains. All of it is documented by the
81
+ vendors, read on 2026-11-03, and not run in the tools. The short version:
79
82
 
80
83
  ```bash
81
84
  # Claude Code: the server alone (skills over MCP) ...
@@ -84,11 +87,10 @@ claude mcp add --transport http mzizi https://mcp.mzizi.dev/mcp
84
87
  /plugin marketplace add mzizi-dev/mzizi-registry
85
88
  /plugin install mzizi@mzizi
86
89
 
87
- # Codex: the server alone, or the same plugin (mzizi-registry#520 adds its Codex manifest)
90
+ # Codex: the server (there is no Codex plugin until mzizi-registry#520 merges)
88
91
  codex mcp add mzizi --url https://mcp.mzizi.dev/mcp
89
- codex plugin marketplace add mzizi-dev/mzizi-registry
90
92
 
91
- # Gemini CLI
93
+ # Gemini CLI (enterprise; free and Pro/Ultra users moved to Antigravity CLI on 2026-06-18)
92
94
  gemini mcp add --transport http mzizi https://mcp.mzizi.dev/mcp
93
95
 
94
96
  # VS Code
@@ -104,10 +106,14 @@ code --add-mcp '{"name":"mzizi","type":"http","url":"https://mcp.mzizi.dev/mcp"}
104
106
  `mcp-client-2025-11-20` beta header.
105
107
  - **OpenAI Responses API and Agents SDK:** a tool
106
108
  `{"type": "mcp", "server_label": "mzizi", "server_url": "https://mcp.mzizi.dev/mcp"}`
107
- (`HostedMCPTool(tool_config=…)` in the SDK).
108
- - **Cursor** (`mcp.json`, `"url"`), **OpenCode** (`opencode.json`, `"type": "remote"`),
109
- **Windsurf** (`mcp_config.json`, `"serverUrl"`) and **Gemini CLI** (`settings.json`,
110
- `"httpUrl"`) each name the URL field differently.
109
+ (`HostedMCPTool(tool_config=...)` in the SDK).
110
+ - **The URL key differs by client:** `url` (Cursor, Zed, Kiro, JetBrains, Devin Desktop,
111
+ Cline, OpenCode), `serverUrl` (Antigravity), `httpUrl` (Gemini CLI `settings.json`). Cline
112
+ needs `"type": "streamableHttp"` set explicitly (without it, it falls back to SSE), and
113
+ Devin Desktop names the transport `transport`, not `type`.
114
+ - **Needs a bridge or stdio:** a client that only speaks stdio (older MCP clients, and Junie,
115
+ whose current help page does not confirm streamable HTTP) runs `npx -y @nyuchi/mzizi-mcp`
116
+ (stdio, below). `mcp-remote` is a third-party bridge this project has not tested.
111
117
 
112
118
  **Skills over MCP.** A client that cannot install the npm package (Claude.ai, ChatGPT, the
113
119
  APIs) gets the skills from `mzizi_get_skills`: no arguments lists them with their category,
@@ -120,8 +126,10 @@ SKILL.md body, `mzizi://skills/<name>/<path>` for each file, listed by `resource
120
126
  resources or offers prompts gets them without calling a tool. `catalogue.json` and
121
127
  `mzizi_mcp_describe` list every skill with its category and files. All of it is read from the
122
128
  skill files bundled at build time, and `test/skills-surfaces.test.ts` fails if any surface
123
- drifts from `mzizi-skills/`. Codex, Cursor, VS Code, Gemini CLI and OpenCode also read
124
- `.agents/skills/`, where `npx skills experimental_sync` links the npm bundle.
129
+ drifts from `mzizi-skills/`. Codex, Cursor, VS Code, Gemini CLI, Antigravity, Zed, OpenCode and
130
+ Devin Desktop also read `.agents/skills/`, where `npx skills experimental_sync` links the npm
131
+ bundle. For instruction formats that cannot hold a skill (Cursor rules, Antigravity and Devin
132
+ rules, `AGENTS.md`), see [`mzizi-skills/adapters/`](../mzizi-skills/adapters/README.md).
125
133
 
126
134
  How a skills change reaches this server: the skills are read from `../mzizi-skills/` in this
127
135
  repository at build time, not from npm and not from the registry pin. A change to a SKILL.md
@@ -129,6 +137,32 @@ merged to `main` redeploys `mcp.mzizi.dev` (Workers Builds), and that deploy ser
129
137
  publish of `@nyuchi/mzizi-skills` and the registry's dependency bump feed the plugin and
130
138
  `api.mzizi.dev/v1/skills`, not this server.
131
139
 
140
+ ### Compatibility with the MCP spec
141
+
142
+ Probed from outside with `curl` on 2026-11-03, with no `Origin` header and again with a CLI
143
+ user agent (`codex_cli_rs/0.1`), against the Streamable HTTP page of the 2026-07-28 revision
144
+ (<https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http>):
145
+
146
+ | Probe | Result |
147
+ | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
148
+ | `initialize` with `2025-06-18`, `2025-11-25` | `200`, `application/json`, same version echoed, tools, resources and prompts with `listChanged`; no `Mcp-Session-Id` (stateless) |
149
+ | `initialize` with `2026-07-28` | `200`, answers `2025-11-25` (the legacy handshake is the highest it negotiates) |
150
+ | `server/discover` and `tools/list` with `_meta` and the headers | `200`; `supportedVersions: ["2026-07-28"]`, 14 tools |
151
+ | `tools/call` without `Mcp-Name` | `400`, JSON-RPC `-32020` `HeaderMismatch`, as the spec requires |
152
+ | `tools/list` with no `initialize` and no version header | `200` (treated as the earliest header-less version, which the spec allows) |
153
+ | notification POST (`notifications/initialized`) | `202`, no body |
154
+ | `GET` on the endpoint | `405` with `Allow: POST, OPTIONS` (the spec's answer for a server with no GET stream) |
155
+ | `Accept: application/json` alone | `406`; the client must list both `application/json` and `text/event-stream` |
156
+ | `MCP-Protocol-Version: 1999-01-01` | `400`, lists the supported versions (`2025-11-25` back to `2024-10-07`) |
157
+ | `Origin: http://evil.example`, `Origin: null` | `403` |
158
+ | `Origin: https://evil.example` | `200` on the deployed 0.14.0: any `https` origin is accepted. The spec says an invalid Origin MUST get `403`. The explicit allowlist merged to `staging` in #319 refuses it, and is live only after the next release. |
159
+ | `/mcp/signin` without a token | `401` with `WWW-Authenticate: Bearer ... resource_metadata=...`, the OAuth flow clients start from |
160
+
161
+ Deviations to know about: a JSON-RPC batch array (removed from the spec in 2025-06-18) answers
162
+ a single object, not an array, and `server/discover` lists only the modern version while the
163
+ legacy handshake still negotiates the older ones. No client was run; these are the server's
164
+ answers to a plain HTTP client.
165
+
132
166
  ### stdio (local / offline)
133
167
 
134
168
  Run the stdio bin via npx — useful for Claude Code, Cursor, or any assistant that
@@ -224,22 +258,22 @@ call, passing the result through untouched:
224
258
 
225
259
  ## Tools
226
260
 
227
- | Tool | Returns |
228
- | ---------------------------------- | ------------------------------------------------------------------------------ |
229
- | `mzizi_search` | Word search over components, conventions, AI instruction sets and releases |
230
- | `mzizi_get_component` | One component in full, **Rust first**; `include: ["docs", "versions"]` |
231
- | `mzizi_list_components` | The lean, paged index; filter by `node`, `owner`, `collection`, `type`, `rust` |
232
- | `mzizi_get_tokens` | Brand system; `family: "colors"` for all 21 colour families |
233
- | `mzizi_get_architecture` | The DNA helix, or one node/rung with `node` (uncapped) |
234
- | `mzizi_get_doctrine` | `ubuntu-pillars`, `ubuntu-principles`, `ai-instructions`, `conventions` |
235
- | `mzizi_get_skills` | The agent skills: listed, one by `name` (with `file`), or a `category` |
236
- | `mzizi_check_accessibility` | WCAG 2 contrast ratio and the Mzizi 7:1 floor, computed locally (not APCA) |
237
- | `mzizi_report_issue` | Your issue desk. **Sign-in** |
238
- | `mzizi_fundi` | Delegate to fundi (when the Worker can reach fundi). **Sign-in** |
239
- | `mzizi_mcp_describe` | This server's tools, their access, what each replaces, and data sources |
240
- | `docs_search_mzizi` | Search the documentation (forwarded) |
241
- | `docs_query_docs_filesystem_mzizi` | Read-only `rg`/`cat`/`tree` over the doc pages (forwarded) |
242
- | `docs_submit_feedback` | Report a documentation problem (forwarded) |
261
+ | Tool | Returns |
262
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
263
+ | `mzizi_search` | Word search over components, conventions, AI instruction sets and releases |
264
+ | `mzizi_get_component` | One component in full, **Rust first**; `include: ["docs", "versions"]` |
265
+ | `mzizi_list_components` | The lean, paged index; filter by `node`, `owner`, `collection`, `type`, `rust` |
266
+ | `mzizi_get_tokens` | Brand system; `family: "colors"` for all 21 colour families, `"floors"` for the Bundu floor |
267
+ | `mzizi_get_architecture` | The DNA helix, or one node/rung with `node` (uncapped) |
268
+ | `mzizi_get_doctrine` | `ubuntu-pillars`, `ubuntu-principles`, `ai-instructions`, `conventions`, `bundu-floor` |
269
+ | `mzizi_get_skills` | The agent skills: listed, one by `name` (with `file`), or a `category` |
270
+ | `mzizi_check_accessibility` | WCAG 2 contrast ratio and the Mzizi 7:1 floor, and spacing values against the spacing floor, computed locally (not APCA) |
271
+ | `mzizi_report_issue` | Your issue desk. **Sign-in** |
272
+ | `mzizi_fundi` | Delegate to fundi (when the Worker can reach fundi). **Sign-in** |
273
+ | `mzizi_mcp_describe` | This server's tools, their access, what each replaces, and data sources |
274
+ | `docs_search_mzizi` | Search the documentation (forwarded) |
275
+ | `docs_query_docs_filesystem_mzizi` | Read-only `rg`/`cat`/`tree` over the doc pages (forwarded) |
276
+ | `docs_submit_feedback` | Report a documentation problem (forwarded) |
243
277
 
244
278
  **How the tools behave (SDK v2, #295).**
245
279
 
@@ -257,13 +291,49 @@ call, passing the result through untouched:
257
291
  `mzizi_list_components` also keeps `hasMore` / `nextOffset` for older clients.
258
292
  - A domain failure (unknown component, bad colour, sign-in needed) is a tool result with
259
293
  `isError: true` and text that says how to recover, never a protocol error.
260
- - **Browser origins (Streamable HTTP §Security):** an `Origin` that does not parse (the opaque
261
- `null` included) or a plain-`http` origin other than localhost is refused with 403. Every
262
- `https` origin passes, because browser-based MCP clients on other sites use this public,
263
- cookie-less server. Clients that send no `Origin` (every non-browser client) are not affected.
294
+ - **Browser origins (Streamable HTTP §Security):** a request with an `Origin` header whose host is
295
+ not on the list is refused with 403 before any server is built, on `/mcp`, `/mcp/signin` and
296
+ every other path starting with `/mcp`, with or without a token, and before the anonymous rate
297
+ limit is charged. The code refuses a present but empty `Origin` (only a missing header counts as
298
+ no `Origin`), though workerd appears to deliver an empty header as absent. The list is `mzizi.dev`, `www.mzizi.dev`, `docs.mzizi.dev`, `app.mzizi.dev` and
299
+ `mcp.mzizi.dev`, each over `https` on the default port only (no `http`, no `:8443`, no other
300
+ subdomain), plus localhost on any port. Add more with the `MZIZI_ALLOWED_ORIGINS` variable,
301
+ comma-separated `https://host` origins (case and a trailing slash are tolerated; anything else
302
+ that is not a plain `https` origin is dropped with a logged warning). Clients that send no
303
+ `Origin` (every non-browser client) are not affected. On `/mcp`, CORS echoes an allowed `Origin`
304
+ with `Vary: Origin` and never answers `Access-Control-Allow-Origin: *`. The OAuth provider's own
305
+ endpoints follow `src/auth/cors.ts` (0.16.0; the provider's own CORS, which echoed any `Origin`,
306
+ is replaced): `/.well-known/*` are public discovery documents and answer
307
+ `Access-Control-Allow-Origin: *` (GET, HEAD, OPTIONS); `/token` and `/register` echo an `Origin`
308
+ only if it is on the same list as `/mcp` and always send `Vary: Origin`. A listed `Origin`
309
+ gets `POST, OPTIONS`, `Content-Type, Authorization` and `Access-Control-Max-Age: 600`; an unlisted
310
+ or `null` `Origin` is still served but gets no `Access-Control-Allow-Origin`, so a browser cannot read
311
+ the answer; a request with no `Origin` is unaffected. Errors (400, 401, 405, 429, 5xx) carry the
312
+ same headers. No `Access-Control-Allow-Credentials` anywhere. `/catalogue.json` is `*` on purpose.
313
+ Browser clients hosted on other sites cannot call `/mcp` (`src/origin.ts` is the one list).
314
+ - **PKCE and token audience:** the authorization-server metadata advertises `S256` only. Audience
315
+ binding applies only to clients that send a `resource` indicator (RFC 8707): such a token is
316
+ refused on a different server or path, but a token for `/mcp` is also accepted on
317
+ `/mcp/signin` (path-prefix matching, the same server). A token minted with no `resource` has no
318
+ audience and is served on `/mcp` and `/mcp/signin` (see the CHANGELOG, #295).
264
319
  - Calls to fundi and to the identity provider time out after 15 s.
265
320
  - `evals.xml` holds ten read-only questions with answers checked against production.
266
321
 
322
+ **The Bundu floor, and the spacing floor in it.** The registry's minimums nothing can go under are
323
+ served three ways, all read from the bundled registry's `/v1/brand` `accessibility` block at the
324
+ pinned commit (`minTouchTarget`, `minInset`, `minControlGap`, `minLabelGap`, `minItemGap`,
325
+ `minBodyLeading`), never typed in this repo (`src/floors.ts`): `mzizi_get_tokens family=floors` (also
326
+ `family=accessibility`, and `family=spacing`, which returns `spacingFloor` beside the scale),
327
+ `mzizi_get_doctrine topic=bundu-floor`, and the `floors` block of `mzizi_mcp_describe`, the
328
+ `mzizi://tools` resource and `GET /catalogue.json`. The spacing floor (owner, 2026-11-04,
329
+ mzizi-registry#617) is a 14px inset between text and its container edge (`--inset-floor`), 7px gaps between
330
+ adjacent controls, between a label and its field and between sibling items (`--gap-floor`), and 1.5
331
+ body leading (`--leading-floor`). `mzizi_check_accessibility` takes `insetPx`, `controlGapPx`,
332
+ `labelGapPx`, `itemGapPx` and `leading` beside (or instead of) the two colours, compares each with that
333
+ floor and returns `spacing: { floor, checks, meetsFloor }`. If the pinned registry commit predates the
334
+ floor, `floors.spacing` is `null`, the tool descriptions name no number, and a spacing check is a tool
335
+ error saying so (or, beside colours, a `spacingUnavailable` note); the tools never answer from a copy. To check a project's source rather than values, run `mzizi check` (`@nyuchi/mzizi-cli`).
336
+
267
337
  **Text colours from `mzizi_get_tokens`.** Every colour family carries `textLight` /
268
338
  `textDark`: use them for text in that colour on the base surface (they are
269
339
  `--color-<name>-text`). On a family's container, use `onContainerLight` /
@@ -319,6 +389,7 @@ tools present the Rust implementation first wherever one exists:
319
389
  project's own. Hash the registry source, not the installed file.
320
390
  - **Breaking for readers of 0.10.x:** the React body moved from `component` to
321
391
  `react`, and `renamedFrom` moved to the top level.
392
+
322
393
  - `mzizi_list_components` rows carry `rustCrate` when the component has Rust (absent
323
394
  otherwise), the response carries `withRust` (how many of `total` have Rust), and
324
395
  `rust: true` / `rust: false` filters on it.
@@ -370,11 +441,12 @@ at. See `AUTH.md`.
370
441
 
371
442
  Plain vars (`[vars]` in `wrangler.toml`, not secrets):
372
443
 
373
- | Var | Notes |
374
- | ---------------------- | ------------------------------------------------------------- |
375
- | `WORKOS_CLIENT_ID` | Connect OAuth app client id (public client; PKCE) |
376
- | `WORKOS_M2M_CLIENT_ID` | Fundi M2M app client id; used only on the HTTP + M2M fallback |
377
- | `FUNDI_URL` | Base URL of the fundi worker, for the HTTP + M2M fallback |
444
+ | Var | Notes |
445
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
446
+ | `WORKOS_CLIENT_ID` | Connect OAuth app client id (public client; PKCE) |
447
+ | `WORKOS_M2M_CLIENT_ID` | Fundi M2M app client id; used only on the HTTP + M2M fallback |
448
+ | `FUNDI_URL` | Base URL of the fundi worker, for the HTTP + M2M fallback |
449
+ | `MZIZI_ALLOWED_ORIGINS` | Optional, unset by default. Extra browser origins for `/mcp`, comma-separated `https://host` (e.g. `https://partner.example`); a public setting, so a var, not a secret. See "Browser origins" |
378
450
 
379
451
  Bindings (`wrangler.toml`): `FUNDI_SERVICE` (Service Binding to `mzizi-fundi`'s
380
452
  `FundiInternal` entrypoint; what makes fundi reachable in production),
@@ -0,0 +1,33 @@
1
+ /**
2
+ * CORS for the OAuth provider's own endpoints.
3
+ *
4
+ * `@cloudflare/workers-oauth-provider` answers every cross-origin request on
5
+ * `/token`, `/register` and `/.well-known/*` by echoing ANY `Origin` back with
6
+ * `Allow-Methods: *`, `Allow-Headers: Authorization, *` and a day of preflight
7
+ * cache. This module takes those answers over: the provider still serves the
8
+ * request, and its CORS headers are replaced with ours. Its own module, not
9
+ * `worker.ts`, because the Worker's entry may export only handlers.
10
+ *
11
+ * /.well-known/* public, credential-less documents: `*`, GET/HEAD only.
12
+ * /token, /register the same allowlist as the `/mcp` Origin gate
13
+ * (`origin.ts`): an allowed `Origin` is echoed with
14
+ * `Vary: Origin`; no `Origin` (every non-browser client)
15
+ * is served without CORS headers; an unlisted or `null`
16
+ * `Origin` is served too, but gets no
17
+ * `Access-Control-Allow-Origin`, so a browser cannot read
18
+ * the answer. Nothing is 403'd: a browser's block is the
19
+ * enforcement, and these endpoints take no cookie.
20
+ *
21
+ * Every answer carries the headers, errors included (a browser cannot read a
22
+ * 400, 401, 405, 429 or 5xx without them). No `Access-Control-Allow-Credentials`.
23
+ * `/authorize` is a top-level navigation, not a fetch, so it gets none.
24
+ */
25
+ export type CorsRoute = "discovery" | "endpoint";
26
+ /** Which CORS rule governs `pathname`, or undefined for a path this module leaves alone. */
27
+ export declare function corsRouteFor(pathname: string): CorsRoute | undefined;
28
+ /**
29
+ * Serve `request` through `next` (the OAuth provider) with the CORS rules above,
30
+ * or return undefined when the path is not one of ours.
31
+ */
32
+ export declare function withProviderCors(request: Request, hosts: readonly string[] | undefined, next: () => Promise<Response>): Promise<Response | undefined>;
33
+ //# sourceMappingURL=cors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../src/auth/cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAiBH,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,UAAU,CAAC;AAEjD,4FAA4F;AAC5F,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAIpE;AAoDD;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,EACpC,IAAI,EAAE,MAAM,OAAO,CAAC,QAAQ,CAAC,GAC5B,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAgB/B"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * CORS for the OAuth provider's own endpoints.
3
+ *
4
+ * `@cloudflare/workers-oauth-provider` answers every cross-origin request on
5
+ * `/token`, `/register` and `/.well-known/*` by echoing ANY `Origin` back with
6
+ * `Allow-Methods: *`, `Allow-Headers: Authorization, *` and a day of preflight
7
+ * cache. This module takes those answers over: the provider still serves the
8
+ * request, and its CORS headers are replaced with ours. Its own module, not
9
+ * `worker.ts`, because the Worker's entry may export only handlers.
10
+ *
11
+ * /.well-known/* public, credential-less documents: `*`, GET/HEAD only.
12
+ * /token, /register the same allowlist as the `/mcp` Origin gate
13
+ * (`origin.ts`): an allowed `Origin` is echoed with
14
+ * `Vary: Origin`; no `Origin` (every non-browser client)
15
+ * is served without CORS headers; an unlisted or `null`
16
+ * `Origin` is served too, but gets no
17
+ * `Access-Control-Allow-Origin`, so a browser cannot read
18
+ * the answer. Nothing is 403'd: a browser's block is the
19
+ * enforcement, and these endpoints take no cookie.
20
+ *
21
+ * Every answer carries the headers, errors included (a browser cannot read a
22
+ * 400, 401, 405, 429 or 5xx without them). No `Access-Control-Allow-Credentials`.
23
+ * `/authorize` is a top-level navigation, not a fetch, so it gets none.
24
+ */
25
+ import { OAUTH_ENDPOINT_CORS, corsHeadersFor, isAllowedOrigin } from "../origin.js";
26
+ /** Headers the provider sets on its answers; removed before ours go on. */
27
+ const PROVIDER_CORS_HEADERS = [
28
+ "Access-Control-Allow-Origin",
29
+ "Access-Control-Allow-Methods",
30
+ "Access-Control-Allow-Headers",
31
+ "Access-Control-Max-Age",
32
+ "Access-Control-Allow-Credentials",
33
+ "Access-Control-Expose-Headers",
34
+ ];
35
+ /** What a browser MCP client sends when it fetches discovery documents (the SDK adds `MCP-Protocol-Version`). */
36
+ const DISCOVERY_ALLOW_HEADERS = "Content-Type, MCP-Protocol-Version";
37
+ /** Which CORS rule governs `pathname`, or undefined for a path this module leaves alone. */
38
+ export function corsRouteFor(pathname) {
39
+ if (pathname === "/.well-known" || pathname.startsWith("/.well-known/"))
40
+ return "discovery";
41
+ if (pathname === "/token" || pathname === "/register")
42
+ return "endpoint";
43
+ return undefined;
44
+ }
45
+ /** The CORS headers for `request` on `route`. */
46
+ function headersFor(route, request, hosts) {
47
+ if (route === "discovery")
48
+ return { "Access-Control-Allow-Origin": "*" };
49
+ const origin = request.headers.get("origin");
50
+ return corsHeadersFor(origin, isAllowedOrigin(origin, hosts), OAUTH_ENDPOINT_CORS);
51
+ }
52
+ function withHeaders(response, extra) {
53
+ const headers = new Headers(response.headers);
54
+ for (const name of PROVIDER_CORS_HEADERS)
55
+ headers.delete(name);
56
+ for (const [key, value] of Object.entries(extra)) {
57
+ if (key === "Vary") {
58
+ if (!/(^|,)\s*origin\s*(,|$)/i.test(headers.get("Vary") ?? ""))
59
+ headers.append(key, value);
60
+ }
61
+ else
62
+ headers.set(key, value);
63
+ }
64
+ return new Response(response.body, {
65
+ status: response.status,
66
+ statusText: response.statusText,
67
+ headers,
68
+ });
69
+ }
70
+ /** The preflight answer: 204, the route's methods and headers, the same origin decision. */
71
+ function preflight(route, request, hosts) {
72
+ if (route === "discovery") {
73
+ return new Response(null, {
74
+ status: 204,
75
+ headers: {
76
+ "Access-Control-Allow-Origin": "*",
77
+ "Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",
78
+ "Access-Control-Allow-Headers": DISCOVERY_ALLOW_HEADERS,
79
+ "Access-Control-Max-Age": "600",
80
+ },
81
+ });
82
+ }
83
+ const origin = request.headers.get("origin");
84
+ return new Response(null, {
85
+ status: 204,
86
+ headers: corsHeadersFor(origin, isAllowedOrigin(origin, hosts), OAUTH_ENDPOINT_CORS),
87
+ });
88
+ }
89
+ /**
90
+ * Serve `request` through `next` (the OAuth provider) with the CORS rules above,
91
+ * or return undefined when the path is not one of ours.
92
+ */
93
+ export async function withProviderCors(request, hosts, next) {
94
+ const route = corsRouteFor(new URL(request.url).pathname);
95
+ if (!route)
96
+ return undefined;
97
+ if (request.method === "OPTIONS")
98
+ return preflight(route, request, hosts);
99
+ let response;
100
+ try {
101
+ response = await next();
102
+ }
103
+ catch (error) {
104
+ // A throw would reach the browser as an opaque network error; answer it.
105
+ console.error("mzizi-mcp: OAuth provider threw", error);
106
+ response = Response.json({ error: "server_error", error_description: "Internal error" }, { status: 500 });
107
+ }
108
+ return withHeaders(response, headersFor(route, request, hosts));
109
+ }
110
+ //# sourceMappingURL=cors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cors.js","sourceRoot":"","sources":["../../src/auth/cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEpF,2EAA2E;AAC3E,MAAM,qBAAqB,GAAG;IAC5B,6BAA6B;IAC7B,8BAA8B;IAC9B,8BAA8B;IAC9B,wBAAwB;IACxB,kCAAkC;IAClC,+BAA+B;CAChC,CAAC;AAEF,iHAAiH;AACjH,MAAM,uBAAuB,GAAG,oCAAoC,CAAC;AAIrE,4FAA4F;AAC5F,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,IAAI,QAAQ,KAAK,cAAc,IAAI,QAAQ,CAAC,UAAU,CAAC,eAAe,CAAC;QAAE,OAAO,WAAW,CAAC;IAC5F,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,WAAW;QAAE,OAAO,UAAU,CAAC;IACzE,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,iDAAiD;AACjD,SAAS,UAAU,CACjB,KAAgB,EAChB,OAAgB,EAChB,KAAoC;IAEpC,IAAI,KAAK,KAAK,WAAW;QAAE,OAAO,EAAE,6BAA6B,EAAE,GAAG,EAAE,CAAC;IACzE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,OAAO,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,mBAAmB,CAAC,CAAC;AACrF,CAAC;AAED,SAAS,WAAW,CAAC,QAAkB,EAAE,KAA6B;IACpE,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,MAAM,IAAI,IAAI,qBAAqB;QAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAC/D,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACjD,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;YACnB,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;gBAAE,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC7F,CAAC;;YAAM,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE;QACjC,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,UAAU,EAAE,QAAQ,CAAC,UAAU;QAC/B,OAAO;KACR,CAAC,CAAC;AACL,CAAC;AAED,4FAA4F;AAC5F,SAAS,SAAS,CAChB,KAAgB,EAChB,OAAgB,EAChB,KAAoC;IAEpC,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;QAC1B,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;YACxB,MAAM,EAAE,GAAG;YACX,OAAO,EAAE;gBACP,6BAA6B,EAAE,GAAG;gBAClC,8BAA8B,EAAE,oBAAoB;gBACpD,8BAA8B,EAAE,uBAAuB;gBACvD,wBAAwB,EAAE,KAAK;aAChC;SACF,CAAC,CAAC;IACL,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7C,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE;QACxB,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,cAAc,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,mBAAmB,CAAC;KACrF,CAAC,CAAC;AACL,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,OAAgB,EAChB,KAAoC,EACpC,IAA6B;IAE7B,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;IAC1D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAC1E,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,IAAI,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,yEAAyE;QACzE,OAAO,CAAC,KAAK,CAAC,iCAAiC,EAAE,KAAK,CAAC,CAAC;QACxD,QAAQ,GAAG,QAAQ,CAAC,IAAI,CACtB,EAAE,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,EAC9D,EAAE,MAAM,EAAE,GAAG,EAAE,CAChB,CAAC;IACJ,CAAC;IACD,OAAO,WAAW,CAAC,QAAQ,EAAE,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;AAClE,CAAC"}
@@ -4,6 +4,14 @@
4
4
  * Its own module, not `worker.ts`: the Worker's entry module may export only
5
5
  * handlers and Durable Object classes, and this is tested directly.
6
6
  */
7
+ /**
8
+ * True for the provider's API route as the PROVIDER matches it: any path that
9
+ * starts with `/mcp` (`/mcp`, `/mcp/signin`, `/mcpx`, `/mcp%2Fsignin`: the
10
+ * provider's `matchApiRoute` is a bare `startsWith`). The Origin gate in
11
+ * `worker.ts` uses this, so no path the provider treats as the API route
12
+ * escapes the gate.
13
+ */
14
+ export declare function isProviderApiPath(pathname: string): boolean;
7
15
  /**
8
16
  * True for a request the free tier serves: the MCP path, no credential at all.
9
17
  *
@@ -1 +1 @@
1
- {"version":3,"file":"front.d.ts","sourceRoot":"","sources":["../../src/auth/front.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAI/D"}
1
+ {"version":3,"file":"front.d.ts","sourceRoot":"","sources":["../../src/auth/front.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAI/D"}
@@ -6,6 +6,16 @@
6
6
  */
7
7
  /** The paths served to a caller with no token. `/mcp/signin` is deliberately not one. */
8
8
  const ANONYMOUS_PATHS = new Set(["/mcp", "/mcp/"]);
9
+ /**
10
+ * True for the provider's API route as the PROVIDER matches it: any path that
11
+ * starts with `/mcp` (`/mcp`, `/mcp/signin`, `/mcpx`, `/mcp%2Fsignin`: the
12
+ * provider's `matchApiRoute` is a bare `startsWith`). The Origin gate in
13
+ * `worker.ts` uses this, so no path the provider treats as the API route
14
+ * escapes the gate.
15
+ */
16
+ export function isProviderApiPath(pathname) {
17
+ return pathname.startsWith("/mcp");
18
+ }
9
19
  /**
10
20
  * True for a request the free tier serves: the MCP path, no credential at all.
11
21
  *
@@ -1 +1 @@
1
- {"version":3,"file":"front.js","sourceRoot":"","sources":["../../src/auth/front.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,yFAAyF;AACzF,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,OAAO,eAAe,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC5D,CAAC"}
1
+ {"version":3,"file":"front.js","sourceRoot":"","sources":["../../src/auth/front.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,yFAAyF;AACzF,MAAM,eAAe,GAAG,IAAI,GAAG,CAAC,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,OAAO,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IAC/C,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;QAAE,OAAO,KAAK,CAAC;IACvD,OAAO,eAAe,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC;AAC5D,CAAC"}
@@ -24,7 +24,7 @@ type AuthedContext = ExecutionContext & {
24
24
  * Takes no `ctx` on purpose, so the anonymous front in `worker.ts` cannot pick
25
25
  * up a `ctx.props` the provider did not set.
26
26
  */
27
- export declare function serveAnonymous(request: Request, env: Env): Promise<Response>;
27
+ export declare function serveAnonymous(request: Request, env: Env, hosts?: readonly string[]): Promise<Response>;
28
28
  export declare const mcpApiHandler: {
29
29
  fetch(request: Request, env: Env, ctx: AuthedContext): Promise<Response>;
30
30
  };
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-api-handler.d.ts","sourceRoot":"","sources":["../../src/auth/mcp-api-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAGvC,KAAK,aAAa,GAAG,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAA;AAEzD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAE5E;AAED,eAAO,MAAM,aAAa;mBACH,OAAO,OAAO,GAAG,OAAO,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC;CA4B/E,CAAA"}
1
+ {"version":3,"file":"mcp-api-handler.d.ts","sourceRoot":"","sources":["../../src/auth/mcp-api-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAIvC,KAAK,aAAa,GAAG,gBAAgB,GAAG;IAAE,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,CAAA;AAEzD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,GAAE,SAAS,MAAM,EAA6B,GAAG,OAAO,CAAC,QAAQ,CAAC,CAEjI;AAED,eAAO,MAAM,aAAa;mBACH,OAAO,OAAO,GAAG,OAAO,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC;CA4B/E,CAAA"}
@@ -15,6 +15,7 @@
15
15
  import { createMziziHttpHandler } from "../http.js";
16
16
  import { createFundiCaller, createUserTokenResolver, fundiConfigured } from "../fundi-client.js";
17
17
  import { deskFor } from "../desk-context.js";
18
+ import { allowedHostsFromEnv } from "../origin.js";
18
19
  /**
19
20
  * The free tier: no user. `fundi` says whether to LIST mzizi_fundi (so its
20
21
  * refusal can say how to sign in); it wires no caller and no desk.
@@ -22,8 +23,8 @@ import { deskFor } from "../desk-context.js";
22
23
  * Takes no `ctx` on purpose, so the anonymous front in `worker.ts` cannot pick
23
24
  * up a `ctx.props` the provider did not set.
24
25
  */
25
- export function serveAnonymous(request, env) {
26
- return createMziziHttpHandler({ anonymous: { fundi: fundiConfigured(env) } })(request);
26
+ export function serveAnonymous(request, env, hosts = allowedHostsFromEnv(env)) {
27
+ return createMziziHttpHandler({ anonymous: { fundi: fundiConfigured(env) }, allowedOrigins: hosts })(request);
27
28
  }
28
29
  export const mcpApiHandler = {
29
30
  async fetch(request, env, ctx) {
@@ -47,7 +48,7 @@ export const mcpApiHandler = {
47
48
  // `idFromName` is deterministic, so the same person reaches the same object
48
49
  // across sessions, devices and years; that is what makes the history real.
49
50
  const desk = deskFor(env.ISSUE_DESK.get(env.ISSUE_DESK.idFromName(props.userId)), props, fundi?.resolveUserToken);
50
- return createMziziHttpHandler({ fundi, desk })(request);
51
+ return createMziziHttpHandler({ fundi, desk, allowedOrigins: allowedHostsFromEnv(env) })(request);
51
52
  },
52
53
  };
53
54
  //# sourceMappingURL=mcp-api-handler.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-api-handler.js","sourceRoot":"","sources":["../../src/auth/mcp-api-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAGhG,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAI5C;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgB,EAAE,GAAQ;IACvD,OAAO,sBAAsB,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;AACxF,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,KAAK,CAAC,KAAK,CAAC,OAAgB,EAAE,GAAQ,EAAE,GAAkB;QACxD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAA;QACvB,kEAAkE;QAClE,IAAI,CAAC,KAAK,EAAE,MAAM;YAAE,OAAO,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;QAEvD,yEAAyE;QACzE,4EAA4E;QAC5E,sCAAsC;QACtC,2EAA2E;QAC3E,2EAA2E;QAC3E,sEAAsE;QACtE,4EAA4E;QAC5E,gCAAgC;QAChC,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC;YAChC,CAAC,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,gBAAgB,EAAE,uBAAuB,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE;YAC3F,CAAC,CAAC,SAAS,CAAA;QACb,4EAA4E;QAC5E,0EAA0E;QAC1E,4EAA4E;QAC5E,2EAA2E;QAC3E,MAAM,IAAI,GAAG,OAAO,CAClB,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAC3D,KAAK,EACL,KAAK,EAAE,gBAAgB,CACxB,CAAA;QAED,OAAO,sBAAsB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;IACzD,CAAC;CACF,CAAA"}
1
+ {"version":3,"file":"mcp-api-handler.js","sourceRoot":"","sources":["../../src/auth/mcp-api-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AAGhG,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAA;AAC5C,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAIlD;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAgB,EAAE,GAAQ,EAAE,QAA2B,mBAAmB,CAAC,GAAG,CAAC;IAC5G,OAAO,sBAAsB,CAAC,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,eAAe,CAAC,GAAG,CAAC,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;AAC/G,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,KAAK,CAAC,KAAK,CAAC,OAAgB,EAAE,GAAQ,EAAE,GAAkB;QACxD,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAA;QACvB,kEAAkE;QAClE,IAAI,CAAC,KAAK,EAAE,MAAM;YAAE,OAAO,cAAc,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;QAEvD,yEAAyE;QACzE,4EAA4E;QAC5E,sCAAsC;QACtC,2EAA2E;QAC3E,2EAA2E;QAC3E,sEAAsE;QACtE,4EAA4E;QAC5E,gCAAgC;QAChC,MAAM,KAAK,GAAG,eAAe,CAAC,GAAG,CAAC;YAChC,CAAC,CAAC,EAAE,MAAM,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,gBAAgB,EAAE,uBAAuB,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE;YAC3F,CAAC,CAAC,SAAS,CAAA;QACb,4EAA4E;QAC5E,0EAA0E;QAC1E,4EAA4E;QAC5E,2EAA2E;QAC3E,MAAM,IAAI,GAAG,OAAO,CAClB,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAC3D,KAAK,EACL,KAAK,EAAE,gBAAgB,CACxB,CAAA;QAED,OAAO,sBAAsB,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;IACnG,CAAC;CACF,CAAA"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The OAuth provider's static options, shared by `worker.ts` and the tests that
3
+ * drive the real provider (`test/oauth-provider.test.ts`), so a test cannot
4
+ * pass against a configuration the Worker does not run.
5
+ *
6
+ * Its own module, not `worker.ts`: the Worker's entry module may export only
7
+ * handlers and Durable Object classes.
8
+ */
9
+ export declare const PROVIDER_OPTIONS: {
10
+ readonly apiRoute: "/mcp";
11
+ readonly authorizeEndpoint: "/authorize";
12
+ readonly tokenEndpoint: "/token";
13
+ readonly clientRegistrationEndpoint: "/register";
14
+ readonly allowPlainPKCE: false;
15
+ };
16
+ //# sourceMappingURL=provider-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-options.d.ts","sourceRoot":"","sources":["../../src/auth/provider-options.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,eAAO,MAAM,gBAAgB;;;;;;CAWnB,CAAA"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The OAuth provider's static options, shared by `worker.ts` and the tests that
3
+ * drive the real provider (`test/oauth-provider.test.ts`), so a test cannot
4
+ * pass against a configuration the Worker does not run.
5
+ *
6
+ * Its own module, not `worker.ts`: the Worker's entry module may export only
7
+ * handlers and Durable Object classes.
8
+ */
9
+ export const PROVIDER_OPTIONS = {
10
+ // A prefix: it covers `/mcp` and `/mcp/signin`.
11
+ apiRoute: "/mcp",
12
+ authorizeEndpoint: "/authorize",
13
+ tokenEndpoint: "/token",
14
+ clientRegistrationEndpoint: "/register",
15
+ // OAuth 2.1 and the MCP authorization spec require S256; `plain` offers no
16
+ // protection against an intercepted code (#295). With this `false` the
17
+ // authorization-server metadata advertises `["S256"]` and the token endpoint
18
+ // refuses a `plain` challenge.
19
+ allowPlainPKCE: false,
20
+ };
21
+ //# sourceMappingURL=provider-options.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-options.js","sourceRoot":"","sources":["../../src/auth/provider-options.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,gDAAgD;IAChD,QAAQ,EAAE,MAAM;IAChB,iBAAiB,EAAE,YAAY;IAC/B,aAAa,EAAE,QAAQ;IACvB,0BAA0B,EAAE,WAAW;IACvC,2EAA2E;IAC3E,uEAAuE;IACvE,6EAA6E;IAC7E,+BAA+B;IAC/B,cAAc,EAAE,KAAK;CACb,CAAA"}
package/dist/env.d.ts CHANGED
@@ -19,6 +19,7 @@ export interface Env {
19
19
  FUNDI_SERVICE?: Fetcher;
20
20
  COOKIE_ENCRYPTION_KEY: string;
21
21
  ISSUE_DESK: DurableObjectNamespace<import("./issue-desk.js").IssueDesk>;
22
+ MZIZI_ALLOWED_ORIGINS?: string;
22
23
  ANON_RATE_LIMIT?: import("./rate-limit.js").RateLimiter;
23
24
  OAUTH_KV: KVNamespace;
24
25
  OAUTH_PROVIDER: OAuthHelpers;
package/dist/env.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,MAAM,WAAW,GAAG;IAiBlB,gBAAgB,EAAE,MAAM,CAAA;IAYxB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAO9B,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAUlB,aAAa,CAAC,EAAE,OAAO,CAAA;IAGvB,qBAAqB,EAAE,MAAM,CAAA;IAM7B,UAAU,EAAE,sBAAsB,CAAC,OAAO,iBAAiB,EAAE,SAAS,CAAC,CAAA;IAMvE,eAAe,CAAC,EAAE,OAAO,iBAAiB,EAAE,WAAW,CAAA;IAGvD,QAAQ,EAAE,WAAW,CAAA;IACrB,cAAc,EAAE,YAAY,CAAA;CAC7B"}
1
+ {"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../src/env.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAA;AAEtE,MAAM,WAAW,GAAG;IAiBlB,gBAAgB,EAAE,MAAM,CAAA;IAYxB,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAO9B,oBAAoB,CAAC,EAAE,MAAM,CAAA;IAC7B,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAUlB,aAAa,CAAC,EAAE,OAAO,CAAA;IAGvB,qBAAqB,EAAE,MAAM,CAAA;IAM7B,UAAU,EAAE,sBAAsB,CAAC,OAAO,iBAAiB,EAAE,SAAS,CAAC,CAAA;IAMvE,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAM9B,eAAe,CAAC,EAAE,OAAO,iBAAiB,EAAE,WAAW,CAAA;IAGvD,QAAQ,EAAE,WAAW,CAAA;IACrB,cAAc,EAAE,YAAY,CAAA;CAC7B"}