machine-bridge-mcp 3.0.0-beta.17 → 3.0.0-beta.21

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 (74) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +2 -0
  3. package/SECURITY.md +9 -7
  4. package/browser-extension/browser-error-boundary.js +45 -0
  5. package/browser-extension/browser-operations.js +1 -1
  6. package/browser-extension/manifest.json +1 -1
  7. package/browser-extension/service-worker.js +9 -8
  8. package/docs/ARCHITECTURE.md +17 -15
  9. package/docs/AUDIT.md +30 -0
  10. package/docs/ENGINEERING.md +3 -3
  11. package/docs/GETTING_STARTED.md +1 -1
  12. package/docs/LOCAL_AUTHORIZATION.md +2 -2
  13. package/docs/LOCAL_AUTOMATION.md +1 -1
  14. package/docs/LOGGING.md +3 -1
  15. package/docs/MULTI_ACCOUNT.md +2 -2
  16. package/docs/OPERATIONS.md +13 -5
  17. package/docs/OVERVIEW.md +2 -2
  18. package/docs/PRIVACY.md +1 -1
  19. package/docs/TESTING.md +17 -13
  20. package/docs/THREAT_MODEL.md +13 -8
  21. package/docs/UPGRADING.md +19 -1
  22. package/package.json +1 -1
  23. package/scripts/coverage-check.mjs +12 -0
  24. package/src/local/account-admin.mjs +68 -1
  25. package/src/local/agent-skill-discovery.mjs +16 -5
  26. package/src/local/app-automation.mjs +27 -7
  27. package/src/local/cli-options.mjs +1 -1
  28. package/src/local/path-inspection.mjs +23 -0
  29. package/src/local/process-tree-ownership.mjs +17 -5
  30. package/src/local/process-tree.mjs +6 -2
  31. package/src/local/relay-connection.mjs +24 -11
  32. package/src/local/resource-operations.mjs +37 -8
  33. package/src/local/runtime-activation.mjs +16 -0
  34. package/src/local/runtime-capabilities.mjs +15 -3
  35. package/src/local/runtime.mjs +7 -4
  36. package/src/local/worker-deployment.mjs +16 -6
  37. package/src/local/workspace-file-service.mjs +29 -11
  38. package/src/worker/daemon-socket-attachment.ts +52 -0
  39. package/src/worker/daemon-sockets.ts +17 -48
  40. package/src/worker/durable-stream-calls.ts +129 -0
  41. package/src/worker/durable-stream-result.ts +22 -0
  42. package/src/worker/http.ts +37 -15
  43. package/src/worker/index.ts +143 -146
  44. package/src/worker/mcp-pending-call-expiry.ts +20 -0
  45. package/src/worker/mcp-pending-call-inspection.ts +33 -0
  46. package/src/worker/mcp-pending-call-records.ts +58 -0
  47. package/src/worker/mcp-pending-call-storage.ts +18 -0
  48. package/src/worker/mcp-pending-call-store.ts +285 -0
  49. package/src/worker/mcp-resumption-index.ts +41 -0
  50. package/src/worker/mcp-resumption-records.ts +8 -2
  51. package/src/worker/mcp-resumption.ts +84 -86
  52. package/src/worker/mcp-stream-dispatch.ts +42 -54
  53. package/src/worker/mcp-stream-proxy.ts +7 -69
  54. package/src/worker/mcp-stream-subscription.ts +98 -0
  55. package/src/worker/oauth-controller.ts +11 -2
  56. package/src/worker/oauth-refresh-exchange.ts +147 -0
  57. package/src/worker/oauth-refresh-families.ts +59 -17
  58. package/src/worker/oauth-state.ts +13 -2
  59. package/src/worker/oauth-token-derivation.ts +33 -0
  60. package/src/worker/oauth-token-issuance.ts +106 -0
  61. package/src/worker/oauth-tokens.ts +12 -184
  62. package/src/worker/observability.ts +19 -0
  63. package/src/worker/pending-admission.ts +15 -0
  64. package/src/worker/pending-call-contract.ts +4 -7
  65. package/src/worker/pending-calls.ts +4 -10
  66. package/src/worker/runtime-alarm-storage.ts +35 -0
  67. package/src/worker/runtime-alarm.ts +30 -20
  68. package/src/worker/websocket-protocol.ts +4 -0
  69. package/src/worker/worker-edge-guard.ts +81 -0
  70. package/src/worker/worker-edge-log.ts +63 -0
  71. package/src/worker/worker-entry.ts +50 -0
  72. package/src/worker/worker-metadata.ts +39 -0
  73. package/src/worker/worker-static-routes.ts +38 -50
  74. package/wrangler.jsonc +8 -0
