@softure-ai/mcp-access 0.2.8 → 0.3.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 (153) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +270 -19
  3. package/dist/cli/run.d.ts +1 -1
  4. package/dist/cli/run.d.ts.map +1 -1
  5. package/dist/cli/run.js +2 -2
  6. package/dist/cli/run.js.map +1 -1
  7. package/dist/contract.d.ts +48 -2
  8. package/dist/contract.d.ts.map +1 -1
  9. package/dist/index.d.ts +96 -3
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +26 -5
  12. package/dist/index.js.map +1 -1
  13. package/dist/jobs/index.d.ts +2 -0
  14. package/dist/jobs/index.d.ts.map +1 -1
  15. package/dist/jobs/index.js +5 -2
  16. package/dist/jobs/index.js.map +1 -1
  17. package/dist/jobs/operation-state.d.ts +58 -0
  18. package/dist/jobs/operation-state.d.ts.map +1 -0
  19. package/dist/jobs/operation-state.js +68 -0
  20. package/dist/jobs/operation-state.js.map +1 -0
  21. package/dist/jobs/operation-tasks.d.ts +61 -0
  22. package/dist/jobs/operation-tasks.d.ts.map +1 -0
  23. package/dist/jobs/operation-tasks.js +161 -0
  24. package/dist/jobs/operation-tasks.js.map +1 -0
  25. package/dist/jobs/operations.d.ts +68 -0
  26. package/dist/jobs/operations.d.ts.map +1 -0
  27. package/dist/jobs/operations.js +110 -0
  28. package/dist/jobs/operations.js.map +1 -0
  29. package/dist/messages/en.d.ts +57 -1
  30. package/dist/messages/en.d.ts.map +1 -1
  31. package/dist/messages/en.js +57 -1
  32. package/dist/messages/en.js.map +1 -1
  33. package/dist/messages/index.d.ts +56 -1
  34. package/dist/messages/index.d.ts.map +1 -1
  35. package/dist/next/issue-token.d.ts +11 -2
  36. package/dist/next/issue-token.d.ts.map +1 -1
  37. package/dist/next/issue-token.js +56 -7
  38. package/dist/next/issue-token.js.map +1 -1
  39. package/dist/next/oauth-page.d.ts +6 -1
  40. package/dist/next/oauth-page.d.ts.map +1 -1
  41. package/dist/next/oauth-page.js +6 -5
  42. package/dist/next/oauth-page.js.map +1 -1
  43. package/dist/next/token-manager-props.d.ts +3 -2
  44. package/dist/next/token-manager-props.d.ts.map +1 -1
  45. package/dist/next/token-manager-props.js +12 -2
  46. package/dist/next/token-manager-props.js.map +1 -1
  47. package/dist/options.d.ts +47 -0
  48. package/dist/options.d.ts.map +1 -1
  49. package/dist/options.js +53 -0
  50. package/dist/options.js.map +1 -1
  51. package/dist/schema.d.ts +214 -0
  52. package/dist/schema.d.ts.map +1 -1
  53. package/dist/schema.js +21 -3
  54. package/dist/schema.js.map +1 -1
  55. package/dist/server/api-tokens.d.ts +64 -0
  56. package/dist/server/api-tokens.d.ts.map +1 -0
  57. package/dist/server/api-tokens.js +176 -0
  58. package/dist/server/api-tokens.js.map +1 -0
  59. package/dist/server/confirmation.d.ts +69 -0
  60. package/dist/server/confirmation.d.ts.map +1 -0
  61. package/dist/server/confirmation.js +220 -0
  62. package/dist/server/confirmation.js.map +1 -0
  63. package/dist/server/endpoint.d.ts.map +1 -1
  64. package/dist/server/endpoint.js +6 -4
  65. package/dist/server/endpoint.js.map +1 -1
  66. package/dist/server/health.d.ts.map +1 -1
  67. package/dist/server/health.js +3 -1
  68. package/dist/server/health.js.map +1 -1
  69. package/dist/server/index.d.ts +5 -2
  70. package/dist/server/index.d.ts.map +1 -1
  71. package/dist/server/index.js +4 -1
  72. package/dist/server/index.js.map +1 -1
  73. package/dist/server/oauth-http.d.ts +5 -0
  74. package/dist/server/oauth-http.d.ts.map +1 -1
  75. package/dist/server/oauth-http.js +11 -7
  76. package/dist/server/oauth-http.js.map +1 -1
  77. package/dist/server/privacy.d.ts +4 -0
  78. package/dist/server/privacy.d.ts.map +1 -1
  79. package/dist/server/privacy.js +5 -1
  80. package/dist/server/privacy.js.map +1 -1
  81. package/dist/server/prune.d.ts +5 -1
  82. package/dist/server/prune.d.ts.map +1 -1
  83. package/dist/server/prune.js +14 -4
  84. package/dist/server/prune.js.map +1 -1
  85. package/dist/server/strict-input.d.ts +3 -1
  86. package/dist/server/strict-input.d.ts.map +1 -1
  87. package/dist/server/strict-input.js +54 -8
  88. package/dist/server/strict-input.js.map +1 -1
  89. package/dist/server/tokens.d.ts +46 -11
  90. package/dist/server/tokens.d.ts.map +1 -1
  91. package/dist/server/tokens.js +46 -24
  92. package/dist/server/tokens.js.map +1 -1
  93. package/dist/server/tools.d.ts +8 -0
  94. package/dist/server/tools.d.ts.map +1 -1
  95. package/dist/server/tools.js +1 -0
  96. package/dist/server/tools.js.map +1 -1
  97. package/dist/server/writes-switch.d.ts +8 -0
  98. package/dist/server/writes-switch.d.ts.map +1 -0
  99. package/dist/server/writes-switch.js +25 -0
  100. package/dist/server/writes-switch.js.map +1 -0
  101. package/dist/stdio/serve.d.ts.map +1 -1
  102. package/dist/stdio/serve.js +5 -7
  103. package/dist/stdio/serve.js.map +1 -1
  104. package/dist/tokens/index.d.ts +6 -0
  105. package/dist/tokens/index.d.ts.map +1 -0
  106. package/dist/tokens/index.js +8 -0
  107. package/dist/tokens/index.js.map +1 -0
  108. package/dist/ui/consent-form.d.ts +2 -2
  109. package/dist/ui/consent-form.d.ts.map +1 -1
  110. package/dist/ui/consent-form.js +3 -2
  111. package/dist/ui/consent-form.js.map +1 -1
  112. package/dist/ui/index.d.ts +1 -1
  113. package/dist/ui/index.d.ts.map +1 -1
  114. package/dist/ui/index.js.map +1 -1
  115. package/dist/ui/token-manager.d.ts +18 -2
  116. package/dist/ui/token-manager.d.ts.map +1 -1
  117. package/dist/ui/token-manager.js +28 -3
  118. package/dist/ui/token-manager.js.map +1 -1
  119. package/migrations/0004_create_used_confirmations.sql +13 -0
  120. package/migrations/0005_add_api_tokens.sql +15 -0
  121. package/migrations/0006_create_operation_tasks.sql +18 -0
  122. package/module.json +10 -4
  123. package/package.json +7 -2
  124. package/src/cli/run.ts +2 -2
  125. package/src/contract.ts +60 -2
  126. package/src/index.ts +39 -4
  127. package/src/jobs/index.ts +41 -2
  128. package/src/jobs/operation-state.ts +119 -0
  129. package/src/jobs/operation-tasks.ts +229 -0
  130. package/src/jobs/operations.ts +213 -0
  131. package/src/messages/en.ts +58 -1
  132. package/src/next/issue-token.ts +64 -9
  133. package/src/next/oauth-page.tsx +12 -6
  134. package/src/next/token-manager-props.ts +15 -4
  135. package/src/options.ts +62 -0
  136. package/src/schema.ts +23 -3
  137. package/src/server/api-tokens.ts +224 -0
  138. package/src/server/confirmation.ts +316 -0
  139. package/src/server/endpoint.ts +6 -4
  140. package/src/server/health.ts +3 -1
  141. package/src/server/index.ts +33 -2
  142. package/src/server/oauth-http.ts +16 -7
  143. package/src/server/privacy.ts +9 -1
  144. package/src/server/prune.ts +17 -4
  145. package/src/server/strict-input.ts +62 -13
  146. package/src/server/tokens.ts +73 -27
  147. package/src/server/tools.ts +9 -0
  148. package/src/server/writes-switch.ts +24 -0
  149. package/src/stdio/serve.ts +5 -7
  150. package/src/tokens/index.ts +22 -0
  151. package/src/ui/consent-form.tsx +12 -8
  152. package/src/ui/index.ts +1 -0
  153. package/src/ui/token-manager.tsx +140 -3
