@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.
- package/README.md +111 -39
- package/dist/auth/cors.d.ts +33 -0
- package/dist/auth/cors.d.ts.map +1 -0
- package/dist/auth/cors.js +110 -0
- package/dist/auth/cors.js.map +1 -0
- package/dist/auth/front.d.ts +8 -0
- package/dist/auth/front.d.ts.map +1 -1
- package/dist/auth/front.js +10 -0
- package/dist/auth/front.js.map +1 -1
- package/dist/auth/mcp-api-handler.d.ts +1 -1
- package/dist/auth/mcp-api-handler.d.ts.map +1 -1
- package/dist/auth/mcp-api-handler.js +4 -3
- package/dist/auth/mcp-api-handler.js.map +1 -1
- package/dist/auth/provider-options.d.ts +16 -0
- package/dist/auth/provider-options.d.ts.map +1 -0
- package/dist/auth/provider-options.js +21 -0
- package/dist/auth/provider-options.js.map +1 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/floors.d.ts +117 -0
- package/dist/floors.d.ts.map +1 -0
- package/dist/floors.js +103 -0
- package/dist/floors.js.map +1 -0
- package/dist/generated/registry-pin.d.ts +8 -1
- package/dist/generated/registry-pin.d.ts.map +1 -1
- package/dist/generated/registry-pin.js +3 -1
- package/dist/generated/registry-pin.js.map +1 -1
- package/dist/generated/registry.d.ts +2 -2
- package/dist/generated/registry.js +198 -198
- package/dist/generated/registry.js.map +1 -1
- package/dist/generated/skills.d.ts +1 -1
- package/dist/generated/skills.js +13 -13
- package/dist/generated/skills.js.map +1 -1
- package/dist/http.d.ts +14 -4
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +38 -54
- package/dist/http.js.map +1 -1
- package/dist/origin.d.ts +68 -0
- package/dist/origin.d.ts.map +1 -0
- package/dist/origin.js +141 -0
- package/dist/origin.js.map +1 -0
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +3 -2
- package/dist/rate-limit.js.map +1 -1
- package/dist/server.d.ts +47 -3
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +14 -8
- package/dist/server.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +115 -48
- package/dist/tools.js.map +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +25 -11
- package/dist/worker.js.map +1 -1
- 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.
|
|
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,
|
|
76
|
-
|
|
77
|
-
[docs.mzizi.dev/toolchain/connect](https://docs.mzizi.dev/toolchain/connect)
|
|
78
|
-
|
|
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
|
|
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
|
|
108
|
-
- **
|
|
109
|
-
|
|
110
|
-
`"
|
|
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
|
|
124
|
-
`.agents/skills/`, where `npx skills experimental_sync` links the npm
|
|
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`
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
|
374
|
-
|
|
|
375
|
-
| `WORKOS_CLIENT_ID`
|
|
376
|
-
| `WORKOS_M2M_CLIENT_ID`
|
|
377
|
-
| `FUNDI_URL`
|
|
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":"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"}
|
package/dist/auth/front.d.ts
CHANGED
|
@@ -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
|
*
|
package/dist/auth/front.d.ts.map
CHANGED
|
@@ -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"}
|
package/dist/auth/front.js
CHANGED
|
@@ -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
|
*
|
package/dist/auth/front.js.map
CHANGED
|
@@ -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;
|
|
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;
|
|
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"}
|