@softure-ai/mcp-access 0.1.7 → 0.1.8
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/CHANGELOG.md +16 -0
- package/README.md +39 -3
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/next/actions.d.ts.map +1 -1
- package/dist/next/actions.js +4 -2
- package/dist/next/actions.js.map +1 -1
- package/dist/next/context.d.ts +7 -1
- package/dist/next/context.d.ts.map +1 -1
- package/dist/next/context.js +12 -10
- package/dist/next/context.js.map +1 -1
- package/dist/next/index.d.ts +1 -0
- package/dist/next/index.d.ts.map +1 -1
- package/dist/next/index.js +1 -0
- package/dist/next/index.js.map +1 -1
- package/dist/next/oauth-page.d.ts.map +1 -1
- package/dist/next/oauth-page.js +3 -2
- package/dist/next/oauth-page.js.map +1 -1
- package/dist/next/oauth-routes.d.ts +5 -2
- package/dist/next/oauth-routes.d.ts.map +1 -1
- package/dist/next/oauth-routes.js +9 -6
- package/dist/next/oauth-routes.js.map +1 -1
- package/dist/options.d.ts +24 -0
- package/dist/options.d.ts.map +1 -1
- package/dist/options.js +66 -0
- package/dist/options.js.map +1 -1
- package/dist/origins.d.ts +27 -0
- package/dist/origins.d.ts.map +1 -0
- package/dist/origins.js +32 -0
- package/dist/origins.js.map +1 -0
- package/dist/server/context.d.ts +4 -0
- package/dist/server/context.d.ts.map +1 -0
- package/dist/server/context.js +13 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/endpoint.d.ts.map +1 -1
- package/dist/server/endpoint.js +5 -3
- package/dist/server/endpoint.js.map +1 -1
- package/dist/server/index.d.ts +5 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +4 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/oauth-http.d.ts +30 -16
- package/dist/server/oauth-http.d.ts.map +1 -1
- package/dist/server/oauth-http.js +64 -41
- package/dist/server/oauth-http.js.map +1 -1
- package/dist/server/options.d.ts +3 -2
- package/dist/server/options.d.ts.map +1 -1
- package/dist/server/options.js +3 -3
- package/dist/server/options.js.map +1 -1
- package/dist/server/origins.d.ts +8 -0
- package/dist/server/origins.d.ts.map +1 -0
- package/dist/server/origins.js +15 -0
- package/dist/server/origins.js.map +1 -0
- package/module.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +7 -2
- package/src/next/actions.ts +4 -2
- package/src/next/context.ts +15 -8
- package/src/next/index.ts +1 -0
- package/src/next/oauth-page.tsx +3 -2
- package/src/next/oauth-routes.ts +13 -5
- package/src/options.ts +75 -0
- package/src/origins.ts +48 -0
- package/src/server/context.ts +14 -0
- package/src/server/endpoint.ts +5 -3
- package/src/server/index.ts +6 -0
- package/src/server/oauth-http.ts +90 -41
- package/src/server/options.ts +4 -3
- package/src/server/origins.ts +19 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** What the origin helpers read from a request: a Web `Request`, or Next's `headers()` with a URL. */
|
|
2
|
+
export interface McpOriginRequest {
|
|
3
|
+
readonly url: string;
|
|
4
|
+
readonly headers: {
|
|
5
|
+
get(name: string): string | null;
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export interface McpOrigins {
|
|
9
|
+
/** The issuer, and the origin of every endpoint, of `resource_metadata` and of the consent form. */
|
|
10
|
+
readonly appOrigin: string;
|
|
11
|
+
/** Other public hosts (`resourceOrigins`), each a bare origin. */
|
|
12
|
+
readonly resourceOrigins: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
/** The host the request was sent to: `Host`, else the URL's host. Behind a proxy the URL names the server's own. */
|
|
15
|
+
export declare function readRequestHost(request: McpOriginRequest): string;
|
|
16
|
+
/**
|
|
17
|
+
* The origin the request was sent to: the first `X-Forwarded-Proto` value when it is http or https
|
|
18
|
+
* (else the URL's scheme) and `Host` (else the URL's host). For `resolveAppOrigin` in an app whose
|
|
19
|
+
* proxy passes `Host` through and refuses hosts it does not serve.
|
|
20
|
+
*/
|
|
21
|
+
export declare function readRequestOrigin(request: McpOriginRequest): string;
|
|
22
|
+
/**
|
|
23
|
+
* The resource origin the request was sent to, when its host is one of `resourceOrigins`; when two
|
|
24
|
+
* share the host, the one whose scheme `X-Forwarded-Proto` names wins, else the first. Null otherwise.
|
|
25
|
+
*/
|
|
26
|
+
export declare function findServedResourceOrigin(origins: McpOrigins, request: McpOriginRequest): string | null;
|
|
27
|
+
//# sourceMappingURL=origins.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origins.d.ts","sourceRoot":"","sources":["../src/origins.ts"],"names":[],"mappings":"AAGA,sGAAsG;AACtG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE;QAAE,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACxD;AAED,MAAM,WAAW,UAAU;IACzB,oGAAoG;IACpG,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;CAC7C;AAOD,oHAAoH;AACpH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAEjE;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAKnE;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,gBAAgB,GAAG,MAAM,GAAG,IAAI,CAKtG"}
|
package/dist/origins.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// The origins one request is served under: the app origin every OAuth URL is built from, and the
|
|
2
|
+
// other public hosts whose root may be named as a protected resource. Pure: no config, no database.
|
|
3
|
+
function firstValue(header) {
|
|
4
|
+
const value = header?.split(",")[0]?.trim().toLowerCase();
|
|
5
|
+
return value === undefined || value === "" ? null : value;
|
|
6
|
+
}
|
|
7
|
+
/** The host the request was sent to: `Host`, else the URL's host. Behind a proxy the URL names the server's own. */
|
|
8
|
+
export function readRequestHost(request) {
|
|
9
|
+
return firstValue(request.headers.get("host")) ?? new URL(request.url).host;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The origin the request was sent to: the first `X-Forwarded-Proto` value when it is http or https
|
|
13
|
+
* (else the URL's scheme) and `Host` (else the URL's host). For `resolveAppOrigin` in an app whose
|
|
14
|
+
* proxy passes `Host` through and refuses hosts it does not serve.
|
|
15
|
+
*/
|
|
16
|
+
export function readRequestOrigin(request) {
|
|
17
|
+
const url = new URL(request.url);
|
|
18
|
+
const proto = firstValue(request.headers.get("x-forwarded-proto"));
|
|
19
|
+
const scheme = proto === "http" || proto === "https" ? proto : url.protocol.replace(/:$/, "");
|
|
20
|
+
return new URL(`${scheme}://${readRequestHost(request)}`).origin;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The resource origin the request was sent to, when its host is one of `resourceOrigins`; when two
|
|
24
|
+
* share the host, the one whose scheme `X-Forwarded-Proto` names wins, else the first. Null otherwise.
|
|
25
|
+
*/
|
|
26
|
+
export function findServedResourceOrigin(origins, request) {
|
|
27
|
+
const host = readRequestHost(request);
|
|
28
|
+
const candidates = origins.resourceOrigins.filter((origin) => new URL(origin).host === host);
|
|
29
|
+
const proto = firstValue(request.headers.get("x-forwarded-proto"));
|
|
30
|
+
return candidates.find((origin) => new URL(origin).protocol === `${proto ?? ""}:`) ?? candidates[0] ?? null;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=origins.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origins.js","sourceRoot":"","sources":["../src/origins.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,oGAAoG;AAepG,SAAS,UAAU,CAAC,MAAqB;IACvC,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC1D,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AAC5D,CAAC;AAED,oHAAoH;AACpH,MAAM,UAAU,eAAe,CAAC,OAAyB;IACvD,OAAO,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAyB;IACzD,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACjC,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC9F,OAAO,IAAI,GAAG,CAAC,GAAG,MAAM,MAAM,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;AACnE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAAmB,EAAE,OAAyB;IACrF,MAAM,IAAI,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC7F,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC,CAAC;IACnE,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,KAAK,GAAG,KAAK,IAAI,EAAE,GAAG,CAAC,IAAI,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AAC9G,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEpD,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAM7F"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// The module context (`{ db, clock, config }`) from the configuration: the process-wide database
|
|
2
|
+
// handle and the wall clock. For apps that compose their own routes from `/server`; the `/next`
|
|
3
|
+
// adapter builds its context with it.
|
|
4
|
+
import { systemClock } from "@softure-ai/core";
|
|
5
|
+
import { getConfiguredDatabase } from "@softure-ai/db";
|
|
6
|
+
export async function createMcpAccessContext(config) {
|
|
7
|
+
if (config.database === null) {
|
|
8
|
+
throw new Error("@softure-ai/mcp-access: softure.config.ts has no database; mcp-access needs one");
|
|
9
|
+
}
|
|
10
|
+
const { db } = await getConfiguredDatabase(config.database);
|
|
11
|
+
return { db, clock: systemClock, config };
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/server/context.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,gGAAgG;AAChG,sCAAsC;AACtC,OAAO,EAAE,WAAW,EAAsB,MAAM,kBAAkB,CAAC;AACnE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAGvD,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAAC,MAAqB;IAChE,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,iFAAiF,CAAC,CAAC;IACrG,CAAC;IACD,MAAM,EAAE,EAAE,EAAE,GAAG,MAAM,qBAAqB,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IAC5D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC;AAC5C,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../../src/server/endpoint.ts"],"names":[],"mappings":"AAKA,OAAO,EAML,KAAK,SAAS,EACd,KAAK,MAAM,EACZ,MAAM,8BAA8B,CAAC;AAGtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"endpoint.d.ts","sourceRoot":"","sources":["../../src/server/endpoint.ts"],"names":[],"mappings":"AAKA,OAAO,EAML,KAAK,SAAS,EACd,KAAK,MAAM,EACZ,MAAM,8BAA8B,CAAC;AAGtC,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAGxD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEvE,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC;AAE3C,8FAA8F;AAC9F,eAAO,MAAM,qBAAqB,QAAQ,CAAC;AAE3C,6FAA6F;AAC7F,eAAO,MAAM,qBAAqB,QAAc,CAAC;AAOjD,mGAAmG;AACnG,MAAM,MAAM,gBAAgB,GAAG,CAAC,QAAQ,EAAE,iBAAiB,KAAK,SAAS,GAAG,MAAM,GAAG,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC,CAAC;AAEjH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC;CACzC;AAED,8CAA8C;AAC9C,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,gBAAgB,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEzF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,YAAY,EAAE,EAAE,kBAAkB,GAAG,WAAW,CAmCnF"}
|
package/dist/server/endpoint.js
CHANGED
|
@@ -7,6 +7,7 @@ import { bearerAuthChallengeResponse, createMcpHandler, OAuthError, OAuthErrorCo
|
|
|
7
7
|
import { errorLogLabel, getModule } from "@softure-ai/core";
|
|
8
8
|
import { consumeRateLimit, identifyClient } from "@softure-ai/security/server";
|
|
9
9
|
import { getProtectedResourceMetadataUrl, isOAuthEnabled } from "./oauth-http.js";
|
|
10
|
+
import { resolveMcpOrigins } from "./origins.js";
|
|
10
11
|
import { MCP_READ_SCOPE, MCP_WRITE_SCOPE } from "./scopes.js";
|
|
11
12
|
import { verifyAccessToken } from "./tokens.js";
|
|
12
13
|
export { MCP_READ_SCOPE, MCP_WRITE_SCOPE };
|
|
@@ -48,7 +49,7 @@ export function createMcpEndpoint({ createServer }) {
|
|
|
48
49
|
if (!limit.ok) {
|
|
49
50
|
return Response.json({ error: "too_many_requests" }, { status: 429, headers: { ...NO_STORE, "retry-after": String(limit.retryAfterSeconds) } });
|
|
50
51
|
}
|
|
51
|
-
const authInfo = await authenticate(ctx, request
|
|
52
|
+
const authInfo = await authenticate(ctx, request);
|
|
52
53
|
if (authInfo instanceof Response)
|
|
53
54
|
return authInfo;
|
|
54
55
|
return handler.fetch(request, { authInfo });
|
|
@@ -61,9 +62,10 @@ export function createMcpEndpoint({ createServer }) {
|
|
|
61
62
|
* names the protected resource metadata (RFC 9728 §5.1): that is how claude.ai and ChatGPT find
|
|
62
63
|
* the authorization server.
|
|
63
64
|
*/
|
|
64
|
-
async function authenticate(ctx,
|
|
65
|
+
async function authenticate(ctx, request) {
|
|
66
|
+
const header = request.headers.get("authorization");
|
|
65
67
|
const challenge = isOAuthEnabled(ctx.config)
|
|
66
|
-
? { requiredScopes: [MCP_READ_SCOPE], resourceMetadataUrl: getProtectedResourceMetadataUrl(ctx.config) }
|
|
68
|
+
? { requiredScopes: [MCP_READ_SCOPE], resourceMetadataUrl: getProtectedResourceMetadataUrl(ctx.config, resolveMcpOrigins(ctx.config, request)) }
|
|
67
69
|
: { requiredScopes: [MCP_READ_SCOPE] };
|
|
68
70
|
if (header === null || !BEARER_PREFIX.test(header)) {
|
|
69
71
|
return bearerAuthChallengeResponse(new OAuthError(OAuthErrorCode.InvalidToken, "Missing Bearer token"), challenge);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../../src/server/endpoint.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"endpoint.js","sourceRoot":"","sources":["../../src/server/endpoint.ts"],"names":[],"mappings":"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"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export { createMcpEndpoint, MAX_MCP_REQUEST_BYTES, MCP_RATE_LIMIT_BUCKET, MCP_READ_SCOPE, MCP_WRITE_SCOPE, type McpEndpoint, type McpEndpointOptions, type McpServerFactory, } from "./endpoint.js";
|
|
2
|
+
export { createMcpAccessContext } from "./context.js";
|
|
2
3
|
export { checkAccessTokensTable } from "./health.js";
|
|
3
4
|
export { CONSENT_CODE_PREFIX, CLIENT_SECRET_PREFIX, createAuthorizationCode, exchangeAuthorizationCode, findOAuthClient, isClientSecretValid, listOAuthGrants, pruneOAuthRecords, REFRESH_TOKEN_PREFIX, refreshMcpGrant, registerMcpClient, revokeOAuthGrant, type CreateAuthorizationCodeInput, type ExchangeAuthorizationCodeInput, type IssuedOAuthTokens, type OAuthClientRegistration, type PrunedOAuthRecords, type RefreshOAuthGrantInput, type RegisteredOAuthClient, type RevokeOAuthGrantInput, } from "./oauth.js";
|
|
4
|
-
export { answerOAuthPreflight, AUTHORIZATION_PARAMS, AUTHORIZATION_SERVER_METADATA_PATH, buildClientRedirect, getAuthorizationServerMetadata, getConsentPath, getOAuthIssuer, getProtectedResourceMetadata, getProtectedResourceMetadataUrl, handleAuthorizationDecision, handleClientRegistration, handleTokenRequest, isAcceptableResource, isOAuthEnabled, isWriteScopeRequested, MCP_OAUTH_RATE_LIMIT_BUCKET, OAUTH_SCOPES, PROTECTED_RESOURCE_METADATA_PATH, readAuthorizationParams, serveDiscoveryDocument, validateAuthorizationRequest, type AuthorizationOutcome, type AuthorizationPageError, type AuthorizationRequest, } from "./oauth-http.js";
|
|
5
|
+
export { answerOAuthPreflight, AUTHORIZATION_PARAMS, AUTHORIZATION_SERVER_METADATA_PATH, buildClientRedirect, getAuthorizationServerMetadata, getConsentPath, getOAuthIssuer, getProtectedResourceMetadata, getProtectedResourceMetadataUrl, getRootProtectedResourceMetadata, handleAuthorizationDecision, handleClientRegistration, handleTokenRequest, isAcceptableResource, isOAuthEnabled, isWriteScopeRequested, MCP_OAUTH_RATE_LIMIT_BUCKET, OAUTH_SCOPES, PROTECTED_RESOURCE_METADATA_PATH, readAuthorizationParams, serveDiscoveryDocument, validateAuthorizationRequest, type AuthorizationOutcome, type AuthorizationPageError, type AuthorizationRequest, type DiscoveryDocument, } from "./oauth-http.js";
|
|
6
|
+
export { findServedResourceOrigin, readRequestHost, readRequestOrigin, type McpOriginRequest, type McpOrigins } from "../origins.js";
|
|
7
|
+
export { resolveMcpOrigins } from "./origins.js";
|
|
8
|
+
export type { OAuthClientRow, OAuthTokenEndpointAuthMethod } from "../schema.js";
|
|
5
9
|
export { describeRedirectUri, isAllowedRedirectUri, MAX_CLIENT_NAME_LENGTH, parseClientRegistration, type ClientRegistrationResult, type RegistrationError } from "./oauth-validation.js";
|
|
6
10
|
export { getCodeChallenge, isPkceVerifierValid, isValidCodeChallenge, PKCE_METHOD } from "./pkce.js";
|
|
7
11
|
export { getMcpAccessMessages, getMcpAccessOptions, getMcpAccessRoutes, getMcpEndpointUrl, type McpAccessRoutes } from "./options.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,yBAAyB,EACzB,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,4BAA4B,EACjC,KAAK,8BAA8B,EACnC,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,kCAAkC,EAClC,mBAAmB,EACnB,8BAA8B,EAC9B,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,+BAA+B,EAC/B,2BAA2B,EAC3B,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,2BAA2B,EAC3B,YAAY,EACZ,gCAAgC,EAChC,uBAAuB,EACvB,sBAAsB,EACtB,4BAA4B,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,EACd,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,yBAAyB,EACzB,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,4BAA4B,EACjC,KAAK,8BAA8B,EACnC,KAAK,iBAAiB,EACtB,KAAK,uBAAuB,EAC5B,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,GAC3B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,kCAAkC,EAClC,mBAAmB,EACnB,8BAA8B,EAC9B,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,+BAA+B,EAC/B,gCAAgC,EAChC,2BAA2B,EAC3B,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,2BAA2B,EAC3B,YAAY,EACZ,gCAAgC,EAChC,uBAAuB,EACvB,sBAAsB,EACtB,4BAA4B,EAC5B,KAAK,oBAAoB,EACzB,KAAK,sBAAsB,EAC3B,KAAK,oBAAoB,EACzB,KAAK,iBAAiB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AACrI,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,4BAA4B,EAAE,MAAM,cAAc,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,uBAAuB,EAAE,KAAK,wBAAwB,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1L,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACrG,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AACtI,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,EAC3B,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,EACjB,KAAK,qBAAqB,EAC1B,KAAK,sBAAsB,EAC3B,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC3B,KAAK,mBAAmB,GACzB,MAAM,aAAa,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// Server-only API of @softure-ai/mcp-access. Every function receives the module context
|
|
2
2
|
// (`{ db, clock, config }`) and never reads request scope; the endpoint takes a Web `Request`.
|
|
3
3
|
export { createMcpEndpoint, MAX_MCP_REQUEST_BYTES, MCP_RATE_LIMIT_BUCKET, MCP_READ_SCOPE, MCP_WRITE_SCOPE, } from "./endpoint.js";
|
|
4
|
+
export { createMcpAccessContext } from "./context.js";
|
|
4
5
|
export { checkAccessTokensTable } from "./health.js";
|
|
5
6
|
export { CONSENT_CODE_PREFIX, CLIENT_SECRET_PREFIX, createAuthorizationCode, exchangeAuthorizationCode, findOAuthClient, isClientSecretValid, listOAuthGrants, pruneOAuthRecords, REFRESH_TOKEN_PREFIX, refreshMcpGrant, registerMcpClient, revokeOAuthGrant, } from "./oauth.js";
|
|
6
|
-
export { answerOAuthPreflight, AUTHORIZATION_PARAMS, AUTHORIZATION_SERVER_METADATA_PATH, buildClientRedirect, getAuthorizationServerMetadata, getConsentPath, getOAuthIssuer, getProtectedResourceMetadata, getProtectedResourceMetadataUrl, handleAuthorizationDecision, handleClientRegistration, handleTokenRequest, isAcceptableResource, isOAuthEnabled, isWriteScopeRequested, MCP_OAUTH_RATE_LIMIT_BUCKET, OAUTH_SCOPES, PROTECTED_RESOURCE_METADATA_PATH, readAuthorizationParams, serveDiscoveryDocument, validateAuthorizationRequest, } from "./oauth-http.js";
|
|
7
|
+
export { answerOAuthPreflight, AUTHORIZATION_PARAMS, AUTHORIZATION_SERVER_METADATA_PATH, buildClientRedirect, getAuthorizationServerMetadata, getConsentPath, getOAuthIssuer, getProtectedResourceMetadata, getProtectedResourceMetadataUrl, getRootProtectedResourceMetadata, handleAuthorizationDecision, handleClientRegistration, handleTokenRequest, isAcceptableResource, isOAuthEnabled, isWriteScopeRequested, MCP_OAUTH_RATE_LIMIT_BUCKET, OAUTH_SCOPES, PROTECTED_RESOURCE_METADATA_PATH, readAuthorizationParams, serveDiscoveryDocument, validateAuthorizationRequest, } from "./oauth-http.js";
|
|
8
|
+
export { findServedResourceOrigin, readRequestHost, readRequestOrigin } from "../origins.js";
|
|
9
|
+
export { resolveMcpOrigins } from "./origins.js";
|
|
7
10
|
export { describeRedirectUri, isAllowedRedirectUri, MAX_CLIENT_NAME_LENGTH, parseClientRegistration } from "./oauth-validation.js";
|
|
8
11
|
export { getCodeChallenge, isPkceVerifierValid, isValidCodeChallenge, PKCE_METHOD } from "./pkce.js";
|
|
9
12
|
export { getMcpAccessMessages, getMcpAccessOptions, getMcpAccessRoutes, getMcpEndpointUrl } from "./options.js";
|
package/dist/server/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,+FAA+F;AAC/F,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,EACd,eAAe,GAIhB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,yBAAyB,EACzB,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GASjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,kCAAkC,EAClC,mBAAmB,EACnB,8BAA8B,EAC9B,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,+BAA+B,EAC/B,2BAA2B,EAC3B,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,2BAA2B,EAC3B,YAAY,EACZ,gCAAgC,EAChC,uBAAuB,EACvB,sBAAsB,EACtB,4BAA4B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,+FAA+F;AAC/F,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,qBAAqB,EACrB,cAAc,EACd,eAAe,GAIhB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EACL,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,yBAAyB,EACzB,eAAe,EACf,mBAAmB,EACnB,eAAe,EACf,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,iBAAiB,EACjB,gBAAgB,GASjB,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,kCAAkC,EAClC,mBAAmB,EACnB,8BAA8B,EAC9B,cAAc,EACd,cAAc,EACd,4BAA4B,EAC5B,+BAA+B,EAC/B,gCAAgC,EAChC,2BAA2B,EAC3B,wBAAwB,EACxB,kBAAkB,EAClB,oBAAoB,EACpB,cAAc,EACd,qBAAqB,EACrB,2BAA2B,EAC3B,YAAY,EACZ,gCAAgC,EAChC,uBAAuB,EACvB,sBAAsB,EACtB,4BAA4B,GAK7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,wBAAwB,EAAE,eAAe,EAAE,iBAAiB,EAA0C,MAAM,eAAe,CAAC;AACrI,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,uBAAuB,EAAyD,MAAM,uBAAuB,CAAC;AAC1L,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACrG,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,iBAAiB,EAAwB,MAAM,cAAc,CAAC;AACtI,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,2BAA2B,GAE5B,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,mBAAmB,EACnB,eAAe,EACf,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,iBAAiB,GAOlB,MAAM,aAAa,CAAC"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type SoftureConfig } from "@softure-ai/core";
|
|
2
|
+
import { type McpOriginRequest, type McpOrigins } from "../origins.js";
|
|
2
3
|
import type { OAuthClientRow } from "../schema.js";
|
|
3
4
|
import type { McpAccessContext } from "./tokens.js";
|
|
4
5
|
/** The security bucket OAuth registration and token requests count in. */
|
|
@@ -12,14 +13,20 @@ export declare const OAUTH_SCOPES: readonly ["mcp:read", "mcp:write"];
|
|
|
12
13
|
export declare const AUTHORIZATION_PARAMS: readonly ["response_type", "client_id", "redirect_uri", "state", "code_challenge", "code_challenge_method", "scope", "resource"];
|
|
13
14
|
/** `OPTIONS` on the registration, token and discovery routes. */
|
|
14
15
|
export declare function answerOAuthPreflight(): Response;
|
|
15
|
-
/**
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* The issuer: the app's origin, without a path or a trailing slash. `origins` are the request's
|
|
18
|
+
* (`resolveMcpOrigins`); without them, the configured `appOrigin`.
|
|
19
|
+
*/
|
|
20
|
+
export declare function getOAuthIssuer(config: SoftureConfig, origins?: McpOrigins): string;
|
|
17
21
|
/** The URL the endpoint's `401` names in `resource_metadata` (RFC 9728 §5.1). */
|
|
18
|
-
export declare function getProtectedResourceMetadataUrl(config: SoftureConfig): string;
|
|
22
|
+
export declare function getProtectedResourceMetadataUrl(config: SoftureConfig, origins?: McpOrigins): string;
|
|
19
23
|
/** Whether the app turned OAuth on; the OAuth routes answer 404 otherwise. */
|
|
20
24
|
export declare function isOAuthEnabled(config: SoftureConfig): boolean;
|
|
21
|
-
/**
|
|
22
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Authorization server metadata (RFC 8414 §2), with the app's `oauth.metadata.authorizationServer`
|
|
27
|
+
* keys added; generated keys win. Tokens are opaque: no OpenID.
|
|
28
|
+
*/
|
|
29
|
+
export declare function getAuthorizationServerMetadata(config: SoftureConfig, origins?: McpOrigins): {
|
|
23
30
|
issuer: string;
|
|
24
31
|
authorization_endpoint: string;
|
|
25
32
|
token_endpoint: string;
|
|
@@ -33,24 +40,30 @@ export declare function getAuthorizationServerMetadata(config: SoftureConfig): {
|
|
|
33
40
|
authorization_response_iss_parameter_supported: boolean;
|
|
34
41
|
};
|
|
35
42
|
/**
|
|
36
|
-
* Protected resource metadata (RFC 9728 §2)
|
|
37
|
-
*
|
|
38
|
-
*
|
|
43
|
+
* Protected resource metadata (RFC 9728 §2), with the app's `oauth.metadata.protectedResource`
|
|
44
|
+
* keys added; generated keys win, except the app's `resource_name`. At the endpoint's own path the
|
|
45
|
+
* resource is the endpoint URL; at the root it is the origin the document was asked on
|
|
46
|
+
* (`servedOrigin`, one of `resourceOrigins`) or else the app origin, because RFC 9728 §3.3 ties
|
|
47
|
+
* `resource` to the URL the metadata URL was built from.
|
|
39
48
|
*/
|
|
40
|
-
export declare function getProtectedResourceMetadata(config: SoftureConfig, variant: "endpoint" | "root"): {
|
|
49
|
+
export declare function getProtectedResourceMetadata(config: SoftureConfig, variant: "endpoint" | "root", origins?: McpOrigins, servedOrigin?: string | null): {
|
|
41
50
|
resource: string;
|
|
42
51
|
authorization_servers: string[];
|
|
43
52
|
scopes_supported: ("mcp:read" | "mcp:write")[];
|
|
44
53
|
bearer_methods_supported: string[];
|
|
45
54
|
resource_name: string;
|
|
46
55
|
};
|
|
47
|
-
/**
|
|
48
|
-
export
|
|
56
|
+
/** Builds a discovery document for the request's origins; the request is null outside a route. */
|
|
57
|
+
export type DiscoveryDocument = (config: SoftureConfig, origins: McpOrigins, request: McpOriginRequest | null) => unknown;
|
|
58
|
+
/** `GET` of a discovery document for `request` (its origins); 404 while OAuth is off. */
|
|
59
|
+
export declare function serveDiscoveryDocument(config: SoftureConfig, document: DiscoveryDocument, request?: McpOriginRequest | null): Response;
|
|
60
|
+
/** `serveDiscoveryDocument`'s builder for the root protected resource metadata, on the host it was asked on. */
|
|
61
|
+
export declare const getRootProtectedResourceMetadata: DiscoveryDocument;
|
|
49
62
|
/**
|
|
50
63
|
* Whether `resource` (RFC 8707) names this server: absent (clients from before RFC 8707), the
|
|
51
|
-
* endpoint URL
|
|
64
|
+
* endpoint URL, the app origin or one of `resourceOrigins`, each with or without a trailing slash.
|
|
52
65
|
*/
|
|
53
|
-
export declare function isAcceptableResource(config: SoftureConfig, resource: string | null): boolean;
|
|
66
|
+
export declare function isAcceptableResource(config: SoftureConfig, resource: string | null, origins?: McpOrigins): boolean;
|
|
54
67
|
/** Whether the client asks to write. Unknown scopes are ignored (RFC 6749 §3.3); every grant reads. */
|
|
55
68
|
export declare function isWriteScopeRequested(scope: string | null): boolean;
|
|
56
69
|
/**
|
|
@@ -88,7 +101,7 @@ export type AuthorizationOutcome = {
|
|
|
88
101
|
* The client's redirect URI with parameters added, keeping the query it registered (RFC 6749
|
|
89
102
|
* §3.1.2), and always `iss` (RFC 9207), so the client knows the answer came from this server.
|
|
90
103
|
*/
|
|
91
|
-
export declare function buildClientRedirect(config: SoftureConfig, redirectUri: string, params: Readonly<Record<string, string | null
|
|
104
|
+
export declare function buildClientRedirect(config: SoftureConfig, redirectUri: string, params: Readonly<Record<string, string | null>>, origins?: McpOrigins): string;
|
|
92
105
|
/** The authorization parameters of a query string or form, each at most 2048 characters. */
|
|
93
106
|
export declare function readAuthorizationParams(source: {
|
|
94
107
|
get(name: string): unknown;
|
|
@@ -97,9 +110,10 @@ export declare function readAuthorizationParams(source: {
|
|
|
97
110
|
* Validates an authorization request (RFC 6749 §4.1.1), for the consent page and the decision.
|
|
98
111
|
* Order is a security rule (RFC 6749 §4.1.2.1): until the client and its redirect URI are
|
|
99
112
|
* confirmed, an error is shown to the person, never redirected, or the server would redirect
|
|
100
|
-
* anyone anywhere. Only then do errors go back to the client.
|
|
113
|
+
* anyone anywhere. Only then do errors go back to the client. `origins` are the request's
|
|
114
|
+
* (the consent page's and the decision's must agree); without them, the configured ones.
|
|
101
115
|
*/
|
|
102
|
-
export declare function validateAuthorizationRequest(ctx: McpAccessContext, params: URLSearchParams): Promise<AuthorizationOutcome>;
|
|
116
|
+
export declare function validateAuthorizationRequest(ctx: McpAccessContext, params: URLSearchParams, origins?: McpOrigins): Promise<AuthorizationOutcome>;
|
|
103
117
|
/** The consent page with the request's parameters: where the decision sends the person back. */
|
|
104
118
|
export declare function getConsentPath(config: SoftureConfig, params: URLSearchParams): string;
|
|
105
119
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-http.d.ts","sourceRoot":"","sources":["../../src/server/oauth-http.ts"],"names":[],"mappings":"AAKA,OAAO,EAA4B,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"oauth-http.d.ts","sourceRoot":"","sources":["../../src/server/oauth-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -5,10 +5,12 @@
|
|
|
5
5
|
// the Next adapter only supplies the context and the session.
|
|
6
6
|
import { errorLogLabel, getModule } from "@softure-ai/core";
|
|
7
7
|
import { consumeRateLimit, identifyClient } from "@softure-ai/security/server";
|
|
8
|
+
import { findServedResourceOrigin } from "../origins.js";
|
|
8
9
|
import { MCP_READ_SCOPE, MCP_WRITE_SCOPE } from "./scopes.js";
|
|
9
10
|
import { createAuthorizationCode, exchangeAuthorizationCode, findOAuthClient, isClientSecretValid, refreshMcpGrant, registerMcpClient, } from "./oauth.js";
|
|
10
11
|
import { parseClientRegistration } from "./oauth-validation.js";
|
|
11
12
|
import { getMcpAccessOptions, getMcpAccessRoutes, getMcpEndpointUrl } from "./options.js";
|
|
13
|
+
import { resolveMcpOrigins } from "./origins.js";
|
|
12
14
|
import { isValidCodeChallenge, PKCE_METHOD } from "./pkce.js";
|
|
13
15
|
/** The security bucket OAuth registration and token requests count in. */
|
|
14
16
|
export const MCP_OAUTH_RATE_LIMIT_BUCKET = "mcp-oauth";
|
|
@@ -32,8 +34,8 @@ const CORS_HEADERS = {
|
|
|
32
34
|
};
|
|
33
35
|
/** Responses that carry credentials must not be stored (RFC 6749 §5.1). */
|
|
34
36
|
const NO_STORE = { "cache-control": "no-store", pragma: "no-cache" };
|
|
35
|
-
/** Discovery documents change only with a deploy. */
|
|
36
|
-
const DISCOVERY_CACHE = { "cache-control": "public, max-age=300" };
|
|
37
|
+
/** Discovery documents change only with a deploy, and with the host they were asked on. */
|
|
38
|
+
const DISCOVERY_CACHE = { "cache-control": "public, max-age=300", vary: "host, x-forwarded-proto" };
|
|
37
39
|
function protocolJson(body, status = 200, extra = {}) {
|
|
38
40
|
return Response.json(body, { status, headers: { ...CORS_HEADERS, ...NO_STORE, ...extra } });
|
|
39
41
|
}
|
|
@@ -47,23 +49,36 @@ export function answerOAuthPreflight() {
|
|
|
47
49
|
}
|
|
48
50
|
// ---------------------------------------------------------------------------------------------
|
|
49
51
|
// Discovery
|
|
50
|
-
/**
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The issuer: the app's origin, without a path or a trailing slash. `origins` are the request's
|
|
54
|
+
* (`resolveMcpOrigins`); without them, the configured `appOrigin`.
|
|
55
|
+
*/
|
|
56
|
+
export function getOAuthIssuer(config, origins = resolveMcpOrigins(config)) {
|
|
57
|
+
return origins.appOrigin;
|
|
53
58
|
}
|
|
54
59
|
/** The URL the endpoint's `401` names in `resource_metadata` (RFC 9728 §5.1). */
|
|
55
|
-
export function getProtectedResourceMetadataUrl(config) {
|
|
56
|
-
return `${getOAuthIssuer(config)}${PROTECTED_RESOURCE_METADATA_PATH}${new URL(getMcpEndpointUrl(config)).pathname}`;
|
|
60
|
+
export function getProtectedResourceMetadataUrl(config, origins = resolveMcpOrigins(config)) {
|
|
61
|
+
return `${getOAuthIssuer(config, origins)}${PROTECTED_RESOURCE_METADATA_PATH}${new URL(getMcpEndpointUrl(config, origins)).pathname}`;
|
|
57
62
|
}
|
|
58
63
|
/** Whether the app turned OAuth on; the OAuth routes answer 404 otherwise. */
|
|
59
64
|
export function isOAuthEnabled(config) {
|
|
60
65
|
return getMcpAccessOptions(config).oauth.enabled;
|
|
61
66
|
}
|
|
62
|
-
/**
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
/** The app's extra keys under the generated ones (`oauth.metadata`). */
|
|
68
|
+
function readExtension(extension, origins) {
|
|
69
|
+
if (extension === undefined)
|
|
70
|
+
return {};
|
|
71
|
+
return typeof extension === "function" ? extension(origins) : extension;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Authorization server metadata (RFC 8414 §2), with the app's `oauth.metadata.authorizationServer`
|
|
75
|
+
* keys added; generated keys win. Tokens are opaque: no OpenID.
|
|
76
|
+
*/
|
|
77
|
+
export function getAuthorizationServerMetadata(config, origins = resolveMcpOrigins(config)) {
|
|
78
|
+
const issuer = getOAuthIssuer(config, origins);
|
|
65
79
|
const routes = getMcpAccessRoutes(config);
|
|
66
80
|
return {
|
|
81
|
+
...readExtension(getMcpAccessOptions(config).oauth.metadata.authorizationServer, origins),
|
|
67
82
|
issuer,
|
|
68
83
|
authorization_endpoint: `${issuer}${routes.oauthConsent}`,
|
|
69
84
|
token_endpoint: `${issuer}${routes.oauthToken}`,
|
|
@@ -79,35 +94,40 @@ export function getAuthorizationServerMetadata(config) {
|
|
|
79
94
|
};
|
|
80
95
|
}
|
|
81
96
|
/**
|
|
82
|
-
* Protected resource metadata (RFC 9728 §2)
|
|
83
|
-
*
|
|
84
|
-
*
|
|
97
|
+
* Protected resource metadata (RFC 9728 §2), with the app's `oauth.metadata.protectedResource`
|
|
98
|
+
* keys added; generated keys win, except the app's `resource_name`. At the endpoint's own path the
|
|
99
|
+
* resource is the endpoint URL; at the root it is the origin the document was asked on
|
|
100
|
+
* (`servedOrigin`, one of `resourceOrigins`) or else the app origin, because RFC 9728 §3.3 ties
|
|
101
|
+
* `resource` to the URL the metadata URL was built from.
|
|
85
102
|
*/
|
|
86
|
-
export function getProtectedResourceMetadata(config, variant) {
|
|
103
|
+
export function getProtectedResourceMetadata(config, variant, origins = resolveMcpOrigins(config), servedOrigin = null) {
|
|
104
|
+
const issuer = getOAuthIssuer(config, origins);
|
|
87
105
|
return {
|
|
88
|
-
|
|
89
|
-
|
|
106
|
+
resource_name: getMcpAccessOptions(config).serverName,
|
|
107
|
+
...readExtension(getMcpAccessOptions(config).oauth.metadata.protectedResource, origins),
|
|
108
|
+
resource: variant === "endpoint" ? getMcpEndpointUrl(config, origins) : (servedOrigin ?? issuer),
|
|
109
|
+
authorization_servers: [issuer],
|
|
90
110
|
scopes_supported: [...OAUTH_SCOPES],
|
|
91
111
|
bearer_methods_supported: ["header"],
|
|
92
|
-
resource_name: getMcpAccessOptions(config).serverName,
|
|
93
112
|
};
|
|
94
113
|
}
|
|
95
|
-
/** `GET` of a discovery document; 404 while OAuth is off. */
|
|
96
|
-
export function serveDiscoveryDocument(config, document) {
|
|
114
|
+
/** `GET` of a discovery document for `request` (its origins); 404 while OAuth is off. */
|
|
115
|
+
export function serveDiscoveryDocument(config, document, request = null) {
|
|
97
116
|
if (!isOAuthEnabled(config))
|
|
98
117
|
return Response.json({ error: "not_found" }, { status: 404, headers: CORS_HEADERS });
|
|
99
|
-
return Response.json(document(config), { headers: { ...CORS_HEADERS, ...DISCOVERY_CACHE } });
|
|
118
|
+
return Response.json(document(config, resolveMcpOrigins(config, request), request), { headers: { ...CORS_HEADERS, ...DISCOVERY_CACHE } });
|
|
100
119
|
}
|
|
120
|
+
/** `serveDiscoveryDocument`'s builder for the root protected resource metadata, on the host it was asked on. */
|
|
121
|
+
export const getRootProtectedResourceMetadata = (config, origins, request) => getProtectedResourceMetadata(config, "root", origins, request === null ? null : findServedResourceOrigin(origins, request));
|
|
101
122
|
/**
|
|
102
123
|
* Whether `resource` (RFC 8707) names this server: absent (clients from before RFC 8707), the
|
|
103
|
-
* endpoint URL
|
|
124
|
+
* endpoint URL, the app origin or one of `resourceOrigins`, each with or without a trailing slash.
|
|
104
125
|
*/
|
|
105
|
-
export function isAcceptableResource(config, resource) {
|
|
126
|
+
export function isAcceptableResource(config, resource, origins = resolveMcpOrigins(config)) {
|
|
106
127
|
if (resource === null || resource === "")
|
|
107
128
|
return true;
|
|
108
|
-
const endpoint = getMcpEndpointUrl(config).replace(/\/+$/, "");
|
|
109
|
-
|
|
110
|
-
return [endpoint, `${endpoint}/`, origin, `${origin}/`].includes(resource);
|
|
129
|
+
const endpoint = getMcpEndpointUrl(config, origins).replace(/\/+$/, "");
|
|
130
|
+
return [endpoint, getOAuthIssuer(config, origins), ...origins.resourceOrigins].some((accepted) => resource === accepted || resource === `${accepted}/`);
|
|
111
131
|
}
|
|
112
132
|
/** Whether the client asks to write. Unknown scopes are ignored (RFC 6749 §3.3); every grant reads. */
|
|
113
133
|
export function isWriteScopeRequested(scope) {
|
|
@@ -222,6 +242,7 @@ const INVALID_GRANT = "The code or refresh token is invalid, expired, used or is
|
|
|
222
242
|
export async function handleTokenRequest(ctx, request) {
|
|
223
243
|
if (!isOAuthEnabled(ctx.config))
|
|
224
244
|
return protocolError("not_found", "OAuth is not enabled.", 404);
|
|
245
|
+
const origins = resolveMcpOrigins(ctx.config, request);
|
|
225
246
|
const form = new URLSearchParams(await request.text().catch(() => ""));
|
|
226
247
|
const credentials = readClientCredentials(request, form);
|
|
227
248
|
const limited = await limitProtocolRequest(ctx, request, credentials.clientId);
|
|
@@ -232,15 +253,15 @@ export async function handleTokenRequest(ctx, request) {
|
|
|
232
253
|
if (client === null || !isClientSecretValid(client, credentials.clientSecret === "" ? null : credentials.clientSecret)) {
|
|
233
254
|
return protocolError("invalid_client", "Unknown client or wrong client credentials.", 401, credentials.isBasic ? { "www-authenticate": 'Basic realm="mcp"' } : {});
|
|
234
255
|
}
|
|
235
|
-
return await answerGrant(ctx, form, client);
|
|
256
|
+
return await answerGrant(ctx, { form, client, origins });
|
|
236
257
|
}
|
|
237
258
|
catch (error) {
|
|
238
259
|
console.error(`@softure-ai/mcp-access: the OAuth token endpoint failed: ${errorLogLabel(error)}`);
|
|
239
260
|
return protocolError("server_error", "The token could not be issued.", 500);
|
|
240
261
|
}
|
|
241
262
|
}
|
|
242
|
-
async function answerGrant(ctx, form, client) {
|
|
243
|
-
if (!isAcceptableResource(ctx.config, form.get("resource"))) {
|
|
263
|
+
async function answerGrant(ctx, { form, client, origins }) {
|
|
264
|
+
if (!isAcceptableResource(ctx.config, form.get("resource"), origins)) {
|
|
244
265
|
return protocolError("invalid_target", "The resource parameter does not name this MCP server.");
|
|
245
266
|
}
|
|
246
267
|
const grantType = form.get("grant_type");
|
|
@@ -267,9 +288,9 @@ async function answerGrant(ctx, form, client) {
|
|
|
267
288
|
* The client's redirect URI with parameters added, keeping the query it registered (RFC 6749
|
|
268
289
|
* §3.1.2), and always `iss` (RFC 9207), so the client knows the answer came from this server.
|
|
269
290
|
*/
|
|
270
|
-
export function buildClientRedirect(config, redirectUri, params) {
|
|
291
|
+
export function buildClientRedirect(config, redirectUri, params, origins = resolveMcpOrigins(config)) {
|
|
271
292
|
const url = new URL(redirectUri);
|
|
272
|
-
for (const [key, value] of Object.entries({ ...params, iss: getOAuthIssuer(config) })) {
|
|
293
|
+
for (const [key, value] of Object.entries({ ...params, iss: getOAuthIssuer(config, origins) })) {
|
|
273
294
|
if (value !== null)
|
|
274
295
|
url.searchParams.set(key, value);
|
|
275
296
|
}
|
|
@@ -289,9 +310,10 @@ export function readAuthorizationParams(source) {
|
|
|
289
310
|
* Validates an authorization request (RFC 6749 §4.1.1), for the consent page and the decision.
|
|
290
311
|
* Order is a security rule (RFC 6749 §4.1.2.1): until the client and its redirect URI are
|
|
291
312
|
* confirmed, an error is shown to the person, never redirected, or the server would redirect
|
|
292
|
-
* anyone anywhere. Only then do errors go back to the client.
|
|
313
|
+
* anyone anywhere. Only then do errors go back to the client. `origins` are the request's
|
|
314
|
+
* (the consent page's and the decision's must agree); without them, the configured ones.
|
|
293
315
|
*/
|
|
294
|
-
export async function validateAuthorizationRequest(ctx, params) {
|
|
316
|
+
export async function validateAuthorizationRequest(ctx, params, origins = resolveMcpOrigins(ctx.config)) {
|
|
295
317
|
const clientId = params.get("client_id");
|
|
296
318
|
const client = clientId === null ? null : await findOAuthClient(ctx, clientId);
|
|
297
319
|
if (client === null)
|
|
@@ -303,7 +325,7 @@ export async function validateAuthorizationRequest(ctx, params) {
|
|
|
303
325
|
const back = (error, description) => ({
|
|
304
326
|
kind: "redirect-error",
|
|
305
327
|
redirectUri,
|
|
306
|
-
location: buildClientRedirect(ctx.config, redirectUri, { error, error_description: description, state }),
|
|
328
|
+
location: buildClientRedirect(ctx.config, redirectUri, { error, error_description: description, state }, origins),
|
|
307
329
|
});
|
|
308
330
|
if (params.get("response_type") !== "code")
|
|
309
331
|
return back("unsupported_response_type", "Only response_type=code is supported.");
|
|
@@ -311,7 +333,7 @@ export async function validateAuthorizationRequest(ctx, params) {
|
|
|
311
333
|
if (codeChallenge === null || !isValidCodeChallenge(codeChallenge) || params.get("code_challenge_method") !== PKCE_METHOD) {
|
|
312
334
|
return back("invalid_request", "PKCE is required: code_challenge with code_challenge_method S256.");
|
|
313
335
|
}
|
|
314
|
-
if (!isAcceptableResource(ctx.config, params.get("resource")))
|
|
336
|
+
if (!isAcceptableResource(ctx.config, params.get("resource"), origins))
|
|
315
337
|
return back("invalid_target", "The resource parameter does not name this MCP server.");
|
|
316
338
|
return { kind: "valid", request: { client, redirectUri, state, codeChallenge, isWriteRequested: isWriteScopeRequested(params.get("scope")) } };
|
|
317
339
|
}
|
|
@@ -322,10 +344,10 @@ export function getConsentPath(config, params) {
|
|
|
322
344
|
function seeOther(location) {
|
|
323
345
|
return new Response(null, { status: 303, headers: { location, "cache-control": "no-store" } });
|
|
324
346
|
}
|
|
325
|
-
/** Whether the form was posted from this app: `Origin` must be the
|
|
326
|
-
function isSameOrigin(
|
|
347
|
+
/** Whether the form was posted from this app: `Origin` must be the request's app origin. */
|
|
348
|
+
function isSameOrigin(request, origins) {
|
|
327
349
|
const origin = request.headers.get("origin");
|
|
328
|
-
return origin !== null && origin ===
|
|
350
|
+
return origin !== null && origin === origins.appOrigin;
|
|
329
351
|
}
|
|
330
352
|
/**
|
|
331
353
|
* `POST` of the consent form, answered with `303`: a plain form post, not a server action, because
|
|
@@ -337,7 +359,8 @@ function isSameOrigin(config, request) {
|
|
|
337
359
|
export async function handleAuthorizationDecision(ctx, request, userId) {
|
|
338
360
|
if (!isOAuthEnabled(ctx.config))
|
|
339
361
|
return new Response(null, { status: 404 });
|
|
340
|
-
|
|
362
|
+
const origins = resolveMcpOrigins(ctx.config, request);
|
|
363
|
+
if (!isSameOrigin(request, origins))
|
|
341
364
|
return new Response(null, { status: 403, headers: { "cache-control": "no-store" } });
|
|
342
365
|
const form = await request.formData().catch(() => null);
|
|
343
366
|
if (form === null)
|
|
@@ -347,14 +370,14 @@ export async function handleAuthorizationDecision(ctx, request, userId) {
|
|
|
347
370
|
if (userId === null)
|
|
348
371
|
return seeOther(consent);
|
|
349
372
|
try {
|
|
350
|
-
const outcome = await validateAuthorizationRequest(ctx, params);
|
|
373
|
+
const outcome = await validateAuthorizationRequest(ctx, params, origins);
|
|
351
374
|
if (outcome.kind === "show-error")
|
|
352
375
|
return seeOther(consent);
|
|
353
376
|
if (outcome.kind === "redirect-error")
|
|
354
377
|
return seeOther(outcome.location);
|
|
355
378
|
const { client, redirectUri, state, codeChallenge, isWriteRequested } = outcome.request;
|
|
356
379
|
if (form.get("decision") !== "allow") {
|
|
357
|
-
return seeOther(buildClientRedirect(ctx.config, redirectUri, { error: "access_denied", error_description: "The person denied access.", state }));
|
|
380
|
+
return seeOther(buildClientRedirect(ctx.config, redirectUri, { error: "access_denied", error_description: "The person denied access.", state }, origins));
|
|
358
381
|
}
|
|
359
382
|
const code = await createAuthorizationCode(ctx, {
|
|
360
383
|
clientRowId: client.id,
|
|
@@ -364,7 +387,7 @@ export async function handleAuthorizationDecision(ctx, request, userId) {
|
|
|
364
387
|
// Writes need the client's request, the app's allowWrites and the person's tick.
|
|
365
388
|
canWrite: isWriteRequested && getMcpAccessOptions(ctx.config).allowWrites && form.get("canWrite") !== null,
|
|
366
389
|
});
|
|
367
|
-
return seeOther(buildClientRedirect(ctx.config, redirectUri, { code, state }));
|
|
390
|
+
return seeOther(buildClientRedirect(ctx.config, redirectUri, { code, state }, origins));
|
|
368
391
|
}
|
|
369
392
|
catch (error) {
|
|
370
393
|
console.error(`@softure-ai/mcp-access: recording an OAuth decision failed: ${errorLogLabel(error)}`);
|