package/CHANGELOG.md CHANGED
@@ -4,6 +4,77 @@ 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.3.0
8
+
9
+ - Long operations as MCP Tasks (#977), in `@softure-ai/mcp-access/jobs`: `startOperation` takes `taskRequest` (the
10
+ tool handler's request context) and `taskTtlSeconds`; when the client declared the Tasks extension
11
+ (`io.modelcontextprotocol/tasks`, revision 2026-07-28) on that request, the tool answers a task instead of an
12
+ operation id. `registerOperationTasks(server, { ctx, identity, readResult?, poll? })` declares the extension and
13
+ serves `tasks/get` (working, completed with the `get_operation` answer, `isError` for a failed operation, cancelled),
14
+ `tasks/update` and `tasks/cancel` (write access only), scoped to the account. Task ids are random and live in the
15
+ new table `mcp.operation_tasks` (migration `0006`), pruned past their expiry by `pruneMcpAccess` (which now also
16
+ counts `operationTasks`, and `softure-mcp prune` prints it) and deleted with the account. Every other client keeps the
17
+ polling tools. Migrate: run `softure migrate`; to offer tasks, pass the handler's second argument as `taskRequest`
18
+ and call `registerOperationTasks` on the same server (README "As MCP Tasks").
19
+ - Long operations as tools (#952), in `@softure-ai/mcp-access/jobs` on top of `@softure-ai/jobs` (with the progress
20
+ and running-job cancel of jobs #951): `startOperation(ctx, identity, { queue, payload, dedupKey? })` enqueues and
21
+ answers `{ operationId, status: "queued", pollAfterSeconds, message }` at once; `registerOperationStatusTool`
22
+ (`get_operation`) answers the status, the handler's progress, a growing `pollAfterSeconds`, the app's `result`
23
+ (`readResult`) and the `errorCode`; `registerOperationCancelTool` (`cancel_operation`, write access only) cancels a
24
+ queued operation and asks a running one to stop. Operations are scoped to the caller's account; no migration.
25
+ Migrate: an app with its own "start returns an id" and "status" tools can replace them with these (README "Long
26
+ operations").
27
+ - Personal tokens for the app's plain HTTP routes (#961): the option `apiTokens` (`prefix`, default `sftapi_`;
28
+ `scopes` with `read`/`write` access and a description; `examples` for the one-time reveal) turns on a second token
29
+ kind. `requireApiToken(ctx, request, { scopes })` checks a route's request: client address counted (bucket `api`),
30
+ Bearer token verified, token counted (bucket `api-token`), scopes and the write switch checked; refusals are values
31
+ that `apiTokenErrorResponse` answers (400/401/403/429/503). The new entry `@softure-ai/mcp-access/tokens` exports
32
+ it with `issueApiToken`, `verifyApiToken`, list, revoke and prune, and loads no MCP SDK. An API token never opens
33
+ the MCP endpoint and an MCP token never passes the check. The token page lists the app's scopes, adds the form
34
+ "New token for other apps" (`kind=api`, `scope:<name>` fields), shows the token once with the app's examples
35
+ (form state `api-ok`, `IssuedApiTokenView`) and names each token's kind. `AccessTokenView` gains `kind` and
36
+ `scopes`; the gate input `kind` and `scopes`; the privacy export `kind` and `scopes`. Both kinds share
37
+ `maxTokensPerUser`. Migration `0005_add_api_tokens.sql` adds `kind` and `scopes` (existing rows stay MCP tokens).
38
+ Migrate: nothing for an app without `apiTokens`, apart from running the migration; an app that compares
39
+ `listAccessTokens` or the privacy export field by field gains the two fields. To use it, add `apiTokens`, spread
40
+ `API_TOKEN_RATE_LIMIT_BUCKETS` into `security({ buckets })` and call `requireApiToken` in the routes.
41
+ - Confirmed tool calls (#953): `withConfirmation(handler, { ctx, identity, tool, estimate, ttlSeconds?,
42
+ runWithoutConfirmationWhen?, secret? })` (`/server`) makes a priced or irreversible tool estimate first and answer
43
+ `{ needsConfirmation: true, estimate, confirmation, expiresAt, message }`; the second call with the same arguments
44
+ and the token does the work. The token is an HMAC bound to the tool, the canonical arguments, the account and access
45
+ token, the estimate's total and the expiry, single use (stored by hash in the new `mcp.used_confirmations`), and a
46
+ mismatch is refused as `input.confirmation_invalid`, `_other_tool`, `_other_account`, `_expired`,
47
+ `_arguments_changed`, `_estimate_changed` or `_used`. `confirmationInput` is the schema field. `ToolCallRecord`
48
+ gains `confirmed: boolean | null` (`null` for other tools), and `pruneMcpAccess` counts `usedConfirmations`.
49
+ Migrate: run `softure migrate` (file 4). To confirm a tool, set `MCP_CONFIRMATION_SECRET` (32+ characters), add
50
+ `confirmation: confirmationInput` to its input schema and wrap its callback; drop an app-level `confirm: true`
51
+ boolean the assistant could set itself. A test that builds a `ToolCallRecord` by hand adds `confirmed: null`.
52
+ - Strict tool input lists a required nullable scalar as required (#919): a `z.number().nullable()` or
53
+ `z.boolean().nullable()` field that the parse requires is in `required` of the listed schema again, and a scalar
54
+ field's `default` (or `prefault`) is listed. The parse rules are unchanged. Migrate: an app that copied the inner
55
+ schema's `optin` onto its own text step, or put a `default` inside it, can drop that wrapper.
56
+ - A tool registered without an input schema on a strict server gets the strict empty object (#919): `tools/call`
57
+ with any argument is refused with the unknown-fields text, and the handler is still called with the context only.
58
+ The listing gains `additionalProperties: false`, so the measured tool list version changes once.
59
+ - `renamedFields` also takes a rule `(field) => newName | undefined` (#919), e.g. for a rename by pattern across many
60
+ fields. `RenamedFields` is exported from `/server`.
61
+
62
+ ## 0.2.9
63
+
64
+ - A runtime kill switch for writes (#904): the manifest switch `mcp-access.writes_enabled` (`WRITES_ENABLED_SWITCH`)
65
+ is read through `readSwitch` on every request. Writes need `allowWrites`, the token's write access and the switch;
66
+ off, write tools are not served, the `WWW-Authenticate` challenge and the default OAuth scope name only `mcp:read`,
67
+ the consent page offers no write tick and a decision grants read only. `isWritingEnabled(ctx)` (`/server`) answers
68
+ the app's side, and `AuthorizationRequest.isWriteOffered` the consent page's. Migrate: nothing; an app that does
69
+ not define the switch behaves as before. To use it, define it in `@softure-ai/feature-switches` (README §7
70
+ recommends `failMode: "closed"`, `override: "towards-fail-mode"`) and drop an app-level write switch.
71
+ - The consent page shows where the access code goes as a row of its own (#906): `consent.redirectLabel` and the
72
+ host (or the whole native-scheme URI) in monospace, with the hint `consent.redirectHint` that the app name is
73
+ self-declared and the address is not. `ConsentForm` gains the `target` slot; `note` now styles the hint.
74
+ `OAuthConsentPage` takes `classNames` and `unstyled` and passes them to `ConsentForm` and `ConsentError`.
75
+ Migrate: `consent.returnsTo` is gone; an app that overrides it moves its text to `consent.redirectLabel`, and an
76
+ app that recomposed the page to show the host can mount `OAuthConsentPage` again.
77
+
7
78
  ## 0.2.8
8
79
 
9
80
  - The content of 0.2.7, which was never published: its release stopped at the gates (ESLint ran out of memory in
package/README.md CHANGED
@@ -15,10 +15,11 @@ limit check made race-free.
15
15
  `Authorization: Bearer` token, then hands the request to a **fresh server from the app's factory**
16
16
  with `{ userId, canWrite, tokenId }`. Unknown, revoked, expired and malformed tokens all get the
17
17
  same `401 invalid_token` with a `WWW-Authenticate: Bearer` challenge. The challenge's `scope` is
18
- `mcp:read mcp:write` while `allowWrites` is on and `mcp:read` otherwise, so an OAuth client asks
18
+ `mcp:read mcp:write` while the app allows writes and `mcp:read` otherwise, so an OAuth client asks
19
19
  for what it may get.
20
- - **Writes need two consents:** the app's `allowWrites` option and a token issued with write
21
- access. `canWrite` is decided on every request, so turning `allowWrites` off makes every token
20
+ - **Writes need the app and the token:** the app's `allowWrites` option, the runtime switch
21
+ `mcp-access.writes_enabled` when the app defines it (§7), and a token issued with write access.
22
+ `canWrite` is decided on every request, so turning `allowWrites` or the switch off makes every token
22
23
  read-only at once.
23
24
  - A token page for the signed-in user: the app's tool catalog, an issue form, the new token with its
24
25
  setup (a prompt for Claude Code and a `claude-cli://` link, the `claude mcp add` command, an
@@ -30,9 +31,10 @@ limit check made race-free.
30
31
  - dynamic client registration (RFC 7591, `POST /api/oauth/register`), public or confidential;
31
32
  open to anyone, or closed outside a window an ops script opens (`oauth.registration`);
32
33
  - `/oauth/authorize`: authorization code with PKCE `S256` only, on a consent page that shows the
33
- app, where the code goes, the signed-in account and the access; writes need the `mcp:write`
34
- scope, `allowWrites` and the person's tick. A request without `scope` gets the default scope:
35
- both scopes while `allowWrites` is on, `mcp:read` otherwise;
34
+ app's self-declared name, where the code goes (a row of its own, in monospace: the one part a
35
+ client cannot fake), the signed-in account and the access; writes need the `mcp:write` scope, the
36
+ app allowing writes (`allowWrites` and the switch) and the person's tick. A request without `scope`
37
+ gets the default scope: both scopes while the app allows writes, `mcp:read` otherwise;
36
38
  - `POST /api/oauth/token`: `authorization_code` and `refresh_token`. Refresh tokens rotate on
