@softure-ai/mcp-access 0.1.6 → 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.
Files changed (148) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/README.md +146 -21
  3. package/adoption/move-app-tables.sql +100 -0
  4. package/dist/contract.d.ts +13 -1
  5. package/dist/contract.d.ts.map +1 -1
  6. package/dist/index.d.ts +58 -3
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +26 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/messages/en.d.ts +28 -0
  11. package/dist/messages/en.d.ts.map +1 -1
  12. package/dist/messages/en.js +28 -0
  13. package/dist/messages/en.js.map +1 -1
  14. package/dist/messages/index.d.ts +56 -0
  15. package/dist/messages/index.d.ts.map +1 -1
  16. package/dist/messages/pl.d.ts.map +1 -1
  17. package/dist/messages/pl.js +28 -0
  18. package/dist/messages/pl.js.map +1 -1
  19. package/dist/next/actions.d.ts +3 -1
  20. package/dist/next/actions.d.ts.map +1 -1
  21. package/dist/next/actions.js +23 -2
  22. package/dist/next/actions.js.map +1 -1
  23. package/dist/next/context.d.ts +7 -1
  24. package/dist/next/context.d.ts.map +1 -1
  25. package/dist/next/context.js +12 -10
  26. package/dist/next/context.js.map +1 -1
  27. package/dist/next/index.d.ts +4 -1
  28. package/dist/next/index.d.ts.map +1 -1
  29. package/dist/next/index.js +5 -2
  30. package/dist/next/index.js.map +1 -1
  31. package/dist/next/oauth-page.d.ts +7 -0
  32. package/dist/next/oauth-page.d.ts.map +1 -0
  33. package/dist/next/oauth-page.js +36 -0
  34. package/dist/next/oauth-page.js.map +1 -0
  35. package/dist/next/oauth-routes.d.ts +19 -0
  36. package/dist/next/oauth-routes.d.ts.map +1 -0
  37. package/dist/next/oauth-routes.js +53 -0
  38. package/dist/next/oauth-routes.js.map +1 -0
  39. package/dist/next/pages.d.ts.map +1 -1
  40. package/dist/next/pages.js +13 -2
  41. package/dist/next/pages.js.map +1 -1
  42. package/dist/options.d.ts +40 -0
  43. package/dist/options.d.ts.map +1 -1
  44. package/dist/options.js +94 -1
  45. package/dist/options.js.map +1 -1
  46. package/dist/origins.d.ts +27 -0
  47. package/dist/origins.d.ts.map +1 -0
  48. package/dist/origins.js +32 -0
  49. package/dist/origins.js.map +1 -0
  50. package/dist/schema.d.ts +486 -0
  51. package/dist/schema.d.ts.map +1 -1
  52. package/dist/schema.js +45 -3
  53. package/dist/schema.js.map +1 -1
  54. package/dist/server/context.d.ts +4 -0
  55. package/dist/server/context.d.ts.map +1 -0
  56. package/dist/server/context.js +13 -0
  57. package/dist/server/context.js.map +1 -0
  58. package/dist/server/endpoint.d.ts +2 -4
  59. package/dist/server/endpoint.d.ts.map +1 -1
  60. package/dist/server/endpoint.js +14 -9
  61. package/dist/server/endpoint.js.map +1 -1
  62. package/dist/server/health.d.ts.map +1 -1
  63. package/dist/server/health.js +5 -3
  64. package/dist/server/health.js.map +1 -1
  65. package/dist/server/index.d.ts +8 -0
  66. package/dist/server/index.d.ts.map +1 -1
  67. package/dist/server/index.js +7 -0
  68. package/dist/server/index.js.map +1 -1
  69. package/dist/server/oauth-http.d.ts +127 -0
  70. package/dist/server/oauth-http.d.ts.map +1 -0
  71. package/dist/server/oauth-http.js +397 -0
  72. package/dist/server/oauth-http.js.map +1 -0
  73. package/dist/server/oauth-validation.d.ts +28 -0
  74. package/dist/server/oauth-validation.d.ts.map +1 -0
  75. package/dist/server/oauth-validation.js +88 -0
  76. package/dist/server/oauth-validation.js.map +1 -0
  77. package/dist/server/oauth.d.ts +106 -0
  78. package/dist/server/oauth.d.ts.map +1 -0
  79. package/dist/server/oauth.js +235 -0
  80. package/dist/server/oauth.js.map +1 -0
  81. package/dist/server/options.d.ts +11 -2
  82. package/dist/server/options.d.ts.map +1 -1
  83. package/dist/server/options.js +11 -4
  84. package/dist/server/options.js.map +1 -1
  85. package/dist/server/origins.d.ts +8 -0
  86. package/dist/server/origins.d.ts.map +1 -0
  87. package/dist/server/origins.js +15 -0
  88. package/dist/server/origins.js.map +1 -0
  89. package/dist/server/pkce.d.ts +7 -0
  90. package/dist/server/pkce.d.ts.map +1 -0
  91. package/dist/server/pkce.js +24 -0
  92. package/dist/server/pkce.js.map +1 -0
  93. package/dist/server/privacy.d.ts +7 -0
  94. package/dist/server/privacy.d.ts.map +1 -1
  95. package/dist/server/privacy.js +19 -9
  96. package/dist/server/privacy.js.map +1 -1
  97. package/dist/server/scopes.d.ts +5 -0
  98. package/dist/server/scopes.d.ts.map +1 -0
  99. package/dist/server/scopes.js +6 -0
  100. package/dist/server/scopes.js.map +1 -0
  101. package/dist/server/tokens.d.ts +18 -6
  102. package/dist/server/tokens.d.ts.map +1 -1
  103. package/dist/server/tokens.js +60 -25
  104. package/dist/server/tokens.js.map +1 -1
  105. package/dist/ui/consent-form.d.ts +31 -0
  106. package/dist/ui/consent-form.d.ts.map +1 -0
  107. package/dist/ui/consent-form.js +24 -0
  108. package/dist/ui/consent-form.js.map +1 -0
  109. package/dist/ui/index.d.ts +2 -1
  110. package/dist/ui/index.d.ts.map +1 -1
  111. package/dist/ui/index.js +1 -0
  112. package/dist/ui/index.js.map +1 -1
  113. package/dist/ui/token-manager.d.ts +16 -2
  114. package/dist/ui/token-manager.d.ts.map +1 -1
  115. package/dist/ui/token-manager.js +6 -2
  116. package/dist/ui/token-manager.js.map +1 -1
  117. package/migrations/0002_create_oauth_grants.sql +68 -0
  118. package/module.json +18 -4
  119. package/package.json +2 -1
  120. package/src/contract.ts +16 -1
  121. package/src/index.ts +33 -4
  122. package/src/messages/en.ts +28 -0
  123. package/src/messages/pl.ts +28 -0
  124. package/src/next/actions.ts +23 -3
  125. package/src/next/context.ts +15 -8
  126. package/src/next/index.ts +12 -2
  127. package/src/next/oauth-page.tsx +68 -0
  128. package/src/next/oauth-routes.ts +73 -0
  129. package/src/next/pages.tsx +16 -3
  130. package/src/options.ts +107 -1
  131. package/src/origins.ts +48 -0
  132. package/src/schema.ts +51 -3
  133. package/src/server/context.ts +14 -0
  134. package/src/server/endpoint.ts +14 -9
  135. package/src/server/health.ts +5 -3
  136. package/src/server/index.ts +56 -0
  137. package/src/server/oauth-http.ts +480 -0
  138. package/src/server/oauth-validation.ts +106 -0
  139. package/src/server/oauth.ts +315 -0
  140. package/src/server/options.ts +20 -4
  141. package/src/server/origins.ts +19 -0
  142. package/src/server/pkce.ts +27 -0
  143. package/src/server/privacy.ts +26 -9
  144. package/src/server/scopes.ts +6 -0
  145. package/src/server/tokens.ts +62 -25
  146. package/src/ui/consent-form.tsx +103 -0
  147. package/src/ui/index.ts +3 -0
  148. package/src/ui/token-manager.tsx +79 -3
