@softure-ai/mcp-access 0.1.12 → 0.1.14

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 (126) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +141 -4
  3. package/dist/cli/bin.d.ts +3 -0
  4. package/dist/cli/bin.d.ts.map +1 -0
  5. package/dist/cli/bin.js +5 -0
  6. package/dist/cli/bin.js.map +1 -0
  7. package/dist/cli/command.d.ts +9 -0
  8. package/dist/cli/command.d.ts.map +1 -0
  9. package/dist/cli/command.js +30 -0
  10. package/dist/cli/command.js.map +1 -0
  11. package/dist/cli/index.d.ts +3 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +4 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/cli/run.d.ts +24 -0
  16. package/dist/cli/run.d.ts.map +1 -0
  17. package/dist/cli/run.js +58 -0
  18. package/dist/cli/run.js.map +1 -0
  19. package/dist/contract.d.ts +19 -4
  20. package/dist/contract.d.ts.map +1 -1
  21. package/dist/index.d.ts +21 -7
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +6 -11
  24. package/dist/index.js.map +1 -1
  25. package/dist/messages/en.d.ts +2 -0
  26. package/dist/messages/en.d.ts.map +1 -1
  27. package/dist/messages/en.js +2 -0
  28. package/dist/messages/en.js.map +1 -1
  29. package/dist/messages/index.d.ts +4 -0
  30. package/dist/messages/index.d.ts.map +1 -1
  31. package/dist/messages/pl.d.ts.map +1 -1
  32. package/dist/messages/pl.js +2 -0
  33. package/dist/messages/pl.js.map +1 -1
  34. package/dist/next/actions.d.ts +2 -2
  35. package/dist/next/actions.d.ts.map +1 -1
  36. package/dist/next/actions.js +8 -47
  37. package/dist/next/actions.js.map +1 -1
  38. package/dist/next/format.d.ts +1 -1
  39. package/dist/next/format.d.ts.map +1 -1
  40. package/dist/next/format.js +3 -1
  41. package/dist/next/format.js.map +1 -1
  42. package/dist/next/index.d.ts +2 -1
  43. package/dist/next/index.d.ts.map +1 -1
  44. package/dist/next/index.js +1 -0
  45. package/dist/next/index.js.map +1 -1
  46. package/dist/next/issue-token.d.ts +22 -0
  47. package/dist/next/issue-token.d.ts.map +1 -0
  48. package/dist/next/issue-token.js +93 -0
  49. package/dist/next/issue-token.js.map +1 -0
  50. package/dist/next/pages.d.ts +9 -1
  51. package/dist/next/pages.d.ts.map +1 -1
  52. package/dist/next/pages.js +2 -2
  53. package/dist/next/pages.js.map +1 -1
  54. package/dist/options.d.ts +17 -0
  55. package/dist/options.d.ts.map +1 -1
  56. package/dist/options.js +13 -0
  57. package/dist/options.js.map +1 -1
  58. package/dist/routes.d.ts +15 -0
  59. package/dist/routes.d.ts.map +1 -0
  60. package/dist/routes.js +17 -0
  61. package/dist/routes.js.map +1 -0
  62. package/dist/server/action-tool.d.ts +21 -0
  63. package/dist/server/action-tool.d.ts.map +1 -0
  64. package/dist/server/action-tool.js +51 -0
  65. package/dist/server/action-tool.js.map +1 -0
  66. package/dist/server/index.d.ts +3 -0
  67. package/dist/server/index.d.ts.map +1 -1
  68. package/dist/server/index.js +3 -0
  69. package/dist/server/index.js.map +1 -1
  70. package/dist/server/prune.d.ts +8 -0
  71. package/dist/server/prune.d.ts.map +1 -0
  72. package/dist/server/prune.js +10 -0
  73. package/dist/server/prune.js.map +1 -0
  74. package/dist/server/tools.d.ts +25 -0
  75. package/dist/server/tools.d.ts.map +1 -0
  76. package/dist/server/tools.js +61 -0
  77. package/dist/server/tools.js.map +1 -0
  78. package/dist/stdio/index.d.ts +2 -0
  79. package/dist/stdio/index.d.ts.map +1 -0
  80. package/dist/stdio/index.js +4 -0
  81. package/dist/stdio/index.js.map +1 -0
  82. package/dist/stdio/register.d.ts +2 -0
  83. package/dist/stdio/register.d.ts.map +1 -0
  84. package/dist/stdio/register.js +10 -0
  85. package/dist/stdio/register.js.map +1 -0
  86. package/dist/stdio/serve.d.ts +45 -0
  87. package/dist/stdio/serve.d.ts.map +1 -0
  88. package/dist/stdio/serve.js +89 -0
  89. package/dist/stdio/serve.js.map +1 -0
  90. package/dist/testing/catalog.d.ts +15 -0
  91. package/dist/testing/catalog.d.ts.map +1 -0
  92. package/dist/testing/catalog.js +51 -0
  93. package/dist/testing/catalog.js.map +1 -0
  94. package/dist/testing/index.d.ts +2 -0
  95. package/dist/testing/index.d.ts.map +1 -0
  96. package/dist/testing/index.js +3 -0
  97. package/dist/testing/index.js.map +1 -0
  98. package/dist/ui/token-manager.js +1 -1
  99. package/dist/ui/token-manager.js.map +1 -1
  100. package/module.json +1 -1
  101. package/package.json +31 -5
  102. package/src/cli/bin.ts +5 -0
  103. package/src/cli/command.ts +38 -0
  104. package/src/cli/index.ts +3 -0
  105. package/src/cli/run.ts +79 -0
  106. package/src/contract.ts +19 -4
  107. package/src/index.ts +10 -12
  108. package/src/messages/en.ts +2 -0
  109. package/src/messages/pl.ts +2 -0
  110. package/src/next/actions.ts +8 -45
  111. package/src/next/format.ts +2 -2
  112. package/src/next/index.ts +2 -1
  113. package/src/next/issue-token.ts +113 -0
  114. package/src/next/pages.tsx +11 -3
  115. package/src/options.ts +13 -0
  116. package/src/routes.ts +17 -0
  117. package/src/server/action-tool.ts +64 -0
  118. package/src/server/index.ts +3 -0
  119. package/src/server/prune.ts +14 -0
  120. package/src/server/tools.ts +72 -0
  121. package/src/stdio/index.ts +12 -0
  122. package/src/stdio/register.ts +11 -0
  123. package/src/stdio/serve.ts +133 -0
  124. package/src/testing/catalog.ts +74 -0
  125. package/src/testing/index.ts +2 -0
  126. package/src/ui/token-manager.tsx +1 -1
