@nyuchi/mzizi-mcp 0.15.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 +10 -4
- 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/origin.d.ts +16 -3
- package/dist/origin.d.ts.map +1 -1
- package/dist/origin.js +20 -6
- package/dist/origin.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +1 -1
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +7 -1
- package/dist/worker.js.map +1 -1
- package/package.json +1 -1
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
|
|
@@ -302,9 +302,15 @@ call, passing the result through untouched:
|
|
|
302
302
|
that is not a plain `https` origin is dropped with a logged warning). Clients that send no
|
|
303
303
|
`Origin` (every non-browser client) are not affected. On `/mcp`, CORS echoes an allowed `Origin`
|
|
304
304
|
with `Vary: Origin` and never answers `Access-Control-Allow-Origin: *`. The OAuth provider's own
|
|
305
|
-
endpoints
|
|
306
|
-
|
|
307
|
-
|
|
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).
|
|
308
314
|
- **PKCE and token audience:** the authorization-server metadata advertises `S256` only. Audience
|
|
309
315
|
binding applies only to clients that send a `resource` indicator (RFC 8707): such a token is
|
|
310
316
|
refused on a different server or path, but a token for `/mcp` is also accepted on
|
|
@@ -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/origin.d.ts
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* - a present `Origin` allowed only if its host is on the list, otherwise
|
|
11
11
|
* 403 before any MCP server is built;
|
|
12
12
|
* - CORS `Access-Control-Allow-Origin` echoes an allowed
|
|
13
|
-
* `Origin` (with `Vary: Origin`). It is never
|
|
13
|
+
* `Origin` (with `Vary: Origin`). It is never `*`
|
|
14
|
+
* here; the public discovery documents are `*` (`auth/cors.ts`).
|
|
14
15
|
*
|
|
15
16
|
* The list is hostnames. A localhost name matches over `http` or `https` on any
|
|
16
17
|
* port (the MCP Inspector runs at http://localhost:6274). Every other name
|
|
@@ -42,14 +43,26 @@ export declare function isAllowedOrigin(origin: string | null, hosts?: readonly
|
|
|
42
43
|
export declare function forbiddenOrigin(origin: string | null): Response;
|
|
43
44
|
/** A 403 for a present-but-unlisted `Origin`, or undefined when the request may proceed. */
|
|
44
45
|
export declare function originRejection(request: Request, hosts?: readonly string[]): Response | undefined;
|
|
46
|
+
/** What a route's CORS answer advertises beyond the origin decision. */
|
|
47
|
+
export interface CorsPolicy {
|
|
48
|
+
readonly methods: string;
|
|
49
|
+
readonly headers: string;
|
|
50
|
+
/** Seconds a browser may cache a preflight; omitted when not set. */
|
|
51
|
+
readonly maxAge?: number;
|
|
52
|
+
}
|
|
53
|
+
/** The MCP endpoint (`/mcp`): its methods and the headers a Streamable HTTP client sends. */
|
|
54
|
+
export declare const MCP_CORS: CorsPolicy;
|
|
55
|
+
/** `/token` and `/register`: POST only, a form or JSON body, and client credentials in `Authorization`. */
|
|
56
|
+
export declare const OAUTH_ENDPOINT_CORS: CorsPolicy;
|
|
45
57
|
/**
|
|
46
58
|
* The CORS headers for a request whose `Origin` is `origin`, already judged
|
|
47
59
|
* `allowed`: the origin echoed back, never `*`. No `Origin`, or one that is not
|
|
48
60
|
* allowed, gets no `Access-Control-Allow-Origin`; `Vary: Origin` is always
|
|
49
61
|
* present so a cache cannot hand one origin's answer to another. Merge `Vary`
|
|
50
|
-
* with `Headers.append`, not `set`.
|
|
62
|
+
* with `Headers.append`, not `set`. No `Access-Control-Allow-Credentials`: no
|
|
63
|
+
* route reads a cookie cross-origin.
|
|
51
64
|
*/
|
|
52
|
-
export declare function corsHeadersFor(origin: string | null, allowed: boolean): Record<string, string>;
|
|
65
|
+
export declare function corsHeadersFor(origin: string | null, allowed: boolean, policy?: CorsPolicy): Record<string, string>;
|
|
53
66
|
/** `corsHeadersFor`, judging the request's own `Origin` against `hosts`. */
|
|
54
67
|
export declare function corsHeaders(request: Request, hosts?: readonly string[]): Record<string, string>;
|
|
55
68
|
//# sourceMappingURL=origin.d.ts.map
|
package/dist/origin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"origin.d.ts","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,8EAA8E;AAC9E,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAMjD,CAAA;AAED,6DAA6D;AAC7D,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAA8C,CAAA;AAIjG;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE;IAAE,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,MAAM,EAAE,CAsB9F;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,KAAK,GAAE,SAAS,MAAM,EAA0B,GAAG,OAAO,CAahH;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,QAAQ,CAK/D;AAED,4FAA4F;AAC5F,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,QAAQ,GAAG,SAAS,CAGjG;AAED,wEAAwE;AACxE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,6FAA6F;AAC7F,eAAO,MAAM,QAAQ,EAAE,UAGtB,CAAA;AAED,2GAA2G;AAC3G,eAAO,MAAM,mBAAmB,EAAE,UAIjC,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAE,UAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS7H;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAG/F"}
|
package/dist/origin.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* - a present `Origin` allowed only if its host is on the list, otherwise
|
|
11
11
|
* 403 before any MCP server is built;
|
|
12
12
|
* - CORS `Access-Control-Allow-Origin` echoes an allowed
|
|
13
|
-
* `Origin` (with `Vary: Origin`). It is never
|
|
13
|
+
* `Origin` (with `Vary: Origin`). It is never `*`
|
|
14
|
+
* here; the public discovery documents are `*` (`auth/cors.ts`).
|
|
14
15
|
*
|
|
15
16
|
* The list is hostnames. A localhost name matches over `http` or `https` on any
|
|
16
17
|
* port (the MCP Inspector runs at http://localhost:6274). Every other name
|
|
@@ -102,20 +103,33 @@ export function originRejection(request, hosts) {
|
|
|
102
103
|
const origin = request.headers.get("origin");
|
|
103
104
|
return isAllowedOrigin(origin, hosts) ? undefined : forbiddenOrigin(origin);
|
|
104
105
|
}
|
|
106
|
+
/** The MCP endpoint (`/mcp`): its methods and the headers a Streamable HTTP client sends. */
|
|
107
|
+
export const MCP_CORS = {
|
|
108
|
+
methods: "GET, POST, DELETE, OPTIONS",
|
|
109
|
+
headers: "Content-Type, MCP-Protocol-Version, MCP-Session-Id, Mcp-Method, Mcp-Name, apikey",
|
|
110
|
+
};
|
|
111
|
+
/** `/token` and `/register`: POST only, a form or JSON body, and client credentials in `Authorization`. */
|
|
112
|
+
export const OAUTH_ENDPOINT_CORS = {
|
|
113
|
+
methods: "POST, OPTIONS",
|
|
114
|
+
headers: "Content-Type, Authorization",
|
|
115
|
+
maxAge: 600,
|
|
116
|
+
};
|
|
105
117
|
/**
|
|
106
118
|
* The CORS headers for a request whose `Origin` is `origin`, already judged
|
|
107
119
|
* `allowed`: the origin echoed back, never `*`. No `Origin`, or one that is not
|
|
108
120
|
* allowed, gets no `Access-Control-Allow-Origin`; `Vary: Origin` is always
|
|
109
121
|
* present so a cache cannot hand one origin's answer to another. Merge `Vary`
|
|
110
|
-
* with `Headers.append`, not `set`.
|
|
122
|
+
* with `Headers.append`, not `set`. No `Access-Control-Allow-Credentials`: no
|
|
123
|
+
* route reads a cookie cross-origin.
|
|
111
124
|
*/
|
|
112
|
-
export function corsHeadersFor(origin, allowed) {
|
|
125
|
+
export function corsHeadersFor(origin, allowed, policy = MCP_CORS) {
|
|
113
126
|
const headers = { Vary: "Origin" };
|
|
114
127
|
if (origin && allowed) {
|
|
115
128
|
headers["Access-Control-Allow-Origin"] = origin;
|
|
116
|
-
headers["Access-Control-Allow-Methods"] =
|
|
117
|
-
headers["Access-Control-Allow-Headers"] =
|
|
118
|
-
|
|
129
|
+
headers["Access-Control-Allow-Methods"] = policy.methods;
|
|
130
|
+
headers["Access-Control-Allow-Headers"] = policy.headers;
|
|
131
|
+
if (policy.maxAge !== undefined)
|
|
132
|
+
headers["Access-Control-Max-Age"] = String(policy.maxAge);
|
|
119
133
|
}
|
|
120
134
|
return headers;
|
|
121
135
|
}
|
package/dist/origin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"origin.js","sourceRoot":"","sources":["../src/origin.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { floorsOf } from "./floors.js";
|
|
|
17
17
|
import { type DocsFederationOptions } from "./docs-federation.js";
|
|
18
18
|
import type { FundiToolContext } from "./fundi-client.js";
|
|
19
19
|
import { type IssueDeskContext } from "./tools.js";
|
|
20
|
-
export declare const MZIZI_MCP_VERSION = "0.
|
|
20
|
+
export declare const MZIZI_MCP_VERSION = "0.16.0";
|
|
21
21
|
export type CreateServerOptions = {
|
|
22
22
|
fundi?: FundiToolContext;
|
|
23
23
|
/** The signed-in user's issue desk. See `tools.ts` — absent off the Worker. */
|
package/dist/server.js
CHANGED
|
@@ -21,7 +21,7 @@ import { registerDocsTools, DOCS_MCP_URL } from "./docs-federation.js";
|
|
|
21
21
|
import { registerMziziTools, TOOL_COVERAGE, TOOL_PREFIX } from "./tools.js";
|
|
22
22
|
import { jsonContent } from "./tool-result.js";
|
|
23
23
|
import { accessFor, accessSummary } from "./access.js";
|
|
24
|
-
export const MZIZI_MCP_VERSION = "0.
|
|
24
|
+
export const MZIZI_MCP_VERSION = "0.16.0";
|
|
25
25
|
/**
|
|
26
26
|
* What this server exposes, computed from what was actually registered.
|
|
27
27
|
*
|
package/dist/worker.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"worker.d.ts","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAKH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAOnC,YAAY,EAAE,GAAG,EAAE,MAAM,UAAU,CAAA;AAMnC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;;mBASpB,OAAO,OAAO,GAAG,OAAO,gBAAgB,GAAG,OAAO,CAAC,QAAQ,CAAC;;AADnF,wBAyBgC"}
|
package/dist/worker.js
CHANGED
|
@@ -32,6 +32,7 @@ import { limitAnonymous } from "./rate-limit.js";
|
|
|
32
32
|
import { isAnonymousMcpRequest, isProviderApiPath } from "./auth/front.js";
|
|
33
33
|
import { PROVIDER_OPTIONS } from "./auth/provider-options.js";
|
|
34
34
|
import { allowedHostsFromEnv, originRejection } from "./origin.js";
|
|
35
|
+
import { withProviderCors } from "./auth/cors.js";
|
|
35
36
|
// The per-user issue desk. Exported here because Cloudflare resolves a Durable
|
|
36
37
|
// Object class off the Worker's entry module, and `wrangler.toml` names this
|
|
37
38
|
// file — an unexported class fails the deploy with "class not found", which
|
|
@@ -51,7 +52,7 @@ export default {
|
|
|
51
52
|
// on its API route, which is why the signed-in front needs the gate here
|
|
52
53
|
// too, and it matches the provider's own prefix rule (any path starting
|
|
53
54
|
// with `/mcp`), so `/mcpx` and `/mcp%2Fsignin` cannot slip past. OAuth's own endpoints (`/token`, `/register`, `/authorize`,
|
|
54
|
-
// `/.well-known/*`) are
|
|
55
|
+
// `/.well-known/*`) are not Origin-gated; a refused Origin there only loses CORS (auth/cors.ts).
|
|
55
56
|
if (isProviderApiPath(new URL(request.url).pathname)) {
|
|
56
57
|
const rejected = originRejection(request, hosts);
|
|
57
58
|
if (rejected)
|
|
@@ -63,6 +64,11 @@ export default {
|
|
|
63
64
|
return limited;
|
|
64
65
|
return serveAnonymous(request, env, hosts);
|
|
65
66
|
}
|
|
67
|
+
// `/token`, `/register` and `/.well-known/*` are served by the provider, but
|
|
68
|
+
// its CORS (any Origin echoed) is replaced with ours (auth/cors.ts).
|
|
69
|
+
const cors = await withProviderCors(request, hosts, () => oauth.fetch(request, env, ctx));
|
|
70
|
+
if (cors)
|
|
71
|
+
return cors;
|
|
66
72
|
return oauth.fetch(request, env, ctx);
|
|
67
73
|
},
|
|
68
74
|
};
|
package/dist/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,aAAa,MAAM,oCAAoC,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAEzE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../src/worker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,aAAa,MAAM,oCAAoC,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAEzE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAC1E,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAIjD,+EAA+E;AAC/E,6EAA6E;AAC7E,4EAA4E;AAC5E,qDAAqD;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,MAAM,KAAK,GAAG,IAAI,aAAa,CAAC;IAC9B,GAAG,gBAAgB;IACnB,UAAU,EAAE,aAAsB;IAClC,cAAc,EAAE,cAAuB;CACxC,CAAC,CAAA;AAEF,eAAe;IACb,KAAK,CAAC,KAAK,CAAC,OAAgB,EAAE,GAAQ,EAAE,GAAqB;QAC3D,MAAM,KAAK,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAA;QACtC,uEAAuE;QACvE,yEAAyE;QACzE,4EAA4E;QAC5E,yEAAyE;QACzE,wEAAwE;QACxE,6HAA6H;QAC7H,iGAAiG;QACjG,IAAI,iBAAiB,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrD,MAAM,QAAQ,GAAG,eAAe,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;YAChD,IAAI,QAAQ;gBAAE,OAAO,QAAQ,CAAA;QAC/B,CAAC;QACD,IAAI,qBAAqB,CAAC,OAAO,CAAC,EAAE,CAAC;YACnC,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,GAAG,CAAC,eAAe,EAAE,OAAO,EAAE,KAAK,CAAC,CAAA;YACzE,IAAI,OAAO;gBAAE,OAAO,OAAO,CAAA;YAC3B,OAAO,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;QAC5C,CAAC;QACD,6EAA6E;QAC7E,qEAAqE;QACrE,MAAM,IAAI,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAA;QACzF,IAAI,IAAI;YAAE,OAAO,IAAI,CAAA;QACrB,OAAO,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;IACvC,CAAC;CAC6B,CAAA"}
|