37
39
  every use; presenting a rotated one again (`previous_refresh_token_hash`) revokes the whole
38
40
  grant, and so does reusing a code (RFC 6749 §4.1.2). `resource` (RFC 8707) must name the
@@ -40,6 +42,9 @@ limit check made race-free.
40
42
  - one grant per account and client, listed on the token page as "connected apps"; revoking it
41
43
  deletes its access tokens at once (`grant_id`, `ON DELETE CASCADE`). OAuth access tokens do not
42
44
  count against `maxTokensPerUser` and are not listed with the hand-issued ones.
45
+ - **Tokens for the app's own HTTP routes** (`apiTokens`, off by default): personal tokens with the app's prefix and
46
+ scopes (`recipes:write`) for a phone Shortcut, an automation tool or a script, checked in a route handler with
47
+ `requireApiToken` from `@softure-ai/mcp-access/tokens`, an entry that loads no MCP SDK (§4 "Tokens for HTTP routes").
43
48
  - Tokens an app issued before adopting the module keep working through `legacyTokenPattern`.
44
49
  - Server functions for scripts and other hosts, and a health check for `GET /api/health`.
45
50
  - For the app's own server: tool result helpers with a safe error boundary and server actions as write tools
@@ -47,7 +52,9 @@ limit check made race-free.
47
52
  (`createMcpServerFactory`, `measureToolList`, `/server`), strict tool input that refuses unknown and renamed
