@peoplethread/saas-mcp 0.0.0-stage → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +1 -0
- package/dist/client/management.d.ts +80 -0
- package/dist/client/management.js +78 -0
- package/dist/core/http.d.ts +27 -0
- package/dist/core/http.js +29 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +5 -0
- package/dist/core/redaction.d.ts +11 -0
- package/dist/core/redaction.js +43 -0
- package/dist/core/scopes.d.ts +13 -0
- package/dist/core/scopes.js +35 -0
- package/dist/core/types.d.ts +252 -0
- package/dist/core/types.js +1 -0
- package/dist/core/urls.d.ts +97 -0
- package/dist/core/urls.js +106 -0
- package/dist/hono/index.d.ts +1 -0
- package/dist/hono/index.js +1 -0
- package/dist/hono/routes.d.ts +295 -0
- package/dist/hono/routes.js +739 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/node/better-auth.d.ts +84 -0
- package/dist/node/better-auth.js +77 -0
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.js +2 -0
- package/dist/node/oauth.d.ts +18 -0
- package/dist/node/oauth.js +39 -0
- package/dist/react/McpActivity.d.ts +32 -0
- package/dist/react/McpActivity.js +182 -0
- package/dist/react/McpConnectedClients.d.ts +27 -0
- package/dist/react/McpConnectedClients.js +34 -0
- package/dist/react/McpConnectorOverview.d.ts +21 -0
- package/dist/react/McpConnectorOverview.js +12 -0
- package/dist/react/McpConsentPanel.d.ts +47 -0
- package/dist/react/McpConsentPanel.js +18 -0
- package/dist/react/McpDeveloperTokens.d.ts +27 -0
- package/dist/react/McpDeveloperTokens.js +46 -0
- package/dist/react/McpToolReference.d.ts +17 -0
- package/dist/react/McpToolReference.js +19 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.js +6 -0
- package/dist/react/mcp-settings-shared.d.ts +29 -0
- package/dist/react/mcp-settings-shared.js +37 -0
- package/package.json +98 -3
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { McpScopeValue } from "./types.ts";
|
|
2
|
+
/** Public route paths advertised through OAuth/MCP metadata documents. */
|
|
3
|
+
export interface McpOAuthPublicRoutes {
|
|
4
|
+
protectedResourceMetadataPath: string;
|
|
5
|
+
/**
|
|
6
|
+
* No `authorizationServerMetadataPath`: RFC 8414 derives that address from the issuer, so
|
|
7
|
+
* the router registers it itself. A configurable one could only ever disagree.
|
|
8
|
+
*/
|
|
9
|
+
authorizePath: string;
|
|
10
|
+
consentPagePath?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Where the product's human sign-in lives. Used by the authorize route to offer sign-in
|
|
13
|
+
* when an authorization request arrives with no signed-in human, instead of reporting the
|
|
14
|
+
* missing session to the OAuth client as a malformed request. Only the product knows this
|
|
15
|
+
* address, and a product that omits it gets the OAuth error path instead.
|
|
16
|
+
*
|
|
17
|
+
* Resolved against `appBaseUrl`, so a host-absolute path (`/sign-in`), a relative one, a path
|
|
18
|
+
* carrying a query (`/sign-in?tenant=acme`), and an absolute URL on a separate identity host
|
|
19
|
+
* all work as written. A `callbackURL` parameter is added to whatever results.
|
|
20
|
+
*/
|
|
21
|
+
signInPath?: string;
|
|
22
|
+
tokenPath: string;
|
|
23
|
+
registrationPath: string;
|
|
24
|
+
revocationPath: string;
|
|
25
|
+
}
|
|
26
|
+
/** Inputs needed to build product-specific OAuth discovery metadata. */
|
|
27
|
+
export interface McpOAuthMetadataInput<S extends McpScopeValue = McpScopeValue> {
|
|
28
|
+
appBaseUrl: string;
|
|
29
|
+
resourceUri: string;
|
|
30
|
+
supportedScopes: readonly S[];
|
|
31
|
+
routes: McpOAuthPublicRoutes;
|
|
32
|
+
}
|
|
33
|
+
/** OAuth protected-resource metadata for the MCP endpoint. */
|
|
34
|
+
export interface McpProtectedResourceMetadata<S extends McpScopeValue = McpScopeValue> {
|
|
35
|
+
resource: string;
|
|
36
|
+
authorization_servers: string[];
|
|
37
|
+
bearer_methods_supported: string[];
|
|
38
|
+
scopes_supported: S[];
|
|
39
|
+
resource_documentation: string;
|
|
40
|
+
}
|
|
41
|
+
/** OAuth authorization-server metadata for MCP client registration and tokens. */
|
|
42
|
+
export interface McpAuthorizationServerMetadata<S extends McpScopeValue = McpScopeValue> {
|
|
43
|
+
issuer: string;
|
|
44
|
+
authorization_endpoint: string;
|
|
45
|
+
token_endpoint: string;
|
|
46
|
+
registration_endpoint: string;
|
|
47
|
+
revocation_endpoint: string;
|
|
48
|
+
response_types_supported: string[];
|
|
49
|
+
grant_types_supported: string[];
|
|
50
|
+
token_endpoint_auth_methods_supported: string[];
|
|
51
|
+
code_challenge_methods_supported: string[];
|
|
52
|
+
scopes_supported: S[];
|
|
53
|
+
resource_parameter_supported: boolean;
|
|
54
|
+
}
|
|
55
|
+
/** Inputs for building a WWW-Authenticate bearer challenge. */
|
|
56
|
+
export interface McpBearerChallengeInput<S extends McpScopeValue = McpScopeValue> {
|
|
57
|
+
appBaseUrl: string;
|
|
58
|
+
protectedResourceMetadataPath: string;
|
|
59
|
+
scopes: readonly S[];
|
|
60
|
+
/**
|
|
61
|
+
* RFC 6750 error code, omitted for a plain "authenticate here" challenge. Set it when
|
|
62
|
+
* a credential was presented and rejected, so the client stops replaying it instead of
|
|
63
|
+
* treating the 401 as a prompt to retry with the same token.
|
|
64
|
+
*/
|
|
65
|
+
error?: string;
|
|
66
|
+
}
|
|
67
|
+
/** Remove trailing slashes from an origin or base URL. */
|
|
68
|
+
export declare function trimTrailingSlash(value: string): string;
|
|
69
|
+
/** Join an app base URL and path without duplicating slashes. */
|
|
70
|
+
export declare function joinUrlPath(appBaseUrl: string, path: string): string;
|
|
71
|
+
/** Extract an origin from a request URL, returning null for invalid input. */
|
|
72
|
+
export declare function originFromRequestUrl(requestUrl?: string): string | null;
|
|
73
|
+
/** Return whether an origin is a localhost or loopback development origin. */
|
|
74
|
+
export declare function isLocalOrigin(origin: string): boolean;
|
|
75
|
+
/** Return whether an origin is public HTTPS rather than local development. */
|
|
76
|
+
export declare function isPublicHttpsOrigin(origin: string): boolean;
|
|
77
|
+
/**
|
|
78
|
+
* The address a client fetches authorization-server metadata from, given an issuer.
|
|
79
|
+
*
|
|
80
|
+
* RFC 8414 §3.1 inserts the well-known segment between the issuer's host and its path, so a
|
|
81
|
+
* root issuer yields `<origin>/.well-known/oauth-authorization-server` and an issuer with a
|
|
82
|
+
* path yields that address with the path appended — never the issuer with the segment tacked
|
|
83
|
+
* on the end. This is the derivation `authorization_servers` commits a deployment to: the
|
|
84
|
+
* routes have to serve the document here, or discovery stops at the first hop.
|
|
85
|
+
*
|
|
86
|
+
* Exported so the rule is written once. A consumer computing it by hand is how the metadata
|
|
87
|
+
* URL ended up in `authorization_servers` in the first place.
|
|
88
|
+
*/
|
|
89
|
+
export declare function authorizationServerMetadataUrlForIssuer(issuer: string): string;
|
|
90
|
+
/** Extract a hostname from a URL, returning null for invalid input. */
|
|
91
|
+
export declare function hostFromUrl(value: string): string | null;
|
|
92
|
+
/** Build RFC 9728 protected-resource metadata for the MCP endpoint. */
|
|
93
|
+
export declare function buildMcpProtectedResourceMetadata<S extends McpScopeValue>(input: McpOAuthMetadataInput<S>): McpProtectedResourceMetadata<S>;
|
|
94
|
+
/** Build OAuth authorization-server metadata for MCP clients. */
|
|
95
|
+
export declare function buildMcpAuthorizationServerMetadata<S extends McpScopeValue>(input: McpOAuthMetadataInput<S>): McpAuthorizationServerMetadata<S>;
|
|
96
|
+
/** Build the bearer challenge returned with unauthorized MCP requests. */
|
|
97
|
+
export declare function buildMcpBearerChallenge<S extends McpScopeValue>(input: McpBearerChallengeInput<S>): string;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { formatMcpScopeString } from "./scopes.js";
|
|
2
|
+
/** Remove trailing slashes from an origin or base URL. */
|
|
3
|
+
export function trimTrailingSlash(value) {
|
|
4
|
+
return value.replace(/\/+$/, "");
|
|
5
|
+
}
|
|
6
|
+
/** Join an app base URL and path without duplicating slashes. */
|
|
7
|
+
export function joinUrlPath(appBaseUrl, path) {
|
|
8
|
+
return new URL(path, `${trimTrailingSlash(appBaseUrl)}/`).toString();
|
|
9
|
+
}
|
|
10
|
+
/** Extract an origin from a request URL, returning null for invalid input. */
|
|
11
|
+
export function originFromRequestUrl(requestUrl) {
|
|
12
|
+
if (!requestUrl)
|
|
13
|
+
return null;
|
|
14
|
+
try {
|
|
15
|
+
return new URL(requestUrl).origin;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Return whether an origin is a localhost or loopback development origin. */
|
|
22
|
+
export function isLocalOrigin(origin) {
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(origin);
|
|
25
|
+
return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** Return whether an origin is public HTTPS rather than local development. */
|
|
32
|
+
export function isPublicHttpsOrigin(origin) {
|
|
33
|
+
try {
|
|
34
|
+
const url = new URL(origin);
|
|
35
|
+
return url.protocol === "https:" && !isLocalOrigin(url.origin);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The address a client fetches authorization-server metadata from, given an issuer.
|
|
43
|
+
*
|
|
44
|
+
* RFC 8414 §3.1 inserts the well-known segment between the issuer's host and its path, so a
|
|
45
|
+
* root issuer yields `<origin>/.well-known/oauth-authorization-server` and an issuer with a
|
|
46
|
+
* path yields that address with the path appended — never the issuer with the segment tacked
|
|
47
|
+
* on the end. This is the derivation `authorization_servers` commits a deployment to: the
|
|
48
|
+
* routes have to serve the document here, or discovery stops at the first hop.
|
|
49
|
+
*
|
|
50
|
+
* Exported so the rule is written once. A consumer computing it by hand is how the metadata
|
|
51
|
+
* URL ended up in `authorization_servers` in the first place.
|
|
52
|
+
*/
|
|
53
|
+
export function authorizationServerMetadataUrlForIssuer(issuer) {
|
|
54
|
+
const url = new URL(issuer);
|
|
55
|
+
const issuerPath = trimTrailingSlash(url.pathname);
|
|
56
|
+
return `${url.origin}/.well-known/oauth-authorization-server${issuerPath}`;
|
|
57
|
+
}
|
|
58
|
+
/** Extract a hostname from a URL, returning null for invalid input. */
|
|
59
|
+
export function hostFromUrl(value) {
|
|
60
|
+
try {
|
|
61
|
+
return new URL(value).host;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Build RFC 9728 protected-resource metadata for the MCP endpoint. */
|
|
68
|
+
export function buildMcpProtectedResourceMetadata(input) {
|
|
69
|
+
return {
|
|
70
|
+
resource: input.resourceUri,
|
|
71
|
+
/**
|
|
72
|
+
* RFC 9728 defines this as a list of authorization server *issuer identifiers*, not
|
|
73
|
+
* metadata document URLs: clients apply RFC 8414 well-known path insertion to what they
|
|
74
|
+
* find here, and RFC 8414 §3.3 requires the result's `issuer` to match the value they
|
|
75
|
+
* started from. Publishing the metadata URL sends them to `/.well-known/…/api/.well-known/…`,
|
|
76
|
+
* which 404s, after which they fall back to origin defaults and fail to register. This is
|
|
77
|
+
* deliberately the same expression `buildMcpAuthorizationServerMetadata` uses for `issuer`
|
|
78
|
+
* so the two cannot drift.
|
|
79
|
+
*/
|
|
80
|
+
authorization_servers: [trimTrailingSlash(input.appBaseUrl)],
|
|
81
|
+
bearer_methods_supported: ["header"],
|
|
82
|
+
scopes_supported: [...input.supportedScopes],
|
|
83
|
+
resource_documentation: trimTrailingSlash(input.appBaseUrl)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Build OAuth authorization-server metadata for MCP clients. */
|
|
87
|
+
export function buildMcpAuthorizationServerMetadata(input) {
|
|
88
|
+
return {
|
|
89
|
+
issuer: trimTrailingSlash(input.appBaseUrl),
|
|
90
|
+
authorization_endpoint: joinUrlPath(input.appBaseUrl, input.routes.authorizePath),
|
|
91
|
+
token_endpoint: joinUrlPath(input.appBaseUrl, input.routes.tokenPath),
|
|
92
|
+
registration_endpoint: joinUrlPath(input.appBaseUrl, input.routes.registrationPath),
|
|
93
|
+
revocation_endpoint: joinUrlPath(input.appBaseUrl, input.routes.revocationPath),
|
|
94
|
+
response_types_supported: ["code"],
|
|
95
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
96
|
+
token_endpoint_auth_methods_supported: ["none"],
|
|
97
|
+
code_challenge_methods_supported: ["S256"],
|
|
98
|
+
scopes_supported: [...input.supportedScopes],
|
|
99
|
+
resource_parameter_supported: true
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
/** Build the bearer challenge returned with unauthorized MCP requests. */
|
|
103
|
+
export function buildMcpBearerChallenge(input) {
|
|
104
|
+
const error = input.error ? `error="${input.error}", ` : "";
|
|
105
|
+
return `Bearer ${error}resource_metadata="${joinUrlPath(input.appBaseUrl, input.protectedResourceMetadataPath)}", scope="${formatMcpScopeString(input.scopes)}"`;
|
|
106
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./routes.ts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./routes.js";
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { Hono } from "hono";
|
|
3
|
+
import { type McpAuditEvent, type McpOAuthPublicRoutes, type McpScopeValue, type McpToolDescriptor } from "../core/index.ts";
|
|
4
|
+
import type { OAuthAuthorizationCodeRecord, OAuthClientRecord, OAuthTokenRecord } from "../core/types.ts";
|
|
5
|
+
/**
|
|
6
|
+
* Product-local Hono paths where the shared MCP router mounts its public HTTP API.
|
|
7
|
+
*
|
|
8
|
+
* These are pathnames relative to the Hono app or route group supplied by the
|
|
9
|
+
* consuming product. The absolute URLs advertised to MCP clients are derived
|
|
10
|
+
* from `McpHonoRuntimeConfig.routes`.
|
|
11
|
+
*/
|
|
12
|
+
export interface McpHonoRouteMounts {
|
|
13
|
+
/**
|
|
14
|
+
* Where this Hono app sits in the host's path space, `""` for the host root.
|
|
15
|
+
*
|
|
16
|
+
* Supplied because authorization-server metadata has no mount of its own: RFC 8414 fixes
|
|
17
|
+
* its address relative to the issuer, so the router works out where to register it and
|
|
18
|
+
* needs this to translate a host-absolute address into a mount on this app. Getting it
|
|
19
|
+
* wrong, or mounting an app somewhere that cannot reach the derived address at all, is
|
|
20
|
+
* refused at registration rather than left to 404 during a client's discovery.
|
|
21
|
+
*/
|
|
22
|
+
basePath?: string;
|
|
23
|
+
/** GET: OAuth protected-resource metadata for the MCP endpoint. */
|
|
24
|
+
protectedResourceMetadata: string;
|
|
25
|
+
/** GET renders consent; POST accepts approve/cancel consent decisions. */
|
|
26
|
+
authorize: string;
|
|
27
|
+
/** POST: OAuth token exchange for authorization_code and refresh_token grants. */
|
|
28
|
+
token: string;
|
|
29
|
+
/** POST: OAuth token revocation endpoint for client-initiated revocation. */
|
|
30
|
+
revocation: string;
|
|
31
|
+
/** POST: dynamic client registration for MCP OAuth clients. */
|
|
32
|
+
registration: string;
|
|
33
|
+
/** Streamable HTTP MCP endpoint that receives JSON-RPC MCP requests. */
|
|
34
|
+
mcp: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Per-request runtime settings used by the shared MCP router.
|
|
38
|
+
*
|
|
39
|
+
* The consuming product owns deployment-specific values such as public URLs,
|
|
40
|
+
* HTTPS policy, allowed origins/hosts, supported scopes, and token TTLs. This
|
|
41
|
+
* keeps the shared package independent of Next.js, Railway, Cloudflare, or any
|
|
42
|
+
* product-specific hosting model.
|
|
43
|
+
*/
|
|
44
|
+
export interface McpHonoRuntimeConfig<S extends McpScopeValue = McpScopeValue> {
|
|
45
|
+
appBaseUrl: string;
|
|
46
|
+
resourceUri: string;
|
|
47
|
+
requireHttps: boolean;
|
|
48
|
+
allowedOrigins: readonly string[];
|
|
49
|
+
allowedHosts: readonly string[];
|
|
50
|
+
supportedScopes: readonly S[];
|
|
51
|
+
defaultScopes: readonly S[];
|
|
52
|
+
latestProtocolVersion: string;
|
|
53
|
+
supportedProtocolVersions: readonly string[];
|
|
54
|
+
routes: McpOAuthPublicRoutes;
|
|
55
|
+
accessTokenTtlSeconds: number;
|
|
56
|
+
refreshTokenTtlSeconds: number;
|
|
57
|
+
authorizationCodeTtlSeconds: number;
|
|
58
|
+
tokenPrefixes?: {
|
|
59
|
+
authorizationCode?: string;
|
|
60
|
+
accessToken?: string;
|
|
61
|
+
refreshToken?: string;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Human identity selected for an OAuth consent decision.
|
|
66
|
+
*
|
|
67
|
+
* Products should resolve this from their signed-in app user and membership
|
|
68
|
+
* model. Query/body user IDs are hints only; products must not trust forged
|
|
69
|
+
* user or workspace identifiers from OAuth clients.
|
|
70
|
+
*/
|
|
71
|
+
export interface McpConsentIdentity {
|
|
72
|
+
workspaceId: string;
|
|
73
|
+
userId: string;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Thrown by `createMcpContext` to refuse a session for a token that authenticated but
|
|
77
|
+
* cannot act on this resource — a principal whose access to the addressed tenant has
|
|
78
|
+
* gone away, say.
|
|
79
|
+
*
|
|
80
|
+
* The router answers `401` with `error="invalid_token"` rather than letting it surface
|
|
81
|
+
* as a server fault. The token is intact, so the description is arguable, but the
|
|
82
|
+
* outcome it produces is the correct one: the condition is durable, and a client that
|
|
83
|
+
* retries the same credential will fail identically until it re-authorizes.
|
|
84
|
+
*/
|
|
85
|
+
export declare class McpContextUnauthorizedError extends Error {
|
|
86
|
+
constructor(message?: string);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Thrown by `resolveConsentIdentity` when no human is signed in to the product.
|
|
90
|
+
*
|
|
91
|
+
* The authorization endpoint answers this by sending the browser to the product's sign-in
|
|
92
|
+
* page and returning it to the same authorization request, because the condition is one the
|
|
93
|
+
* user can fix in place. It is deliberately distinct from every other failure that resolver
|
|
94
|
+
* can raise: those are reported to the OAuth client, and this one is not, since telling a
|
|
95
|
+
* client "invalid request" for a missing cookie leaves the user with nothing to act on.
|
|
96
|
+
*/
|
|
97
|
+
export declare class McpConsentSignInRequiredError extends Error {
|
|
98
|
+
constructor(message?: string);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Thrown by `resolveConsentIdentity` when a human *is* signed in but may not authorize the
|
|
102
|
+
* requested resource — no membership in the addressed workspace, say.
|
|
103
|
+
*
|
|
104
|
+
* Terminal, unlike `McpConsentSignInRequiredError`: signing in again cannot change the
|
|
105
|
+
* answer, so the router refuses to the client with `access_denied` rather than looping the
|
|
106
|
+
* browser through sign-in. The message reaches the client as `error_description`, so it must
|
|
107
|
+
* not name a resource the caller has not been shown to be entitled to know exists.
|
|
108
|
+
*/
|
|
109
|
+
export declare class McpConsentForbiddenError extends Error {
|
|
110
|
+
constructor(message?: string);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Product-owned storage, identity, membership, audit, and tool adapters used by
|
|
114
|
+
* the shared Hono implementation.
|
|
115
|
+
*
|
|
116
|
+
* The shared router owns protocol mechanics and DTO shape. The product owns all
|
|
117
|
+
* durable records, workspace authorization, human identity lookup, audit writes,
|
|
118
|
+
* and construction of the product-specific `McpServer` instance.
|
|
119
|
+
*/
|
|
120
|
+
export interface McpHonoAdapters<S extends McpScopeValue, C> {
|
|
121
|
+
/** Create or update a dynamically registered OAuth client. */
|
|
122
|
+
registerOAuthClient(input: {
|
|
123
|
+
name: string;
|
|
124
|
+
displayName?: string;
|
|
125
|
+
redirectUris: string[];
|
|
126
|
+
scopes: S[];
|
|
127
|
+
}): Promise<OAuthClientRecord<S>>;
|
|
128
|
+
/** Look up an OAuth client by client_id. */
|
|
129
|
+
getOAuthClient(clientId: string): Promise<OAuthClientRecord<S> | null>;
|
|
130
|
+
/** Persist a hashed, single-use OAuth authorization code after consent. */
|
|
131
|
+
createOAuthAuthorizationCode(input: {
|
|
132
|
+
codeHash: string;
|
|
133
|
+
clientId: string;
|
|
134
|
+
redirectUri: string;
|
|
135
|
+
workspaceId: string;
|
|
136
|
+
userId: string;
|
|
137
|
+
scopes: S[];
|
|
138
|
+
resource: string;
|
|
139
|
+
codeChallenge: string;
|
|
140
|
+
codeChallengeMethod: "S256";
|
|
141
|
+
expiresAt: string;
|
|
142
|
+
}): Promise<OAuthAuthorizationCodeRecord<S>>;
|
|
143
|
+
/** Atomically consume a valid authorization code during token exchange. */
|
|
144
|
+
consumeOAuthAuthorizationCode(input: {
|
|
145
|
+
codeHash: string;
|
|
146
|
+
clientId: string;
|
|
147
|
+
redirectUri: string;
|
|
148
|
+
resource: string;
|
|
149
|
+
now?: string;
|
|
150
|
+
}): Promise<OAuthAuthorizationCodeRecord<S> | null>;
|
|
151
|
+
/** Store a hashed OAuth access or refresh token. */
|
|
152
|
+
issueOAuthToken(input: {
|
|
153
|
+
tokenHash: string;
|
|
154
|
+
kind: OAuthTokenRecord<S>["kind"];
|
|
155
|
+
grantId: string;
|
|
156
|
+
clientId: string;
|
|
157
|
+
workspaceId: string;
|
|
158
|
+
userId: string;
|
|
159
|
+
scopes: S[];
|
|
160
|
+
resource: string;
|
|
161
|
+
sourceKind?: OAuthTokenRecord<S>["sourceKind"];
|
|
162
|
+
displayName?: string | null;
|
|
163
|
+
expiresAt: string;
|
|
164
|
+
}): Promise<OAuthTokenRecord<S>>;
|
|
165
|
+
/** Find a token record by hash, optionally including revoked tokens. */
|
|
166
|
+
getOAuthTokenByHash(input: {
|
|
167
|
+
tokenHash: string;
|
|
168
|
+
kind?: OAuthTokenRecord<S>["kind"];
|
|
169
|
+
resource?: string;
|
|
170
|
+
includeRevoked?: boolean;
|
|
171
|
+
}): Promise<OAuthTokenRecord<S> | null>;
|
|
172
|
+
/** Validate a token for bearer use and optionally update last-used metadata. */
|
|
173
|
+
validateOAuthToken(input: {
|
|
174
|
+
tokenHash: string;
|
|
175
|
+
kind?: OAuthTokenRecord<S>["kind"];
|
|
176
|
+
resource: string;
|
|
177
|
+
now?: string;
|
|
178
|
+
recordLastUsed?: boolean;
|
|
179
|
+
}): Promise<OAuthTokenRecord<S> | null>;
|
|
180
|
+
/**
|
|
181
|
+
* Revoke an OAuth or developer token by token hash.
|
|
182
|
+
*
|
|
183
|
+
* Deliberately carries no actor. Revocation here is always self-revocation — the
|
|
184
|
+
* caller presents the token — so the product can derive who that is from the token
|
|
185
|
+
* it stores, in whatever grain it records actors in. Asserting a user id would
|
|
186
|
+
* impose a grain the product may not use.
|
|
187
|
+
*/
|
|
188
|
+
revokeOAuthToken(tokenHash: string, input?: {
|
|
189
|
+
revokedReason?: string | null;
|
|
190
|
+
}): Promise<boolean>;
|
|
191
|
+
/** Confirm the token or consent user still belongs to the selected workspace. */
|
|
192
|
+
hasWorkspaceMembership(input: {
|
|
193
|
+
workspaceId: string;
|
|
194
|
+
userId: string;
|
|
195
|
+
}): Promise<boolean>;
|
|
196
|
+
/**
|
|
197
|
+
* Resolve the signed-in human who is viewing or submitting OAuth consent.
|
|
198
|
+
*
|
|
199
|
+
* Throw `McpConsentSignInRequiredError` when no human is signed in, so the router offers
|
|
200
|
+
* sign-in rather than failing the request, and `McpConsentForbiddenError` when a signed-in
|
|
201
|
+
* human may not authorize the requested resource. Those two are the only throws whose
|
|
202
|
+
* message reaches the OAuth client.
|
|
203
|
+
*
|
|
204
|
+
* Anything else thrown here is treated as a server fault: the client is told `server_error`
|
|
205
|
+
* with a fixed description and nothing about the failure. A resolver that reads a database
|
|
206
|
+
* or an identity provider can throw messages carrying connection strings, internal ids, or
|
|
207
|
+
* provider detail, and this endpoint hands its errors to whatever redirect URI a
|
|
208
|
+
* self-registered client supplied.
|
|
209
|
+
*/
|
|
210
|
+
resolveConsentIdentity(input: {
|
|
211
|
+
headers: Headers;
|
|
212
|
+
workspaceId?: string | null;
|
|
213
|
+
userId?: string | null;
|
|
214
|
+
}): Promise<McpConsentIdentity>;
|
|
215
|
+
/** Record a newly initialized Streamable HTTP MCP session. */
|
|
216
|
+
trackMcpSession(input: {
|
|
217
|
+
id: string;
|
|
218
|
+
accessTokenId: string;
|
|
219
|
+
clientId: string;
|
|
220
|
+
workspaceId: string;
|
|
221
|
+
userId: string;
|
|
222
|
+
scopes: S[];
|
|
223
|
+
protocolVersion?: string | null;
|
|
224
|
+
}): Promise<unknown>;
|
|
225
|
+
/** Refresh session metadata after a valid non-initialize MCP request. */
|
|
226
|
+
touchMcpSession(sessionId: string, input?: {
|
|
227
|
+
accessTokenId?: string;
|
|
228
|
+
scopes?: S[];
|
|
229
|
+
}): Promise<unknown>;
|
|
230
|
+
/** Mark a Streamable HTTP MCP session closed. */
|
|
231
|
+
closeMcpSession(sessionId: string): Promise<unknown>;
|
|
232
|
+
/** Persist an MCP audit event after tool calls or management activity. */
|
|
233
|
+
recordMcpAuditEvent(input: {
|
|
234
|
+
workspaceId: string;
|
|
235
|
+
userId: string;
|
|
236
|
+
clientId: string;
|
|
237
|
+
accessTokenId?: string | null;
|
|
238
|
+
sourceKind?: OAuthTokenRecord<S>["sourceKind"];
|
|
239
|
+
activityKind?: string;
|
|
240
|
+
toolName: string;
|
|
241
|
+
targetRecordIds?: string[];
|
|
242
|
+
outcome: McpAuditEvent["outcome"];
|
|
243
|
+
metadata?: Record<string, unknown>;
|
|
244
|
+
}): Promise<McpAuditEvent>;
|
|
245
|
+
/**
|
|
246
|
+
* Build the product-specific tool execution context from the bearer token.
|
|
247
|
+
*
|
|
248
|
+
* May return a promise: a product whose context carries identity beyond the token
|
|
249
|
+
* — a tenant-scoped actor id, say — has to read it, and doing that once per session
|
|
250
|
+
* here is cheaper than repeating it inside every tool call.
|
|
251
|
+
*
|
|
252
|
+
* Throw `McpContextUnauthorizedError` to refuse the session when that identity cannot
|
|
253
|
+
* be resolved; anything else thrown here is a server fault and is reported as one.
|
|
254
|
+
*/
|
|
255
|
+
createMcpContext(input: {
|
|
256
|
+
token: OAuthTokenRecord<S>;
|
|
257
|
+
config: McpHonoRuntimeConfig<S>;
|
|
258
|
+
}): C | Promise<C>;
|
|
259
|
+
/** Update an existing session context when token metadata changes. */
|
|
260
|
+
refreshMcpContext?(context: C, token: OAuthTokenRecord<S>): C | void;
|
|
261
|
+
/** Create the product MCP server and register product-owned tools. */
|
|
262
|
+
createMcpServer(context: C): McpServer;
|
|
263
|
+
/** Optional lookup used to classify tool audit activity before execution. */
|
|
264
|
+
getToolByName?(name: string): McpToolDescriptor<S> | undefined;
|
|
265
|
+
/**
|
|
266
|
+
* Report a server-side fault the router caught and did not tell the client about.
|
|
267
|
+
*
|
|
268
|
+
* Supplied by the product because the errors that reach here come from the product's own
|
|
269
|
+
* identity stack, and are the class most likely to carry a connection string, an internal
|
|
270
|
+
* id, or provider detail — exactly what a product's logger exists to scrub and route.
|
|
271
|
+
* Defaults to `console.error`, which keeps the failure visible for a product that has no
|
|
272
|
+
* logger of its own but is the wrong destination for one that does.
|
|
273
|
+
*/
|
|
274
|
+
logError?(message: string, error: unknown): void;
|
|
275
|
+
}
|
|
276
|
+
/** Options for registering the shared MCP OAuth and Streamable HTTP routes. */
|
|
277
|
+
export interface RegisterMcpHonoRoutesOptions<S extends McpScopeValue, C> {
|
|
278
|
+
/** Resolve runtime configuration for the incoming request URL. */
|
|
279
|
+
resolveConfig(requestUrl?: string): McpHonoRuntimeConfig<S>;
|
|
280
|
+
/** Product-local route mount points. */
|
|
281
|
+
mounts: McpHonoRouteMounts;
|
|
282
|
+
/** Product adapters for persistence, identity, tools, and audit. */
|
|
283
|
+
adapters: McpHonoAdapters<S, C>;
|
|
284
|
+
/** Generate an MCP session id for initialized Streamable HTTP sessions. */
|
|
285
|
+
sessionIdGenerator(): string;
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Register OAuth discovery, dynamic client registration, authorization-code
|
|
289
|
+
* flow, token revocation, and Streamable HTTP MCP routes on a Hono app.
|
|
290
|
+
*
|
|
291
|
+
* The function is intentionally adapter-driven: it does not import product
|
|
292
|
+
* repositories, auth systems, or tool handlers. Consumers mount these routes
|
|
293
|
+
* inside their own API surface and supply product-specific adapters.
|
|
294
|
+
*/
|
|
295
|
+
export declare function registerMcpHonoRoutes<S extends McpScopeValue, C>(app: Hono, options: RegisterMcpHonoRoutesOptions<S, C>): void;
|