@@ -4,13 +4,10 @@ import {
4
4
  daemonReadyDeadlineMs,
5
5
  } from "./daemon-liveness.ts";
6
6
  import type { DaemonSocketRegistry } from "./daemon-sockets.ts";
7
+ import type { McpPendingCallStore, PendingStreamCallView } from "./mcp-pending-call-store.ts";
7
8
  import type { PendingCallRegistry } from "./pending-calls.ts";
8
9
  import { closeWebSocketQuietly, sendWebSocketQuietly } from "./websocket-protocol.ts";
9
-
10
- interface AlarmStorage {
11
- setAlarm(scheduledTime: number | Date): Promise<void>;
12
- deleteAlarm(): Promise<void>;
13
- }
10
+ import { writeEarliestRuntimeAlarm, type AlarmStorage } from "./runtime-alarm-storage.ts";
14
11
 
15
12
  type InvalidateDaemonSocket = (
16
13
  socket: WebSocket,
@@ -22,14 +19,20 @@ type InvalidateDaemonSocket = (
22
19
  interface RuntimeAlarmContext {
23
20
  storage: AlarmStorage;
24
21
  pending: PendingCallRegistry;
22
+ durableCalls?: Pick<McpPendingCallStore, "due" | "nextDeadlineDelayMs">;
23
+ expireDurableCall?: (call: PendingStreamCallView) => Promise<void>;
25
24
  daemonRegistry: DaemonSocketRegistry;
26
25
  invalidateDaemonSocket: InvalidateDaemonSocket;
27
26
  onScheduleError: (error: unknown) => void;
27
+ onAlarmMutation?: (action: "set" | "delete" | "noop") => void;
28
28
  }
29
29
 
30
30
  export async function processRuntimeAlarm(context: RuntimeAlarmContext, now = Date.now()): Promise<void> {
31
31
  await context.pending.expireDue();
32
- let nextDeadline = pendingAlarmDeadline(context.pending, now);
32
+ if (context.durableCalls && context.expireDurableCall) {
33
+ for (const call of await context.durableCalls.due(now)) await context.expireDurableCall(call);
34
+ }
35
+ let nextDeadline = await pendingAlarmDeadline(context, now);
33
36
  for (const socket of context.daemonRegistry.candidateSockets()) {
34
37
  const attachment = context.daemonRegistry.attachment(socket);
35
38
  const connectedAt = Date.parse(attachment?.connectedAt ?? "");
@@ -65,11 +68,17 @@ export async function processRuntimeAlarm(context: RuntimeAlarmContext, now = Da
65
68
  }
66
69
  nextDeadline = Math.min(nextDeadline, deadline);
67
70
  }
68
- await writeRuntimeAlarm(context, nextDeadline, now);
71
+ await writeEarliestRuntimeAlarm({
72
+ storage: context.storage,
73
+ nextDeadline,
74
+ now,
75
+ onError: context.onScheduleError,
76
+ onMutation: context.onAlarmMutation,
77
+ });
69
78
  }
70
79
 
71
80
  export async function scheduleRuntimeAlarm(context: RuntimeAlarmContext, now = Date.now()): Promise<void> {
72
- let nextDeadline = pendingAlarmDeadline(context.pending, now);
81
+ let nextDeadline = await pendingAlarmDeadline(context, now);
73
82
  for (const socket of context.daemonRegistry.candidateSockets()) {
74
83
  const connectedAt = Date.parse(context.daemonRegistry.attachment(socket)?.connectedAt ?? "");
75
84
  if (!Number.isFinite(connectedAt)) {
@@ -101,19 +110,20 @@ export async function scheduleRuntimeAlarm(context: RuntimeAlarmContext, now = D
101
110
  }
102
111
  nextDeadline = Math.min(nextDeadline, deadline);
103
112
  }
104
- await writeRuntimeAlarm(context, nextDeadline, now);
113
+ await writeEarliestRuntimeAlarm({
114
+ storage: context.storage,
115
+ nextDeadline,
116
+ now,
117
+ onError: context.onScheduleError,
118
+ onMutation: context.onAlarmMutation,
119
+ });
105
120
  }
106
121
 
107
- function pendingAlarmDeadline(pending: PendingCallRegistry, now: number): number {
108
- const delay = pending.nextDeadlineDelayMs();
122
+ async function pendingAlarmDeadline(context: RuntimeAlarmContext, now: number): Promise<number> {
123
+ const transientDelay = context.pending.nextDeadlineDelayMs();
124
+ const durableDelay = context.durableCalls
125
+ ? await context.durableCalls.nextDeadlineDelayMs()
126
+ : Number.POSITIVE_INFINITY;
127
+ const delay = Math.min(transientDelay, durableDelay);
109
128
  return Number.isFinite(delay) ? now + Math.max(1, Math.ceil(delay)) : Number.POSITIVE_INFINITY;
110
129
  }
111
-
112
- async function writeRuntimeAlarm(context: RuntimeAlarmContext, nextDeadline: number, now: number): Promise<void> {
113
- try {
114
- if (Number.isFinite(nextDeadline)) await context.storage.setAlarm(Math.max(now, nextDeadline));
115
- else await context.storage.deleteAlarm();
116
- } catch (error) {
117
- context.onScheduleError(error);
118
- }
119
- }
@@ -21,6 +21,10 @@ export function sendWebSocketQuietly(ws: WebSocket, value: unknown): void {
21
21
  trySendWebSocket(ws, value);
22
22
  }
23
23
 
24
+ export function daemonErrorCloseCode(errorCode: string): number {
25
+ return errorCode === "daemon_transport_error" || errorCode === "daemon_liveness_timeout" ? 1012 : 1008;
26
+ }
27
+
24
28
  export function closeWebSocketQuietly(ws: WebSocket, code?: number, reason?: string): void {
25
29
  try {
26
30
  ws.close(code, reason);
@@ -0,0 +1,81 @@
1
+ import { applyCors, baseUrl, json } from "./http.ts";
2
+ import { createThrottledEdgeLogger } from "./worker-edge-log.ts";
3
+
4
+ type RateLimiter = { limit(input: { key: string }): Promise<{ success: boolean }> };
5
+ const logLimiterFailure = createThrottledEdgeLogger();
6
+
7
+ export async function admitStatefulRequest(request: Request, limiter: RateLimiter, extraOrigins = ""): Promise<Response | null> {
8
+ const key = `stateful:${new URL(request.url).host.toLowerCase()}`;
9
+ let result: { success: boolean };
10
+ try {
11
+ result = await limiter.limit({ key });
12
+ } catch {
13
+ logLimiterFailure("warn", "stateful.rate_limiter.unavailable");
14
+ return null;
15
+ }
16
+ if (result.success) return null;
17
+ return applyCors(json({
18
+ error: "rate_limit_exceeded",
19
+ message: "The remote bridge is temporarily limiting stateful requests.",
20
+ retryable: true,
21
+ }, 429, { "retry-after": "60" }), request, baseUrl(request), extraOrigins);
22
+ }
23
+
24
+ export function isDurableObjectQuotaError(error: unknown): boolean {
25
+ for (const current of boundedErrorChain(error)) {
26
+ const message = current instanceof Error ? current.message : String(current ?? "");
27
+ const code = typeof current === "object" && current !== null ? String((current as { code?: unknown }).code ?? "") : "";
28
+ if (/Exceeded allowed volume of requests in Durable Objects free tier/i.test(message)) return true;
29
+ if (/Durable Objects free tier/i.test(message) && /quota|limit|exceed|volume/i.test(message)) return true;
30
+ if (code === "ERR_DURABLE_OBJECT_QUOTA_EXCEEDED") return true;
31
+ }
32
+ return false;
33
+ }
34
+
35
+ export function durableObjectQuotaResponse(request: Request, extraOrigins = ""): Response {
36
+ return errorResponse(request, extraOrigins, 503, "durable_object_quota_exceeded",
37
+ "Durable Objects free-tier capacity is exhausted until the daily UTC reset.", "3600");
38
+ }
39
+
40
+ export function workerGatewayErrorResponse(request: Request, extraOrigins = ""): Response {
41
+ return errorResponse(request, extraOrigins, 502, "worker_gateway_error",
42
+ "The remote bridge could not reach its durable state owner.", "1");
43
+ }
44
+
45
+ export function outerWorkerErrorClass(error: unknown): string {
46
+ const parts: string[] = [];
47
+ for (const current of boundedErrorChain(error)) {
48
+ if (typeof current !== "object" || current === null) break;
49
+ const name = String((current as { name?: unknown }).name ?? "");
50
+ const code = String((current as { code?: unknown }).code ?? "");
51
+ if (name) parts.push(name);
52
+ if (code) parts.push(code);
53
+ }
54
+ return (parts.join(":") || "unknown").toLowerCase().replace(/[^a-z0-9._:-]/g, "_").slice(0, 160);
55
+ }
56
+
57
+ function errorResponse(request: Request, extraOrigins: string, status: number, error: string, message: string, retryAfter: string): Response {
58
+ return applyCors(json({ error, message, retryable: true }, status, { "retry-after": retryAfter }),
59
+ request, baseUrl(request), extraOrigins);
60
+ }
61
+
62
+ function boundedErrorChain(value: unknown): unknown[] {
63
+ const chain: unknown[] = [];
64
+ const seen = new WeakSet<object>();
65
+ let current: unknown = value;
66
+ while (current !== undefined && chain.length < 8) {
67
+ if (!isObjectLike(current)) {
68
+ chain.push(current);
69
+ break;
70
+ }
71
+ if (seen.has(current)) break;
72
+ seen.add(current);
73
+ chain.push(current);
74
+ current = (current as { cause?: unknown }).cause;
75
+ }
76
+ return chain;
77
+ }
78
+
79
+ function isObjectLike(value: unknown): value is object {
80
+ return typeof value === "object" && value !== null;
81
+ }
@@ -0,0 +1,63 @@
1
+ type EdgeLogLevel = "warn" | "error";
2
+ type EdgeLogWriter = (level: EdgeLogLevel, text: string) => void;
3
+ const SENSITIVE_FIELD = /(?:authorization|cookie|password|secret|token|key|credential|proof)/i;
4
+
5
+ export function createThrottledEdgeLogger(options: {
6
+ intervalMs?: number;
7
+ now?: () => number;
8
+ write?: EdgeLogWriter;
9
+ } = {}): (level: EdgeLogLevel, event: string, fields?: Record<string, unknown>) => boolean {
10
+ const intervalMs = positiveInterval(options.intervalMs, 60_000);
11
+ const now = options.now ?? Date.now;
12
+ const write = options.write ?? defaultWrite;
13
+ let nextAt = 0;
14
+ let suppressed = 0;
15
+ return (level, event, fields = {}) => {
16
+ const current = now();
17
+ if (current < nextAt) {
18
+ suppressed += 1;
19
+ return false;
20
+ }
21
+ const entry = {
22
+ ...safeFields(fields),
23
+ ...(suppressed > 0 ? { suppressed } : {}),
24
+ timestamp: new Date(current).toISOString(),
25
+ level,
26
+ component: "worker-edge",
27
+ event: safeName(event),
28
+ };
29
+ suppressed = 0;
30
+ nextAt = current + intervalMs;
31
+ write(level, JSON.stringify(entry));
32
+ return true;
33
+ };
34
+ }
35
+
36
+ function safeFields(fields: Record<string, unknown>): Record<string, unknown> {
37
+ const out: Record<string, unknown> = {};
38
+ for (const [key, value] of Object.entries(fields).slice(0, 16)) {
39
+ const name = safeName(key);
40
+ if (!name) continue;
41
+ if (SENSITIVE_FIELD.test(name)) {
42
+ out[name] = "<redacted>";
43
+ continue;
44
+ }
45
+ if (typeof value === "number" || typeof value === "boolean" || value === null) out[name] = value;
46
+ else if (typeof value === "string") out[name] = value.replace(/[\u0000-\u001f\u007f]/g, "_").slice(0, 160);
47
+ }
48
+ return out;
49
+ }
50
+
51
+ function safeName(value: unknown): string {
52
+ return String(value || "").toLowerCase().replace(/[^a-z0-9._-]/g, "_").slice(0, 96);
53
+ }
54
+
55
+ function positiveInterval(value: unknown, fallback: number): number {
56
+ const parsed = Number(value);
57
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
58
+ }
59
+
60
+ function defaultWrite(level: EdgeLogLevel, text: string): void {
61
+ if (level === "error") console.error(text);
62
+ else console.warn(text);
63
+ }
@@ -0,0 +1,50 @@
1
+ import {
2
+ proxyMcpEventStream,
3
+ sanitizeBridgeRequest,
4
+ } from "./mcp-stream-proxy.ts";
5
+ import { respondWithoutDurableObject } from "./worker-static-routes.ts";
6
+ import { createThrottledEdgeLogger } from "./worker-edge-log.ts";
7
+ import {
8
+ admitStatefulRequest,
9
+ durableObjectQuotaResponse,
10
+ isDurableObjectQuotaError,
11
+ outerWorkerErrorClass,
12
+ workerGatewayErrorResponse,
13
+ } from "./worker-edge-guard.ts";
14
+
15
+ type OuterBridgeStub = { fetch(request: Request): Promise<Response> };
16
+ type OuterBridgeNamespace = { getByName(name: string): OuterBridgeStub };
17
+
18
+ export interface OuterWorkerEnv {
19
+ BRIDGE: OuterBridgeNamespace;
20
+ STATEFUL_RATE_LIMITER: RateLimit;
21
+ MBM_ALLOWED_ORIGINS?: string;
22
+ }
23
+
24
+ const logOuterFetchFailure = createThrottledEdgeLogger();
25
+
26
+ export async function handleOuterWorkerFetch(
27
+ request: Request,
28
+ env: OuterWorkerEnv,
29
+ ctx: ExecutionContext,
30
+ identity: { server: string; version: string },
31
+ ): Promise<Response> {
32
+ const extraOrigins = env.MBM_ALLOWED_ORIGINS ?? "";
33
+ const staticResponse = respondWithoutDurableObject(request, identity, extraOrigins);
34
+ if (staticResponse) return staticResponse;
35
+
36
+ try {
37
+ const limited = await admitStatefulRequest(request, env.STATEFUL_RATE_LIMITER, extraOrigins);
38
+ if (limited) return limited;
39
+ const stub = env.BRIDGE.getByName("default");
40
+ const streamed = await proxyMcpEventStream({ request, bridge: stub, extraOrigins, ctx });
41
+ return streamed ?? stub.fetch(sanitizeBridgeRequest(request));
42
+ } catch (error) {
43
+ if (isDurableObjectQuotaError(error)) return durableObjectQuotaResponse(request, extraOrigins);
44
+ logOuterFetchFailure("error", "outer.fetch.failed", {
45
+ path: new URL(request.url).pathname,
46
+ error_class: outerWorkerErrorClass(error),
47
+ });
48
+ return workerGatewayErrorResponse(request, extraOrigins);
49
+ }
50
+ }
@@ -0,0 +1,39 @@
1
+ import serverMetadata from "../shared/server-metadata.json" with { type: "json" };
2
+
3
+ export type WorkerIdentity = { server: string; version: string };
4
+
5
+ export function mcpMetadata(base: string, identity: WorkerIdentity): Record<string, unknown> {
6
+ return {
7
+ name: identity.server,
8
+ version: identity.version,
9
+ protocolVersion: String(serverMetadata.protocolVersion),
10
+ protocolVersions: serverMetadata.supportedProtocolVersions.map((value) => String(value)),
11
+ transport: { type: "streamable-http", url: `${base}/mcp` },
12
+ auth: { type: "oauth", authorization_servers: [base] },
13
+ };
14
+ }
15
+
16
+ export function authorizationServerMetadata(base: string, serverName: string): Record<string, unknown> {
17
+ return {
18
+ issuer: base,
19
+ authorization_endpoint: `${base}/oauth/authorize`,
20
+ token_endpoint: `${base}/oauth/token`,
21
+ registration_endpoint: `${base}/oauth/register`,
22
+ response_types_supported: ["code"],
23
+ grant_types_supported: ["authorization_code", "refresh_token"],
24
+ token_endpoint_auth_methods_supported: ["none"],
25
+ dpop_signing_alg_values_supported: ["ES256"],
26
+ code_challenge_methods_supported: ["S256"],
27
+ scopes_supported: [serverName, "offline_access"],
28
+ };
29
+ }
30
+
31
+ export function protectedResourceMetadata(base: string, serverName: string): Record<string, unknown> {
32
+ return {
33
+ resource: `${base}/mcp`,
34
+ authorization_servers: [base],
35
+ scopes_supported: [serverName, "offline_access"],
36
+ bearer_methods_supported: ["header"],
37
+ resource_name: serverName,
38
+ };
39
+ }
@@ -1,60 +1,48 @@
1
+ import { applyCors, baseUrl, corsPreflight, json, methodNotAllowed } from "./http.ts";
1
2
  import {
2
- applyCors,
3
- baseUrl,
4
- corsPreflight,
5
- json,
6
- methodNotAllowed,
7
- } from "./http.ts";
3
+ authorizationServerMetadata, mcpMetadata, protectedResourceMetadata, type WorkerIdentity,
4
+ } from "./worker-metadata.ts";
8
5
 
9
- export function respondWithoutDurableObject(
10
- request: Request,
11
- identity: { server: string; version: string },
12
- extraOrigins = "",
13
- ): Response | null {
6
+ const STATEFUL_METHODS = new Map([
7
+ ["/admin/accounts", "GET, POST, PATCH, DELETE"],
8
+ ["/admin/accounts/rotate-password", "POST"],
9
+ ["/admin/clients", "GET, DELETE"],
10
+ ["/daemon/ws", "GET"],
11
+ ["/mcp", "GET, POST"],
12
+ ["/oauth/authorize", "GET, POST"],
13
+ ["/oauth/register", "POST"],
14
+ ["/oauth/token", "POST"],
15
+ ]);
16
+ const AUTHORIZATION_METADATA_PATHS = new Set([
17
+ "/.well-known/oauth-authorization-server", "/.well-known/oauth-authorization-server/mcp",
18
+ "/.well-known/openid-configuration", "/.well-known/openid-configuration/mcp",
19
+ ]);
20
+ const PROTECTED_RESOURCE_PATHS = new Set([
21
+ "/.well-known/oauth-protected-resource", "/.well-known/oauth-protected-resource/mcp",
22
+ ]);
23
+
24
+ export function respondWithoutDurableObject(request: Request, identity: WorkerIdentity, extraOrigins = ""): Response | null {
14
25
  const url = new URL(request.url);
15
26
  const base = baseUrl(request);
16
27
  const path = url.pathname;
17
- const serverName = identity.server;
18
- const serverVersion = identity.version;
19
-
20
- if (request.method === "OPTIONS" && request.headers.has("Origin")) {
21
- // CORS preflight does not need Durable Object state and must not consume DO quota.
22
- return corsPreflight(request, base, extraOrigins);
23
- }
24
-
25
- if (path === "/healthz") {
26
- if (request.method !== "GET") return applyCors(methodNotAllowed("GET"), request, base, extraOrigins);
27
- return applyCors(json({ ok: true, server: serverName, version: serverVersion }), request, base, extraOrigins);
28
+ if (request.method === "OPTIONS" && request.headers.has("Origin")) return corsPreflight(request, base, extraOrigins);
29
+ const statefulMethods = STATEFUL_METHODS.get(path);
30
+ if (statefulMethods) {
31
+ const allowed = new Set(statefulMethods.split(",").map((method) => method.trim()));
32
+ if (allowed.has(request.method)) return null;
33
+ return applyCors(methodNotAllowed(statefulMethods), request, base, extraOrigins);
28
34
  }
29
35
 
30
- if (path === "/") {
31
- if (request.method !== "GET") return applyCors(methodNotAllowed("GET"), request, base, extraOrigins);
32
- return applyCors(
33
- json({ ok: true, server: serverName, version: serverVersion, mcp: `${base}/mcp` }),
34
- request,
35
- base,
36
- extraOrigins,
37
- );
38
- }
39
-
40
- return null;
41
- }
42
-
43
- export function isDurableObjectQuotaError(error: unknown): boolean {
44
- const message = error instanceof Error ? error.message : String(error ?? "");
45
- return /Exceeded allowed volume of requests in Durable Objects free tier/i.test(message)
46
- || /Durable Objects free tier/i.test(message);
36
+ let response: Response;
37
+ if (path === "/healthz") response = getOnly(request, json({ ok: true, server: identity.server, version: identity.version }));
38
+ else if (path === "/") response = getOnly(request, json({ ok: true, server: identity.server, version: identity.version, mcp: `${base}/mcp` }));
39
+ else if (path === "/.well-known/mcp.json") response = getOnly(request, json(mcpMetadata(base, identity)));
40
+ else if (AUTHORIZATION_METADATA_PATHS.has(path)) response = getOnly(request, json(authorizationServerMetadata(base, identity.server)));
41
+ else if (PROTECTED_RESOURCE_PATHS.has(path)) response = getOnly(request, json(protectedResourceMetadata(base, identity.server)));
42
+ else response = json({ error: "not_found" }, 404);
43
+ return applyCors(response, request, base, extraOrigins);
47
44
  }
48
45
 
49
- export function durableObjectQuotaResponse(request: Request, extraOrigins = ""): Response {
50
- return applyCors(
51
- json({
52
- error: "durable_object_quota_exceeded",
53
- message: "Durable Objects free-tier request volume is exhausted until the daily UTC reset.",
54
- retryable: true,
55
- }, 503),
56
- request,
57
- baseUrl(request),
58
- extraOrigins,
59
- );
46
+ function getOnly(request: Request, response: Response): Response {
47
+ return request.method === "GET" ? response : methodNotAllowed("GET");
60
48
  }
package/wrangler.jsonc CHANGED
@@ -5,6 +5,14 @@
5
5
  "compatibility_date": "2026-07-11",
6
6
  "compatibility_flags": ["nodejs_compat", "enable_request_signal", "request_signal_passthrough"],
7
7
  "workers_dev": true,
8
+ "preview_urls": false,
9
+ "ratelimits": [
10
+ {
11
+ "name": "STATEFUL_RATE_LIMITER",
12
+ "namespace_id": "4301701",
13
+ "simple": { "limit": 120, "period": 60 }
14
+ }
15
+ ],
8
16
  "durable_objects": {
9
17
  "bindings": [
10
18
  {