@@ -43,8 +43,12 @@ function TokenItem({ row, action, messages, slot, unstyled, }) {
43
43
  const [state, formAction, isPending] = useActionState(action, { status: "idle" });
44
44
  return (_jsxs("li", { className: slot("item"), "data-token-id": row.id, children: [_jsxs("div", { className: slot("itemHeader"), children: [_jsx("p", { className: slot("heading"), children: row.name }), _jsxs("form", { action: formAction, children: [_jsx("input", { type: "hidden", name: "id", value: row.id }), _jsx(Button, { type: "submit", variant: "danger", size: "sm", pending: isPending, "aria-label": `${messages.list.revoke}: ${row.name}`, unstyled: unstyled, children: isPending ? messages.list.revoking : messages.list.revoke })] })] }), _jsxs("p", { className: row.isExpired ? slot("details") : slot("badge"), children: [row.statusText, " \u00B7 ", row.scopeText] }), _jsx("p", { className: slot("details"), children: row.details.join(" · ") }), _jsx(FormError, { message: state.status === "error" ? getTokenErrorMessage(messages, state.error) : undefined, unstyled: unstyled })] }));
45
45
  }
46
- export function TokenManager({ tools, tokens, allowWrites, maxTokens, issueAction, revokeAction, messages, locale, classNames, unstyled }) {
46
+ function GrantItem({ grant, action, messages, slot, unstyled, }) {
47
+ const [state, formAction, isPending] = useActionState(action, { status: "idle" });
48
+ return (_jsxs("li", { className: slot("item"), "data-grant-id": grant.id, children: [_jsxs("div", { className: slot("itemHeader"), children: [_jsx("p", { className: slot("heading"), children: grant.clientName }), _jsxs("form", { action: formAction, children: [_jsx("input", { type: "hidden", name: "id", value: grant.id }), _jsx(Button, { type: "submit", variant: "danger", size: "sm", pending: isPending, "aria-label": `${messages.grants.disconnect}: ${grant.clientName}`, unstyled: unstyled, children: isPending ? messages.grants.disconnecting : messages.grants.disconnect })] })] }), _jsx("p", { className: slot("badge"), children: grant.scopeText }), _jsx("p", { className: slot("details"), children: grant.details.join(" · ") }), _jsx(FormError, { message: state.status === "error" ? getTokenErrorMessage(messages, state.error) : undefined, unstyled: unstyled })] }));
49
+ }
50
+ export function TokenManager({ tools, tokens, allowWrites, maxTokens, issueAction, revokeAction, grants, revokeGrantAction, messages, locale, classNames, unstyled, }) {
47
51
  const slot = createSlotClassGetter({ defaults: DEFAULT_CLASSES, classNames, unstyled });
48
- return (_jsxs("div", { className: slot("root"), children: [_jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.tools.title }), tools.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.tools.empty })) : (_jsx("ul", { className: slot("list"), children: tools.map((tool) => (_jsxs("li", { className: slot("item"), children: [_jsxs("div", { className: slot("itemHeader"), children: [_jsx("code", { className: slot("heading"), children: tool.name }), _jsx("span", { className: slot("badge"), children: tool.accessText })] }), _jsx("p", { className: slot("text"), children: tool.description })] }, tool.name))) }))] }), _jsx(IssueForm, { action: issueAction, allowWrites: allowWrites, maxTokens: maxTokens, messages: messages, locale: locale, slot: slot, unstyled: unstyled }), _jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.list.title }), tokens.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.list.empty })) : (_jsx("ul", { className: slot("list"), children: tokens.map((row) => (_jsx(TokenItem, { row: row, action: revokeAction, messages: messages, slot: slot, unstyled: unstyled }, row.id))) }))] })] }));
52
+ return (_jsxs("div", { className: slot("root"), children: [_jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.tools.title }), tools.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.tools.empty })) : (_jsx("ul", { className: slot("list"), children: tools.map((tool) => (_jsxs("li", { className: slot("item"), children: [_jsxs("div", { className: slot("itemHeader"), children: [_jsx("code", { className: slot("heading"), children: tool.name }), _jsx("span", { className: slot("badge"), children: tool.accessText })] }), _jsx("p", { className: slot("text"), children: tool.description })] }, tool.name))) }))] }), _jsx(IssueForm, { action: issueAction, allowWrites: allowWrites, maxTokens: maxTokens, messages: messages, locale: locale, slot: slot, unstyled: unstyled }), _jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.list.title }), tokens.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.list.empty })) : (_jsx("ul", { className: slot("list"), children: tokens.map((row) => (_jsx(TokenItem, { row: row, action: revokeAction, messages: messages, slot: slot, unstyled: unstyled }, row.id))) }))] }), grants === undefined || revokeGrantAction === undefined ? null : (_jsxs("section", { className: slot("section"), children: [_jsx("h2", { className: slot("heading"), children: messages.grants.title }), _jsx("p", { className: slot("text"), children: messages.grants.lead }), grants.length === 0 ? (_jsx("p", { className: slot("text"), children: messages.grants.empty })) : (_jsx("ul", { className: slot("list"), children: grants.map((grant) => (_jsx(GrantItem, { grant: grant, action: revokeGrantAction, messages: messages, slot: slot, unstyled: unstyled }, grant.id))) }))] }))] }));
49
53
  }
