@forwardreach/saas-mcp 0.1.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 (46) hide show
  1. package/README.md +7 -0
  2. package/dist/client/index.d.ts +1 -0
  3. package/dist/client/index.js +1 -0
  4. package/dist/client/management.d.ts +80 -0
  5. package/dist/client/management.js +78 -0
  6. package/dist/core/http.d.ts +27 -0
  7. package/dist/core/http.js +29 -0
  8. package/dist/core/index.d.ts +5 -0
  9. package/dist/core/index.js +5 -0
  10. package/dist/core/redaction.d.ts +11 -0
  11. package/dist/core/redaction.js +43 -0
  12. package/dist/core/scopes.d.ts +13 -0
  13. package/dist/core/scopes.js +35 -0
  14. package/dist/core/types.d.ts +256 -0
  15. package/dist/core/types.js +1 -0
  16. package/dist/core/urls.d.ts +69 -0
  17. package/dist/core/urls.js +85 -0
  18. package/dist/hono/index.d.ts +1 -0
  19. package/dist/hono/index.js +1 -0
  20. package/dist/hono/routes.d.ts +212 -0
  21. package/dist/hono/routes.js +535 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +1 -0
  24. package/dist/node/better-auth.d.ts +84 -0
  25. package/dist/node/better-auth.js +77 -0
  26. package/dist/node/index.d.ts +2 -0
  27. package/dist/node/index.js +2 -0
  28. package/dist/node/oauth.d.ts +18 -0
  29. package/dist/node/oauth.js +39 -0
  30. package/dist/react/McpActivity.d.ts +32 -0
  31. package/dist/react/McpActivity.js +182 -0
  32. package/dist/react/McpConnectedClients.d.ts +27 -0
  33. package/dist/react/McpConnectedClients.js +34 -0
  34. package/dist/react/McpConnectorOverview.d.ts +21 -0
  35. package/dist/react/McpConnectorOverview.js +12 -0
  36. package/dist/react/McpConsentPanel.d.ts +47 -0
  37. package/dist/react/McpConsentPanel.js +18 -0
  38. package/dist/react/McpDeveloperTokens.d.ts +27 -0
  39. package/dist/react/McpDeveloperTokens.js +46 -0
  40. package/dist/react/McpToolReference.d.ts +17 -0
  41. package/dist/react/McpToolReference.js +19 -0
  42. package/dist/react/index.d.ts +6 -0
  43. package/dist/react/index.js +6 -0
  44. package/dist/react/mcp-settings-shared.d.ts +29 -0
  45. package/dist/react/mcp-settings-shared.js +37 -0
  46. package/package.json +98 -0
