stables-mcp-server 2.4.0 → 2.5.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 (40) hide show
  1. package/README.md +27 -3
  2. package/build/http/handler.d.ts +9 -0
  3. package/build/http/handler.d.ts.map +1 -1
  4. package/build/http/handler.js +22 -8
  5. package/build/http/handler.js.map +1 -1
  6. package/build/lib/stables-client.d.ts +17 -1
  7. package/build/lib/stables-client.d.ts.map +1 -1
  8. package/build/lib/stables-client.js +23 -1
  9. package/build/lib/stables-client.js.map +1 -1
  10. package/build/oauth/auth0.d.ts +76 -0
  11. package/build/oauth/auth0.d.ts.map +1 -0
  12. package/build/oauth/auth0.js +163 -0
  13. package/build/oauth/auth0.js.map +1 -0
  14. package/build/oauth/handlers.d.ts +11 -0
  15. package/build/oauth/handlers.d.ts.map +1 -0
  16. package/build/oauth/handlers.js +195 -0
  17. package/build/oauth/handlers.js.map +1 -0
  18. package/build/oauth/pages.d.ts +10 -0
  19. package/build/oauth/pages.d.ts.map +1 -0
  20. package/build/oauth/pages.js +80 -0
  21. package/build/oauth/pages.js.map +1 -0
  22. package/build/oauth/provider.d.ts +21 -0
  23. package/build/oauth/provider.d.ts.map +1 -0
  24. package/build/oauth/provider.js +95 -0
  25. package/build/oauth/provider.js.map +1 -0
  26. package/build/oauth/types.d.ts +41 -0
  27. package/build/oauth/types.d.ts.map +1 -0
  28. package/build/oauth/types.js +14 -0
  29. package/build/oauth/types.js.map +1 -0
  30. package/build/tools/webhooks.d.ts +8 -0
  31. package/build/tools/webhooks.d.ts.map +1 -1
  32. package/build/tools/webhooks.js +15 -2
  33. package/build/tools/webhooks.js.map +1 -1
  34. package/build/version.d.ts +1 -1
  35. package/build/version.js +1 -1
  36. package/build/worker.d.ts +20 -7
  37. package/build/worker.d.ts.map +1 -1
  38. package/build/worker.js +35 -7
  39. package/build/worker.js.map +1 -1
  40. package/package.json +3 -2
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Auth0 as the identity step behind the hosted server's OAuth, and the
3
+ * Stables API call that turns a signed-in user into an API key.
4
+ *
5
+ * Sandbox and production are separate Auth0 organisations with separate
6
+ * dashboard API audiences, so the environment is chosen before the redirect
7
+ * to Auth0 and decides which audience is requested and which API mints the
8
+ * key. A token for the dashboard audience is what /dashboard/v1 routes
9
+ * accept, so no api-service change is needed.
10
+ */
11
+ export const API_BASE = {
12
+ sandbox: "https://api.sandbox.stables.money",
13
+ production: "https://api.stables.money",
14
+ };
15
+ /** Auth0 API identifier the dashboard uses for this environment. */
16
+ export function dashboardAudience(environment) {
17
+ return `${API_BASE[environment]}/dashboard`;
18
+ }
19
+ export function parseEnvironment(value) {
20
+ return value === "sandbox" || value === "production" ? value : null;
21
+ }
22
+ export function environmentForKey(apiKey) {
23
+ return apiKey.split("_")[1] === "live" ? "production" : "sandbox";
24
+ }
25
+ export function auth0ConfigFrom(env) {
26
+ if (!env.AUTH0_DOMAIN || !env.AUTH0_CLIENT_ID || !env.AUTH0_CLIENT_SECRET)
27
+ return null;
28
+ return {
29
+ domain: env.AUTH0_DOMAIN,
30
+ clientId: env.AUTH0_CLIENT_ID,
31
+ clientSecret: env.AUTH0_CLIENT_SECRET,
32
+ };
33
+ }
34
+ /** A failure in one step of the sign-in, shown to the user on our own page. */
35
+ export class OAuthStepError extends Error {
36
+ step;
37
+ status;
38
+ detail;
39
+ constructor(step, message, status, detail) {
40
+ super(message);
41
+ this.step = step;
42
+ this.status = status;
43
+ this.detail = detail;
44
+ this.name = "OAuthStepError";
45
+ }
46
+ }
47
+ // ---- PKCE towards Auth0 ----------------------------------------------------
48
+ function base64url(bytes) {
49
+ let binary = "";
50
+ for (const b of bytes)
51
+ binary += String.fromCharCode(b);
52
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
53
+ }
54
+ export function randomVerifier() {
55
+ return base64url(crypto.getRandomValues(new Uint8Array(32)));
56
+ }
57
+ export async function s256(verifier) {
58
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
59
+ return base64url(new Uint8Array(digest));
60
+ }
61
+ // ---- Auth0 endpoints -------------------------------------------------------
62
+ export function auth0AuthorizeUrl(config, params) {
63
+ const url = new URL(`https://${config.domain}/authorize`);
64
+ url.searchParams.set("response_type", "code");
65
+ url.searchParams.set("client_id", config.clientId);
66
+ url.searchParams.set("redirect_uri", params.redirectUri);
67
+ url.searchParams.set("scope", "openid profile email");
68
+ url.searchParams.set("audience", dashboardAudience(params.environment));
69
+ url.searchParams.set("state", params.state);
70
+ url.searchParams.set("code_challenge", params.codeChallenge);
71
+ url.searchParams.set("code_challenge_method", "S256");
72
+ return url.toString();
73
+ }
74
+ export async function exchangeAuth0Code(config, params, fetchImpl = fetch) {
75
+ const response = await fetchImpl(`https://${config.domain}/oauth/token`, {
76
+ method: "POST",
77
+ headers: { "content-type": "application/x-www-form-urlencoded" },
78
+ body: new URLSearchParams({
79
+ grant_type: "authorization_code",
80
+ client_id: config.clientId,
81
+ client_secret: config.clientSecret,
82
+ code: params.code,
83
+ redirect_uri: params.redirectUri,
84
+ code_verifier: params.verifier,
85
+ }).toString(),
86
+ });
87
+ if (!response.ok) {
88
+ const detail = await response.text().catch(() => "");
89
+ throw new OAuthStepError("auth0_token", `Sign-in could not be completed (Auth0 answered HTTP ${response.status}).`, response.status, detail);
90
+ }
91
+ const body = (await response.json());
92
+ if (!body.access_token || !body.id_token) {
93
+ throw new OAuthStepError("auth0_token", "Sign-in returned no tokens.");
94
+ }
95
+ return { access_token: body.access_token, id_token: body.id_token };
96
+ }
97
+ /**
98
+ * The ID token came straight from Auth0's token endpoint over TLS in the
99
+ * exchange above, so its claims are read without a signature check.
100
+ */
101
+ export function decodeJwtPayload(jwt) {
102
+ const parts = jwt.split(".");
103
+ if (parts.length < 2)
104
+ throw new OAuthStepError("identity", "Malformed ID token.");
105
+ const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
106
+ const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
107
+ const bytes = Uint8Array.from(atob(padded), (c) => c.charCodeAt(0));
108
+ return JSON.parse(new TextDecoder().decode(bytes));
109
+ }
110
+ export function identityFromIdToken(idToken) {
111
+ const payload = decodeJwtPayload(idToken);
112
+ const sub = typeof payload.sub === "string" ? payload.sub : "";
113
+ if (!sub)
114
+ throw new OAuthStepError("identity", "ID token has no subject.");
115
+ return {
116
+ sub,
117
+ email: typeof payload.email === "string" ? payload.email.toLowerCase() : undefined,
118
+ name: typeof payload.name === "string" ? payload.name : undefined,
119
+ orgId: typeof payload.org_id === "string" ? payload.org_id : undefined,
120
+ };
121
+ }
122
+ function explainMintFailure(status, environment) {
123
+ switch (status) {
124
+ case 401:
125
+ return `Stables did not accept the sign-in for ${environment}. Pick an organisation that exists on ${environment}, or sign in again.`;
126
+ case 403:
127
+ return "Your role in this organisation can't create API keys. Ask an organisation admin to connect, or to grant you API-key access.";
128
+ case 404:
129
+ return `This organisation isn't set up on ${environment} yet.`;
130
+ default:
131
+ return `Stables answered HTTP ${status} while creating the agent's API key. Try again in a moment.`;
132
+ }
133
+ }
134
+ export async function mintApiKey(params, fetchImpl = fetch) {
135
+ const response = await fetchImpl(`${API_BASE[params.environment]}/dashboard/v1/api-keys`, {
136
+ method: "POST",
137
+ headers: {
138
+ authorization: `Bearer ${params.accessToken}`,
139
+ "content-type": "application/json",
140
+ "idempotency-key": crypto.randomUUID(),
141
+ },
142
+ body: JSON.stringify({ name: params.name, metadata: params.metadata }),
143
+ });
144
+ if (!response.ok) {
145
+ const detail = await response.text().catch(() => "");
146
+ throw new OAuthStepError("mint_key", explainMintFailure(response.status, params.environment), response.status, detail);
147
+ }
148
+ const body = (await response.json());
149
+ if (!body.plaintextKey || !body.apiKey?.apiKeyId) {
150
+ throw new OAuthStepError("mint_key", "Stables returned no API key.");
151
+ }
152
+ return {
153
+ apiKeyId: body.apiKey.apiKeyId,
154
+ prefix: body.apiKey.prefix ?? "",
155
+ plaintextKey: body.plaintextKey,
156
+ };
157
+ }
158
+ /** API keys are listed by name in the dashboard, so say which agent and who. */
159
+ export function apiKeyName(clientName, identity) {
160
+ const who = identity.email ?? identity.sub;
161
+ return `${clientName} via MCP (${who})`.slice(0, 255);
162
+ }
163
+ //# sourceMappingURL=auth0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth0.js","sourceRoot":"","sources":["../../src/oauth/auth0.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The browser-facing half of the OAuth flow: the consent page at /authorize
3
+ * and the Auth0 return at /callback. The provider owns everything else
4
+ * (discovery, registration, the token endpoint) and hands requests without
5
+ * a valid token to handleDefault.
6
+ */
7
+ import { type OAuthEnv } from "./types.js";
8
+ /** The scopes a grant gets: what the client asked for, limited to ours, always the base scope. */
9
+ export declare function grantScope(requested: string[]): string[];
10
+ export declare function handleDefault(request: Request, env: OAuthEnv): Promise<Response>;
11
+ //# sourceMappingURL=handlers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/oauth/handlers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAuBH,OAAO,EAIL,KAAK,QAAQ,EAEd,MAAM,YAAY,CAAC;AAyBpB,kGAAkG;AAClG,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAIxD;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CA8CtF"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * The browser-facing half of the OAuth flow: the consent page at /authorize
3
+ * and the Auth0 return at /callback. The provider owns everything else
4
+ * (discovery, registration, the token endpoint) and hands requests without
5
+ * a valid token to handleDefault.
6
+ */
7
+ import { AuthorizationError, CimdFetchError, authorizationErrorRedirect, } from "@cloudflare/workers-oauth-provider";
8
+ import { MCP_PATH, handleRequest, json, withCors } from "../http/handler.js";
9
+ import { OAuthStepError, apiKeyName, auth0AuthorizeUrl, auth0ConfigFrom, exchangeAuth0Code, identityFromIdToken, mintApiKey, parseEnvironment, randomVerifier, s256, } from "./auth0.js";
10
+ import { consentPage, errorPage } from "./pages.js";
11
+ import { SCOPE, SUPPORTED_SCOPES, } from "./types.js";
12
+ function html(body, status = 200, headers) {
13
+ const h = new Headers(headers);
14
+ h.set("content-type", "text/html; charset=utf-8");
15
+ h.set("cache-control", "no-store");
16
+ return new Response(body, { status, headers: h });
17
+ }
18
+ function redirect(headers, location) {
19
+ if (location)
20
+ headers.set("Location", location);
21
+ return new Response(null, { status: 302, headers });
22
+ }
23
+ function publicOrigin(request, env) {
24
+ return (env.MCP_PUBLIC_URL ?? new URL(request.url).origin).replace(/\/$/, "");
25
+ }
26
+ /** The scopes a grant gets: what the client asked for, limited to ours, always the base scope. */
27
+ export function grantScope(requested) {
28
+ const scope = requested.filter((s) => SUPPORTED_SCOPES.includes(s));
29
+ if (!scope.includes(SCOPE))
30
+ scope.unshift(SCOPE);
31
+ return scope;
32
+ }
33
+ export async function handleDefault(request, env) {
34
+ const url = new URL(request.url);
35
+ const path = url.pathname;
36
+ if (path === "/authorize" || path === "/callback") {
37
+ const oauth = env.OAUTH_PROVIDER;
38
+ if (!oauth) {
39
+ return html(errorPage("Sign-in unavailable", "OAuth is not configured on this server."), 503);
40
+ }
41
+ try {
42
+ if (path === "/authorize" && request.method === "GET") {
43
+ return await authorizeGet(request, env, oauth);
44
+ }
45
+ if (path === "/authorize" && request.method === "POST") {
46
+ return await authorizePost(request, env, oauth);
47
+ }
48
+ if (path === "/callback" && request.method === "GET") {
49
+ return await callback(request, env, oauth);
50
+ }
51
+ return new Response("Method not allowed", { status: 405, headers: { allow: "GET, POST" } });
52
+ }
53
+ catch (error) {
54
+ return renderOAuthError(error);
55
+ }
56
+ }
57
+ // The provider sends an /mcp request here only when it carries no valid
58
+ // token. Answer with the spec's challenge so clients find the metadata.
59
+ if (path === MCP_PATH || path === `${MCP_PATH}/`) {
60
+ const origin = publicOrigin(request, env);
61
+ return withCors(json({
62
+ error: "unauthorized",
63
+ message: "Send a Stables API key as a bearer token, or connect with OAuth (see WWW-Authenticate).",
64
+ docs: "https://docs.stables.money/get-started/getting-started/quickstart/building-with-ai",
65
+ }, 401, {
66
+ "www-authenticate": `Bearer realm="OAuth", resource_metadata="${origin}/.well-known/oauth-protected-resource/mcp", scope="${SCOPE}"`,
67
+ }));
68
+ }
69
+ return handleRequest(request);
70
+ }
71
+ async function authorizeGet(request, env, oauth) {
72
+ if (!auth0ConfigFrom(env)) {
73
+ return html(errorPage("Sign-in unavailable", "This server has no identity provider configured yet."), 503);
74
+ }
75
+ const authRequest = await oauth.parseAuthRequest(request);
76
+ // describeConsent first: a failed client lookup leaves nothing in KV.
77
+ const details = await oauth.describeConsent(authRequest);
78
+ const consent = await oauth.beginConsent(authRequest);
79
+ return html(consentPage(details, consent.handle), 200, consent.headers);
80
+ }
81
+ async function authorizePost(request, env, oauth) {
82
+ const config = auth0ConfigFrom(env);
83
+ if (!config) {
84
+ return html(errorPage("Sign-in unavailable", "This server has no identity provider configured yet."), 503);
85
+ }
86
+ const form = await request.formData();
87
+ const handle = String(form.get("handle") ?? "");
88
+ if (form.get("decision") !== "approve") {
89
+ const denied = await oauth.denyConsent(request, handle);
90
+ return redirect(denied.headers);
91
+ }
92
+ const environment = parseEnvironment(form.get("environment"));
93
+ if (!environment) {
94
+ return html(errorPage("Pick an environment", "Choose Sandbox or Production and try again."), 400);
95
+ }
96
+ const approved = await oauth.approveConsent(request, handle);
97
+ const scope = grantScope(approved.request.scope);
98
+ const verifier = randomVerifier();
99
+ const data = { environment, verifier, scope };
100
+ const { state, headers } = await oauth.beginUpstream(approved.request, {
101
+ data,
102
+ headers: approved.headers,
103
+ });
104
+ const location = auth0AuthorizeUrl(config, {
105
+ redirectUri: `${publicOrigin(request, env)}/callback`,
106
+ environment,
107
+ state,
108
+ codeChallenge: await s256(verifier),
109
+ });
110
+ return redirect(headers, location);
111
+ }
112
+ async function callback(request, env, oauth) {
113
+ const config = auth0ConfigFrom(env);
114
+ if (!config) {
115
+ return html(errorPage("Sign-in unavailable", "This server has no identity provider configured yet."), 503);
116
+ }
117
+ const { request: original, data, headers } = await oauth.finishUpstream(request);
118
+ const params = new URL(request.url).searchParams;
119
+ if (params.get("error")) {
120
+ return redirect(headers, authorizationErrorRedirect(original, "access_denied"));
121
+ }
122
+ const code = params.get("code");
123
+ if (!code)
124
+ throw new OAuthStepError("callback", "Auth0 sent the user back without a code.");
125
+ const origin = publicOrigin(request, env);
126
+ const tokens = await exchangeAuth0Code(config, {
127
+ code,
128
+ redirectUri: `${origin}/callback`,
129
+ verifier: data.verifier,
130
+ });
131
+ const identity = identityFromIdToken(tokens.id_token);
132
+ const clientName = (await clientNameFor(oauth, original)) ?? "MCP client";
133
+ const minted = await mintApiKey({
134
+ environment: data.environment,
135
+ accessToken: tokens.access_token,
136
+ name: apiKeyName(clientName, identity),
137
+ metadata: {
138
+ source: "mcp_oauth",
139
+ client: clientName,
140
+ user: identity.email ?? identity.sub,
141
+ },
142
+ });
143
+ const props = {
144
+ apiKey: minted.plaintextKey,
145
+ apiKeyId: minted.apiKeyId,
146
+ environment: data.environment,
147
+ email: identity.email,
148
+ orgId: identity.orgId,
149
+ clientName,
150
+ };
151
+ const { redirectTo } = await oauth.completeAuthorization({
152
+ request: original,
153
+ // One grant per user, organisation and environment, so re-authorising
154
+ // sandbox never revokes a production grant for the same person.
155
+ userId: [data.environment, identity.orgId ?? "-", identity.sub].join("|").replace(/:/g, "_"),
156
+ metadata: {
157
+ environment: data.environment,
158
+ client: clientName,
159
+ email: identity.email,
160
+ orgId: identity.orgId,
161
+ apiKeyId: minted.apiKeyId,
162
+ keyPrefix: minted.prefix,
163
+ },
164
+ scope: data.scope,
165
+ props,
166
+ });
167
+ return redirect(headers, redirectTo);
168
+ }
169
+ async function clientNameFor(oauth, authRequest) {
170
+ try {
171
+ const client = await oauth.lookupClient(authRequest.clientId);
172
+ const name = client?.clientName?.trim();
173
+ return name ? name.slice(0, 80) : null;
174
+ }
175
+ catch {
176
+ return null;
177
+ }
178
+ }
179
+ function renderOAuthError(error) {
180
+ if (error instanceof AuthorizationError && error.redirectTo) {
181
+ return Response.redirect(error.redirectTo, 302);
182
+ }
183
+ if (error instanceof AuthorizationError) {
184
+ return html(errorPage("Connection request expired", error.description ||
185
+ "This page was already used, opened in another browser, or has expired."), 400);
186
+ }
187
+ if (error instanceof CimdFetchError) {
188
+ return html(errorPage("App could not be verified", "The app's identity document could not be fetched."), 400);
189
+ }
190
+ if (error instanceof OAuthStepError) {
191
+ return html(errorPage("Connection failed", error.message), error.status === 403 ? 403 : 400);
192
+ }
193
+ throw error;
194
+ }
195
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/oauth/handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * The two pages the OAuth flow shows in a browser: consent and error.
3
+ * Everything that came from the client (its name, domain, scopes) is
4
+ * escaped, since a dynamically registered client chooses those strings.
5
+ */
6
+ import type { ConsentDescription } from "@cloudflare/workers-oauth-provider";
7
+ export declare function escapeHtml(value: string): string;
8
+ export declare function consentPage(details: ConsentDescription, handle: string): string;
9
+ export declare function errorPage(title: string, message: string, hint?: string): string;
10
+ //# sourceMappingURL=pages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.d.ts","sourceRoot":"","sources":["../../src/oauth/pages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAE7E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEhD;AA0CD,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CA6B/E;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAQ/E"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The two pages the OAuth flow shows in a browser: consent and error.
3
+ * Everything that came from the client (its name, domain, scopes) is
4
+ * escaped, since a dynamically registered client chooses those strings.
5
+ */
6
+ export function escapeHtml(value) {
7
+ return value.replace(/[&<>"']/g, (char) => `&#${char.charCodeAt(0)};`);
8
+ }
9
+ const STYLE = `
10
+ :root { color-scheme: light; }
11
+ body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: #f4f3f8;
12
+ font: 16px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, sans-serif; color: #17151f; }
13
+ main { width: min(440px, calc(100vw - 32px)); background: #fff; border-radius: 16px;
14
+ padding: 32px 28px; box-shadow: 0 8px 32px rgba(23, 21, 31, 0.08); }
15
+ h1 { font-size: 22px; margin: 0 0 8px; }
16
+ p { margin: 0 0 14px; color: #4a4759; }
17
+ .brand { font-weight: 700; letter-spacing: 0.02em; color: #5b3df5; margin-bottom: 18px; font-size: 14px; text-transform: uppercase; }
18
+ .warn { background: #fff7e6; border: 1px solid #f5d58b; border-radius: 10px; padding: 10px 12px; color: #6b4b00; }
19
+ fieldset { border: 1px solid #e3e1ec; border-radius: 12px; padding: 12px 14px; margin: 18px 0; }
20
+ legend { padding: 0 6px; font-weight: 600; font-size: 14px; }
21
+ label.env { display: flex; gap: 10px; align-items: flex-start; padding: 8px 0; cursor: pointer; }
22
+ label.env input { margin-top: 5px; }
23
+ label.env small { display: block; color: #6c6880; }
24
+ .actions { display: flex; gap: 10px; margin-top: 8px; }
25
+ button { flex: 1; padding: 12px 16px; border-radius: 10px; border: 1px solid #d9d6e6; font: inherit; font-weight: 600; cursor: pointer; background: #fff; }
26
+ button.primary { background: #5b3df5; border-color: #5b3df5; color: #fff; }
27
+ code { background: #f1f0f7; padding: 1px 6px; border-radius: 6px; font-size: 14px; }
28
+ `;
29
+ function layout(title, body) {
30
+ return `<!doctype html>
31
+ <html lang="en">
32
+ <head>
33
+ <meta charset="utf-8">
34
+ <meta name="viewport" content="width=device-width, initial-scale=1">
35
+ <meta name="robots" content="noindex">
36
+ <title>${escapeHtml(title)}</title>
37
+ <style>${STYLE}</style>
38
+ </head>
39
+ <body>
40
+ <main>
41
+ <div class="brand">Stables</div>
42
+ ${body}
43
+ </main>
44
+ </body>
45
+ </html>`;
46
+ }
47
+ export function consentPage(details, handle) {
48
+ const name = escapeHtml(details.clientName);
49
+ const origin = details.clientDomain
50
+ ? `Published by <strong>${escapeHtml(details.clientDomain)}</strong>.`
51
+ : "This app registered itself, so its name is not verified.";
52
+ const loopback = details.redirectIsLoopback
53
+ ? `<p class="warn"><strong>This sends access to an app on your computer.</strong> Continue only if you just started connecting from it.</p>`
54
+ : "";
55
+ return layout(`Connect ${details.clientName} to Stables`, `<h1>Connect ${name} to Stables?</h1>
56
+ <p>${origin} Access will be sent to <strong>${escapeHtml(details.redirectHost)}</strong>.</p>
57
+ ${loopback}
58
+ <p>You'll sign in with your Stables dashboard account. Allowing creates an API key named after ${name} in the organisation you choose; revoke it any time under Developer → API keys.</p>
59
+ <form method="post">
60
+ <input type="hidden" name="handle" value="${escapeHtml(handle)}">
61
+ <fieldset>
62
+ <legend>Environment</legend>
63
+ <label class="env"><input type="radio" name="environment" value="sandbox" checked>
64
+ <span>Sandbox<small>Test data, no real money. Keys start with <code>sti_test_</code>.</small></span></label>
65
+ <label class="env"><input type="radio" name="environment" value="production">
66
+ <span>Production<small>Live organisation. The agent can move real money.</small></span></label>
67
+ </fieldset>
68
+ <div class="actions">
69
+ <button type="submit" name="decision" value="deny">Cancel</button>
70
+ <button type="submit" name="decision" value="approve" class="primary">Allow and sign in</button>
71
+ </div>
72
+ </form>`);
73
+ }
74
+ export function errorPage(title, message, hint) {
75
+ return layout(title, `<h1>${escapeHtml(title)}</h1>
76
+ <p>${escapeHtml(message)}</p>
77
+ ${hint ? `<p>${escapeHtml(hint)}</p>` : ""}
78
+ <p>Go back to the app that sent you here and try connecting again.</p>`);
79
+ }
80
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pages.js","sourceRoot":"","sources":["../../src/oauth/pages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;CAmBb,CAAC;AAEF,SAAS,MAAM,CAAC,KAAa,EAAE,IAAY;IACzC,OAAO;;;;;;SAMA,UAAU,CAAC,KAAK,CAAC;SACjB,KAAK;;;;;EAKZ,IAAI;;;QAGE,CAAC;AACT,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAA2B,EAAE,MAAc;IACrE,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY;QACjC,CAAC,CAAC,wBAAwB,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC,YAAY;QACtE,CAAC,CAAC,0DAA0D,CAAC;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,kBAAkB;QACzC,CAAC,CAAC,0IAA0I;QAC5I,CAAC,CAAC,EAAE,CAAC;IACP,OAAO,MAAM,CACX,WAAW,OAAO,CAAC,UAAU,aAAa,EAC1C,eAAe,IAAI;KAClB,MAAM,mCAAmC,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC;EAC5E,QAAQ;iGACuF,IAAI;;8CAEvD,UAAU,CAAC,MAAM,CAAC;;;;;;;;;;;;QAYxD,CACL,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,OAAe,EAAE,IAAa;IACrE,OAAO,MAAM,CACX,KAAK,EACL,OAAO,UAAU,CAAC,KAAK,CAAC;KACvB,UAAU,CAAC,OAAO,CAAC;EACtB,IAAI,CAAC,CAAC,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;uEAC6B,CACpE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The OAuth provider the Worker runs: authorization server and protected
3
+ * resource in one, as @cloudflare/workers-oauth-provider's OAuthProvider.
4
+ *
5
+ * - /mcp is the protected resource. A grant's props carry the Stables API key
6
+ * minted at sign-in, and the request is served with it.
7
+ * - A plain Stables API key sent as a bearer token keeps working through
8
+ * resolveExternalToken, so coding agents configured with a key need no
9
+ * OAuth.
10
+ * - /.well-known/oauth-protected-resource/mcp, /.well-known/oauth-authorization-server,
11
+ * /oauth/token and /oauth/register are the provider's; /authorize and
12
+ * /callback are ours (handlers.ts).
13
+ */
14
+ import { OAuthProvider } from "@cloudflare/workers-oauth-provider";
15
+ import { StablesApiClient } from "../lib/stables-client.js";
16
+ import { type OAuthEnv } from "./types.js";
17
+ export declare function resourceFor(origin: string): string;
18
+ export declare function apiClientFor(apiKey: string, env: OAuthEnv): StablesApiClient | null;
19
+ /** One provider per public origin; metadata and token audiences are bound to it. */
20
+ export declare function providerFor(origin: string): OAuthProvider<OAuthEnv>;
21
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/oauth/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAsB,aAAa,EAAE,MAAM,oCAAoC,CAAC;AAGvF,OAAO,EAAE,gBAAgB,EAAgB,MAAM,0BAA0B,CAAC;AAG1E,OAAO,EAA4C,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAErF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,gBAAgB,GAAG,IAAI,CAOnF;AAyBD,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAiDnE"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * The OAuth provider the Worker runs: authorization server and protected
3
+ * resource in one, as @cloudflare/workers-oauth-provider's OAuthProvider.
4
+ *
5
+ * - /mcp is the protected resource. A grant's props carry the Stables API key
6
+ * minted at sign-in, and the request is served with it.
7
+ * - A plain Stables API key sent as a bearer token keeps working through
8
+ * resolveExternalToken, so coding agents configured with a key need no
9
+ * OAuth.
10
+ * - /.well-known/oauth-protected-resource/mcp, /.well-known/oauth-authorization-server,
11
+ * /oauth/token and /oauth/register are the provider's; /authorize and
12
+ * /callback are ours (handlers.ts).
13
+ */
14
+ import { ExternalTokenError, OAuthProvider } from "@cloudflare/workers-oauth-provider";
15
+ import { SERVER_VERSION } from "../version.js";
16
+ import { serveMcp, withCors, json } from "../http/handler.js";
17
+ import { StablesApiClient, apiUrlForKey } from "../lib/stables-client.js";
18
+ import { environmentForKey } from "./auth0.js";
19
+ import { handleDefault } from "./handlers.js";
20
+ import { SCOPE, SUPPORTED_SCOPES } from "./types.js";
21
+ export function resourceFor(origin) {
22
+ return `${origin}/mcp`;
23
+ }
24
+ export function apiClientFor(apiKey, env) {
25
+ const baseUrl = apiUrlForKey(apiKey);
26
+ if (!baseUrl)
27
+ return null;
28
+ return new StablesApiClient(apiKey, baseUrl, {
29
+ gatewaySigningSecret: env.MCP_GATEWAY_SIGNING_SECRET,
30
+ userAgent: `stables-mcp-server/${SERVER_VERSION} (hosted)`,
31
+ });
32
+ }
33
+ async function serveAuthorizedMcp(request, env, props) {
34
+ const client = props?.apiKey ? apiClientFor(props.apiKey, env) : null;
35
+ if (!client) {
36
+ return withCors(json({
37
+ error: "invalid_grant",
38
+ message: "This connection carries no usable Stables API key. Disconnect and connect again.",
39
+ }, 401));
40
+ }
41
+ return withCors(await serveMcp(request, client));
42
+ }
43
+ const providers = new Map();
44
+ /** One provider per public origin; metadata and token audiences are bound to it. */
45
+ export function providerFor(origin) {
46
+ const cached = providers.get(origin);
47
+ if (cached)
48
+ return cached;
49
+ const resource = resourceFor(origin);
50
+ const provider = new OAuthProvider({
51
+ apiRoute: "/mcp",
52
+ apiHandler: {
53
+ fetch(request, env, ctx) {
54
+ const props = ctx.props;
55
+ return serveAuthorizedMcp(request, env, props);
56
+ },
57
+ },
58
+ defaultHandler: {
59
+ fetch(request, env) {
60
+ return handleDefault(request, env);
61
+ },
62
+ },
63
+ authorizeEndpoint: "/authorize",
64
+ tokenEndpoint: "/oauth/token",
65
+ // DCR stays on for clients without CIMD support; MCP prefers CIMD.
66
+ clientRegistrationEndpoint: "/oauth/register",
67
+ clientIdMetadataDocumentEnabled: true,
68
+ scopesSupported: SUPPORTED_SCOPES,
69
+ requiredScopes: [SCOPE],
70
+ resourceMetadata: {
71
+ resource,
72
+ authorization_servers: [origin],
73
+ resource_name: "Stables MCP server",
74
+ },
75
+ accessTokenTTL: 60 * 60,
76
+ refreshTokenTTL: 30 * 24 * 60 * 60,
77
+ // A Stables API key presented directly. Sandbox keys go to sandbox and
78
+ // live keys to production, as the stdio server does.
79
+ resolveExternalToken: async ({ token }) => {
80
+ if (!token.startsWith("sti_"))
81
+ return null;
82
+ if (!apiUrlForKey(token)) {
83
+ throw new ExternalTokenError("invalid_token", {
84
+ description: "Unrecognised API key. Send a sti_test_ (sandbox) or sti_live_ (production) key, or connect with OAuth.",
85
+ statusCode: 401,
86
+ });
87
+ }
88
+ const props = { apiKey: token, environment: environmentForKey(token) };
89
+ return { props, audience: resource };
90
+ },
91
+ });
92
+ providers.set(origin, provider);
93
+ return provider;
94
+ }
95
+ //# sourceMappingURL=provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/oauth/provider.ts"],"names":[],"mappings":"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"}