50
54
  //# sourceMappingURL=token-manager.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/ui/token-manager.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"token-manager.js","sourceRoot":"","sources":["../../src/ui/token-manager.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ -- OAuth 2.1 for MCP clients (opt-in, `oauth.enabled`): clients registered dynamically, short-lived
2
+ -- authorization codes, and one grant per account and client carrying a rotated refresh token. An
3
+ -- access token issued through OAuth is a row of access_tokens with its grant; revoking the grant
4
+ -- deletes its tokens by cascade. Every secret is stored as its sha256 only.
5
+ -- Rollback: ALTER TABLE mcp.access_tokens DROP COLUMN grant_id;
6
+ -- DROP TABLE mcp.oauth_grants, mcp.oauth_authorization_codes, mcp.oauth_clients; then
7
+ -- DELETE FROM softure.migrations WHERE module = 'mcp-access' AND version = 2;
8
+ CREATE TABLE oauth_clients (
9
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
10
+ -- Public identifier the client sends (RFC 6749 §2.2); not a secret.
11
+ client_id text NOT NULL CHECK (char_length(client_id) BETWEEN 1 AND 255),
12
+ -- Shown on the consent page and in the connected apps list; also the name of its access tokens.
13
+ client_name text NOT NULL CHECK (char_length(client_name) BETWEEN 1 AND 60 AND client_name = btrim(client_name)),
14
+ redirect_uris jsonb NOT NULL CHECK (jsonb_typeof(redirect_uris) = 'array' AND jsonb_array_length(redirect_uris) BETWEEN 1 AND 10),
15
+ token_endpoint_auth_method text NOT NULL CHECK (token_endpoint_auth_method IN ('none', 'client_secret_post', 'client_secret_basic')),
16
+ -- Only for confidential clients; a public client (`none`) is protected by PKCE alone.
17
+ client_secret_hash text CHECK (client_secret_hash ~ '^[0-9a-f]{64}$'),
18
+ created_at timestamptz NOT NULL,
19
+ CONSTRAINT oauth_clients_client_id_key UNIQUE (client_id),
20
+ CONSTRAINT oauth_clients_secret_matches_method CHECK ((token_endpoint_auth_method = 'none') = (client_secret_hash IS NULL))
21
+ );
22
+
23
+ CREATE TABLE oauth_authorization_codes (
24
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
25
+ code_hash text NOT NULL CHECK (code_hash ~ '^[0-9a-f]{64}$'),
26
+ client_id uuid NOT NULL REFERENCES oauth_clients (id) ON DELETE CASCADE,
27
+ user_id uuid NOT NULL REFERENCES auth.users (id) ON DELETE CASCADE,
28
+ redirect_uri text NOT NULL,
29
+ -- PKCE S256 challenge: base64url of a sha256, always 43 characters.
30
+ code_challenge text NOT NULL CHECK (code_challenge ~ '^[A-Za-z0-9_-]{43}$'),
31
+ can_write boolean NOT NULL,
32
+ expires_at timestamptz NOT NULL,
33
+ -- Set by the one exchange that may use the code; a second use revokes the grant it produced.
34
+ used_at timestamptz,
35
+ created_at timestamptz NOT NULL,
36
+ CHECK (expires_at > created_at),
37
+ CONSTRAINT oauth_authorization_codes_code_hash_key UNIQUE (code_hash)
38
+ );
39
+
40
+ CREATE INDEX oauth_authorization_codes_client_id_idx ON oauth_authorization_codes (client_id);
41
+ CREATE INDEX oauth_authorization_codes_user_id_idx ON oauth_authorization_codes (user_id);
42
+ CREATE INDEX oauth_authorization_codes_expires_at_idx ON oauth_authorization_codes (expires_at);
43
+
44
+ CREATE TABLE oauth_grants (
45
+ id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
46
+ user_id uuid NOT NULL REFERENCES auth.users (id) ON DELETE CASCADE,
47
+ client_id uuid NOT NULL REFERENCES oauth_clients (id) ON DELETE CASCADE,
48
+ -- Granted write access; writes also need the app's allowWrites option.
49
+ can_write boolean NOT NULL,
50
+ refresh_token_hash text NOT NULL CHECK (refresh_token_hash ~ '^[0-9a-f]{64}$'),
51
+ -- The refresh token before the last rotation: presenting it again revokes the grant.
52
+ previous_refresh_token_hash text CHECK (previous_refresh_token_hash ~ '^[0-9a-f]{64}$'),
53
+ refresh_expires_at timestamptz NOT NULL,
54
+ -- Informational, written at most once a minute; never an access decision.
55
+ last_used_at timestamptz,
56
+ created_at timestamptz NOT NULL,
57
+ CONSTRAINT oauth_grants_refresh_token_hash_key UNIQUE (refresh_token_hash),
58
+ -- One grant per account and client: a new consent replaces the old grant.
59
+ CONSTRAINT oauth_grants_user_id_client_id_key UNIQUE (user_id, client_id)
60
+ );
61
+
62
+ CREATE INDEX oauth_grants_client_id_idx ON oauth_grants (client_id);
63
+ CREATE INDEX oauth_grants_previous_refresh_token_hash_idx ON oauth_grants (previous_refresh_token_hash);
64
+ CREATE INDEX oauth_grants_refresh_expires_at_idx ON oauth_grants (refresh_expires_at);
65
+
66
+ -- The grant an access token was issued from; null for a token issued on the token page.
67
+ ALTER TABLE access_tokens ADD COLUMN grant_id uuid REFERENCES oauth_grants (id) ON DELETE CASCADE;
68
+ CREATE INDEX access_tokens_grant_id_idx ON access_tokens (grant_id);
package/module.json CHANGED
@@ -1,15 +1,29 @@
1
1
  {
2
2
  "id": "mcp-access",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "dependsOn": { "security": "^0.1.0", "auth": "^0.1.0" },
5
5
  "dbSchema": "mcp",
6
- "tables": ["access_tokens"],
6
+ "tables": ["access_tokens", "oauth_clients", "oauth_authorization_codes", "oauth_grants"],
7
7
  "env": [],
8
8
  "switches": [],
9
- "routes": { "page": "/account/mcp", "endpoint": "/api/mcp" },
9
+ "routes": {
10
+ "page": "/account/mcp",
11
+ "endpoint": "/api/mcp",
12
+ "oauthConsent": "/oauth/authorize",
13
+ "oauthDecision": "/api/oauth/authorize",
14
+ "oauthToken": "/api/oauth/token",
15
+ "oauthRegister": "/api/oauth/register"
16
+ },
10
17
  "mount": [
11
18
  { "kind": "page", "path": "app/account/mcp/page.tsx", "export": "McpAccessPage" },
12
- { "kind": "route-handler", "path": "app/api/mcp/route.ts", "export": "createMcpRoute" }
19
+ { "kind": "route-handler", "path": "app/api/mcp/route.ts", "export": "createMcpRoute" },
20
+ { "kind": "page", "path": "app/oauth/authorize/page.tsx", "export": "OAuthConsentPage" },
21
+ { "kind": "route-handler", "path": "app/api/oauth/authorize/route.ts", "export": "decideOAuthAuthorizationRoute" },
22
+ { "kind": "route-handler", "path": "app/api/oauth/token/route.ts", "export": "exchangeOAuthTokenRoute" },
23
+ { "kind": "route-handler", "path": "app/api/oauth/register/route.ts", "export": "registerOAuthClientRoute" },
24
+ { "kind": "route-handler", "path": "app/.well-known/oauth-authorization-server/route.ts", "export": "getAuthorizationServerMetadataRoute" },
25
+ { "kind": "route-handler", "path": "app/.well-known/oauth-protected-resource/route.ts", "export": "getProtectedResourceMetadataRoute" },
26
+ { "kind": "route-handler", "path": "app/.well-known/oauth-protected-resource/api/mcp/route.ts", "export": "getProtectedResourceMetadataRoute" }
13
27
  ],
14
28
  "privacy": { "exports": true, "deletes": true }
15
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softure-ai/mcp-access",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "SOFTURE AI MCP access module: hashed, scoped, expiring access tokens, a rate-limited Bearer endpoint around the app's MCP server factory, and a token page with client setup instructions for Next.js.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,6 +20,7 @@
20
20
  "!src/**/*.test.ts",
21
21
  "!src/**/*.test.tsx",
22
22
  "migrations",
23
+ "adoption",
23
24
  "module.json"
24
25
  ],