@@ -0,0 +1,69 @@
1
+ import type { McpScopeValue } from "./types.js";
2
+ /** Public route paths advertised through OAuth/MCP metadata documents. */
3
+ export interface McpOAuthPublicRoutes {
4
+ protectedResourceMetadataPath: string;
5
+ authorizationServerMetadataPath: string;
6
+ authorizePath: string;
7
+ consentPagePath?: string;
8
+ tokenPath: string;
9
+ registrationPath: string;
10
+ revocationPath: string;
11
+ mcpEndpointPath: string;
12
+ }
13
+ /** Inputs needed to build product-specific OAuth discovery metadata. */
14
+ export interface McpOAuthMetadataInput<S extends McpScopeValue = McpScopeValue> {
15
+ appBaseUrl: string;
16
+ resourceUri: string;
17
+ supportedScopes: readonly S[];
18
+ routes: McpOAuthPublicRoutes;
19
+ }
20
+ /** OAuth protected-resource metadata for the MCP endpoint. */
21
+ export interface McpProtectedResourceMetadata<S extends McpScopeValue = McpScopeValue> {
22
+ resource: string;
23
+ authorization_servers: string[];
24
+ bearer_methods_supported: string[];
25
+ scopes_supported: S[];
26
+ resource_documentation: string;
27
+ }
28
+ /** OAuth authorization-server metadata for MCP client registration and tokens. */
29
+ export interface McpAuthorizationServerMetadata<S extends McpScopeValue = McpScopeValue> {
30
+ issuer: string;
31
+ authorization_endpoint: string;
32
+ token_endpoint: string;
33
+ registration_endpoint: string;
34
+ revocation_endpoint: string;
35
+ response_types_supported: string[];
36
+ grant_types_supported: string[];
37
+ token_endpoint_auth_methods_supported: string[];
38
+ code_challenge_methods_supported: string[];
39
+ scopes_supported: S[];
40
+ resource_parameter_supported: boolean;
41
+ protected_resources: string[];
42
+ mcp_endpoint: string;
43
+ }
44
+ /** Inputs for building a WWW-Authenticate bearer challenge. */
45
+ export interface McpBearerChallengeInput<S extends McpScopeValue = McpScopeValue> {
46
+ appBaseUrl: string;
47
+ protectedResourceMetadataPath: string;
48
+ scopes: readonly S[];
49
+ }
50
+ /** Remove trailing slashes from an origin or base URL. */
51
+ export declare function trimTrailingSlash(value: string): string;
52
+ /** Join an app base URL and path without duplicating slashes. */
53
+ export declare function joinUrlPath(appBaseUrl: string, path: string): string;
54
+ /** Build the canonical MCP resource URI from a public app URL and endpoint path. */
55
+ export declare function deriveMcpResourceUri(appBaseUrl: string, mcpEndpointPath: string): string;
56
+ /** Extract an origin from a request URL, returning null for invalid input. */
57
+ export declare function originFromRequestUrl(requestUrl?: string): string | null;
58
+ /** Return whether an origin is a localhost or loopback development origin. */
59
+ export declare function isLocalOrigin(origin: string): boolean;
60
+ /** Return whether an origin is public HTTPS rather than local development. */
61
+ export declare function isPublicHttpsOrigin(origin: string): boolean;
62
+ /** Extract a hostname from a URL, returning null for invalid input. */
63
+ export declare function hostFromUrl(value: string): string | null;
64
+ /** Build RFC 9728 protected-resource metadata for the MCP endpoint. */
65
+ export declare function buildMcpProtectedResourceMetadata<S extends McpScopeValue>(input: McpOAuthMetadataInput<S>): McpProtectedResourceMetadata<S>;
66
+ /** Build OAuth authorization-server metadata for MCP clients. */
67
+ export declare function buildMcpAuthorizationServerMetadata<S extends McpScopeValue>(input: McpOAuthMetadataInput<S>): McpAuthorizationServerMetadata<S>;
68
+ /** Build the bearer challenge returned with unauthorized MCP requests. */
69
+ export declare function buildMcpBearerChallenge<S extends McpScopeValue>(input: McpBearerChallengeInput<S>): string;
@@ -0,0 +1,85 @@
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
+ /** Build the canonical MCP resource URI from a public app URL and endpoint path. */
11
+ export function deriveMcpResourceUri(appBaseUrl, mcpEndpointPath) {
12
+ return joinUrlPath(appBaseUrl, mcpEndpointPath);
13
+ }
14
+ /** Extract an origin from a request URL, returning null for invalid input. */
15
+ export function originFromRequestUrl(requestUrl) {
16
+ if (!requestUrl)
17
+ return null;
18
+ try {
19
+ return new URL(requestUrl).origin;
20
+ }
21
+ catch {
22
+ return null;
23
+ }
24
+ }
25
+ /** Return whether an origin is a localhost or loopback development origin. */
26
+ export function isLocalOrigin(origin) {
27
+ try {
28
+ const url = new URL(origin);
29
+ return url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "::1";
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /** Return whether an origin is public HTTPS rather than local development. */
36
+ export function isPublicHttpsOrigin(origin) {
37
+ try {
38
+ const url = new URL(origin);
39
+ return url.protocol === "https:" && !isLocalOrigin(url.origin);
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ }
45
+ /** Extract a hostname from a URL, returning null for invalid input. */
46
+ export function hostFromUrl(value) {
47
+ try {
48
+ return new URL(value).host;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
54
+ /** Build RFC 9728 protected-resource metadata for the MCP endpoint. */
55
+ export function buildMcpProtectedResourceMetadata(input) {
56
+ return {
57
+ resource: input.resourceUri,
58
+ authorization_servers: [joinUrlPath(input.appBaseUrl, input.routes.authorizationServerMetadataPath)],
59
+ bearer_methods_supported: ["header"],
60
+ scopes_supported: [...input.supportedScopes],
61
+ resource_documentation: trimTrailingSlash(input.appBaseUrl)
62
+ };
63
+ }
64
+ /** Build OAuth authorization-server metadata for MCP clients. */
65
+ export function buildMcpAuthorizationServerMetadata(input) {
66
+ return {
67
+ issuer: trimTrailingSlash(input.appBaseUrl),
68
+ authorization_endpoint: joinUrlPath(input.appBaseUrl, input.routes.authorizePath),
69
+ token_endpoint: joinUrlPath(input.appBaseUrl, input.routes.tokenPath),
70
+ registration_endpoint: joinUrlPath(input.appBaseUrl, input.routes.registrationPath),
71
+ revocation_endpoint: joinUrlPath(input.appBaseUrl, input.routes.revocationPath),
72
+ response_types_supported: ["code"],
73
+ grant_types_supported: ["authorization_code", "refresh_token"],
74
+ token_endpoint_auth_methods_supported: ["none"],
75
+ code_challenge_methods_supported: ["S256"],
76
+ scopes_supported: [...input.supportedScopes],
77
+ resource_parameter_supported: true,
78
+ protected_resources: [joinUrlPath(input.appBaseUrl, input.routes.protectedResourceMetadataPath)],
79
+ mcp_endpoint: joinUrlPath(input.appBaseUrl, input.routes.mcpEndpointPath)
80
+ };
81
+ }
82
+ /** Build the bearer challenge returned with unauthorized MCP requests. */
83
+ export function buildMcpBearerChallenge(input) {
84
+ return `Bearer resource_metadata="${joinUrlPath(input.appBaseUrl, input.protectedResourceMetadataPath)}", scope="${formatMcpScopeString(input.scopes)}"`;
85
+ }
@@ -0,0 +1 @@
1
+ export * from "./routes.js";
@@ -0,0 +1 @@
1
+ export * from "./routes.js";
@@ -0,0 +1,212 @@
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.js";
4
+ import type { OAuthAuthorizationCodeRecord, OAuthClientRecord, OAuthTokenRecord } from "../core/types.js";
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
+ /** GET: OAuth protected-resource metadata for the MCP endpoint. */
14
+ protectedResourceMetadata: string;
15
+ /** GET: OAuth authorization-server metadata for dynamic MCP clients. */
16
+ authorizationServerMetadata: string;
17
+ /** GET renders consent; POST accepts approve/cancel consent decisions. */
18
+ authorize: string;
19
+ /** POST: OAuth token exchange for authorization_code and refresh_token grants. */
20
+ token: string;
21
+ /** POST: OAuth token revocation endpoint for client-initiated revocation. */
22
+ revocation: string;
23
+ /** POST: dynamic client registration for MCP OAuth clients. */
24
+ registration: string;
25
+ /** Streamable HTTP MCP endpoint that receives JSON-RPC MCP requests. */
26
+ mcp: string;
27
+ }
28
+ /**
29
+ * Per-request runtime settings used by the shared MCP router.
30
+ *
31
+ * The consuming product owns deployment-specific values such as public URLs,
32
+ * HTTPS policy, allowed origins/hosts, supported scopes, and token TTLs. This
33
+ * keeps the shared package independent of Next.js, Railway, Cloudflare, or any
34
+ * product-specific hosting model.
35
+ */
36
+ export interface McpHonoRuntimeConfig<S extends McpScopeValue = McpScopeValue> {
37
+ appBaseUrl: string;
38
+ resourceUri: string;
39
+ requireHttps: boolean;
40
+ allowedOrigins: readonly string[];
41
+ allowedHosts: readonly string[];
42
+ supportedScopes: readonly S[];
43
+ defaultScopes: readonly S[];
44
+ latestProtocolVersion: string;
45
+ supportedProtocolVersions: readonly string[];
46
+ routes: McpOAuthPublicRoutes;
47
+ accessTokenTtlSeconds: number;
48
+ refreshTokenTtlSeconds: number;
49
+ authorizationCodeTtlSeconds: number;
50
+ tokenPrefixes?: {
51
+ authorizationCode?: string;
52
+ accessToken?: string;
53
+ refreshToken?: string;
54
+ };
55
+ }
56
+ /**
57
+ * Human identity selected for an OAuth consent decision.
58
+ *
59
+ * Products should resolve this from their signed-in app user and membership
60
+ * model. Query/body user IDs are hints only; products must not trust forged
61
+ * user or workspace identifiers from OAuth clients.
62
+ */
63
+ export interface McpConsentIdentity {
64
+ workspaceId: string;
65
+ userId: string;
66
+ }
67
+ /**
68
+ * Product-owned storage, identity, membership, audit, and tool adapters used by
69
+ * the shared Hono implementation.
70
+ *
71
+ * The shared router owns protocol mechanics and DTO shape. The product owns all
72
+ * durable records, workspace authorization, human identity lookup, audit writes,
73
+ * and construction of the product-specific `McpServer` instance.
74
+ */
75
+ export interface McpHonoAdapters<S extends McpScopeValue, C> {
76
+ /** Create or update a dynamically registered OAuth client. */
77
+ registerOAuthClient(input: {
78
+ name: string;
79
+ displayName?: string;
80
+ redirectUris: string[];
81
+ scopes: S[];
82
+ }): Promise<OAuthClientRecord<S>>;
83
+ /** Look up an OAuth client by client_id. */
84
+ getOAuthClient(clientId: string): Promise<OAuthClientRecord<S> | null>;
85
+ /** Persist a hashed, single-use OAuth authorization code after consent. */
86
+ createOAuthAuthorizationCode(input: {
87
+ codeHash: string;
88
+ clientId: string;
89
+ redirectUri: string;
90
+ workspaceId: string;
91
+ userId: string;
92
+ scopes: S[];
93
+ resource: string;
94
+ codeChallenge: string;
95
+ codeChallengeMethod: "S256";
96
+ expiresAt: string;
97
+ }): Promise<OAuthAuthorizationCodeRecord<S>>;
98
+ /** Atomically consume a valid authorization code during token exchange. */
99
+ consumeOAuthAuthorizationCode(input: {
100
+ codeHash: string;
101
+ clientId: string;
102
+ redirectUri: string;
103
+ resource: string;
104
+ now?: string;
105
+ }): Promise<OAuthAuthorizationCodeRecord<S> | null>;
106
+ /** Store a hashed OAuth access or refresh token. */
107
+ issueOAuthToken(input: {
108
+ tokenHash: string;
109
+ kind: OAuthTokenRecord<S>["kind"];
110
+ grantId: string;
111
+ clientId: string;
112
+ workspaceId: string;
113
+ userId: string;
114
+ scopes: S[];
115
+ resource: string;
116
+ sourceKind?: OAuthTokenRecord<S>["sourceKind"];
117
+ displayName?: string | null;
118
+ expiresAt: string;
119
+ }): Promise<OAuthTokenRecord<S>>;
120
+ /** Find a token record by hash, optionally including revoked tokens. */
121
+ getOAuthTokenByHash(input: {
122
+ tokenHash: string;
123
+ kind?: OAuthTokenRecord<S>["kind"];
124
+ resource?: string;
125
+ includeRevoked?: boolean;
126
+ }): Promise<OAuthTokenRecord<S> | null>;
127
+ /** Validate a token for bearer use and optionally update last-used metadata. */
128
+ validateOAuthToken(input: {
129
+ tokenHash: string;
130
+ kind?: OAuthTokenRecord<S>["kind"];
131
+ resource: string;
132
+ now?: string;
133
+ recordLastUsed?: boolean;
134
+ }): Promise<OAuthTokenRecord<S> | null>;
135
+ /** Revoke an OAuth or developer token by token hash. */
136
+ revokeOAuthToken(tokenHash: string, input?: {
137
+ revokedByUserId?: string | null;
138
+ revokedReason?: string | null;
139
+ }): Promise<boolean>;
140
+ /** Confirm the token or consent user still belongs to the selected workspace. */
141
+ hasWorkspaceMembership(input: {
142
+ workspaceId: string;
143
+ userId: string;
144
+ }): Promise<boolean>;
145
+ /** Resolve the signed-in human who is viewing or submitting OAuth consent. */
146
+ resolveConsentIdentity(input: {
147
+ headers: Headers;
148
+ workspaceId?: string | null;
149
+ userId?: string | null;
150
+ }): Promise<McpConsentIdentity>;
151
+ /** Record a newly initialized Streamable HTTP MCP session. */
152
+ trackMcpSession(input: {
153
+ id: string;
154
+ accessTokenId: string;
155
+ clientId: string;
156
+ workspaceId: string;
157
+ userId: string;
158
+ scopes: S[];
159
+ protocolVersion?: string | null;
160
+ }): Promise<unknown>;
161
+ /** Refresh session metadata after a valid non-initialize MCP request. */
162
+ touchMcpSession(sessionId: string, input?: {
163
+ accessTokenId?: string;
164
+ scopes?: S[];
165
+ }): Promise<unknown>;
166
+ /** Mark a Streamable HTTP MCP session closed. */
167
+ closeMcpSession(sessionId: string): Promise<unknown>;
168
+ /** Persist an MCP audit event after tool calls or management activity. */
169
+ recordMcpAuditEvent(input: {
170
+ workspaceId: string;
171
+ userId: string;
172
+ clientId: string;
173
+ accessTokenId?: string | null;
174
+ sourceKind?: OAuthTokenRecord<S>["sourceKind"];
175
+ activityKind?: string;
176
+ toolName: string;
177
+ targetRecordIds?: string[];
178
+ outcome: McpAuditEvent["outcome"];
179
+ metadata?: Record<string, unknown>;
180
+ }): Promise<McpAuditEvent>;
181
+ /** Build the product-specific tool execution context from the bearer token. */
182
+ createMcpContext(input: {
183
+ token: OAuthTokenRecord<S>;
184
+ config: McpHonoRuntimeConfig<S>;
185
+ }): C;
186
+ /** Update an existing session context when token metadata changes. */
187
+ refreshMcpContext?(context: C, token: OAuthTokenRecord<S>): C | void;
188
+ /** Create the product MCP server and register product-owned tools. */
189
+ createMcpServer(context: C): McpServer;
190
+ /** Optional lookup used to classify tool audit activity before execution. */
191
+ getToolByName?(name: string): McpToolDescriptor<S> | undefined;
192
+ }
193
+ /** Options for registering the shared MCP OAuth and Streamable HTTP routes. */
194
+ export interface RegisterMcpHonoRoutesOptions<S extends McpScopeValue, C> {
195
+ /** Resolve runtime configuration for the incoming request URL. */
196
+ resolveConfig(requestUrl?: string): McpHonoRuntimeConfig<S>;
197
+ /** Product-local route mount points. */
198
+ mounts: McpHonoRouteMounts;
199
+ /** Product adapters for persistence, identity, tools, and audit. */
200
+ adapters: McpHonoAdapters<S, C>;
201
+ /** Generate an MCP session id for initialized Streamable HTTP sessions. */
202
+ sessionIdGenerator(): string;
203
+ }
204
+ /**
205
+ * Register OAuth discovery, dynamic client registration, authorization-code
206
+ * flow, token revocation, and Streamable HTTP MCP routes on a Hono app.
207
+ *
208
+ * The function is intentionally adapter-driven: it does not import product
209
+ * repositories, auth systems, or tool handlers. Consumers mount these routes
210
+ * inside their own API surface and supply product-specific adapters.
211
+ */
212
+ export declare function registerMcpHonoRoutes<S extends McpScopeValue, C>(app: Hono, options: RegisterMcpHonoRoutesOptions<S, C>): void;