48
53
  fields (`withStrictToolInput`, `/server`), an opt-in `get_server_info` tool that tells which build answers
49
54
  (`serverInfoTool`, `/server`), a stdio entry for local assistants (`/stdio`), a catalog parity check (`/testing`), a gate before a
50
- token is issued (`issueToken`, `/next`) and the `softure-mcp prune` command for a daily cleanup (§4, §5).
55
+ token is issued (`issueToken`, `/next`), long operations as tools over `@softure-ai/jobs` (`startOperation`,
56
+ `get_operation`, `cancel_operation`, `/jobs`) or as MCP Tasks for clients that declare them (`registerOperationTasks`)
57
+ and the `softure-mcp prune` command for a daily cleanup (§4, §5).
51
58
 
52
59
  ## 2. Installation
53
60
 
@@ -191,7 +198,7 @@ input schema registered on the server from then on, or pass `strictToolInput: tr
191
198
 
192
199
  ```ts
193
200
  const server = withStrictToolInput(new McpServer({ name: "acme", version }), {
194
- renamedFields: { notes: "note" }, // old name → new name
201
+ renamedFields: { notes: "note" }, // old name → new name, or a rule: (field) => newName | undefined
195
202
  coerceTextScalars: true, // default
196
203
  messages: getMcpAccessMessages(config).toolInput, // default: the English dictionary
197
204
  });
