@softure-ai/mcp-access 0.0.0-stage → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +21 -0
- package/README.md +177 -2
- package/dist/client-setup.d.ts +32 -0
- package/dist/client-setup.d.ts.map +1 -0
- package/dist/client-setup.js +57 -0
- package/dist/client-setup.js.map +1 -0
- package/dist/contract.d.ts +80 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +2 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +119 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/messages/en.d.ts +77 -0
- package/dist/messages/en.d.ts.map +1 -0
- package/dist/messages/en.js +77 -0
- package/dist/messages/en.js.map +1 -0
- package/dist/messages/index.d.ts +161 -0
- package/dist/messages/index.d.ts.map +1 -0
- package/dist/messages/index.js +14 -0
- package/dist/messages/index.js.map +1 -0
- package/dist/messages/pl.d.ts +3 -0
- package/dist/messages/pl.d.ts.map +1 -0
- package/dist/messages/pl.js +77 -0
- package/dist/messages/pl.js.map +1 -0
- package/dist/next/actions.d.ts +6 -0
- package/dist/next/actions.d.ts.map +1 -0
- package/dist/next/actions.js +88 -0
- package/dist/next/actions.js.map +1 -0
- package/dist/next/context.d.ts +4 -0
- package/dist/next/context.d.ts.map +1 -0
- package/dist/next/context.js +13 -0
- package/dist/next/context.js.map +1 -0
- package/dist/next/format.d.ts +4 -0
- package/dist/next/format.d.ts.map +1 -0
- package/dist/next/format.js +7 -0
- package/dist/next/format.js.map +1 -0
- package/dist/next/index.d.ts +4 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +6 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/pages.d.ts +2 -0
- package/dist/next/pages.d.ts.map +1 -0
- package/dist/next/pages.js +61 -0
- package/dist/next/pages.js.map +1 -0
- package/dist/next/route.d.ts +3 -0
- package/dist/next/route.d.ts.map +1 -0
- package/dist/next/route.js +24 -0
- package/dist/next/route.js.map +1 -0
- package/dist/options.d.ts +43 -0
- package/dist/options.d.ts.map +1 -0
- package/dist/options.js +47 -0
- package/dist/options.js.map +1 -0
- package/dist/schema.d.ts +145 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +18 -0
- package/dist/schema.js.map +1 -0
- package/dist/server/endpoint.d.ts +26 -0
- package/dist/server/endpoint.d.ts.map +1 -0
- package/dist/server/endpoint.js +107 -0
- package/dist/server/endpoint.js.map +1 -0
- package/dist/server/health.d.ts +3 -0
- package/dist/server/health.d.ts.map +1 -0
- package/dist/server/health.js +11 -0
- package/dist/server/health.js.map +1 -0
- package/dist/server/index.d.ts +6 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/options.d.ts +20 -0
- package/dist/server/options.d.ts.map +1 -0
- package/dist/server/options.js +40 -0
- package/dist/server/options.js.map +1 -0
- package/dist/server/privacy.d.ts +15 -0
- package/dist/server/privacy.d.ts.map +1 -0
- package/dist/server/privacy.js +36 -0
- package/dist/server/privacy.js.map +1 -0
- package/dist/server/tokens.d.ts +66 -0
- package/dist/server/tokens.d.ts.map +1 -0
- package/dist/server/tokens.js +138 -0
- package/dist/server/tokens.js.map +1 -0
- package/dist/token-status.d.ts +9 -0
- package/dist/token-status.d.ts.map +1 -0
- package/dist/token-status.js +14 -0
- package/dist/token-status.js.map +1 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +4 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/token-manager.d.ts +43 -0
- package/dist/ui/token-manager.d.ts.map +1 -0
- package/dist/ui/token-manager.js +50 -0
- package/dist/ui/token-manager.js.map +1 -0
- package/migrations/0001_create_access_tokens.sql +24 -0
- package/module.json +15 -0
- package/package.json +69 -4
- package/src/client-setup.ts +71 -0
- package/src/contract.ts +80 -0
- package/src/index.ts +81 -0
- package/src/messages/en.ts +76 -0
- package/src/messages/index.ts +18 -0
- package/src/messages/pl.ts +78 -0
- package/src/next/actions.ts +91 -0
- package/src/next/context.ts +14 -0
- package/src/next/format.ts +10 -0
- package/src/next/index.ts +5 -0
- package/src/next/next-modules.d.ts +16 -0
- package/src/next/pages.tsx +80 -0
- package/src/next/route.ts +24 -0
- package/src/options.ts +58 -0
- package/src/schema.ts +19 -0
- package/src/server/endpoint.ts +141 -0
- package/src/server/health.ts +12 -0
- package/src/server/index.ts +36 -0
- package/src/server/options.ts +54 -0
- package/src/server/privacy.ts +49 -0
- package/src/server/tokens.ts +188 -0
- package/src/token-status.ts +27 -0
- package/src/ui/index.ts +11 -0
- package/src/ui/token-manager.tsx +254 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Newest first. Each version lists what changed for an app that uses `@softure-ai/mcp-access`. When an app has run a version in
|
|
4
|
+
production, the version gets a line `verified in: <app>@<commit>` ([docs/05](../../docs/05-adoption-playbook.md),
|
|
5
|
+
"Definition of done"). Versions before the first one below are described in their GitHub Releases (`mcp-access@x.y.z`).
|
|
6
|
+
|
|
7
|
+
## 0.1.6
|
|
8
|
+
|
|
9
|
+
- Adapters use the configured database handle; `@softure-ai/ui` is a peer dependency.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SOFTURE
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,178 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @softure-ai/mcp-access
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Lets the users of a Next.js app connect an AI assistant (Claude Code, Claude Desktop, Cursor) to
|
|
4
|
+
their own data over MCP: hashed, scoped, expiring access tokens, a rate-limited Bearer endpoint
|
|
5
|
+
around the app's own MCP server, and a token page with ready setup instructions. Built from
|
|
6
|
+
FIRE_TRACKER's assistant access (`src/db/access-tokens.ts`, `src/lib/{mcp-auth,access-token-status,mcp-client-config}.ts`,
|
|
7
|
+
`src/app/api/mcp/route.ts`, `src/app/actions/{manage-tokens,tokens,tokens-contract}.ts`), with the
|
|
8
|
+
domain server left to the app and the limit check made race-free.
|
|
9
|
+
|
|
10
|
+
## 1. What it provides
|
|
11
|
+
|
|
12
|
+
- The table `mcp.access_tokens`: name, read or write access, expiry, last use; **only the sha256** of
|
|
13
|
+
each token. A token is `sftmcp_` and 32 random bytes; the plaintext is shown once and stored nowhere.
|
|
14
|
+
- `POST /api/mcp`: identifies the client and counts the request (bucket `mcp`), verifies the
|
|
15
|
+
`Authorization: Bearer` token, then hands the request to a **fresh server from the app's factory**
|
|
16
|
+
with `{ userId, canWrite, tokenId }`. Unknown, revoked, expired and malformed tokens all get the
|
|
17
|
+
same `401 invalid_token` with a `WWW-Authenticate: Bearer` challenge.
|
|
18
|
+
- **Writes need two consents:** the app's `allowWrites` option and a token issued with write
|
|
19
|
+
access. `canWrite` is decided on every request, so turning `allowWrites` off makes every token
|
|
20
|
+
read-only at once.
|
|
21
|
+
- A token page for the signed-in user: the app's tool catalog, an issue form, the new token with its
|
|
22
|
+
setup (a prompt for Claude Code and a `claude-cli://` link, the `claude mcp add` command, an
|
|
23
|
+
`mcpServers` file, Claude Desktop through `mcp-remote`, the header), and the user's tokens with
|
|
24
|
+
their status (active, expiring in n days, expired), last use and a revoke button.
|
|
25
|
+
- Server functions for scripts and other hosts, and a health check for `GET /api/health`.
|
|
26
|
+
|
|
27
|
+
OAuth for MCP (discovery, dynamic client registration) is not part of this module: clients that
|
|
28
|
+
send a static header cover the personal-token case.
|
|
29
|
+
|
|
30
|
+
## 2. Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @softure-ai/mcp-access @softure-ai/auth @softure-ai/security @softure-ai/core @softure-ai/db @softure-ai/ui @modelcontextprotocol/server drizzle-orm zod
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Peer dependencies: `@modelcontextprotocol/server` `^2.0.0` (the app's factory and the endpoint
|
|
37
|
+
must use one copy of the SDK), `next` 16, `react` 19, `drizzle-orm`. The module depends on `auth`
|
|
38
|
+
and `security`; a configuration without them fails at startup.
|
|
39
|
+
|
|
40
|
+
## 3. Configuration
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { auth, AUTH_RATE_LIMIT_BUCKETS } from "@softure-ai/auth";
|
|
44
|
+
import { MCP_RATE_LIMIT_BUCKETS, mcpAccess } from "@softure-ai/mcp-access";
|
|
45
|
+
import { cloudflareIp, security } from "@softure-ai/security";
|
|
46
|
+
import { en } from "./messages/en";
|
|
47
|
+
import { pl } from "./messages/pl";
|
|
48
|
+
|
|
49
|
+
// in defineSoftureConfig({ modules: [...] }):
|
|
50
|
+
security({ clientIp: cloudflareIp(), buckets: { ...AUTH_RATE_LIMIT_BUCKETS, ...MCP_RATE_LIMIT_BUCKETS } }),
|
|
51
|
+
auth({ ... }),
|
|
52
|
+
mcpAccess({
|
|
53
|
+
serverName: "acme",
|
|
54
|
+
allowWrites: true,
|
|
55
|
+
tools: [
|
|
56
|
+
{ name: "list_orders", access: "read", description: { en: en.mcp.listOrders, pl: pl.mcp.listOrders } },
|
|
57
|
+
{ name: "cancel_order", access: "write", description: { en: en.mcp.cancelOrder, pl: pl.mcp.cancelOrder } },
|
|
58
|
+
],
|
|
59
|
+
}),
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
| Option | Type | Default | Meaning |
|
|
63
|
+
| --- | --- | --- | --- |
|
|
64
|
+
| `serverName` | `string` | required | The name clients list the server under (lowercase, digits, inner dashes; no quotes needed in a shell). |
|
|
65
|
+
| `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. |
|
|
66
|
+
| `allowWrites` | `boolean` | `false` | Lets write tokens be issued and used. Off, the page offers no write tokens and every token reads only. |
|
|
67
|
+
| `tokenLifetimeDays` | `integer` 1–365 | `90` | How long a token works. There is no renewal: the user issues a new one. |
|
|
68
|
+
| `maxTokensPerUser` | `integer` 1–100 | `20` | Unexpired tokens one account may hold. Counted under a per-user lock, so parallel requests cannot overshoot. |
|
|
69
|
+
| `expiryWarningDays` | `integer` 0–60 | `14` | From how many calendar days before the expiry the list warns. |
|
|
70
|
+
| `routes` | `{ page?, endpoint? }` | `/account/mcp`, `/api/mcp` | Move the page or the endpoint; the endpoint URL in the setup is `appOrigin` + `endpoint`. |
|
|
71
|
+
| `messages` | partial `en` / `pl` | — | Copy overrides. |
|
|
72
|
+
|
|
73
|
+
`MCP_RATE_LIMIT_BUCKETS` is `{ mcp: { limit: 200, windowMinutes: 15 } }` per client address. Every
|
|
74
|
+
request counts, valid or not, and before the token lookup; assistants behind one address share it.
|
|
75
|
+
|
|
76
|
+
## 4. Mounting
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
// app/api/mcp/route.ts
|
|
80
|
+
import { createMcpRoute } from "@softure-ai/mcp-access/next";
|
|
81
|
+
import { createServer } from "../../../lib/mcp-server";
|
|
82
|
+
|
|
83
|
+
export const POST = createMcpRoute({ createServer });
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// app/account/mcp/page.tsx
|
|
88
|
+
export { McpAccessPage as default } from "@softure-ai/mcp-access/next";
|
|
89
|
+
export const dynamic = "force-dynamic";
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The factory is the app's own MCP server, built per request:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
// lib/mcp-server.ts
|
|
96
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
97
|
+
import type { McpServerIdentity } from "@softure-ai/mcp-access";
|
|
98
|
+
|
|
99
|
+
export function createServer({ userId, canWrite }: McpServerIdentity): McpServer {
|
|
100
|
+
const server = new McpServer({ name: "acme", version: "1.0.0" });
|
|
101
|
+
server.registerTool("list_orders", { description: "Lists your orders." }, () => listOrders(userId));
|
|
102
|
+
if (canWrite) server.registerTool("cancel_order", { ... }, (input) => cancelOrder(userId, input));
|
|
103
|
+
return server;
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Every query of the server must be scoped to `userId`; register write tools only when `canWrite`
|
|
108
|
+
is true. GET and DELETE are not exported, so Next answers them with 405: the endpoint is stateless.
|
|
109
|
+
Answers to 2025-era clients come as one SSE `message` event, to 2026-07-28 clients as plain JSON;
|
|
110
|
+
mid-call notifications (progress, logging) are dropped. Keep `/api/mcp` out of a proxy guard that
|
|
111
|
+
redirects to the login page: the endpoint authenticates by token, not by cookie.
|
|
112
|
+
|
|
113
|
+
`TokenManager` (`@softure-ai/mcp-access/ui`) takes the actions `issueTokenAction` and
|
|
114
|
+
`revokeTokenAction` (`/next`) as props for an app that composes its own page.
|
|
115
|
+
|
|
116
|
+
## 5. Migrations and tables
|
|
117
|
+
|
|
118
|
+
`migrations/0001_create_access_tokens.sql` creates `mcp.access_tokens`:
|
|
119
|
+
|
|
120
|
+
| Column | Meaning |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| `id uuid` | The token's id: what the list and the revoke action name, and what logs may carry. |
|
|
123
|
+
| `user_id uuid` | The owner, `REFERENCES auth.users ON DELETE CASCADE`: deleting an account deletes its tokens. |
|
|
124
|
+
| `name text` | 1–60 characters, trimmed. |
|
|
125
|
+
| `token_hash text` | sha256 hex, unique. |
|
|
126
|
+
| `can_write boolean` | Issued with write access (still needs `allowWrites`). |
|
|
127
|
+
| `created_at`, `expires_at` | `expires_at > created_at`; a token works while `expires_at > now`. |
|
|
128
|
+
| `last_used_at` | Written on verification at most once a minute; informational only. |
|
|
129
|
+
|
|
130
|
+
Issuing deletes the issuer's expired tokens; `pruneAccessTokens(ctx)` (`/server`) deletes every
|
|
131
|
+
expired token, for a scheduled job. Rollback: in the migration header.
|
|
132
|
+
|
|
133
|
+
## 6. Environment variables
|
|
134
|
+
|
|
135
|
+
None of its own. The endpoint URL comes from `appOrigin` in `softure.config.ts`.
|
|
136
|
+
|
|
137
|
+
## 7. Switches
|
|
138
|
+
|
|
139
|
+
None. `allowWrites` is configuration, not a runtime switch: changing who may write is a deploy.
|
|
140
|
+
|
|
141
|
+
## 8. Appearance
|
|
142
|
+
|
|
143
|
+
`TokenManager` slots: `root`, `section`, `heading`, `text`, `list`, `item`, `itemHeader`, `badge`,
|
|
144
|
+
`details`, `snippet`, `code`; `unstyled` renders structure only. Styling comes from
|
|
145
|
+
`@softure-ai/ui` classes on the `--sft-*` tokens; the module ships no CSS of its own.
|
|
146
|
+
|
|
147
|
+
## 9. Copy
|
|
148
|
+
|
|
149
|
+
`mcpAccessMessages.{en,pl}`: `page`, `tools`, `issue`, `issued`, `setup.assistantPrompt`
|
|
150
|
+
(placeholders `{serverName}` and `{command}`), `list`, and `errors` for the codes
|
|
151
|
+
`mcp-access.name_required`, `name_too_long`, `token_limit_reached`, `token_not_found`,
|
|
152
|
+
`auth.unauthenticated`, `core.database_failed` and `core.unexpected`. Tool descriptions come from
|
|
153
|
+
the `tools` option, per locale.
|
|
154
|
+
|
|
155
|
+
## 10. Hooks
|
|
156
|
+
|
|
157
|
+
The MCP server factory passed to `createMcpRoute` is the module's only hook. `/server` also exports
|
|
158
|
+
`createMcpEndpoint({ createServer })`, a `(ctx, request) => Response` for hosts other than Next, and
|
|
159
|
+
`issueAccessToken`, `listAccessTokens`, `revokeAccessToken`, `verifyAccessToken` and
|
|
160
|
+
`pruneAccessTokens` for scripts.
|
|
161
|
+
|
|
162
|
+
## 11. GDPR
|
|
163
|
+
|
|
164
|
+
The rows hold no personal data beyond the owner's id and the token names. The module contributes
|
|
165
|
+
to `@softure-ai/privacy` (`privacy` flags on):
|
|
166
|
+
|
|
167
|
+
- **Export** (`exportMcpAccessUserData`): `accessTokens`, each token's name, write flag and
|
|
168
|
+
created, expiry and last-use dates. Never the token hash.
|
|
169
|
+
- **Deletion** (`deleteMcpAccessUserData`): every token of the user, before auth deletes the
|
|
170
|
+
account (the foreign key's cascade would remove them too).
|
|
171
|
+
|
|
172
|
+
## 12. Limitations / known gaps
|
|
173
|
+
|
|
174
|
+
- No OAuth for MCP: clients without a header field use `mcp-remote` (Claude Desktop) or another
|
|
175
|
+
bridge.
|
|
176
|
+
- Write access is per token, not per tool; finer scopes would be a new option.
|
|
177
|
+
- The rate limit is per client address, not per token.
|
|
178
|
+
- `responseMode: "json"` drops mid-call notifications; a streaming tool would need a different mode.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { McpClientSetup } from "./contract.js";
|
|
2
|
+
export interface McpClientSetupInput {
|
|
3
|
+
readonly serverName: string;
|
|
4
|
+
readonly endpointUrl: string;
|
|
5
|
+
readonly token: string;
|
|
6
|
+
/** The assistant prompt template from the messages: `{serverName}` and `{command}`. */
|
|
7
|
+
readonly promptTemplate: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The `Authorization` value, with its scheme. Clients with a header field send it verbatim, and
|
|
11
|
+
* the endpoint refuses a header without `Bearer `.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getAuthorizationHeader(token: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* `claude mcp add` with the token in it, not a placeholder: the plaintext is shown once, so this
|
|
16
|
+
* is the only moment a working command can be built for the user. `--scope user` connects the
|
|
17
|
+
* server everywhere, not only in the folder the terminal happens to be in.
|
|
18
|
+
*/
|
|
19
|
+
export declare function getClaudeCodeCommand(serverName: string, endpointUrl: string, token: string): string;
|
|
20
|
+
/** The `mcpServers` block for clients configured by a file. */
|
|
21
|
+
export declare function getJsonConfig(serverName: string, endpointUrl: string, token: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Claude Desktop's file runs local servers only, so a remote one goes through `mcp-remote`. The
|
|
24
|
+
* header sits in `args` without a space (`Authorization:${AUTH_HEADER}`) and its value in `env`:
|
|
25
|
+
* Claude Desktop on Windows splits arguments on spaces. `${AUTH_HEADER}` is a literal that
|
|
26
|
+
* `mcp-remote` substitutes. `-y` keeps `npx` from waiting for a confirmation nobody can give.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getDesktopConfig(serverName: string, endpointUrl: string, token: string): string;
|
|
29
|
+
/** Opens Claude Code with the prompt; Claude Code shows it and waits for Enter, it runs nothing alone. */
|
|
30
|
+
export declare function getClaudeCodeLink(prompt: string): string;
|
|
31
|
+
export declare function getMcpClientSetup({ serverName, endpointUrl, token, promptTemplate }: McpClientSetupInput): McpClientSetup;
|
|
32
|
+
//# sourceMappingURL=client-setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-setup.d.ts","sourceRoot":"","sources":["../src/client-setup.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,uFAAuF;IACvF,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEnG;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAG5F;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAO/F;AAED,0GAA0G;AAC1G,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,iBAAiB,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,mBAAmB,GAAG,cAAc,CAazH"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Ready-to-paste MCP client setup, built around the plaintext token (FIRE_TRACKER
|
|
2
|
+
// `src/lib/mcp-client-config.ts`, generalised to the app's server name and URL). Pure functions:
|
|
3
|
+
// the issue action calls them once, while the plaintext exists.
|
|
4
|
+
import { formatMessage } from "@softure-ai/core";
|
|
5
|
+
/**
|
|
6
|
+
* The `Authorization` value, with its scheme. Clients with a header field send it verbatim, and
|
|
7
|
+
* the endpoint refuses a header without `Bearer `.
|
|
8
|
+
*/
|
|
9
|
+
export function getAuthorizationHeader(token) {
|
|
10
|
+
return `Bearer ${token.trim()}`;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `claude mcp add` with the token in it, not a placeholder: the plaintext is shown once, so this
|
|
14
|
+
* is the only moment a working command can be built for the user. `--scope user` connects the
|
|
15
|
+
* server everywhere, not only in the folder the terminal happens to be in.
|
|
16
|
+
*/
|
|
17
|
+
export function getClaudeCodeCommand(serverName, endpointUrl, token) {
|
|
18
|
+
return ["claude mcp add --transport http --scope user", serverName, endpointUrl, `--header "Authorization: ${getAuthorizationHeader(token)}"`].join(" ");
|
|
19
|
+
}
|
|
20
|
+
/** The `mcpServers` block for clients configured by a file. */
|
|
21
|
+
export function getJsonConfig(serverName, endpointUrl, token) {
|
|
22
|
+
const server = { type: "http", url: endpointUrl, headers: { Authorization: getAuthorizationHeader(token) } };
|
|
23
|
+
return JSON.stringify({ mcpServers: { [serverName]: server } }, null, 2);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Claude Desktop's file runs local servers only, so a remote one goes through `mcp-remote`. The
|
|
27
|
+
* header sits in `args` without a space (`Authorization:${AUTH_HEADER}`) and its value in `env`:
|
|
28
|
+
* Claude Desktop on Windows splits arguments on spaces. `${AUTH_HEADER}` is a literal that
|
|
29
|
+
* `mcp-remote` substitutes. `-y` keeps `npx` from waiting for a confirmation nobody can give.
|
|
30
|
+
*/
|
|
31
|
+
export function getDesktopConfig(serverName, endpointUrl, token) {
|
|
32
|
+
const server = {
|
|
33
|
+
command: "npx",
|
|
34
|
+
args: ["-y", "mcp-remote", endpointUrl, "--header", "Authorization:${AUTH_HEADER}"],
|
|
35
|
+
env: { AUTH_HEADER: getAuthorizationHeader(token) },
|
|
36
|
+
};
|
|
37
|
+
return JSON.stringify({ mcpServers: { [serverName]: server } }, null, 2);
|
|
38
|
+
}
|
|
39
|
+
/** Opens Claude Code with the prompt; Claude Code shows it and waits for Enter, it runs nothing alone. */
|
|
40
|
+
export function getClaudeCodeLink(prompt) {
|
|
41
|
+
return `claude-cli://open?q=${encodeURIComponent(prompt)}`;
|
|
42
|
+
}
|
|
43
|
+
export function getMcpClientSetup({ serverName, endpointUrl, token, promptTemplate }) {
|
|
44
|
+
const claudeCodeCommand = getClaudeCodeCommand(serverName, endpointUrl, token);
|
|
45
|
+
const assistantPrompt = formatMessage(promptTemplate, { serverName, command: claudeCodeCommand });
|
|
46
|
+
return {
|
|
47
|
+
serverName,
|
|
48
|
+
endpointUrl,
|
|
49
|
+
claudeCodeCommand,
|
|
50
|
+
assistantPrompt,
|
|
51
|
+
claudeCodeLink: getClaudeCodeLink(assistantPrompt),
|
|
52
|
+
jsonConfig: getJsonConfig(serverName, endpointUrl, token),
|
|
53
|
+
desktopConfig: getDesktopConfig(serverName, endpointUrl, token),
|
|
54
|
+
authorizationHeader: getAuthorizationHeader(token),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=client-setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-setup.js","sourceRoot":"","sources":["../src/client-setup.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,iGAAiG;AACjG,gEAAgE;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAWjD;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,OAAO,UAAU,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;AAClC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,WAAmB,EAAE,KAAa;IACzF,OAAO,CAAC,8CAA8C,EAAE,UAAU,EAAE,WAAW,EAAE,4BAA4B,sBAAsB,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3J,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,aAAa,CAAC,UAAkB,EAAE,WAAmB,EAAE,KAAa;IAClF,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,EAAE,aAAa,EAAE,sBAAsB,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IAC7G,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB,EAAE,WAAmB,EAAE,KAAa;IACrF,MAAM,MAAM,GAAG;QACb,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,8BAA8B,CAAC;QACnF,GAAG,EAAE,EAAE,WAAW,EAAE,sBAAsB,CAAC,KAAK,CAAC,EAAE;KACpD,CAAC;IACF,OAAO,IAAI,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,0GAA0G;AAC1G,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO,uBAAuB,kBAAkB,CAAC,MAAM,CAAC,EAAE,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,cAAc,EAAuB;IACvG,MAAM,iBAAiB,GAAG,oBAAoB,CAAC,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC,CAAC;IAC/E,MAAM,eAAe,GAAG,aAAa,CAAC,cAAc,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAClG,OAAO;QACL,UAAU;QACV,WAAW;QACX,iBAAiB;QACjB,eAAe;QACf,cAAc,EAAE,iBAAiB,CAAC,eAAe,CAAC;QAClD,UAAU,EAAE,aAAa,CAAC,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC;QACzD,aAAa,EAAE,gBAAgB,CAAC,UAAU,EAAE,WAAW,EAAE,KAAK,CAAC;QAC/D,mBAAmB,EAAE,sBAAsB,CAAC,KAAK,CAAC;KACnD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { CoreErrorCode } 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";
|
|
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;
|
|
5
|
+
/** What the app's MCP server factory receives for one request. */
|
|
6
|
+
export interface McpServerIdentity {
|
|
7
|
+
/** The auth user id of the token's owner: every query of the server is scoped to it. */
|
|
8
|
+
readonly userId: string;
|
|
9
|
+
/** Whether write tools may be registered: the app allows writes and the token was issued for them. */
|
|
10
|
+
readonly canWrite: boolean;
|
|
11
|
+
/** The token's id, for the server's own logs. Never the token. */
|
|
12
|
+
readonly tokenId: string;
|
|
13
|
+
}
|
|
14
|
+
/** A stored token as its owner sees it. The hash never leaves the server functions. */
|
|
15
|
+
export interface AccessTokenView {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly name: string;
|
|
18
|
+
/** Issued with write access (it still writes only while the app allows writes). */
|
|
19
|
+
readonly canWrite: boolean;
|
|
20
|
+
readonly createdAt: Date;
|
|
21
|
+
readonly expiresAt: Date;
|
|
22
|
+
readonly lastUsedAt: Date | null;
|
|
23
|
+
}
|
|
24
|
+
/** Where a token stands on its expiry, for the list. */
|
|
25
|
+
export type AccessTokenStatus = {
|
|
26
|
+
readonly kind: "active";
|
|
27
|
+
}
|
|
28
|
+
/** `daysLeft`: calendar days in the app's time zone, from 0 (today) to the warning threshold. */
|
|
29
|
+
| {
|
|
30
|
+
readonly kind: "expiring";
|
|
31
|
+
readonly daysLeft: number;
|
|
32
|
+
} | {
|
|
33
|
+
readonly kind: "expired";
|
|
34
|
+
};
|
|
35
|
+
/** Ready-to-paste setup for MCP clients, built around the plaintext token. */
|
|
36
|
+
export interface McpClientSetup {
|
|
37
|
+
readonly serverName: string;
|
|
38
|
+
readonly endpointUrl: string;
|
|
39
|
+
/** `claude mcp add …` for Claude Code, with the token in it. */
|
|
40
|
+
readonly claudeCodeCommand: string;
|
|
41
|
+
/** A prompt asking the assistant to run that command and confirm the connection. */
|
|
42
|
+
readonly assistantPrompt: string;
|
|
43
|
+
/** A `claude-cli://` link that opens Claude Code with the prompt (it runs nothing by itself). */
|
|
44
|
+
readonly claudeCodeLink: string;
|
|
45
|
+
/** `mcpServers` JSON for clients configured by file (Cursor, VS Code, Windsurf). */
|
|
46
|
+
readonly jsonConfig: string;
|
|
47
|
+
/** Claude Desktop's file, bridged through `mcp-remote` (it cannot send headers itself). */
|
|
48
|
+
readonly desktopConfig: string;
|
|
49
|
+
/** The `Authorization` header value, for clients with a header field. */
|
|
50
|
+
readonly authorizationHeader: string;
|
|
51
|
+
}
|
|
52
|
+
/** A token right after it was issued: the only moment its plaintext exists outside the client. */
|
|
53
|
+
export interface IssuedToken {
|
|
54
|
+
readonly id: string;
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly canWrite: boolean;
|
|
57
|
+
/** The expiry, already formatted in the app's locale and time zone. */
|
|
58
|
+
readonly expiresText: string;
|
|
59
|
+
readonly setup: McpClientSetup;
|
|
60
|
+
}
|
|
61
|
+
/** What the issue action returns to its form (`useActionState`). */
|
|
62
|
+
export type IssueTokenFormState = {
|
|
63
|
+
readonly status: "idle";
|
|
64
|
+
} | {
|
|
65
|
+
readonly status: "ok";
|
|
66
|
+
readonly issued: IssuedToken;
|
|
67
|
+
} | {
|
|
68
|
+
readonly status: "error";
|
|
69
|
+
readonly error: TokenFormErrorCode;
|
|
70
|
+
};
|
|
71
|
+
/** What the revoke action returns to one token's form. */
|
|
72
|
+
export type RevokeTokenFormState = {
|
|
73
|
+
readonly status: "idle";
|
|
74
|
+
} | {
|
|
75
|
+
readonly status: "ok";
|
|
76
|
+
} | {
|
|
77
|
+
readonly status: "error";
|
|
78
|
+
readonly error: TokenFormErrorCode;
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"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"}
|
package/dist/contract.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":""}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
export declare const MODULE_ID = "mcp-access";
|
|
2
|
+
/**
|
|
3
|
+
* The rate limit bucket of `POST /api/mcp`, to spread into `security({ buckets })`: requests per
|
|
4
|
+
* client address, counted before the token is verified. Every request counts, so the limit is for
|
|
5
|
+
* the traffic behind one address (several assistants share it), not for one token.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MCP_RATE_LIMIT_BUCKETS: {
|
|
8
|
+
readonly mcp: {
|
|
9
|
+
readonly limit: 200;
|
|
10
|
+
readonly windowMinutes: 15;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Enables MCP access in `softure.config.ts` (after `security` and `auth`):
|
|
15
|
+
* `mcpAccess({ serverName: "acme", tools: [{ name: "list_orders", access: "read", description: { en: "Lists your orders." } }] })`.
|
|
16
|
+
*/
|
|
17
|
+
export declare const mcpAccess: import("@softure-ai/core").ModuleFactory<{
|
|
18
|
+
page: string;
|
|
19
|
+
endpoint: string;
|
|
20
|
+
}, {
|
|
21
|
+
page: {
|
|
22
|
+
title: string;
|
|
23
|
+
lead: string;
|
|
24
|
+
};
|
|
25
|
+
tools: {
|
|
26
|
+
title: string;
|
|
27
|
+
empty: string;
|
|
28
|
+
read: string;
|
|
29
|
+
write: string;
|
|
30
|
+
writeUnavailable: string;
|
|
31
|
+
};
|
|
32
|
+
issue: {
|
|
33
|
+
title: string;
|
|
34
|
+
name: string;
|
|
35
|
+
nameHint: string;
|
|
36
|
+
canWrite: string;
|
|
37
|
+
canWriteHint: string;
|
|
38
|
+
submit: string;
|
|
39
|
+
pending: string;
|
|
40
|
+
limit: string;
|
|
41
|
+
};
|
|
42
|
+
issued: {
|
|
43
|
+
title: string;
|
|
44
|
+
once: string;
|
|
45
|
+
expires: string;
|
|
46
|
+
readOnly: string;
|
|
47
|
+
readWrite: string;
|
|
48
|
+
promptTitle: string;
|
|
49
|
+
promptHint: string;
|
|
50
|
+
openClaudeCode: string;
|
|
51
|
+
commandTitle: string;
|
|
52
|
+
jsonTitle: string;
|
|
53
|
+
desktopTitle: string;
|
|
54
|
+
headerTitle: string;
|
|
55
|
+
copy: string;
|
|
56
|
+
copied: string;
|
|
57
|
+
done: string;
|
|
58
|
+
};
|
|
59
|
+
setup: {
|
|
60
|
+
assistantPrompt: string;
|
|
61
|
+
};
|
|
62
|
+
list: {
|
|
63
|
+
title: string;
|
|
64
|
+
empty: string;
|
|
65
|
+
readOnly: string;
|
|
66
|
+
readWrite: string;
|
|
67
|
+
active: string;
|
|
68
|
+
expired: string;
|
|
69
|
+
expiresToday: string;
|
|
70
|
+
expiresTomorrow: string;
|
|
71
|
+
expiresInDays: string;
|
|
72
|
+
expiresOn: string;
|
|
73
|
+
expiredOn: string;
|
|
74
|
+
createdOn: string;
|
|
75
|
+
lastUsed: string;
|
|
76
|
+
neverUsed: string;
|
|
77
|
+
revoke: string;
|
|
78
|
+
revoking: string;
|
|
79
|
+
};
|
|
80
|
+
errors: {
|
|
81
|
+
"mcp-access": {
|
|
82
|
+
name_required: string;
|
|
83
|
+
name_too_long: string;
|
|
84
|
+
token_limit_reached: string;
|
|
85
|
+
token_not_found: string;
|
|
86
|
+
};
|
|
87
|
+
auth: {
|
|
88
|
+
unauthenticated: string;
|
|
89
|
+
};
|
|
90
|
+
core: {
|
|
91
|
+
database_failed: string;
|
|
92
|
+
unexpected: string;
|
|
93
|
+
};
|
|
94
|
+
};
|
|
95
|
+
}, import("zod").ZodObject<{
|
|
96
|
+
serverName: import("zod").ZodString;
|
|
97
|
+
tools: import("zod").ZodDefault<import("zod").ZodArray<import("zod").ZodObject<{
|
|
98
|
+
name: import("zod").ZodString;
|
|
99
|
+
access: import("zod").ZodEnum<{
|
|
100
|
+
read: "read";
|
|
101
|
+
write: "write";
|
|
102
|
+
}>;
|
|
103
|
+
description: import("zod").ZodRecord<import("zod").ZodEnum<{
|
|
104
|
+
en: "en";
|
|
105
|
+
pl: "pl";
|
|
106
|
+
}> & import("zod/v4/core").$partial, import("zod").ZodString>;
|
|
107
|
+
}, import("zod/v4/core").$strict>>>;
|
|
108
|
+
allowWrites: import("zod").ZodDefault<import("zod").ZodBoolean>;
|
|
109
|
+
tokenLifetimeDays: import("zod").ZodDefault<import("zod").ZodInt>;
|
|
110
|
+
maxTokensPerUser: import("zod").ZodDefault<import("zod").ZodInt>;
|
|
111
|
+
expiryWarningDays: import("zod").ZodDefault<import("zod").ZodInt>;
|
|
112
|
+
}, import("zod/v4/core").$strict>>;
|
|
113
|
+
export { getAuthorizationHeader, getClaudeCodeCommand, getClaudeCodeLink, getDesktopConfig, getJsonConfig, getMcpClientSetup, type McpClientSetupInput, } from "./client-setup.js";
|
|
114
|
+
export type { AccessTokenStatus, AccessTokenView, IssuedToken, IssueTokenFormState, McpAccessErrorCode, McpClientSetup, McpServerIdentity, RevokeTokenFormState, TokenFormErrorCode, } from "./contract.js";
|
|
115
|
+
export { getTokenErrorMessage, mcpAccessMessages, type McpAccessMessages } from "./messages/index.js";
|
|
116
|
+
export { MAX_TOKEN_NAME_LENGTH, SERVER_NAME_PATTERN, TOOL_NAME_PATTERN, type McpAccessOptions, type McpAccessOptionsInput, type McpToolAccess, type McpToolDefinition, type McpToolDefinitionInput, } from "./options.js";
|
|
117
|
+
export { accessTokens } from "./schema.js";
|
|
118
|
+
export { getAccessTokenStatus, type AccessTokenStatusOptions } from "./token-status.js";
|
|
119
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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;;;;;CAEzB,CAAC;AAEX;;;GAGG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAqBpB,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,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,EACjB,KAAK,gBAAgB,EACrB,KAAK,qBAAqB,EAC1B,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,GAC5B,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Public API of @softure-ai/mcp-access: the module factory for softure.config.ts, its rate limit
|
|
2
|
+
// bucket, types, messages, table and the pure helpers for token status and client setup. Database
|
|
3
|
+
// work and the endpoint are in `@softure-ai/mcp-access/server`, the Next.js adapter (route, page,
|
|
4
|
+
// actions) in `/next`, the token manager in `/ui`.
|
|
5
|
+
import { defineModule, resolveMigrationsDir } from "@softure-ai/core";
|
|
6
|
+
import { mcpAccessMessages } from "./messages/index.js";
|
|
7
|
+
import { mcpAccessOptionsSchema } from "./options.js";
|
|
8
|
+
import { checkAccessTokensTable } from "./server/health.js";
|
|
9
|
+
import { mcpAccessPrivacyContributor } from "./server/privacy.js";
|
|
10
|
+
export const MODULE_ID = "mcp-access";
|
|
11
|
+
/**
|
|
12
|
+
* The rate limit bucket of `POST /api/mcp`, to spread into `security({ buckets })`: requests per
|
|
13
|
+
* client address, counted before the token is verified. Every request counts, so the limit is for
|
|
14
|
+
* the traffic behind one address (several assistants share it), not for one token.
|
|
15
|
+
*/
|
|
16
|
+
export const MCP_RATE_LIMIT_BUCKETS = {
|
|
17
|
+
mcp: { limit: 200, windowMinutes: 15 },
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Enables MCP access in `softure.config.ts` (after `security` and `auth`):
|
|
21
|
+
* `mcpAccess({ serverName: "acme", tools: [{ name: "list_orders", access: "read", description: { en: "Lists your orders." } }] })`.
|
|
22
|
+
*/
|
|
23
|
+
export const mcpAccess = defineModule({
|
|
24
|
+
manifest: {
|
|
25
|
+
id: MODULE_ID,
|
|
26
|
+
version: "0.1.6",
|
|
27
|
+
dependsOn: { security: "^0.1.0", auth: "^0.1.0" },
|
|
28
|
+
dbSchema: "mcp",
|
|
29
|
+
tables: ["access_tokens"],
|
|
30
|
+
env: [],
|
|
31
|
+
switches: [],
|
|
32
|
+
routes: { page: "/account/mcp", endpoint: "/api/mcp" },
|
|
33
|
+
mount: [
|
|
34
|
+
{ kind: "page", path: "app/account/mcp/page.tsx", export: "McpAccessPage" },
|
|
35
|
+
{ kind: "route-handler", path: "app/api/mcp/route.ts", export: "createMcpRoute" },
|
|
36
|
+
],
|
|
37
|
+
privacy: { exports: true, deletes: true },
|
|
38
|
+
},
|
|
39
|
+
messages: mcpAccessMessages,
|
|
40
|
+
options: mcpAccessOptionsSchema,
|
|
41
|
+
migrations: { dir: resolveMigrationsDir(import.meta.url, "../migrations/") },
|
|
42
|
+
privacy: mcpAccessPrivacyContributor,
|
|
43
|
+
health: checkAccessTokensTable,
|
|
44
|
+
});
|
|
45
|
+
export { getAuthorizationHeader, getClaudeCodeCommand, getClaudeCodeLink, getDesktopConfig, getJsonConfig, getMcpClientSetup, } from "./client-setup.js";
|
|
46
|
+
export { getTokenErrorMessage, mcpAccessMessages } from "./messages/index.js";
|
|
47
|
+
export { MAX_TOKEN_NAME_LENGTH, SERVER_NAME_PATTERN, TOOL_NAME_PATTERN, } from "./options.js";
|
|
48
|
+
export { accessTokens } from "./schema.js";
|
|
49
|
+
export { getAccessTokenStatus } from "./token-status.js";
|
|
50
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,kGAAkG;AAClG,kGAAkG;AAClG,mDAAmD;AACnD,OAAO,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,2BAA2B,EAAE,MAAM,qBAAqB,CAAC;AAElE,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC;AAEtC;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,aAAa,EAAE,EAAE,EAAE;CAC9B,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,YAAY,CAAC;IACpC,QAAQ,EAAE;QACR,EAAE,EAAE,SAAS;QACb,OAAO,EAAE,OAAO;QAChB,SAAS,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE;QACjD,QAAQ,EAAE,KAAK;QACf,MAAM,EAAE,CAAC,eAAe,CAAC;QACzB,GAAG,EAAE,EAAE;QACP,QAAQ,EAAE,EAAE;QACZ,MAAM,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,QAAQ,EAAE,UAAU,EAAE;QACtD,KAAK,EAAE;YACL,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,0BAA0B,EAAE,MAAM,EAAE,eAAe,EAAE;YAC3E,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,sBAAsB,EAAE,MAAM,EAAE,gBAAgB,EAAE;SAClF;QACD,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE;KAC1C;IACD,QAAQ,EAAE,iBAAiB;IAC3B,OAAO,EAAE,sBAAsB;IAC/B,UAAU,EAAE,EAAE,GAAG,EAAE,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,gBAAgB,CAAC,EAAE;IAC5E,OAAO,EAAE,2BAA2B;IACpC,MAAM,EAAE,sBAAsB;CAC/B,CAAC,CAAC;AAEH,OAAO,EACL,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,GAElB,MAAM,mBAAmB,CAAC;AAY3B,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAA0B,MAAM,qBAAqB,CAAC;AACtG,OAAO,EACL,qBAAqB,EACrB,mBAAmB,EACnB,iBAAiB,GAMlB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAiC,MAAM,mBAAmB,CAAC"}
|