@fdkey/mcp 0.2.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.
- package/CHANGELOG.md +47 -0
- package/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/guard.d.ts +17 -0
- package/dist/guard.d.ts.map +1 -0
- package/dist/guard.js +49 -0
- package/dist/guard.js.map +1 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +409 -0
- package/dist/index.js.map +1 -0
- package/dist/router-static.d.ts +13 -0
- package/dist/router-static.d.ts.map +1 -0
- package/dist/router-static.js +16 -0
- package/dist/router-static.js.map +1 -0
- package/dist/session-store.d.ts +45 -0
- package/dist/session-store.d.ts.map +1 -0
- package/dist/session-store.js +71 -0
- package/dist/session-store.js.map +1 -0
- package/dist/types.d.ts +156 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/vps-client.d.ts +42 -0
- package/dist/vps-client.d.ts.map +1 -0
- package/dist/vps-client.js +90 -0
- package/dist/vps-client.js.map +1 -0
- package/dist/vps-router.d.ts +29 -0
- package/dist/vps-router.d.ts.map +1 -0
- package/dist/vps-router.js +146 -0
- package/dist/vps-router.js.map +1 -0
- package/dist/well-known.d.ts +14 -0
- package/dist/well-known.d.ts.map +1 -0
- package/dist/well-known.js +42 -0
- package/dist/well-known.js.map +1 -0
- package/package.json +72 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@fdkey/mcp` will be documented in this file.
|
|
4
|
+
|
|
5
|
+
## 0.2.0 — 2026-05-09
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **Cloudflare Workers / Bun / Deno support** on the default routing path.
|
|
10
|
+
The single-VPS `StaticRouter` now uses the runtime's global `fetch` and
|
|
11
|
+
imports zero Node-only dependencies. Multi-VPS routing (set via
|
|
12
|
+
`discoveryUrl`) still uses `undici` for IP-pinning and is lazy-loaded
|
|
13
|
+
via dynamic `import()`, so Workers bundles never pull undici unless the
|
|
14
|
+
integrator explicitly opts in. `undici` moved from `dependencies` to
|
|
15
|
+
`optionalDependencies`.
|
|
16
|
+
- **`score` and `tier` as first-class fields on `FdkeyContext`.**
|
|
17
|
+
Previously available only inside `FdkeyContext.claims`. The wire shape
|
|
18
|
+
reserves `score` as a 0..1 float for forward-compat with graduated
|
|
19
|
+
capability scoring (today the value is binary 1.0/0.0).
|
|
20
|
+
- **Bounded session store** — sessions now evict on a 1h idle TTL with a
|
|
21
|
+
hard 10k LRU cap (~2 MB max). Long-lived shared MCP servers no longer
|
|
22
|
+
leak per-session memory.
|
|
23
|
+
- **Actionable error message** when `discoveryUrl` is set but `undici`
|
|
24
|
+
is not installed.
|
|
25
|
+
- Internal `index.test.ts` covering: lazy-router contract, score/tier
|
|
26
|
+
shape, SDK_VERSION sync, and SessionStore eviction semantics.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- `withFdkey()` no longer reaches into a static `import './vps-router.js'`.
|
|
31
|
+
The default URL is now `https://api.fdkey.com` when neither `vpsUrl`
|
|
32
|
+
nor `discoveryUrl` is set.
|
|
33
|
+
- `getFdkeyContext()` reads via `store.peek()` — querying context no
|
|
34
|
+
longer extends a session's lifetime.
|
|
35
|
+
|
|
36
|
+
### Migration from 0.1.0
|
|
37
|
+
|
|
38
|
+
No public API breaks. If you currently relied on `FdkeyContext.claims.score`
|
|
39
|
+
you can keep doing that, or migrate to the first-class `ctx.score` /
|
|
40
|
+
`ctx.tier` fields. If you use multi-VPS routing (`discoveryUrl`), make
|
|
41
|
+
sure `undici` is in your dependencies — it's no longer pulled in by
|
|
42
|
+
default.
|
|
43
|
+
|
|
44
|
+
## 0.1.0 — 2026-04-XX
|
|
45
|
+
|
|
46
|
+
Initial pre-publish release. MCP middleware: tool injection, policy gating,
|
|
47
|
+
Ed25519 JWT verify, IP-pinned multi-VPS routing.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 FDKEY
|
|
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
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# @fdkey/mcp
|
|
2
|
+
|
|
3
|
+
> **FDKEY verification middleware for MCP servers.** Gate AI-agent access to
|
|
4
|
+
> your tools behind LLM-only puzzles. Drop-in for any
|
|
5
|
+
> [Model Context Protocol](https://modelcontextprotocol.io) server.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
- Injects two MCP tools into your server: `fdkey_get_challenge` and
|
|
10
|
+
`fdkey_submit_challenge`.
|
|
11
|
+
- Wraps the tools you want to protect — they return
|
|
12
|
+
`fdkey_verification_required` until the connecting agent has solved a
|
|
13
|
+
challenge.
|
|
14
|
+
- Talks to `https://api.fdkey.com` for challenge issuance and scoring.
|
|
15
|
+
- Verifies the Ed25519 JWT response **offline** using the public key from
|
|
16
|
+
`https://api.fdkey.com/.well-known/fdkey.json`.
|
|
17
|
+
|
|
18
|
+
The agent never handles a token. The connection itself becomes verified —
|
|
19
|
+
verification state lives server-side in the integrator's process, keyed by
|
|
20
|
+
the MCP session id. Every agent verifies for itself; verification doesn't
|
|
21
|
+
transfer between agents.
|
|
22
|
+
|
|
23
|
+
## Runtime support
|
|
24
|
+
|
|
25
|
+
| Runtime | Default (single-VPS) | `discoveryUrl` set (multi-VPS) |
|
|
26
|
+
| -------------------- | :------------------: | :----------------------------: |
|
|
27
|
+
| Node 18+ | ✅ | ✅ |
|
|
28
|
+
| Cloudflare Workers | ✅ | ❌ (requires `undici`, Node-only) |
|
|
29
|
+
| Bun | ✅ | ✅ |
|
|
30
|
+
| Deno | ✅ | ⚠️ untested |
|
|
31
|
+
|
|
32
|
+
By default the SDK runs on the global `fetch` and pulls in zero
|
|
33
|
+
Node-only dependencies — works on edge runtimes out of the box. The
|
|
34
|
+
multi-VPS routing path (set via `discoveryUrl`) is lazy-loaded and
|
|
35
|
+
requires `undici` (declared as an `optionalDependency`).
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install @fdkey/mcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
You also need the official MCP server SDK (`@modelcontextprotocol/sdk`) — it's
|
|
44
|
+
declared as a `peerDependency`, so install your own version:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
npm install @modelcontextprotocol/sdk
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Get an API key at [app.fdkey.com](https://app.fdkey.com).
|
|
51
|
+
|
|
52
|
+
## Usage
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
56
|
+
import { withFdkey } from '@fdkey/mcp';
|
|
57
|
+
|
|
58
|
+
const server = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
59
|
+
|
|
60
|
+
// Wrap the server. `protect` lists tool names that require verification.
|
|
61
|
+
withFdkey(server, {
|
|
62
|
+
apiKey: process.env.FDKEY_API_KEY!,
|
|
63
|
+
protect: {
|
|
64
|
+
sensitive_action: { policy: 'each_call' },
|
|
65
|
+
register: { policy: 'once_per_session' },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Register your tools as normal.
|
|
70
|
+
server.registerTool('sensitive_action', {
|
|
71
|
+
description: 'Does something that needs verification',
|
|
72
|
+
inputSchema: { /* ... */ },
|
|
73
|
+
}, async (args, extra) => {
|
|
74
|
+
// Reaches here only if the agent has solved a challenge first.
|
|
75
|
+
return { content: [{ type: 'text', text: 'verified' }] };
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Serve over your transport of choice (stdio, HTTP, etc.)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Policies
|
|
82
|
+
|
|
83
|
+
Per-tool gating policy — passed as `{ policy: ... }` in the `protect` map:
|
|
84
|
+
|
|
85
|
+
- `'each_call'` — verification required for every invocation. Use for
|
|
86
|
+
irreversible actions (payments, deletes).
|
|
87
|
+
- `'once_per_session'` — verification required once per connection. Use
|
|
88
|
+
for account creation, signup-style flows.
|
|
89
|
+
- `{ type: 'every_minutes', minutes: N }` — verification good for N
|
|
90
|
+
minutes after the puzzle was solved. Use as a middle ground when
|
|
91
|
+
"every call" is too aggressive but "once forever" is too loose. Note
|
|
92
|
+
the timer does NOT extend on calls — it expires `minutes` after the
|
|
93
|
+
puzzle solve, regardless of activity.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
protect: {
|
|
97
|
+
delete_account: { policy: 'each_call' },
|
|
98
|
+
register: { policy: 'once_per_session' },
|
|
99
|
+
refresh_dashboard: { policy: { type: 'every_minutes', minutes: 15 } },
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Configuration reference
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
withFdkey(server, {
|
|
107
|
+
apiKey: 'fdk_...', // required
|
|
108
|
+
protect: { ... }, // tool name → policy (above)
|
|
109
|
+
vpsUrl?: 'https://api.fdkey.com', // override for self-hosted
|
|
110
|
+
discoveryUrl?: 'https://...endpoints.json', // multi-VPS routing (Node only; lazy-loaded)
|
|
111
|
+
difficulty?: 'easy' | 'medium' | 'hard', // default 'medium'
|
|
112
|
+
onFail?: 'block' | 'allow', // default 'block' — what happens when puzzle is failed
|
|
113
|
+
onVpsError?: 'block' | 'allow', // default 'allow' (see below)
|
|
114
|
+
inlineChallenge?: boolean, // default false — embed puzzle JSON in blocked-tool errors
|
|
115
|
+
// so the agent can submit without a separate
|
|
116
|
+
// `fdkey_get_challenge` round-trip
|
|
117
|
+
tags?: { env: 'prod', region: 'eu' }, // free-form non-PII labels forwarded to FDKEY for analytics
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Failure-mode defaults
|
|
122
|
+
|
|
123
|
+
`onVpsError: 'allow'` is the default — if the FDKEY scoring service is
|
|
124
|
+
unreachable, the protected tool falls through to your handler instead of
|
|
125
|
+
blocking. We chose this so an FDKEY outage doesn't brick your workflow
|
|
126
|
+
in the worst case (think: we shut down, your DNS can't resolve api.fdkey.com,
|
|
127
|
+
etc.). FDKEY is verification, not gating — your service still serves traffic
|
|
128
|
+
when our service is down.
|
|
129
|
+
|
|
130
|
+
If your threat model is the opposite — you'd rather drop traffic than admit
|
|
131
|
+
unverified callers during an outage — set `onVpsError: 'block'` and you
|
|
132
|
+
get HTTP-503-style errors instead.
|
|
133
|
+
|
|
134
|
+
## What FDKEY sees
|
|
135
|
+
|
|
136
|
+
- The MCP `clientInfo` your agent reports (name, version, protocol version,
|
|
137
|
+
transport).
|
|
138
|
+
- Challenge IDs, scores, timestamps.
|
|
139
|
+
- Your integrator-supplied `tags`.
|
|
140
|
+
|
|
141
|
+
## Security notes
|
|
142
|
+
|
|
143
|
+
- **JWT `aud` is not validated by the SDK.** The audience claim binds the
|
|
144
|
+
JWT to the integrator's `vps_users.id`, which the SDK doesn't know at
|
|
145
|
+
verify time. The VPS already binds `aud` to the API key that requested
|
|
146
|
+
the challenge — defense in depth — but in principle, a JWT issued for
|
|
147
|
+
one FDKEY-protected service could be replayed against a different
|
|
148
|
+
one within the JWT lifetime (~5 min default). Keep the JWT lifetime
|
|
149
|
+
short on the VPS side if your threat model includes cross-integrator
|
|
150
|
+
replay.
|
|
151
|
+
|
|
152
|
+
## What FDKEY does NOT see
|
|
153
|
+
|
|
154
|
+
- Your prompts.
|
|
155
|
+
- Tool inputs or outputs.
|
|
156
|
+
- Your end users' identities or PII.
|
|
157
|
+
- Anything about the agent beyond the MCP `clientInfo` it self-reports.
|
|
158
|
+
|
|
159
|
+
## Reading verification context
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { getFdkeyContext, type FdkeyContext } from '@fdkey/mcp';
|
|
163
|
+
|
|
164
|
+
server.registerTool('whoami', { /*...*/ }, async (args, extra) => {
|
|
165
|
+
const ctx = getFdkeyContext(server, extra);
|
|
166
|
+
if (ctx?.verified) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{
|
|
169
|
+
type: 'text',
|
|
170
|
+
text: `Verified at ${ctx.verifiedAt} (score=${ctx.score}, tier=${ctx.tier})`,
|
|
171
|
+
}],
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return { content: [{ type: 'text', text: 'Not verified yet' }] };
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`FdkeyContext` shape:
|
|
179
|
+
|
|
180
|
+
```ts
|
|
181
|
+
interface FdkeyContext {
|
|
182
|
+
verified: boolean; // true once the agent has solved a challenge
|
|
183
|
+
verifiedAt: number | null; // ms epoch of the most recent successful verify
|
|
184
|
+
score: number | null; // 0..1 capability score (today binary 1.0/0.0)
|
|
185
|
+
tier: string | null; // VPS-issued tier label (e.g. "free", "gold")
|
|
186
|
+
claims: Record<string, unknown> | null; // raw decoded JWT, for power users
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`score` is reserved as a 0..1 float for graduated capability scoring (combined T1 correctness + T3 tau + future T4-T6 frequency); today the value is effectively binary. The field shape will not change when the internal scoring grows.
|
|
191
|
+
|
|
192
|
+
## Links
|
|
193
|
+
|
|
194
|
+
- Marketing + docs: <https://fdkey.com>
|
|
195
|
+
- Dashboard (sign up + manage keys): <https://app.fdkey.com>
|
|
196
|
+
- Source: <https://github.com/fdkey/sdks>
|
|
197
|
+
- Issues: <https://github.com/fdkey/sdks/issues>
|
|
198
|
+
|
|
199
|
+
## License
|
|
200
|
+
|
|
201
|
+
MIT — see [LICENSE](./LICENSE).
|
package/dist/guard.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Policy, SessionState } from './types.js';
|
|
2
|
+
export declare function newSession(): SessionState;
|
|
3
|
+
/**
|
|
4
|
+
* Decides whether a protected tool call passes given the current policy and session state.
|
|
5
|
+
*
|
|
6
|
+
* once_per_session: pass if the session has ever been verified.
|
|
7
|
+
* each_call: pass only if there is an unconsumed fresh verification ticket.
|
|
8
|
+
* every_minutes: N: pass if (now - verifiedAt) < N minutes — the timer does NOT
|
|
9
|
+
* extend on calls; it expires N minutes after the puzzle was solved.
|
|
10
|
+
*/
|
|
11
|
+
export declare function canCall(policy: Policy, _toolName: string, session: SessionState): boolean;
|
|
12
|
+
/** Called only when fdkey_submit_challenge succeeds. Replenishes session state. */
|
|
13
|
+
export declare function markVerified(session: SessionState): void;
|
|
14
|
+
/** Called after a protected tool call completes. Consumes the fresh-verification
|
|
15
|
+
* ticket for each_call policies. once_per_session and every_minutes do nothing. */
|
|
16
|
+
export declare function consumePolicy(policy: Policy, session: SessionState): void;
|
|
17
|
+
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEvD,wBAAgB,UAAU,IAAI,YAAY,CAazC;AAED;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAWzF;AAED,mFAAmF;AACnF,wBAAgB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAIxD;AAED;oFACoF;AACpF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,IAAI,CAIzE"}
|
package/dist/guard.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function newSession() {
|
|
2
|
+
return {
|
|
3
|
+
verified: false,
|
|
4
|
+
verifiedAt: null,
|
|
5
|
+
lastTouchedAt: Date.now(),
|
|
6
|
+
freshVerificationAvailable: false,
|
|
7
|
+
pendingChallengeId: null,
|
|
8
|
+
lastClaims: null,
|
|
9
|
+
clientInfo: null,
|
|
10
|
+
protocolVersion: null,
|
|
11
|
+
mcpSessionId: null,
|
|
12
|
+
transport: 'unknown',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Decides whether a protected tool call passes given the current policy and session state.
|
|
17
|
+
*
|
|
18
|
+
* once_per_session: pass if the session has ever been verified.
|
|
19
|
+
* each_call: pass only if there is an unconsumed fresh verification ticket.
|
|
20
|
+
* every_minutes: N: pass if (now - verifiedAt) < N minutes — the timer does NOT
|
|
21
|
+
* extend on calls; it expires N minutes after the puzzle was solved.
|
|
22
|
+
*/
|
|
23
|
+
export function canCall(policy, _toolName, session) {
|
|
24
|
+
switch (policy.type) {
|
|
25
|
+
case 'once_per_session':
|
|
26
|
+
return session.verified;
|
|
27
|
+
case 'each_call':
|
|
28
|
+
return session.verified && session.freshVerificationAvailable;
|
|
29
|
+
case 'every_minutes': {
|
|
30
|
+
if (session.verifiedAt === null)
|
|
31
|
+
return false;
|
|
32
|
+
return Date.now() - session.verifiedAt < policy.minutes * 60 * 1000;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Called only when fdkey_submit_challenge succeeds. Replenishes session state. */
|
|
37
|
+
export function markVerified(session) {
|
|
38
|
+
session.verified = true;
|
|
39
|
+
session.verifiedAt = Date.now();
|
|
40
|
+
session.freshVerificationAvailable = true;
|
|
41
|
+
}
|
|
42
|
+
/** Called after a protected tool call completes. Consumes the fresh-verification
|
|
43
|
+
* ticket for each_call policies. once_per_session and every_minutes do nothing. */
|
|
44
|
+
export function consumePolicy(policy, session) {
|
|
45
|
+
if (policy.type === 'each_call') {
|
|
46
|
+
session.freshVerificationAvailable = false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,QAAQ,EAAE,KAAK;QACf,UAAU,EAAE,IAAI;QAChB,aAAa,EAAE,IAAI,CAAC,GAAG,EAAE;QACzB,0BAA0B,EAAE,KAAK;QACjC,kBAAkB,EAAE,IAAI;QACxB,UAAU,EAAE,IAAI;QAChB,UAAU,EAAE,IAAI;QAChB,eAAe,EAAE,IAAI;QACrB,YAAY,EAAE,IAAI;QAClB,SAAS,EAAE,SAAS;KACrB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,OAAO,CAAC,MAAc,EAAE,SAAiB,EAAE,OAAqB;IAC9E,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,kBAAkB;YACrB,OAAO,OAAO,CAAC,QAAQ,CAAC;QAC1B,KAAK,WAAW;YACd,OAAO,OAAO,CAAC,QAAQ,IAAI,OAAO,CAAC,0BAA0B,CAAC;QAChE,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,IAAI,OAAO,CAAC,UAAU,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YAC9C,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,UAAU,GAAG,MAAM,CAAC,OAAO,GAAG,EAAE,GAAG,IAAI,CAAC;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,OAAqB;IAChD,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAChC,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC;AAC5C,CAAC;AAED;oFACoF;AACpF,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,OAAqB;IACjE,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAChC,OAAO,CAAC,0BAA0B,GAAG,KAAK,CAAC;IAC7C,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { FdkeyConfig, IVpsRouter, RoutingTarget } from './types.js';
|
|
3
|
+
export type { FdkeyConfig, Policy, AgentMeta, IntegratorMeta, ChallengeMeta } from './types.js';
|
|
4
|
+
/** @internal Test-only export. Not part of the supported public API;
|
|
5
|
+
* may change or disappear without notice. Used by `index.test.ts` to
|
|
6
|
+
* verify the lazy-import contract directly. */
|
|
7
|
+
export { LazyVpsRouter as __LazyVpsRouterForTesting };
|
|
8
|
+
/** Read-only context surfaced to integrator tool handlers. */
|
|
9
|
+
export interface FdkeyContext {
|
|
10
|
+
verified: boolean;
|
|
11
|
+
verifiedAt: number | null;
|
|
12
|
+
/** Capability score from the most recent verification, in [0, 1].
|
|
13
|
+
* Today this is effectively binary (1.0 = passed, 0.0 = failed); the
|
|
14
|
+
* type signature reserves the float so future capability scoring
|
|
15
|
+
* (combined T1 correctness + T3 tau + T4-T6 frequency) can land
|
|
16
|
+
* without an API change. Null until first successful verify. */
|
|
17
|
+
score: number | null;
|
|
18
|
+
/** VPS-issued tier label from the most recent verification (e.g.
|
|
19
|
+
* capability bucket the agent fell into). Null until first verify. */
|
|
20
|
+
tier: string | null;
|
|
21
|
+
/** Decoded JWT payload from the most recent verification. Includes
|
|
22
|
+
* `score`, `tier`, `puzzle_summary`, etc. Null until first successful verify. */
|
|
23
|
+
claims: Record<string, unknown> | null;
|
|
24
|
+
}
|
|
25
|
+
/** Lazy wrapper around the multi-VPS `VpsRouter`. Defers `await import('./vps-router.js')`
|
|
26
|
+
* until the first `getTarget()` call so Workers/Bun/Deno builds — which never hit this
|
|
27
|
+
* path unless `discoveryUrl` is set — don't pull undici into the bundle.
|
|
28
|
+
*
|
|
29
|
+
* If `undici` is not installed (it's an `optionalDependency`), the dynamic
|
|
30
|
+
* import will fail. We catch that and rethrow with a clear, actionable
|
|
31
|
+
* error so the integrator doesn't have to debug a `Cannot find module
|
|
32
|
+
* 'undici'` stack trace. */
|
|
33
|
+
declare class LazyVpsRouter implements IVpsRouter {
|
|
34
|
+
private readonly discoveryUrl;
|
|
35
|
+
private inner;
|
|
36
|
+
constructor(discoveryUrl: string | undefined);
|
|
37
|
+
getTarget(): Promise<RoutingTarget>;
|
|
38
|
+
recordFailure(ip: string | undefined): void;
|
|
39
|
+
}
|
|
40
|
+
/** Read the current FDKEY context for a given session. Pass either the `extra`
|
|
41
|
+
* argument from a tool handler or a session ID string. Returns null if the server
|
|
42
|
+
* was not wrapped with withFdkey() or the session doesn't exist yet.
|
|
43
|
+
*
|
|
44
|
+
* Reads use `store.peek()` which does NOT slide the LRU position — querying
|
|
45
|
+
* the context shouldn't extend a session's lifetime; only actual tool calls
|
|
46
|
+
* do that. */
|
|
47
|
+
export declare function getFdkeyContext(server: McpServer, extraOrSessionId: {
|
|
48
|
+
sessionId?: string;
|
|
49
|
+
} | string | undefined): FdkeyContext | null;
|
|
50
|
+
/**
|
|
51
|
+
* Wraps an MCP server with FDKEY verification middleware.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* const server = withFdkey(new McpServer({ name: 'my-server', version: '1.0.0' }), {
|
|
55
|
+
* apiKey: process.env.FDKEY_API_KEY!,
|
|
56
|
+
* protect: {
|
|
57
|
+
* login: { policy: 'each_call' },
|
|
58
|
+
* register: { policy: 'once_per_session' },
|
|
59
|
+
* },
|
|
60
|
+
* });
|
|
61
|
+
* server.registerTool('login', { inputSchema: { username: z.string() } }, async (args) => { ... });
|
|
62
|
+
*/
|
|
63
|
+
export declare function withFdkey(server: McpServer, config: FdkeyConfig): McpServer;
|
|
64
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEzE,OAAO,KAAK,EACV,WAAW,EAKX,UAAU,EACV,aAAa,EACd,MAAM,YAAY,CAAC;AAQpB,YAAY,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhG;;gDAEgD;AAChD,OAAO,EAAE,aAAa,IAAI,yBAAyB,EAAE,CAAC;AAWtD,8DAA8D;AAC9D,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;qEAIiE;IACjE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB;2EACuE;IACvE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB;sFACkF;IAClF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CACxC;AAED;;;;;;;6BAO6B;AAC7B,cAAM,aAAc,YAAW,UAAU;IAE3B,OAAO,CAAC,QAAQ,CAAC,YAAY;IADzC,OAAO,CAAC,KAAK,CAA2B;gBACX,YAAY,EAAE,MAAM,GAAG,SAAS;IACvD,SAAS,IAAI,OAAO,CAAC,aAAa,CAAC;IA4BzC,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;CAG5C;AAMD;;;;;;eAMe;AACf,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,EACjB,gBAAgB,EAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,GAC5D,YAAY,GAAG,IAAI,CAgBrB;AA+CD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,CA+U3E"}
|