@@ -204,10 +211,16 @@ const server = withStrictToolInput(new McpServer({ name: "acme", version }), {
204
211
  `.catchall(x)`) keeps it; refinements and the object's own error messages stay.
205
212
  - **Renamed fields:** a field sent under an old name where the object accepts the new one is refused with the
206
213
  stable code `input.field_renamed` and the new name, so a session on an older tool list learns it in one call.
214
+ `renamedFields` is a map or a rule, e.g. `(field) => field.includes("Pln") ? field.replace("Pln", "Home") :
215
+ undefined` for a rename across many fields; a name the rule leaves alone is an unknown field.
207
216
  - **Text scalars:** a client on an older tool list sends a field it does not know as text, so a boolean field also
208
217
  takes the exact text `"true"`/`"false"` and a number field a JSON number written as text, converted before the
209
218
  field's own rules (`"-1"` still fails `min(0)`). Any other text is refused. The listed JSON Schema keeps `boolean`
210
- and `number` and gains `additionalProperties: false`.
219
+ and `number`, the fields' `required` and the `default` of a scalar field, and gains `additionalProperties: false`.
220
+ A required `z.number().nullable()` stays required. (zod lists no `default` on an object or array that holds a
221
+ scalar field, since its input is transformed.)
222
+ - **No input schema:** a tool registered without one gets the strict empty object, so `tools/call` with any argument
223
+ is refused; its handler is still called with the request context only.
211
224
  - The SDK validates before the handler runs, so a refused call saves nothing. A raw shape is wrapped in
212
225
  `z.object` first; a schema that is not zod passes unchanged. `toStrictToolInput(schema, options?)` gives the
213
226
  strict schema on its own.
@@ -294,6 +307,128 @@ export async function createServer({ userId, canWrite, tokenId }: McpServerIdent
294
307
  }
295
308
  ```
296
309
 
310
+ ### Long operations: start, status, cancel
311
+
312
+ A tool whose work takes minutes (a render, an import) cannot hold the call open: the endpoint answers in JSON mode
313
+ and drops mid-call notifications, a proxy may cut a long response (Cloudflare at 100 seconds), and assistants time out
314
+ tool calls. `@softure-ai/mcp-access/jobs` turns such work into an operation on top of `@softure-ai/jobs`: the tool
315
+ starts a job and answers an id at once, the assistant asks `get_operation` until it ends, and may stop it with
316
+ `cancel_operation`.
317
+
318
+ ```ts
319
+ import { registerOperationCancelTool, registerOperationStatusTool, startOperation } from "@softure-ai/mcp-access/jobs";
320
+
321
+ export async function createServer(identity: McpServerIdentity) {
322
+ server.registerTool("render_video", { inputSchema: z.object({ videoId: z.string() }) }, (args) =>
323
+ withToolErrors(() => startOperation(ctx, identity, { queue: "video.render", payload: args, dedupKey: `video:${args.videoId}` })),
324
+ );
325
+ registerOperationStatusTool(server, { ctx, identity, readResult: (job) => findRenderUrl(db, job.id) });
326
+ registerOperationCancelTool(server, { ctx, identity }); // registers nothing without identity.canWrite
327
+ }
328
+ ```
329
+
330
+ - `startOperation(ctx, identity, { queue, payload, dedupKey?, statusToolName?, poll? })` enqueues the job and answers
331
+ `{ operationId, status: "queued", progress: null, pollAfterSeconds, message }`; the message tells the assistant to
332
+ call the status tool and not to start again. With a `dedupKey` (at most 150 characters), starting again while the
333
+ account's operation of that queue runs joins it: the answer carries its id and status.
334
+ - `get_operation` (`registerOperationStatusTool`, read only) answers `{ operationId, status, progress,
335
+ pollAfterSeconds?, cancelRequested?, result?, errorCode?, message }`. `status` is `queued`, `running` (also while a
336
+ failed attempt waits for its retry), `succeeded`, `failed` or `cancelled`; `progress` is what the handler reports
337
+ with `context.reportProgress` (codes and counts only); `pollAfterSeconds` is there while the operation runs and
338
+ grows with its age (a quarter of its seconds, between `poll.minSeconds` 2 and `poll.maxSeconds` 30); `result` is
339
+ what `readResult(job)` reads from the app's tables once it succeeded (jobs stores no result); `errorCode` is the
340
+ failure's code (`jobs.cancelled` for a cancelled one).
341
+ - `cancel_operation` (`registerOperationCancelTool`, a write) ends a queued operation at once and asks a running one
342
+ to stop (`requestJobCancel`: the handler's `signal` aborts on its next heartbeat); the answer says
343
+ `cancelRequested: true` until it has ended. An ended one answers "already ended" as a tool error.
344
+ - Scoped to the account: an operation's job has origin `mcp` and the dedup key `mcp:<userId>:<key>` (a random key
345
+ without `dedupKey`). Another account's operation, a job that is not an operation and a malformed id all answer
346
+ "No operation with this id belongs to this account." No table of its own: an operation lives as long as its job
347
+ (`retention` of jobs).
348
+ - `getOperation` and `cancelOperation` are the same reads as functions; the texts are under `operations` in the
349
+ module dictionary, and `name`/`description` rename a tool.
350
+
351
+ #### As MCP Tasks
352
+
353
+ A client that speaks the Tasks extension (`io.modelcontextprotocol/tasks`, protocol revision 2026-07-28) can follow an
354
+ operation as a task instead of calling `get_operation`. It is opt-in per tool and needs two things on the same server:
355
+ pass the tool handler's request context as `taskRequest`, and register the task handlers.
356
+
357
+ ```ts
358
+ import { registerOperationTasks, startOperation } from "@softure-ai/mcp-access/jobs";
359
+
360
+ server.registerTool("render_video", { inputSchema: z.object({ videoId: z.string() }) }, (args, request) =>
361
+ withToolErrors(() => startOperation(ctx, identity, { queue: "video.render", payload: args, taskRequest: request })),
362
+ );
363
+ registerOperationStatusTool(server, { ctx, identity, readResult }); // still there for every other client
364
+ registerOperationTasks(server, { ctx, identity, readResult });
365
+ ```
366
+
367
+ - Only a request whose client declared the extension in its per-request capabilities gets a task (`resultType:
368
+ "task"`, `status: "working"`, `ttlMs`, `pollIntervalMs`). A client that did not, including every 2025-era client,
369
+ gets the operation id exactly as above, so the polling tools stay the default.
370
+ - `registerOperationTasks(server, { ctx, identity, readResult?, poll? })` declares the extension in `server/discover`
371
+ and answers `tasks/get`, `tasks/update` and `tasks/cancel` for the request's account:
372
+ - `tasks/get`: `working` while the operation is queued or running (`statusMessage` says which, or that a stop was
373
+ asked; `pollIntervalMs` is `pollAfterSeconds` in milliseconds); `completed` once it ended with a `result` that is
374
+ the `get_operation` answer as a tool result (with the app's `readResult`), marked `isError: true` when the
375
+ operation failed (the spec keeps the `failed` status for protocol errors); `cancelled` when it was cancelled.
376
+ - `tasks/cancel`: like `cancel_operation`, a write: a read-only token gets `-32600`. An operation that has ended is
377
+ acknowledged all the same (cancelling is cooperative in the spec).
378
+ - `tasks/update`: operations never ask for input, so an owned task is acknowledged.
379
+ - An unknown task, another account's, or one whose job is gone is `-32602` "Task not found"; an expired one `-32602`
380
+ "Task has expired"; a request without the declared extension `-32021`.
381
+ - A task id is random (32 bytes, base64url) and stored in `mcp.operation_tasks` (§5) with its owner, its job and an
382
+ expiry: `taskTtlSeconds` on `startOperation`, a minute to thirty days, default a day. The same account starting the
383
+ same running operation again (same `dedupKey`) gets the same task. Progress counts stay with `get_operation`.
384
+
385
+ ### Confirmed calls
386
+
387
+ A tool that spends money (a paid provider call) or cannot be undone follows the `--commit` rule over MCP:
388
+ `withConfirmation` (`/server`) makes the first call estimate only, the person agrees in the chat, and only a second
389
+ call with the same input does the work. The assistant cannot agree on its own: the token comes only from an estimate,
390
+ is single use and bound to that exact input.
391
+
392
+ ```ts
393
+ import { confirmationInput, withConfirmation } from "@softure-ai/mcp-access/server";
394
+
395
+ server.registerTool(
396
+ "render_film",
397
+ { inputSchema: { scene: z.string(), seconds: z.number(), confirmation: confirmationInput } },
398
+ withConfirmation((args) => renderFilm(ctx, userId, args), {
399
+ ctx, // the module context, as for the app's queries
400
+ identity, // the factory's McpServerIdentity: the token is bound to its userId and tokenId
401
+ tool: "render_film",
402
+ estimate: (args) => estimateFilm(ctx, userId, args), // { total: 12, currency: "credits", ... }
403
+ runWithoutConfirmationWhen: (estimate) => estimate.total === 0, // everything was cached
404
+ }),
405
+ );
406
+ ```
407
+
408
+ - **First call** (no `confirmation`): runs `estimate(args)` only and answers
409
+ `{ needsConfirmation: true, estimate, confirmation, expiresAt, message }`; `message` is the fixed sentence
410
+ `confirmation.ask` of the dictionary, which tells the assistant to show the estimate and get the person's yes.
411
+ `estimate` returns an object with a finite numeric `total`; every other field reaches the assistant as it is.
412
+ When `runWithoutConfirmationWhen(estimate)` is true the handler runs at once instead.
413
+ - **The token** is an HMAC-SHA256 signed with `MCP_CONFIRMATION_SECRET` (§6, or the `secret` option) over the tool
414
+ name, a digest of the canonical JSON of the arguments without `confirmation` (`toCanonicalJson`: keys sorted,
415
+ `undefined` left out), a digest of the account and the access token, the estimate's total and the expiry
416
+ (`ttlSeconds`, default 900).
417
+ - **Second call** (same arguments and `confirmation`): the estimate runs again, then the checks, each refused with its
418
+ own code and the text of `confirmation.*` in the dictionary (an app's `hints` replace them):
419
+ `input.confirmation_invalid` (not signed by this secret, malformed), `input.confirmation_other_tool`,
420
+ `input.confirmation_other_account` (another account or access token), `input.confirmation_expired`,
421
+ `input.confirmation_arguments_changed`, `input.confirmation_estimate_changed` (the total changed after the yes)
422
+ and `input.confirmation_used`. Only when all pass is the token stored by its hash and the handler run with the
423
+ arguments without `confirmation`; two parallel calls with one token run once. A refused call never uses up a valid
424
+ token; a handler that fails after the check needs a new estimate.
425
+ - **The record**: `onToolCall` records gain `confirmed`: `true` for a call that ran on a valid confirmation, `false`
426
+ for an estimate, a refusal or a call run through `runWithoutConfirmationWhen`, `null` for tools without
427
+ confirmation. `argumentNames` leave `confirmation` out.
428
+ - `confirmationInput` is the field for the input schema (an optional string), so a strict server lists and accepts
429
+ it. Without `MCP_CONFIRMATION_SECRET` the call fails as a bug, with one log line naming the variable: it never runs
430
+ without asking.
431
+
297
432
  ### Stdio
298
433
 
299
434
  A local assistant on the machine that holds the database can run the same factory over stdio, without a token:
@@ -489,6 +624,67 @@ An app that composes its own routes from `/server` builds the context with
489
624
  resolves origins with `resolveMcpOrigins(config, request)` and passes them to the URL builders,
490
625
  whose last argument they are; `OAuthClientRow` is exported from the package root.
491
626
 
627
+ ### Tokens for HTTP routes
628
+
629
+ A person who calls the app from outside the browser (an iPhone Shortcut, Make or Zapier, a script) gets a personal
630
+ token for the app's plain HTTP routes. Declare the scopes the routes ask for, and the buckets:
631
+
632
+ ```ts
633
+ // softure.config.ts
634
+ security({ buckets: { ...AUTH_RATE_LIMIT_BUCKETS, ...MCP_RATE_LIMIT_BUCKETS, ...API_TOKEN_RATE_LIMIT_BUCKETS } }),
635
+ mcpAccess({
636
+ serverName: "acme",
637
+ allowWrites: true,
638
+ apiTokens: {
639
+ prefix: "acme_", // default "sftapi_"; never one of the module's own sftmc* prefixes
640
+ scopes: [
641
+ { name: "recipes:read", access: "read", description: { en: "Reads your recipes." } },
642
+ { name: "recipes:write", access: "write", description: { en: "Adds and changes recipes." } },
643
+ ],
644
+ // Shown once with a new token; {token} and {appOrigin} are filled in.
645
+ examples: [{ title: { en: "curl" }, text: { en: "curl -H 'Authorization: Bearer {token}' {appOrigin}/api/recipes" } }],
646
+ },
647
+ }),
648
+ ```
649
+
650
+ ```ts
651
+ // app/api/recipes/route.ts
652
+ import { getSoftureConfig } from "@softure-ai/core/next";
653
+ import { apiTokenErrorResponse, createMcpAccessContext, requireApiToken } from "@softure-ai/mcp-access/tokens";
654
+
655
+ export async function POST(request: Request): Promise<Response> {
656
+ const ctx = await createMcpAccessContext(getSoftureConfig());
657
+ const caller = await requireApiToken(ctx, request, { scopes: ["recipes:write"] });
658
+ if (!caller.ok) return apiTokenErrorResponse(caller);
659
+ // caller.value: { userId, tokenId, scopes }; scope every query to userId.
660
+ return Response.json(await addRecipe(caller.value.userId, await request.json()));
661
+ }
662
+ ```
663
+
664
+ - **The two kinds stay apart.** An API token (`kind = 'api'`, the app's prefix) never opens `POST /api/mcp`, and an
665
+ MCP token (`sftmcp_`, `mcp:read`/`mcp:write`) never passes `requireApiToken`. Both share the table, the hashing,
666
+ `tokenLifetimeDays`, `maxTokensPerUser` (one count for both), the revoke action, privacy and prune.
667
+ - **Order of the check:** the client address is counted in `api` before any lookup, the `Authorization: Bearer`
668
+ token is verified (expiry in the query, last use recorded), the token is counted in `api-token`, then the scopes.
669
+ - **Refusals are values** for `apiTokenErrorResponse`: `400 client_unidentified`; `401 api_token_missing` and
670
+ `api_token_invalid` (unknown, revoked, expired, an MCP token) with `WWW-Authenticate: Bearer`; `403
671
+ api_scope_missing` with `missingScopes` and `error="insufficient_scope"`; `403 api_writes_disabled` when the token
672
+ holds the write scope but writing is off; `429 rate_limited` with `Retry-After`; `503 api_unavailable` (logged)
673
+ when the count or the lookup fails. A scope the app does not declare, or missing buckets, throws: a setup bug.
674
+ - **Writes:** a `write` scope is granted at issue only with `allowWrites`, and counts on a request only while
675
+ writing is enabled (`allowWrites` and the `mcp-access.writes_enabled` switch, §7).
676
+ - **The token page** lists the app's scopes and offers a second form, "New token for other apps", that posts to the
677
+ same issue action with `kind=api` and a `scope:<name>` checkbox per offered scope. The new token is shown once
678
+ with its header and the app's `examples`; the action answers `{ status: "api-ok", issued }`
679
+ (`IssuedApiTokenView`), so an app reading `status === "ok"` for MCP tokens is unchanged. The list names each
680
+ token's kind ("AI assistant", "Other apps") and an API token's scopes. A `beforeIssue` gate gets `kind` and
681
+ `scopes`, with `canWrite` true when a write scope is asked.
682
+ - **Without MCP:** an app that has no MCP server enables `mcpAccess` with `apiTokens`, mounts only the token page
683
+ and its routes, and imports `@softure-ai/mcp-access/tokens` (which loads no `@modelcontextprotocol/*` module). The
684
+ npm peer list still names the SDK.
685
+ - Server functions: `issueApiToken`, `verifyApiToken`, `requireApiToken`, `apiTokenErrorResponse`,
686
+ `isApiTokenShape`, plus `listAccessTokens`, `revokeAccessToken`, `pruneAccessTokens` (`/tokens`, also in `/server`).
687
+
492
688
  ### Closed registration
493
689
 
494
690
  Registration is public by default: claude.ai and ChatGPT register a client before anyone signs in, and a client
@@ -544,9 +740,25 @@ expired (with their access tokens) and clients registered over a day ago that ho
544
740
  (`id = 1`) with `opened_at` and `open_until` (`open_until > opened_at`), the window of "Closed registration" (§4). It
545
741
  holds no personal data.
546
742
 
743
+ `migrations/0004_create_used_confirmations.sql` creates `mcp.used_confirmations`: the sha256 of each confirmation
744
+ token that has been used (`token_hash`, the primary key), `used_at` and the token's `expires_at`, which makes a
745
+ confirmation single use ("Confirmed calls", §4). It holds no personal data; `pruneUsedConfirmations(ctx)` deletes the
746
+ rows past `expires_at`.
747
+
748
+ `migrations/0005_add_api_tokens.sql` adds `access_tokens.kind` (`mcp` or `api`, default `mcp`) and
749
+ `access_tokens.scopes` (`text[]`, default empty), with one check: an `mcp` row has no scopes, an `api` row has 1 to
750
+ 32 scopes and no grant. Existing rows become MCP tokens through the defaults. An API token's `token_hash` is the
751
+ sha256 of its random part after the prefix (the prefix is checked by shape before any lookup). Rollback: in the migration header.
752
+
753
+ `migrations/0006_create_operation_tasks.sql` creates `mcp.operation_tasks` for long operations as MCP Tasks (§4):
754
+ `task_id` (random, 43 base64url characters, the primary key), `user_id` (`REFERENCES auth.users ON DELETE CASCADE`),
755
+ `job_id` (the operation's job in `@softure-ai/jobs`, no foreign key since jobs is optional), `created_at` and
756
+ `expires_at`, unique per account and job. `pruneOperationTasks(ctx)` (`/server`) deletes the rows past `expires_at`;
757
+ the jobs themselves follow the `retention` of jobs.
758
+
547
759
  ### Scheduled pruning
548
760
 
549
- `pruneMcpAccess(ctx)` (`/server`) runs both prunes and counts what each removed. The `softure-mcp prune` command
761
+ `pruneMcpAccess(ctx)` (`/server`) runs the prunes of tokens, OAuth records, used confirmations and operation tasks and counts what each removed. The `softure-mcp prune` command
550
762
  runs it with the app's config (loaded like `softure migrate`, or from an app script through `runMcpAccessCli` from
551
763
  `/cli`) and prints one line. Run it daily, e.g. as a `maintain` hook of `@softure-ai/deploy`:
552
764
 
@@ -591,16 +803,49 @@ migrations after the baseline.
591
803
 
592
804
  ## 6. Environment variables
593
805
 
594
- None of its own. The endpoint URL comes from `appOrigin` in `softure.config.ts`.
806
+ The endpoint URL comes from `appOrigin` in `softure.config.ts`.
807
+
808
+ | Variable | Required | Meaning |
809
+ | --- | --- | --- |
810
+ | `MCP_CONFIRMATION_SECRET` | only for confirmed calls | Signs and verifies the tokens of `withConfirmation` (§4), at least 32 characters, read on every call. Changing it voids the tokens in flight (the person is asked again). |
595
811
 
596
812
  ## 7. Switches
597
813
 
598
- None. `allowWrites` is configuration, not a runtime switch: changing who may write is a deploy.
814
+ `mcp-access.writes_enabled` (`WRITES_ENABLED_SWITCH`): a kill switch for writes, read through `readSwitch` of
815
+ `@softure-ai/core` on every MCP request, consent page and decision. `allowWrites` stays the configuration (no
816
+ writes at all without it); the switch takes writes away at once, without a deploy, when a token or a connected
817
+ assistant is suspected of misuse. Off, write tools are not served (the factory gets `canWrite: false`), the
818
+ challenge and the default OAuth scope name only `mcp:read`, and the consent page offers no write tick. Stored
819
+ tokens and grants keep their write access, so turning the switch back on restores it without new consent.
820
+
821
+ The app defines it in `@softure-ai/feature-switches`. Recommended: on by default, `failMode: "closed"` and
822
+ `override: "towards-fail-mode"`, so the environment variable `SOFTURE_SWITCH_MCP_ACCESS_WRITES_ENABLED=false`
823
+ can pull writes off but never push them back on:
824
+
825
+ ```ts
826
+ featureSwitches({
827
+ switches: [
828
+ {
829
+ name: "mcp-access.writes_enabled",
830
+ label: { en: "Assistant writes" },
831
+ description: { en: "Lets AI assistants with write access change data. Off, every assistant reads only." },
832
+ default: true,
833
+ failMode: "closed",
834
+ override: "towards-fail-mode",
835
+ },
836
+ ],
837
+ });
838
+ ```
839
+
840
+ An app that does not define it (or has no feature-switches) keeps `allowWrites` alone. A switch read that throws
841
+ counts as off and is logged by the switch's name. The stdio entry reads the switch once, when it starts.
599
842
 
600
843
  ## 8. Appearance
601
844
 
602
845
  `TokenManager` slots: `root`, `section`, `heading`, `text`, `list`, `item`, `itemHeader`, `badge`,
603
- `details`, `snippet`, `code`; `unstyled` renders structure only. Styling comes from
846
+ `details`, `snippet`, `code`; `unstyled` renders structure only. `ConsentForm` and `ConsentError` slots: `root`,
847
+ `details`, `term`, `value`, `target` (where the code goes), `note` (the hint under it), `actions`, `text`, `link`;
848
+ `OAuthConsentPage` takes `classNames` and `unstyled` and passes them to both. Styling comes from
604
849
  `@softure-ai/ui` classes on the `--sft-*` tokens; the module ships no CSS of its own.
605
850
 
606
851
  ## 9. Copy
@@ -632,11 +877,12 @@ The MCP server factory passed to `createMcpRoute` (and to `serveMcpStdio`) and t
632
877
  The rows hold no personal data beyond the owner's id and the token names. The module contributes
633
878
  to `@softure-ai/privacy` (`privacy` flags on):
634
879
 
635
- - **Export** (`exportMcpAccessUserData`): `accessTokens`, each hand-issued token's name, write
636
- flag and created, expiry and last-use dates, and `connectedApps`, each grant's client name, write
880
+ - **Export** (`exportMcpAccessUserData`): `accessTokens`, each hand-issued token's name, kind
881
+ (`mcp` or `api`), scopes, write flag and created, expiry and last-use dates, and `connectedApps`, each grant's client name, write
637
882
  flag, created and last-use dates. Never a hash.
638
- - **Deletion** (`deleteMcpAccessUserData`): the user's authorization codes, grants and tokens,
639
- before auth deletes the account (the foreign keys' cascade would remove them too). Registered
883
+ - **Deletion** (`deleteMcpAccessUserData`): the user's authorization codes, grants, tokens and MCP tasks,
884
+ before auth deletes the account (the foreign keys' cascade would remove them too). Tasks hold a job id and dates
885
+ only, so the export leaves them out. Registered
640
886
  clients hold no personal data and are pruned when unused.
641
887
 
642
888
  ## 12. Limitations / known gaps
@@ -646,5 +892,10 @@ to `@softure-ai/privacy` (`privacy` flags on):
646
892
  app that wants `jwks_uri` adds it through `oauth.metadata` and serves the empty set itself).
647
893
  - Only `S256` PKCE and the `authorization_code` and `refresh_token` grants.
648
894
  - Write access is per token, not per tool; finer scopes would be a new option.
649
- - The rate limit is per client address, not per token.
650
- - `responseMode: "json"` drops mid-call notifications; a streaming tool would need a different mode.
895
+ - The MCP endpoint's rate limit is per client address, not per token (API tokens are counted per token as well).
896
+ - One API token kind per app (one prefix); the `kind` column takes more kinds without a migration of the rows.
897
+ - `responseMode: "json"` drops mid-call notifications; a streaming tool would need a different mode. Long work is an
898
+ operation instead (start, `get_operation`, `cancel_operation`).
899
+ - MCP Tasks follow the extension of revision 2026-07-28 only; the 2025-11-25 core Tasks (task-augmented requests,
900
+ `tasks/result`, `tasks/list`) are not served, and those clients use the polling tools. Tasks carry no input
901
+ requests and no `notifications/tasks`.
package/dist/cli/run.d.ts CHANGED
@@ -18,7 +18,7 @@ export interface RunMcpAccessCliOptions {
18
18
  export declare const EXIT_OK = 0;
19
19
  export declare const EXIT_FAILED = 1;
20
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";
21
+ export declare const MCP_ACCESS_USAGE = "Usage:\n softure-mcp prune deletes expired access tokens, authorization codes, grants, abandoned clients and used confirmations";
22
22
  /** Runs one command and answers its exit code. */
23
23
  export declare function runMcpAccessCli(options: RunMcpAccessCliOptions): Promise<number>;
24
24
  //# sourceMappingURL=run.d.ts.map
@@ -1 +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"}
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,wIAC+F,CAAC;AAO7H,kDAAkD;AAClD,wBAAsB,eAAe,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC,CAatF"}
package/dist/cli/run.js CHANGED
@@ -13,7 +13,7 @@ export const EXIT_OK = 0;
13
13
  export const EXIT_FAILED = 1;
14
14
  export const EXIT_USAGE = 2;
15
15
  export const MCP_ACCESS_USAGE = `Usage:
16
- softure-mcp prune deletes expired access tokens, authorization codes, grants and abandoned clients`;
16
+ softure-mcp prune deletes expired access tokens, authorization codes, grants, abandoned clients and used confirmations`;
17
17
  const consoleOutput = {
18
18
  log: (line) => console.log(line),
19
19
  error: (line) => console.error(line),
@@ -43,7 +43,7 @@ async function prune(options, output) {
43
43
  try {
44
44
  opened = await (options.openDatabase ?? ((settings) => openCommandDatabase(settings, { max: 1 })))(database);
45
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`);
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, ${String(removed.usedConfirmations)} used confirmations, ${String(removed.operationTasks)} operation tasks`);
47
47
  return EXIT_OK;
48
48
  }
49
49
  catch (error) {
@@ -1 +1 @@
1
- {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,24 @@
1
1
  import type { CoreErrorCode, Err } from "@softure-ai/core";
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";
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" | "mcp-access.api_disabled" | "mcp-access.scope_required" | "mcp-access.scope_unknown";
3
+ /** Which door a token opens: the MCP endpoint, or the app's HTTP routes through `requireApiToken`. */
4
+ export type AccessTokenKind = "mcp" | "api";
5
+ /** Why `requireApiToken` refused a request; `apiTokenErrorResponse` turns each into its HTTP answer. */
6
+ export type ApiTokenErrorCode = "mcp-access.api_token_missing" | "mcp-access.api_token_invalid" | "mcp-access.api_scope_missing" | "mcp-access.api_writes_disabled" | "mcp-access.api_unavailable" | "security.client_unidentified" | "security.rate_limited";
7
+ /** A refusal of `requireApiToken`. */
8
+ export type ApiTokenRefusal = Err<Exclude<ApiTokenErrorCode, "security.rate_limited" | "mcp-access.api_scope_missing" | "mcp-access.api_writes_disabled">> | (Err<"mcp-access.api_scope_missing" | "mcp-access.api_writes_disabled"> & {
9
+ readonly missingScopes: readonly string[];
10
+ }) | (Err<"security.rate_limited"> & {
11
+ readonly retryAfterSeconds: number;
12
+ });
13
+ /** Who called a route with a valid API token. */
14
+ export interface ApiTokenIdentity {
15
+ /** The auth user id of the token's owner: scope every query of the route to it. */
16
+ readonly userId: string;
17
+ /** The token's id, for the route's own logs. Never the token. */
18
+ readonly tokenId: string;
19
+ /** The token's effective scopes: write scopes only while the app allows writes. */
20
+ readonly scopes: readonly string[];
21
+ }
3
22
  /** What an app's `beforeIssue` gate may answer instead of letting a token be issued. */
4
23
  export type McpIssueRefusalCode = "mcp-access.issue_refused" | "mcp-access.write_refused";
5
24
  /**
@@ -24,6 +43,9 @@ export interface McpServerIdentity {
24
43
  export interface AccessTokenView {
25
44
  readonly id: string;
26
45
  readonly name: string;
46
+ readonly kind: AccessTokenKind;
47
+ /** The app scopes of an API token; empty for an MCP token. */
48
+ readonly scopes: readonly string[];
27
49
  /** Issued with write access (it still writes only while the app allows writes). */
28
50
  readonly canWrite: boolean;
29
51
  readonly createdAt: Date;
@@ -59,7 +81,7 @@ export interface McpClientSetup {
59
81
  /** The `Authorization` header value, for clients with a header field. */
60
82
  readonly authorizationHeader: string;
61
83
  }
62
- /** A token right after it was issued: the only moment its plaintext exists outside the client. */
84
+ /** An MCP token right after it was issued: the only moment its plaintext exists outside the client. */
63
85
  export interface IssuedToken {
64
86
  readonly id: string;
65
87
  readonly name: string;
@@ -70,6 +92,25 @@ export interface IssuedToken {
70
92
  readonly expiresAt: string;
71
93
  readonly setup: McpClientSetup;
72
94
  }
95
+ /** A snippet the app gave for API tokens (`apiTokens.examples`), filled in with the new token. */
96
+ export interface ApiTokenExample {
97
+ readonly title: string;
98
+ readonly text: string;
99
+ }
100
+ /** An API token right after it was issued, for the one-time reveal. */
101
+ export interface IssuedApiTokenView {
102
+ readonly id: string;
103
+ readonly name: string;
104
+ /** The scopes granted (write scopes only with `allowWrites`). */
105
+ readonly scopes: readonly string[];
106
+ readonly expiresText: string;
107
+ readonly expiresAt: string;
108
+ /** The plaintext, shown once. */
109
+ readonly token: string;
110
+ /** `Bearer <token>`. */
111
+ readonly authorizationHeader: string;
112
+ readonly examples: readonly ApiTokenExample[];
113
+ }
73
114
  /** What the issue action returns to its form (`useActionState`). */
74
115
  export type IssueTokenFormState = {
75
116
  readonly status: "idle";
@@ -77,6 +118,11 @@ export type IssueTokenFormState = {
77
118
  readonly status: "ok";
78
119
  readonly issued: IssuedToken;
79
120
  }
121
+ /** An API token was issued (`kind=api` in the form): its one-time reveal. */
122
+ | {
123
+ readonly status: "api-ok";
124
+ readonly issued: IssuedApiTokenView;
125
+ }
80
126
  /** `message`: the app's own refusal text from the `beforeIssue` gate, shown instead of the code's copy. */
81
127
  | {
82
128
  readonly status: "error";
@@ -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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"}