package/CHANGELOG.md CHANGED
@@ -4,6 +4,38 @@ Newest first. Each version lists what changed for an app that uses `@softure-ai/
4
4
  production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
5
5
  "Definition of done"). Versions before the first one below are described in their GitHub Releases (`mcp-access@x.y.z`).
6
6
 
7
+ ## 0.1.14
8
+
9
+ - `issueToken` can replace an app's own token-issue action (#386):
10
+ - `mcpAccess({ clientServerName })` (default `serverName`): the name the setup snippets register the server under
11
+ (`claude mcp add`, the JSON configs, the assistant prompt), so users keep the entry they already added.
12
+ - The `beforeIssue` gate refuses with its own text: `refuseIssue(code, message)` (`/next`, an `Err` with a
13
+ `message`), or a thrown `PublicError` (code `mcp-access.issue_refused`). `IssueTokenFormState`'s error carries it
14
+ as `message`, which `TokenManager` shows instead of the code's copy. `McpIssueRefusal` (root) and
15
+ `IssueGateResult` (`/next`) are exported.
16
+ - `IssuedToken.expiresAt` (ISO 8601) next to `expiresText`.
17
+
18
+ ## 0.1.13
19
+
20
+ - Token and grant dates use core's `formatCalendarDay` on the day in the app's zone; same output. Requires
21
+ `@softure-ai/core` `^0.1.9` (#312).
22
+ - What an adopting app wrote around the endpoint ships with the package (#315):
23
+ - `toolResult`, `toolError` and `withToolErrors(work, { hints, label })` (`/server`): a thrown error reaches the
24
+ assistant only as a `PublicError` message or a hint, and the log only by kind, never with its SQL.
25
+ - `actionTool(handler, { message, hints })` (`/server`): a server action as a write tool, its arguments sent as
26
+ the form the action reads.
27
+ - `@softure-ai/mcp-access/stdio`: `serveMcpStdio({ config, createServer, userIdEnv, allowWrites })` serves the
28
+ factory over stdio for the account in `MCP_USER_ID`, or the only account; `/stdio/register` resolves
29
+ `server-only` to an empty module for `node`/`tsx --import`.
30
+ - `@softure-ai/mcp-access/testing`: `expectToolCatalogMatchesServer(config, createServer)` and
31
+ `getToolCatalogDifferences`. Catalog tools take optional localized `title` and `example`.
32
+ - `DEFAULT_MCP_ACCESS_ROUTES` from the root entry; the manifest uses it.
33
+ - `issueToken(previous, form, { beforeIssue })` (`/next`) for a gate before a token is issued, with the codes
34
+ `mcp-access.issue_refused` and `mcp-access.write_refused` (copy in en and pl); `McpAccessPage` takes an
35
+ optional `issueAction`. `issueTokenAction` is unchanged.
36
+ - `pruneMcpAccess(ctx)` (`/server`) and the `softure-mcp prune` command (bin, `/cli`) for a daily job such as a
37
+ deploy `maintain` hook.
38
+
7
39
  ## 0.1.12
8
40
 
9
41
  - Without a `resolveAppOrigin` option, the app origin comes from core's `resolveAppOrigin` (#311): a listed origin
@@ -12,6 +44,8 @@ production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../
12
44
  answers `null` for a host that is no host). The consent decision still reads `Host` only. Discovery documents vary
13
45
  on `x-forwarded-host` too.
14
46
  - `getRequestOrigins(config, path)` is exported from `@softure-ai/mcp-access/next`. Needs `@softure-ai/core` 0.1.8.
47
+ - `MCP_RATE_LIMIT_BUCKETS` declares `key: "ip"` on `mcp` and `mcp-oauth` (security 0.1.8 bucket kinds).
48
+ - Requires `@softure-ai/security` `^0.1.8`: earlier versions refuse the `key` field.
15
49
 
16
50
  ## 0.1.11
17
51
 
package/README.md CHANGED
@@ -38,6 +38,9 @@ limit check made race-free.
38
38
  count against `maxTokensPerUser` and are not listed with the hand-issued ones.
39
39
  - Tokens an app issued before adopting the module keep working through `legacyTokenPattern`.
40
40
  - Server functions for scripts and other hosts, and a health check for `GET /api/health`.
41
+ - For the app's own server: tool result helpers with a safe error boundary and server actions as write tools
42
+ (`/server`), a stdio entry for local assistants (`/stdio`), a catalog parity check (`/testing`), a gate before a
43
+ token is issued (`issueToken`, `/next`) and the `softure-mcp prune` command for a daily cleanup (§4, §5).
41
44
 
42
45
  ## 2. Installation
43
46
 
@@ -74,7 +77,8 @@ mcpAccess({
74
77
  | Option | Type | Default | Meaning |
75
78
  | --- | --- | --- | --- |
76
79
  | `serverName` | `string` | required | The name clients list the server under (lowercase, digits, inner dashes; no quotes needed in a shell). |
77
- | `tools` | `{ name, access: "read" \| "write", description: { en, pl? } }[]` | `[]` | The catalog the token page shows. Keep it next to the factory; the example app tests that both list the same tools. |
80
+ | `clientServerName` | `string` | `serverName` | The name the setup snippets register the server under (`claude mcp add`, the JSON configs, the assistant prompt), same pattern. Set it when users already added the server under another name: a new name gives everyone who reconnects a second entry. |
81
+ | `tools` | `{ name, access: "read" \| "write", description: { en, pl? }, title?: { en, pl? }, example?: { en, pl? } }[]` | `[]` | The catalog the token page shows. `title` (a display name) and `example` (a request a person could make) are for agent docs that read the catalog. Keep it next to the factory; `expectToolCatalogMatchesServer` (`/testing`) checks that both list the same tools. |
78
82
  | `allowWrites` | `boolean` | `false` | Lets write tokens be issued and used. Off, the page offers no write tokens and every token reads only. |
79
83
  | `tokenLifetimeDays` | `integer` 1–365 | `90` | How long a hand-issued token works. There is no renewal: the user issues a new one. |
80
84
  | `maxTokensPerUser` | `integer` 1–100 | `20` | Unexpired tokens one account may hold. Counted under a per-user lock, so parallel requests cannot overshoot. |
@@ -88,7 +92,7 @@ mcpAccess({
88
92
  | `oauth.metadata` | `{ authorizationServer?, protectedResource? }`, each an object or `(origins) => object` | — | Extra keys of the discovery documents (`jwks_uri`, `service_documentation`, `resource_documentation`, `agent_auth`…). Generated keys win; a static object that sets one is refused at startup. The protected resource document takes the app's `resource_name` (default: `serverName`). |
89
93
  | `resolveAppOrigin` | `(request) => string \| null` | — | The app origin of one request, ahead of the config's `origins` block (prefer that block: every module reads it). `readRequestOrigin` reads it from `Host`. Null falls back to the block's rule; anything but a bare http(s) origin throws. |
90
94
  | `resourceOrigins` | `string[]` (≤ 16 http(s) origins) | `[]` | Other public hosts of the app, e.g. the apex next to `app.`: the root protected resource metadata asked on one of them names it as `resource`, and the OAuth endpoints accept it as `resource`. |
91
- | `routes` | `{ page?, endpoint?, oauthConsent?, oauthDecision?, oauthToken?, oauthRegister? }` | `/account/mcp`, `/api/mcp`, `/oauth/authorize`, `/api/oauth/authorize`, `/api/oauth/token`, `/api/oauth/register` | Move the page, the endpoint or the OAuth paths; URLs in the setup and the metadata are the app origin (`appOrigin`, or `resolveAppOrigin`'s) + the path. |
95
+ | `routes` | `{ page?, endpoint?, oauthConsent?, oauthDecision?, oauthToken?, oauthRegister? }` | `DEFAULT_MCP_ACCESS_ROUTES` (root entry): `/account/mcp`, `/api/mcp`, `/oauth/authorize`, `/api/oauth/authorize`, `/api/oauth/token`, `/api/oauth/register` | Move the page, the endpoint or the OAuth paths; URLs in the setup and the metadata are the app origin (`appOrigin`, or `resolveAppOrigin`'s) + the path. |
92
96
  | `messages` | partial `en` / `pl` | — | Copy overrides. |
93
97
 
94
98
  `allowWrites` is a deploy decision, so read it from the environment rather than hard-coding it:
@@ -138,6 +142,126 @@ Answers to 2025-era clients come as one SSE `message` event, to 2026-07-28 clien
138
142
  mid-call notifications (progress, logging) are dropped. Keep `/api/mcp` out of a proxy guard that
139
143
  redirects to the login page: the endpoint authenticates by token, not by cookie.
140
144
 
145
+ ### Writing tools
146
+
147
+ `/server` answers tools safely, so a failure never hands the assistant a query or an error's text:
148
+
149
+ ```ts
150
+ import { actionTool, toolResult, withToolErrors } from "@softure-ai/mcp-access/server";
151
+
152
+ server.registerTool("list_orders", { description: "Lists your orders." }, () =>
153
+ withToolErrors(() => findOrders(db, userId), { label: "list_orders", hints: { "orders.locked": "The shop is closed for stock-taking." } }),
154
+ );
155
+ if (canWrite) {
156
+ server.registerTool("place_order", { inputSchema: z.object({ sku: z.string(), quantity: z.number() }) }, actionTool(
157
+ (form) => placeOrderAction({ status: "idle" }, form),
158
+ { message: "Order placed.", hints: { "orders.out_of_stock": "That item is out of stock." } },
159
+ ));
160
+ }
161
+ ```
162
+
163
+ - `toolResult(value)`: a string as text, anything else as JSON. `toolError(message)`: the same with `isError`.
164
+ - `withToolErrors(work, { hints?, label? })`: a `CallToolResult` passes; a result value is unwrapped (`ok` → its
165
+ value, else the hint for its error code, else the code); any other value becomes `toolResult`. A thrown
166
+ `PublicError` (core) gives its message. Anything else is logged by kind only (`errorLogLabel`, never the SQL or
167
+ its parameters) and answered with the hint for its `safeError` code (`core.database_failed`, `core.unexpected`),
168
+ else a fixed English text.
169
+ - `actionTool(handler, { message?, hints?, label? })`: the server action behind a form as a write tool, so both
170
+ share one validation path. The arguments become `FormData` (numbers as text, `true` as `"on"`, `false` and `null`
171
+ left out, arrays one entry per item, dates as ISO, objects as JSON). A `{ ok: false, error }` or
172
+ `{ status: "error", error }` state is a tool error through the hints; otherwise the answer is `message` (text or
173
+ `(state) => text`), else the state as JSON.
174
+
175
+ ### Stdio
176
+
177
+ A local assistant on the machine that holds the database can run the same factory over stdio, without a token:
178
+
179
+ ```ts
180
+ // scripts/mcp-stdio.ts
181
+ import { serveMcpStdio } from "@softure-ai/mcp-access/stdio";
182
+ import config from "../softure.config";
183
+ import { createServer } from "../lib/mcp-server";
184
+
185
+ const started = await serveMcpStdio({ config, createServer, allowWrites: process.env.MCP_ALLOW_WRITES === "1" });
186
+ if (!started.ok) {
187
+ console.error(started.error.message);
188
+ process.exitCode = 1;
189
+ }
190
+ ```
191
+
192
+ ```bash
193
+ claude mcp add acme -e MCP_USER_ID=<account id> -- npx tsx --import @softure-ai/mcp-access/stdio/register scripts/mcp-stdio.ts
194
+ ```
195
+
196
+ - The account: `MCP_USER_ID` (or `userIdEnv`) when set, which must name an account; else the only account in the
197
+ database. With several accounts and no id the entry refuses (`mcp-access.stdio_account_ambiguous`): it never
198
+ picks one.
199
+ - `canWrite` needs `allowWrites: true` here and the app's `allowWrites`. The token id is `"stdio"`.
200
+ - The database opens like a package command's (`database.handle`, else `database.url`) and closes with the
201
+ connection. Messages go to stderr; stdout is the protocol.
202
+ - `@softure-ai/mcp-access/stdio/register` (`--import`) resolves `server-only` to an empty module, so a factory
203
+ written for Next runs in plain Node. It needs Node 22.15 or later (`module.registerHooks`).
204
+
205
+ ### Catalog parity
206
+
207
+ ```ts
208
+ // lib/mcp-server.test.ts
209
+ import { expectToolCatalogMatchesServer } from "@softure-ai/mcp-access/testing";
210
+
211
+ it("lists the same tools as the server", () => expectToolCatalogMatchesServer(config, createServer));
212
+ ```
213
+
214
+ It lists the server's tools for a read-only and a write identity and throws naming every configured tool the
215
+ server lacks, every registered tool the catalog lacks, and every write tool a read-only token gets.
216
+ `getToolCatalogDifferences` returns the same as lists.
217
+
218
+ ### Gating token issue
219
+
220
+ An app that checks something before a token is issued (a plan that includes write access) calls `issueToken` from
221
+ its own server action instead of copying `issueTokenAction`:
222
+
223
+ ```ts
224
+ // app/account/mcp/actions.ts
225
+ "use server";
226
+ import { err, ok } from "@softure-ai/core";
227
+ import { issueToken } from "@softure-ai/mcp-access/next";
228
+ import type { IssueTokenFormState } from "@softure-ai/mcp-access";
229
+
230
+ export async function issueGatedToken(previous: IssueTokenFormState, form: FormData) {
231
+ return issueToken(previous, form, {
232
+ beforeIssue: async ({ userId, canWrite }) => (canWrite && !(await hasWritePlan(userId)) ? err("mcp-access.write_refused") : ok()),
233
+ });
234
+ }
235
+ ```
236
+
237
+ ```tsx
238
+ // app/account/mcp/page.tsx
239
+ import { McpAccessPage } from "@softure-ai/mcp-access/next";
240
+ import { issueGatedToken } from "./actions";
241
+
242
+ export default function Page() {
243
+ return <McpAccessPage issueAction={issueGatedToken} />;
244
+ }
245
+ ```
246
+
247
+ The gate runs after the session check and before anything is issued. `mcp-access.write_refused` and
248
+ `mcp-access.issue_refused` have copy in `messages.errors["mcp-access"]` (override it per app). A gate with a sentence
249
+ per reason returns `refuseIssue(code, message)` (`/next`) or throws a `PublicError` (core, code `mcp-access.issue_refused`): the
250
+ form state carries the text as `message` and `TokenManager` shows it instead of the code's copy (a blank text falls
251
+ back to it). Any other thrown error becomes a `safeError` code. The gate stays in the app's code, out of
252
+ `softure.config.ts`.
253
+
254
+ ```ts
255
+ beforeIssue: async ({ userId }) => {
256
+ const plan = await getPlan(userId);
257
+ if (plan.trialEnded) return refuseIssue("mcp-access.issue_refused", t("billing.trialEnded"));
258
+ return ok();
259
+ },
260
+ ```
261
+
262
+ The issued token carries `expiresText` (formatted in the app's locale and zone) and `expiresAt` (ISO 8601), for an
263
+ app that renders the expiry itself.
264
+
141
265
  `TokenManager` (`@softure-ai/mcp-access/ui`) takes the actions `issueTokenAction` and
142
266
  `revokeTokenAction` (`/next`) as props for an app that composes its own page, and `grants` with
143
267
  `revokeGrantAction` for the connected apps list.
@@ -226,7 +350,7 @@ whose last argument they are; `OAuthClientRow` is exported from the package root
226
350
  | `last_used_at` | Written on verification at most once a minute; informational only. |
227
351
 
228
352
  Issuing deletes the issuer's expired tokens; `pruneAccessTokens(ctx)` (`/server`) deletes every
229
- expired token, for a scheduled job. Rollback: in the migration header.
353
+ expired token, for a scheduled job (see "Scheduled pruning" below). Rollback: in the migration header.
230
354
 
231
355
  `migrations/0002_create_oauth_grants.sql` creates `mcp.oauth_clients` (client id, name, redirect
232
356
  URIs, auth method, sha256 of a confidential client's secret), `mcp.oauth_authorization_codes`
@@ -236,6 +360,18 @@ expiry, last use), and adds `access_tokens.grant_id` (null for hand-issued token
236
360
  with the account. `pruneOAuthRecords(ctx)` deletes expired codes, grants whose refresh token has
237
361
  expired (with their access tokens) and clients registered over a day ago that hold no grant.
238
362
 
363
+ ### Scheduled pruning
364
+
365
+ `pruneMcpAccess(ctx)` (`/server`) runs both prunes and counts what each removed. The `softure-mcp prune` command
366
+ runs it with the app's config (loaded like `softure migrate`, or from an app script through `runMcpAccessCli` from
367
+ `/cli`) and prints one line. Run it daily, e.g. as a `maintain` hook of `@softure-ai/deploy`:
368
+
369
+ ```json
370
+ { "hooks": { "maintain": [{ "name": "mcp-prune", "compose": ["exec", "-T", "app", "npx", "softure-mcp", "prune"] }] } }
371
+ ```
372
+
373
+ An app that pruned inline on every token exchange can drop that code.
374
+
239
375
  ### Adopting an app's own tables
240
376
 
241
377
  An app that ran its own `public.access_tokens` (with `grant_id`) and `oauth_clients`,
@@ -280,7 +416,8 @@ the `tools` option, per locale.
280
416
 
281
417
  ## 10. Hooks
282
418
 
283
- The MCP server factory passed to `createMcpRoute` is the module's only hook. `/server` also exports
419
+ The MCP server factory passed to `createMcpRoute` (and to `serveMcpStdio`) and the `beforeIssue` gate of
420
+ `issueToken` are the module's hooks. `/server` also exports
284
421
  `createMcpEndpoint({ createServer })`, a `(ctx, request) => Response` for hosts other than Next, and
285
422
  `issueAccessToken`, `listAccessTokens`, `revokeAccessToken`, `verifyAccessToken` and
286
423
  `pruneAccessTokens` for scripts. For OAuth: `listOAuthGrants`, `revokeOAuthGrant`,
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ // The `softure-mcp` executable. All logic lives in command.ts and run.ts, which the tests call directly.
3
+ import { runMcpAccessCommand } from "./command.js";
4
+ process.exitCode = await runMcpAccessCommand({ argv: process.argv.slice(2), cwd: process.cwd() });
5
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":";AACA,yGAAyG;AACzG,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,CAAC,QAAQ,GAAG,MAAM,mBAAmB,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { type CliOutput } from "./run.js";
2
+ export interface RunMcpAccessCommandOptions {
3
+ readonly argv: readonly string[];
4
+ readonly cwd: string;
5
+ readonly output?: CliOutput;
6
+ }
7
+ /** Loads the config (unless only help is asked for) and runs `runMcpAccessCli`. */
8
+ export declare function runMcpAccessCommand(options: RunMcpAccessCommandOptions): Promise<number>;
9
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/cli/command.ts"],"names":[],"mappings":"AAIA,OAAO,EAA8D,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAEtG,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;CAC7B;AASD,mFAAmF;AACnF,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB9F"}
@@ -0,0 +1,30 @@
1
+ // The `softure-mcp` bin. It loads the app's config through `@softure-ai/core/cli`, like `softure-mail`
2
+ // (@softure-ai/mailing): the file must be loadable by Node itself (`.js`/`.mjs`, or `.ts` where Node
3
+ // strips types). Anything else uses an app script with `runMcpAccessCli` instead (see run.ts).
4
+ import { loadAppConfig, takeConfigOption } from "@softure-ai/core/cli";
5
+ import { EXIT_FAILED, EXIT_USAGE, MCP_ACCESS_USAGE, runMcpAccessCli } from "./run.js";
6
+ const APP_SCRIPT = { runner: "runMcpAccessCli", packageName: "@softure-ai/mcp-access" };
7
+ const consoleOutput = {
8
+ log: (line) => console.log(line),
9
+ error: (line) => console.error(line),
10
+ };
11
+ /** Loads the config (unless only help is asked for) and runs `runMcpAccessCli`. */
12
+ export async function runMcpAccessCommand(options) {
13
+ const output = options.output ?? consoleOutput;
14
+ const taken = takeConfigOption(options.argv);
15
+ if (!taken.ok) {
16
+ output.error(`softure-mcp: ${taken.problem}`);
17
+ output.error(MCP_ACCESS_USAGE);
18
+ return EXIT_USAGE;
19
+ }
20
+ const { configPath, argv } = taken;
21
+ if (argv[0] !== "prune")
22
+ return runMcpAccessCli({ argv, output });
23
+ const loaded = await loadAppConfig({ cwd: options.cwd, configPath, appScript: APP_SCRIPT });
24
+ if (!loaded.ok) {
25
+ output.error(`softure-mcp: ${loaded.problem}`);
26
+ return EXIT_FAILED;
27
+ }
28
+ return runMcpAccessCli({ config: loaded.config, argv, output });
29
+ }
30
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../../src/cli/command.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,qGAAqG;AACrG,+FAA+F;AAC/F,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAkB,MAAM,UAAU,CAAC;AAQtG,MAAM,UAAU,GAAG,EAAE,MAAM,EAAE,iBAAiB,EAAE,WAAW,EAAE,wBAAwB,EAAE,CAAC;AAExF,MAAM,aAAa,GAAc;IAC/B,GAAG,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;IAChC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC;CACrC,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,OAAmC;IAC3E,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC;IAC/C,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;QACd,MAAM,CAAC,KAAK,CAAC,gBAAgB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QAC9C,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;QAC/B,OAAO,UAAU,CAAC;IACpB,CAAC;IACD,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IACnC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,eAAe,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;IAElE,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,CAAC;IAC5F,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,gBAAgB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/C,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,eAAe,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { runMcpAccessCommand, type RunMcpAccessCommandOptions } from "./command.js";
2
+ export { EXIT_FAILED, EXIT_OK, EXIT_USAGE, MCP_ACCESS_USAGE, runMcpAccessCli, type CliOutput, type RunMcpAccessCliOptions } from "./run.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,KAAK,0BAA0B,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,KAAK,sBAAsB,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,4 @@
1
+ // `@softure-ai/mcp-access/cli`: the `softure-mcp` command for app scripts and container bundles.
2
+ export { runMcpAccessCommand } from "./command.js";
3
+ export { EXIT_FAILED, EXIT_OK, EXIT_USAGE, MCP_ACCESS_USAGE, runMcpAccessCli } from "./run.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,OAAO,EAAE,mBAAmB,EAAmC,MAAM,cAAc,CAAC;AACpF,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAA+C,MAAM,UAAU,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type Clock, type SoftureConfig, type SoftureDatabaseConfig } from "@softure-ai/core";
2
+ import { type CommandDatabase } from "@softure-ai/db";
3
+ export interface CliOutput {
4
+ readonly log: (line: string) => void;
5
+ readonly error: (line: string) => void;
6
+ }
7
+ export interface RunMcpAccessCliOptions {
8
+ /** The app's config; only help needs none. */
9
+ readonly config?: SoftureConfig;
10
+ /** The arguments after the executable, e.g. `["prune"]`. */
11
+ readonly argv: readonly string[];
12
+ readonly output?: CliOutput;
13
+ /** Default: the wall clock. */
14
+ readonly clock?: Clock;
15
+ /** Default: the config's `database.handle`, else a connection on `database.url`. */
16
+ readonly openDatabase?: (database: SoftureDatabaseConfig) => Promise<CommandDatabase>;
17
+ }
18
+ export declare const EXIT_OK = 0;
19
+ export declare const EXIT_FAILED = 1;
20
+ export declare const EXIT_USAGE = 2;
21
+ export declare const MCP_ACCESS_USAGE = "Usage:\n softure-mcp prune deletes expired access tokens, authorization codes, grants and abandoned clients";
22
+ /** Runs one command and answers its exit code. */
23
+ export declare function runMcpAccessCli(options: RunMcpAccessCliOptions): Promise<number>;
24
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"AAQA,OAAO,EAA8B,KAAK,KAAK,EAAE,KAAK,aAAa,EAAE,KAAK,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC1H,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAG3E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACxC;AAED,MAAM,WAAW,sBAAsB;IACrC,8CAA8C;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,4DAA4D;IAC5D,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,+BAA+B;IAC/B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;IACvB,oFAAoF;IACpF,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,qBAAqB,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;CACvF;AAED,eAAO,MAAM,OAAO,IAAI,CAAC;AACzB,eAAO,MAAM,WAAW,IAAI,CAAC;AAC7B,eAAO,MAAM,UAAU,IAAI,CAAC;AAE5B,eAAO,MAAM,gBAAgB,oHAC2E,CAAC;AAOzG,kDAAkD;AAClD,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAatF"}
@@ -0,0 +1,58 @@
1
+ // `softure-mcp`: the module's maintenance command. `prune` deletes expired access tokens and dead OAuth
2
+ // records (`pruneMcpAccess`), for a daily job such as a deploy `maintain` hook. The app passes its
3
+ // validated config, so the same function serves the bin and an app script a bundler packs:
4
+ //
5
+ // // scripts/mcp.ts
6
+ // import { runMcpAccessCli } from "@softure-ai/mcp-access/cli";
7
+ // import config from "../softure.config";
8
+ // process.exitCode = await runMcpAccessCli({ config, argv: process.argv.slice(2) });
9
+ import { errorLogLabel, systemClock } from "@softure-ai/core";
10
+ import { openCommandDatabase } from "@softure-ai/db";
11
+ import { pruneMcpAccess } from "../server/prune.js";
12
+ export const EXIT_OK = 0;
13
+ export const EXIT_FAILED = 1;
14
+ export const EXIT_USAGE = 2;
15
+ export const MCP_ACCESS_USAGE = `Usage:
16
+ softure-mcp prune deletes expired access tokens, authorization codes, grants and abandoned clients`;
17
+ const consoleOutput = {
18
+ log: (line) => console.log(line),
19
+ error: (line) => console.error(line),
20
+ };
21
+ /** Runs one command and answers its exit code. */
22
+ export async function runMcpAccessCli(options) {
23
+ const output = options.output ?? consoleOutput;
24
+ const [command, ...rest] = options.argv;
25
+ if (command === undefined || command === "--help" || command === "help") {
26
+ output.log(MCP_ACCESS_USAGE);
27
+ return EXIT_OK;
28
+ }
29
+ if (command !== "prune" || rest.length > 0) {
30
+ output.error(command === "prune" ? `softure-mcp prune: unexpected argument "${rest[0] ?? ""}"` : `softure-mcp: unknown command "${command}"`);
31
+ output.error(MCP_ACCESS_USAGE);
32
+ return EXIT_USAGE;
33
+ }
34
+ return prune(options, output);
35
+ }
36
+ async function prune(options, output) {
37
+ const database = options.config?.database ?? null;
38
+ if (options.config === undefined || database === null) {
39
+ output.error("softure-mcp prune: the config has no database; set database.url in softure.config");
40
+ return EXIT_FAILED;
41
+ }
42
+ let opened;
43
+ try {
44
+ opened = await (options.openDatabase ?? ((settings) => openCommandDatabase(settings, { max: 1 })))(database);
45
+ const removed = await pruneMcpAccess({ db: opened.handle.db, clock: options.clock ?? systemClock, config: options.config });
46
+ output.log(`softure-mcp prune: removed ${String(removed.accessTokens)} access tokens, ${String(removed.codes)} authorization codes, ${String(removed.grants)} grants, ${String(removed.clients)} clients`);
47
+ return EXIT_OK;
48
+ }
49
+ catch (error) {
50
+ // The kind only: a driver message can carry the database address or credentials.
51
+ output.error(`softure-mcp prune: failed: ${errorLogLabel(error)}`);
52
+ return EXIT_FAILED;
53
+ }
54
+ finally {
55
+ await opened?.close();
56
+ }
57
+ }
58
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,16 @@
1
- import type { CoreErrorCode } from "@softure-ai/core";
1
+ import type { CoreErrorCode, Err } from "@softure-ai/core";
2
2
  export type McpAccessErrorCode = "mcp-access.name_required" | "mcp-access.name_too_long" | "mcp-access.token_limit_reached" | "mcp-access.token_not_found" | "mcp-access.grant_not_found";
3
- /** Every code the token page can show: its own, a missing session and the generic ones. */
4
- export type TokenFormErrorCode = McpAccessErrorCode | "auth.unauthenticated" | CoreErrorCode;
3
+ /** What an app's `beforeIssue` gate may answer instead of letting a token be issued. */
4
+ export type McpIssueRefusalCode = "mcp-access.issue_refused" | "mcp-access.write_refused";
5
+ /**
6
+ * A gate's refusal with the app's own text for the user ("Your trial has ended."), shown instead of the
7
+ * code's copy: an `Err` with a `message`, made by `refuseIssue` (`/next`).
8
+ */
9
+ export interface McpIssueRefusal extends Err<McpIssueRefusalCode> {
10
+ readonly message: string;
11
+ }
12
+ /** Every code the token page can show: its own, a gate's refusal, a missing session and the generic ones. */
13
+ export type TokenFormErrorCode = McpAccessErrorCode | McpIssueRefusalCode | "auth.unauthenticated" | CoreErrorCode;
5
14
  /** What the app's MCP server factory receives for one request. */
6
15
  export interface McpServerIdentity {
7
16
  /** The auth user id of the token's owner: every query of the server is scoped to it. */
@@ -34,6 +43,7 @@ export type AccessTokenStatus = {
34
43
  };
35
44
  /** Ready-to-paste setup for MCP clients, built around the plaintext token. */
36
45
  export interface McpClientSetup {
46
+ /** The name the client lists the server under: `clientServerName`, else `serverName`. */
37
47
  readonly serverName: string;
38
48
  readonly endpointUrl: string;
39
49
  /** `claude mcp add …` for Claude Code, with the token in it. */
@@ -56,6 +66,8 @@ export interface IssuedToken {
56
66
  readonly canWrite: boolean;
57
67
  /** The expiry, already formatted in the app's locale and time zone. */
58
68
  readonly expiresText: string;
69
+ /** The expiry as ISO 8601, for an app that formats it itself. */
70
+ readonly expiresAt: string;
59
71
  readonly setup: McpClientSetup;
60
72
  }
61
73
  /** What the issue action returns to its form (`useActionState`). */
@@ -64,9 +76,12 @@ export type IssueTokenFormState = {
64
76
  } | {
65
77
  readonly status: "ok";
66
78
  readonly issued: IssuedToken;
67
- } | {
79
+ }
80
+ /** `message`: the app's own refusal text from the `beforeIssue` gate, shown instead of the code's copy. */
81
+ | {
68
82
  readonly status: "error";
69
83
  readonly error: TokenFormErrorCode;
84
+ readonly message?: string;
70
85
  };
71
86
  /** A connected app (an OAuth grant) as its owner sees it. */
72
87
  export interface OAuthGrantView {
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -8,6 +8,7 @@ export declare const MCP_RATE_LIMIT_BUCKETS: {
8
8
  readonly mcp: {
9
9
  readonly limit: 200;
10
10
  readonly windowMinutes: 15;
11
+ readonly key: "ip";
11
12
  };
12
13
  /**
13
14
  * OAuth registration (per address) and token requests (per address and client id), when
@@ -17,6 +18,7 @@ export declare const MCP_RATE_LIMIT_BUCKETS: {
17
18
  readonly "mcp-oauth": {
18
19
  readonly limit: 60;
19
20
  readonly windowMinutes: 15;
21
+ readonly key: "ip";
20
22
  };
21
23
  };
22
24
  /**
@@ -24,12 +26,12 @@ export declare const MCP_RATE_LIMIT_BUCKETS: {
24
26
  * `mcpAccess({ serverName: "acme", tools: [{ name: "list_orders", access: "read", description: { en: "Lists your orders." } }] })`.
25
27
  */
26
28
  export declare const mcpAccess: import("@softure-ai/core").ModuleFactory<{
27
- page: string;
28
- endpoint: string;
29
- oauthConsent: string;
30
- oauthDecision: string;
31
- oauthToken: string;
32
- oauthRegister: string;
29
+ page: "/account/mcp";
30
+ endpoint: "/api/mcp";
31
+ oauthConsent: "/oauth/authorize";
32
+ oauthDecision: "/api/oauth/authorize";
33
+ oauthToken: "/api/oauth/token";
34
+ oauthRegister: "/api/oauth/register";
33
35
  }, {
34
36
  page: {
35
37
  title: string;
@@ -124,6 +126,8 @@ export declare const mcpAccess: import("@softure-ai/core").ModuleFactory<{
124
126
  token_limit_reached: string;
125
127
  token_not_found: string;
126
128
  grant_not_found: string;
129
+ issue_refused: string;
130
+ write_refused: string;
127
131
  };
128
132
  auth: {
129
133
  unauthenticated: string;
@@ -135,6 +139,7 @@ export declare const mcpAccess: import("@softure-ai/core").ModuleFactory<{
135
139
  };
136
140
  }, import("zod").ZodObject<{
137
141
  serverName: import("zod").ZodString;
142
+ clientServerName: import("zod").ZodOptional<import("zod").ZodString>;
138
143
  tools: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodObject<{
139
144
  name: import("zod").ZodString;
140
145
  access: import("zod").ZodEnum<{
@@ -145,6 +150,14 @@ export declare const mcpAccess: import("@softure-ai/core").ModuleFactory<{
145
150
  en: "en";
146
151
  pl: "pl";
147
152
  }> & import("zod/v4/core").$partial, import("zod").ZodString>;
153
+ title: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodEnum<{
154
+ en: "en";
155
+ pl: "pl";
156
+ }> & import("zod/v4/core").$partial, import("zod").ZodString>>;
157
+ example: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodEnum<{
158
+ en: "en";
159
+ pl: "pl";
160
+ }> & import("zod/v4/core").$partial, import("zod").ZodString>>;
148
161
  }, import("zod/v4/core").$strict>>>;
149
162
  allowWrites: import("zod").ZodDefault<import("zod").ZodBoolean>;
150
163
  tokenLifetimeDays: import("zod").ZodDefault<import("zod").ZodInt>;
@@ -166,9 +179,10 @@ export declare const mcpAccess: import("@softure-ai/core").ModuleFactory<{
166
179
  }, import("zod/v4/core").$strict>>;
167
180
  }, import("zod/v4/core").$strict>>;
168
181
  export { getAuthorizationHeader, getClaudeCodeCommand, getClaudeCodeLink, getDesktopConfig, getJsonConfig, getMcpClientSetup, type McpClientSetupInput, } from "./client-setup.js";
169
- export type { AccessTokenStatus, AccessTokenView, IssuedToken, IssueTokenFormState, McpAccessErrorCode, McpClientSetup, McpServerIdentity, OAuthGrantView, RevokeGrantFormState, RevokeTokenFormState, TokenFormErrorCode, } from "./contract.js";
182
+ export type { AccessTokenStatus, AccessTokenView, IssuedToken, IssueTokenFormState, McpAccessErrorCode, McpIssueRefusal, McpIssueRefusalCode, McpClientSetup, McpServerIdentity, OAuthGrantView, RevokeGrantFormState, RevokeTokenFormState, TokenFormErrorCode, } from "./contract.js";
170
183
  export { getTokenErrorMessage, mcpAccessMessages, type McpAccessMessages } from "./messages/index.js";
171
184
  export { MAX_PRESENTED_TOKEN_LENGTH, MAX_TOKEN_NAME_LENGTH, SERVER_NAME_PATTERN, TOOL_NAME_PATTERN, GENERATED_AUTHORIZATION_SERVER_KEYS, GENERATED_PROTECTED_RESOURCE_KEYS, type McpAccessOptions, type McpAccessOptionsInput, type McpAppOriginResolver, type McpMetadataExtension, type McpOAuthOptions, type McpToolAccess, type McpToolDefinition, type McpToolDefinitionInput, } from "./options.js";
185
+ export { DEFAULT_MCP_ACCESS_ROUTES } from "./routes.js";
172
186
  export { readRequestOrigin, type McpOriginRequest, type McpOrigins } from "./origins.js";
173
187
  export { accessTokens, oauthAuthorizationCodes, oauthClients, oauthGrants, type OAuthClientRow, type OAuthTokenEndpointAuthMethod } from "./schema.js";
174
188
  export { getAccessTokenStatus, type AccessTokenStatusOptions } from "./token-status.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,SAAS,eAAe,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB;;;;;IAEjC;;;;OAIG;;;;;CAEK,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAmCpB,CAAC;AAEH,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,KAAK,mBAAmB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,mCAAmC,EACnC,iCAAiC,EACjC,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,4BAA4B,EAAE,MAAM,aAAa,CAAC;AACvJ,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,SAAS,eAAe,CAAC;AAEtC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB;;;;;;IAEjC;;;;OAIG;;;;;;CAE8D,CAAC;AAEpE;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCA4BpB,CAAC;AAEH,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,KAAK,mBAAmB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,WAAW,EACX,mBAAmB,EACnB,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,EACL,0BAA0B,EAC1B,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,mCAAmC,EACnC,iCAAiC,EACjC,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,KAAK,gBAAgB,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,uBAAuB,EAAE,YAAY,EAAE,WAAW,EAAE,KAAK,cAAc,EAAE,KAAK,4BAA4B,EAAE,MAAM,aAAa,CAAC;AACvJ,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC"}