25
26
  "publishConfig": {
package/src/contract.ts CHANGED
@@ -6,7 +6,8 @@ export type McpAccessErrorCode =
6
6
  | "mcp-access.name_required"
7
7
  | "mcp-access.name_too_long"
8
8
  | "mcp-access.token_limit_reached"
9
- | "mcp-access.token_not_found";
9
+ | "mcp-access.token_not_found"
10
+ | "mcp-access.grant_not_found";
10
11
 
11
12
  /** Every code the token page can show: its own, a missing session and the generic ones. */
12
13
  export type TokenFormErrorCode = McpAccessErrorCode | "auth.unauthenticated" | CoreErrorCode;
@@ -73,8 +74,22 @@ export type IssueTokenFormState =
73
74
  | { readonly status: "ok"; readonly issued: IssuedToken }
74
75
  | { readonly status: "error"; readonly error: TokenFormErrorCode };
75
76
 
77
+ /** A connected app (an OAuth grant) as its owner sees it. */
78
+ export interface OAuthGrantView {
79
+ readonly id: string;
80
+ /** The name the client registered with, cut to 60 characters. */
81
+ readonly clientName: string;
82
+ /** Granted with write access (it still writes only while the app allows writes). */
83
+ readonly canWrite: boolean;
84
+ readonly createdAt: Date;
85
+ readonly lastUsedAt: Date | null;
86
+ }
87
+
76
88
  /** What the revoke action returns to one token's form. */
77
89
  export type RevokeTokenFormState =
78
90
  | { readonly status: "idle" }
79
91
  | { readonly status: "ok" }
80
92
  | { readonly status: "error"; readonly error: TokenFormErrorCode };
93
+
94
+ /** What the disconnect action returns to one connected app's form. */
95
+ export type RevokeGrantFormState = RevokeTokenFormState;
package/src/index.ts CHANGED
@@ -17,6 +17,12 @@ export const MODULE_ID = "mcp-access";
17
17
  */
18
18
  export const MCP_RATE_LIMIT_BUCKETS = {
19
19
  mcp: { limit: 200, windowMinutes: 15 },
20
+ /**
21
+ * OAuth registration (per address) and token requests (per address and client id), when
22
+ * `oauth.enabled`. Enough for an assistant platform refreshing many users' tokens per client,
23
+ * too little for a script filling the clients table.
24
+ */
25
+ "mcp-oauth": { limit: 60, windowMinutes: 15 },
20
26
  } as const;
21
27
 
22
28
  /**
@@ -26,16 +32,30 @@ export const MCP_RATE_LIMIT_BUCKETS = {
26
32
  export const mcpAccess = defineModule({
27
33
  manifest: {
28
34
  id: MODULE_ID,
29
- version: "0.1.6",
35
+ version: "0.1.8",
30
36
  dependsOn: { security: "^0.1.0", auth: "^0.1.0" },
31
37
  dbSchema: "mcp",
32
- tables: ["access_tokens"],
38
+ tables: ["access_tokens", "oauth_clients", "oauth_authorization_codes", "oauth_grants"],
33
39
  env: [],
34
40
  switches: [],
35
- routes: { page: "/account/mcp", endpoint: "/api/mcp" },
41
+ routes: {
42
+ page: "/account/mcp",
43
+ endpoint: "/api/mcp",
44
+ oauthConsent: "/oauth/authorize",
45
+ oauthDecision: "/api/oauth/authorize",
46
+ oauthToken: "/api/oauth/token",
47
+ oauthRegister: "/api/oauth/register",
48
+ },
36
49
  mount: [
37
50
  { kind: "page", path: "app/account/mcp/page.tsx", export: "McpAccessPage" },
38
51
  { kind: "route-handler", path: "app/api/mcp/route.ts", export: "createMcpRoute" },
52
+ { kind: "page", path: "app/oauth/authorize/page.tsx", export: "OAuthConsentPage" },
53
+ { kind: "route-handler", path: "app/api/oauth/authorize/route.ts", export: "decideOAuthAuthorizationRoute" },
54
+ { kind: "route-handler", path: "app/api/oauth/token/route.ts", export: "exchangeOAuthTokenRoute" },
55
+ { kind: "route-handler", path: "app/api/oauth/register/route.ts", export: "registerOAuthClientRoute" },
56
+ { kind: "route-handler", path: "app/.well-known/oauth-authorization-server/route.ts", export: "getAuthorizationServerMetadataRoute" },
57
+ { kind: "route-handler", path: "app/.well-known/oauth-protected-resource/route.ts", export: "getProtectedResourceMetadataRoute" },
58
+ { kind: "route-handler", path: "app/.well-known/oauth-protected-resource/api/mcp/route.ts", export: "getProtectedResourceMetadataRoute" },
39
59
  ],
40
60
  privacy: { exports: true, deletes: true },
41
61
  },
@@ -63,19 +83,28 @@ export type {
63
83
  McpAccessErrorCode,
64
84
  McpClientSetup,
65
85
  McpServerIdentity,
86
+ OAuthGrantView,
87
+ RevokeGrantFormState,
66
88
  RevokeTokenFormState,
67
89
  TokenFormErrorCode,
68
90
  } from "./contract.js";
69
91
  export { getTokenErrorMessage, mcpAccessMessages, type McpAccessMessages } from "./messages/index.js";
70
92
  export {
93
+ MAX_PRESENTED_TOKEN_LENGTH,
71
94
  MAX_TOKEN_NAME_LENGTH,
72
95
  SERVER_NAME_PATTERN,
73
96
  TOOL_NAME_PATTERN,
97
+ GENERATED_AUTHORIZATION_SERVER_KEYS,
98
+ GENERATED_PROTECTED_RESOURCE_KEYS,
74
99
  type McpAccessOptions,
75
100
  type McpAccessOptionsInput,
101
+ type McpAppOriginResolver,
102
+ type McpMetadataExtension,
103
+ type McpOAuthOptions,
76
104
  type McpToolAccess,
77
105
  type McpToolDefinition,
78
106
  type McpToolDefinitionInput,
79
107
  } from "./options.js";
80
- export { accessTokens } from "./schema.js";
108
+ export { readRequestOrigin, type McpOriginRequest, type McpOrigins } from "./origins.js";
109
+ export { accessTokens, oauthAuthorizationCodes, oauthClients, oauthGrants, type OAuthClientRow, type OAuthTokenEndpointAuthMethod } from "./schema.js";
81
110
  export { getAccessTokenStatus, type AccessTokenStatusOptions } from "./token-status.js";
@@ -58,12 +58,40 @@ export const en = {
58
58
  revoke: "Revoke",
59
59
  revoking: "Revoking…",
60
60
  },
61
+ grants: {
62
+ title: "Connected apps",
63
+ lead: "Assistants you connected by signing in, such as claude.ai or ChatGPT. Disconnecting one stops its access at once.",
64
+ empty: "No app is connected.",
65
+ connectedOn: "Connected {date}",
66
+ disconnect: "Disconnect",
67
+ disconnecting: "Disconnecting…",
68
+ },
69
+ consent: {
70
+ title: "{client} wants access to your account",
71
+ lead: "The assistant can answer questions with your data in this app. You can disconnect it at any time on the AI assistant access page.",
72
+ app: "App",
73
+ returnsTo: "returns to {target}",
74
+ account: "Account",
75
+ access: "Access",
76
+ readAccess: "Read your data",
77
+ allowWrite: "Also allow changes",
78
+ allowWriteHint: "Without it, the assistant can only read.",
79
+ allow: "Allow",
80
+ deny: "Deny",
81
+ errorTitle: "The assistant cannot be connected",
82
+ unknownClient: "The app that sent you here is not registered. Connect it again from the assistant.",
83
+ redirectMismatch: "The return address does not match the one the app registered. Connect it again from the assistant.",
84
+ invalidRequest: "The app sent a connection request this app cannot accept.",
85
+ nothingGranted: "No access was granted.",
86
+ backToApp: "Back to the app ({target})",
87
+ },
61
88
  errors: {
62
89
  "mcp-access": {
63
90
  name_required: "Give the token a name, so you can tell it from the others.",
64
91
  name_too_long: "The name is too long.",
65
92
  token_limit_reached: "You already hold the most active tokens allowed. Revoke one before creating another.",
66
93
  token_not_found: "This token no longer exists. It may have been revoked already.",
94
+ grant_not_found: "This app is no longer connected.",
67
95
  },
68
96
  auth: {
69
97
  unauthenticated: "Your session has ended. Log in again.",
@@ -60,12 +60,40 @@ export const pl: typeof en = {
60
60
  revoke: "Unieważnij",
61
61
  revoking: "Unieważnianie…",
62
62
  },
63
+ grants: {
64
+ title: "Połączone aplikacje",
65
+ lead: "Asystenci połączeni przez logowanie, na przykład claude.ai albo ChatGPT. Odłączenie od razu odbiera im dostęp.",
66
+ empty: "Żadna aplikacja nie jest połączona.",
67
+ connectedOn: "Połączono {date}",
68
+ disconnect: "Odłącz",
69
+ disconnecting: "Odłączanie…",
70
+ },
71
+ consent: {
72
+ title: "{client} prosi o dostęp do Twojego konta",
73
+ lead: "Asystent odpowie na pytania, korzystając z Twoich danych w tej aplikacji. Możesz go odłączyć w każdej chwili na stronie dostępu dla asystenta AI.",
74
+ app: "Aplikacja",
75
+ returnsTo: "wróci na {target}",
76
+ account: "Konto",
77
+ access: "Dostęp",
78
+ readAccess: "Odczyt Twoich danych",
79
+ allowWrite: "Pozwól też na zmiany",
80
+ allowWriteHint: "Bez tego asystent może tylko czytać.",
81
+ allow: "Zezwól",
82
+ deny: "Odmów",
83
+ errorTitle: "Nie da się połączyć asystenta",
84
+ unknownClient: "Aplikacja, która Cię tu przysłała, nie jest zarejestrowana. Połącz ją od nowa z poziomu asystenta.",
85
+ redirectMismatch: "Adres powrotu nie zgadza się z tym, który aplikacja zarejestrowała. Połącz ją od nowa z poziomu asystenta.",
86
+ invalidRequest: "Aplikacja wysłała żądanie połączenia, którego ta aplikacja nie może przyjąć.",
87
+ nothingGranted: "Nie nadano żadnego dostępu.",
88
+ backToApp: "Wróć do aplikacji ({target})",
89
+ },
63
90
  errors: {
64
91
  "mcp-access": {
65
92
  name_required: "Nadaj tokenowi nazwę, żeby odróżnić go od pozostałych.",
66
93
  name_too_long: "Nazwa jest za długa.",
67
94
  token_limit_reached: "Masz już najwięcej aktywnych tokenów, ile można. Unieważnij któryś, zanim utworzysz nowy.",
68
95
  token_not_found: "Ten token już nie istnieje. Mógł zostać unieważniony.",
96
+ grant_not_found: "Ta aplikacja nie jest już połączona.",
69
97
  },
70
98
  auth: {
71
99
  unauthenticated: "Twoja sesja wygasła. Zaloguj się ponownie.",
@@ -9,11 +9,12 @@ import { getSoftureConfig } from "@softure-ai/core/next";
9
9
  import { revalidatePath } from "next/cache";
10
10
  import { z } from "zod";
11
11
  import { getMcpClientSetup } from "../client-setup.js";
12
- import type { IssueTokenFormState, RevokeTokenFormState } from "../contract.js";
12
+ import type { IssueTokenFormState, RevokeGrantFormState, RevokeTokenFormState } from "../contract.js";
13
13
  import { MAX_TOKEN_NAME_LENGTH } from "../options.js";
14
14
  import { getMcpAccessMessages, getMcpAccessOptions, getMcpAccessRoutes, getMcpEndpointUrl } from "../server/options.js";
15
+ import { revokeOAuthGrant } from "../server/oauth.js";
15
16
  import { issueAccessToken, revokeAccessToken } from "../server/tokens.js";
16
- import { getMcpAccessContext } from "./context.js";
17
+ import { getMcpAccessContext, getRequestOrigins } from "./context.js";
17
18
  import { formatDate } from "./format.js";
18
19
 
19
20
  /** Longer values are cut: the server functions refuse them anyway, and nothing huge is echoed back. */
@@ -48,6 +49,8 @@ export async function issueTokenAction(_previous: IssueTokenFormState, formData:
48
49
  // Every field has a `catch`, so parsing cannot fail.
49
50
  const input = issueInput.parse({ name: formData.get("name"), canWrite: formData.get("canWrite") });
50
51
  try {
52
+ // Resolved before issuing: a failure afterwards would leave a token nobody received.
53
+ const endpointUrl = getMcpEndpointUrl(config, await getRequestOrigins(config, getMcpAccessRoutes(config).page));
51
54
  const result = await issueAccessToken(await getMcpAccessContext(config), { userId: user.id, name: input.name, canWrite: input.canWrite !== null });
52
55
  if (!result.ok) return { status: "error", error: result.error };
53
56
  refreshPage(config);
@@ -62,7 +65,7 @@ export async function issueTokenAction(_previous: IssueTokenFormState, formData:
62
65
  expiresText: formatMessage(messages.issued.expires, { date: formatDate(config, issued.expiresAt) }),
63
66
  setup: getMcpClientSetup({
64
67
  serverName: getMcpAccessOptions(config).serverName,
65
- endpointUrl: getMcpEndpointUrl(config),
68
+ endpointUrl,
66
69
  token: issued.token,
67
70
  promptTemplate: messages.setup.assistantPrompt,
68
71
  }),
@@ -89,3 +92,20 @@ export async function revokeTokenAction(_previous: RevokeTokenFormState, formDat
89
92
  return { status: "error", error: reportFailure("revoking a token", error) };
90
93
  }
91
94
  }
95
+
96
+ /** Disconnects one of the signed-in user's OAuth apps: the grant and its access tokens go at once. */
97
+ export async function revokeGrantAction(_previous: RevokeGrantFormState, formData: FormData): Promise<RevokeGrantFormState> {
98
+ const config = getSoftureConfig();
99
+ const user = await getCurrentUser();
100
+ if (user === null) return { status: "error", error: "auth.unauthenticated" };
101
+
102
+ const input = revokeInput.parse({ id: formData.get("id") });
103
+ try {
104
+ const result = await revokeOAuthGrant(await getMcpAccessContext(config), { userId: user.id, grantId: input.id });
105
+ if (!result.ok) return { status: "error", error: result.error };
106
+ refreshPage(config);
107
+ return { status: "ok" };
108
+ } catch (error) {
109
+ return { status: "error", error: reportFailure("disconnecting an app", error) };
110
+ }
111
+ }
@@ -1,14 +1,21 @@
1
1
  // What the adapter hands the server functions: the registered config, the process-wide database
2
2
  // handle and the wall clock.
3
- import { systemClock, type SoftureConfig } from "@softure-ai/core";
3
+ import type { SoftureConfig } from "@softure-ai/core";
4
4
  import { getSoftureConfig } from "@softure-ai/core/next";
5
- import { getConfiguredDatabase } from "@softure-ai/db";
5
+ import type { McpOrigins } from "../origins.js";
6
+ import { createMcpAccessContext } from "../server/context.js";
7
+ import { resolveMcpOrigins } from "../server/origins.js";
6
8
  import type { McpAccessContext } from "../server/tokens.js";
7
9
 
8
- export async function getMcpAccessContext(config: SoftureConfig = getSoftureConfig()): Promise<McpAccessContext> {
9
- if (config.database === null) {
10
- throw new Error("@softure-ai/mcp-access: softure.config.ts has no database; mcp-access needs one");
11
- }
12
- const { db } = await getConfiguredDatabase(config.database);
13
- return { db, clock: systemClock, config };
10
+ export function getMcpAccessContext(config: SoftureConfig = getSoftureConfig()): Promise<McpAccessContext> {
11
+ return createMcpAccessContext(config);
12
+ }
13
+
14
+ /**
15
+ * The origins of the request a page or a server action runs in, from Next's `headers()`: the same
16
+ * ones the route handlers resolve, so the consent page and its decision agree.
17
+ */
18
+ export async function getRequestOrigins(config: SoftureConfig, path: string): Promise<McpOrigins> {
19
+ const { headers } = await import("next/headers");
20
+ return resolveMcpOrigins(config, { url: new URL(path, config.appOrigin).toString(), headers: await headers() });
14
21
  }
package/src/next/index.ts CHANGED
@@ -1,5 +1,15 @@
1
1
  // The Next.js adapter of @softure-ai/mcp-access: the MCP route factory, the token page and its
2
- // server actions (docs/02-module-standard.md §8).
3
- export { issueTokenAction, revokeTokenAction } from "./actions.js";
2
+ // server actions, and the OAuth routes and consent page (docs/02-module-standard.md §8).
3
+ export { issueTokenAction, revokeGrantAction, revokeTokenAction } from "./actions.js";
4
+ export { OAuthConsentPage, type OAuthConsentPageProps } from "./oauth-page.js";
5
+ export {
6
+ answerOAuthPreflight,
7
+ decideOAuthAuthorizationRoute,
8
+ exchangeOAuthTokenRoute,
9
+ getAuthorizationServerMetadataRoute,
10
+ getProtectedResourceMetadataRoute,
11
+ registerOAuthClientRoute,
12
+ } from "./oauth-routes.js";
13
+ export { getMcpAccessContext } from "./context.js";
4
14
  export { McpAccessPage } from "./pages.js";
5
15
  export { createMcpRoute } from "./route.js";
@@ -0,0 +1,68 @@
1
+ // The OAuth consent page, ready to mount with one line:
2
+ // `export { OAuthConsentPage as default } from "@softure-ai/mcp-access/next"`.
3
+ // The authorization endpoint clients send the person to. Without a session it redirects to the
4
+ // login page and back with every parameter (state, challenge, resource). Errors before the client
5
+ // and its redirect URI are confirmed are shown here, never redirected; later ones offer a link back
6
+ // to the client, never an automatic redirect (anyone can register a redirect URI).
7
+ import { requireUser } from "@softure-ai/auth/next";
8
+ import { formatMessage } from "@softure-ai/core";
9
+ import { getSoftureConfig } from "@softure-ai/core/next";
10
+ import { Card } from "@softure-ai/ui";
11
+ import { notFound } from "next/navigation";
12
+ import { getConsentPath, isOAuthEnabled, readAuthorizationParams, validateAuthorizationRequest } from "../server/oauth-http.js";
13
+ import { describeRedirectUri } from "../server/oauth-validation.js";
14
+ import { getMcpAccessMessages, getMcpAccessOptions, getMcpAccessRoutes } from "../server/options.js";
15
+ import { ConsentError, ConsentForm } from "../ui/consent-form.js";
16
+ import { getMcpAccessContext, getRequestOrigins } from "./context.js";
17
+
18
+ type SearchParams = Promise<Record<string, string | string[] | undefined>>;
19
+
20
+ export interface OAuthConsentPageProps {
21
+ readonly searchParams?: SearchParams;
22
+ }
23
+
24
+ const LAYOUT_CLASS = "sft:mx-auto sft:box-border sft:w-full sft:sm:max-w-md sft:px-4 sft:py-4";
25
+
26
+ export async function OAuthConsentPage({ searchParams }: OAuthConsentPageProps) {
27
+ const config = getSoftureConfig();
28
+ if (!isOAuthEnabled(config)) notFound();
29
+ const query = (await searchParams) ?? {};
30
+ const params = readAuthorizationParams({ get: (name: string) => query[name] });
31
+ const user = await requireUser({ next: getConsentPath(config, params), searchParams: query });
32
+
33
+ const messages = getMcpAccessMessages(config);
34
+ const origins = await getRequestOrigins(config, getMcpAccessRoutes(config).oauthConsent);
35
+ const outcome = await validateAuthorizationRequest(await getMcpAccessContext(config), params, origins);
36
+ if (outcome.kind !== "valid") {
37
+ const message =
38
+ outcome.kind === "redirect-error" ? messages.consent.invalidRequest : outcome.reason === "unknown_client" ? messages.consent.unknownClient : messages.consent.redirectMismatch;
39
+ return (
40
+ <main className={LAYOUT_CLASS}>
41
+ <Card title={messages.consent.errorTitle}>
42
+ <ConsentError
43
+ message={message}
44
+ {...(outcome.kind === "redirect-error" ? { backLocation: outcome.location, backTarget: describeRedirectUri(outcome.redirectUri) } : {})}
45
+ messages={messages}
46
+ />
47
+ </Card>
48
+ </main>
49
+ );
50
+ }
51
+
52
+ const { client, redirectUri, isWriteRequested } = outcome.request;
53
+ return (
54
+ <main className={LAYOUT_CLASS}>
55
+ <Card title={formatMessage(messages.consent.title, { client: client.clientName })} subtitle={messages.consent.lead}>
56
+ <ConsentForm
57
+ action={getMcpAccessRoutes(config).oauthDecision}
58
+ params={[...params.entries()]}
59
+ clientName={client.clientName}
60
+ redirectTarget={describeRedirectUri(redirectUri)}
61
+ accountEmail={user.email}
62
+ isWriteOffered={isWriteRequested && getMcpAccessOptions(config).allowWrites}
63
+ messages={messages}
64
+ />
65
+ </Card>
66
+ </main>
67
+ );
68
+ }
@@ -0,0 +1,73 @@
1
+ // The OAuth routes, each mounted with one line (README §4), e.g.
2
+ // `app/api/oauth/token/route.ts → export { exchangeOAuthTokenRoute as POST, answerOAuthPreflight as OPTIONS } from "@softure-ai/mcp-access/next";`
3
+ // They supply the module context (and, for the decision, the session's user) to the handlers in
4
+ // `/server`. With `oauth.enabled` off every one answers 404.
5
+ import { getCurrentUser } from "@softure-ai/auth/next";
6
+ import { errorLogLabel } from "@softure-ai/core";
7
+ import { getSoftureConfig } from "@softure-ai/core/next";
8
+ import {
9
+ answerOAuthPreflight,
10
+ getAuthorizationServerMetadata,
11
+ getProtectedResourceMetadata,
12
+ getRootProtectedResourceMetadata,
13
+ handleAuthorizationDecision,
14
+ handleClientRegistration,
15
+ handleTokenRequest,
16
+ serveDiscoveryDocument,
17
+ } from "../server/oauth-http.js";
18
+ import type { McpAccessContext } from "../server/tokens.js";
19
+ import { getMcpAccessContext } from "./context.js";
20
+
21
+ const UNAVAILABLE = { status: 503, headers: { "cache-control": "no-store" } };
22
+
23
+ async function withContext(request: Request, handle: (ctx: McpAccessContext, request: Request) => Promise<Response>): Promise<Response> {
24
+ let ctx;
25
+ try {
26
+ ctx = await getMcpAccessContext();
27
+ } catch (error) {
28
+ console.error(`@softure-ai/mcp-access: opening the database failed: ${errorLogLabel(error)}`);
29
+ return Response.json({ error: "temporarily_unavailable" }, UNAVAILABLE);
30
+ }
31
+ return handle(ctx, request);
32
+ }
33
+
34
+ /**
35
+ * `GET /.well-known/oauth-authorization-server`. Reads the request, so Next never renders it at
36
+ * build time with one origin baked in.
37
+ */
38
+ export function getAuthorizationServerMetadataRoute(request: Request): Response {
39
+ return serveDiscoveryDocument(getSoftureConfig(), (config, origins) => getAuthorizationServerMetadata(config, origins), request);
40
+ }
41
+
42
+ /**
43
+ * `GET /.well-known/oauth-protected-resource` and `…/oauth-protected-resource/api/mcp`: the same
44
+ * export at both paths; the request's path picks the variant.
45
+ */
46
+ export function getProtectedResourceMetadataRoute(request: Request): Response {
47
+ const isRoot = new URL(request.url).pathname.replace(/\/+$/, "").endsWith("/oauth-protected-resource");
48
+ return serveDiscoveryDocument(
49
+ getSoftureConfig(),
50
+ isRoot ? getRootProtectedResourceMetadata : (config, origins) => getProtectedResourceMetadata(config, "endpoint", origins),
51
+ request,
52
+ );
53
+ }
54
+
55
+ /** `POST /api/oauth/register`. */
56
+ export function registerOAuthClientRoute(request: Request): Promise<Response> {
57
+ return withContext(request, handleClientRegistration);
58
+ }
59
+
60
+ /** `POST /api/oauth/token`. */
61
+ export function exchangeOAuthTokenRoute(request: Request): Promise<Response> {
62
+ return withContext(request, handleTokenRequest);
63
+ }
64
+
65
+ /** `POST /api/oauth/authorize`: the consent form's decision, answered with `303`. */
66
+ export function decideOAuthAuthorizationRoute(request: Request): Promise<Response> {
67
+ return withContext(request, async (ctx, req) => {
68
+ const user = await getCurrentUser();
69
+ return handleAuthorizationDecision(ctx, req, user?.id ?? null);
70
+ });
71
+ }
72
+
73
+ export { answerOAuthPreflight };