@attest-dev/sdk 0.1.0-beta.1

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/dist/mcp.d.ts ADDED
@@ -0,0 +1,312 @@
1
+ /**
2
+ * @attest-dev/sdk/mcp — Attest credential enforcement middleware for MCP servers.
3
+ *
4
+ * Wraps any MCP server instance and enforces Attest credential checking on
5
+ * every tool call before the underlying handler executes.
6
+ *
7
+ * ## Two-line integration
8
+ *
9
+ * ```ts
10
+ * import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ * import { withAttest } from "@attest-dev/sdk/mcp";
12
+ *
13
+ * const server = new McpServer({ name: "my-tools", version: "1.0.0" });
14
+ * const protectedServer = withAttest(server, {
15
+ * issuerUri: "https://api.attest.dev",
16
+ * });
17
+ *
18
+ * // Register tools exactly as before — every call is now credential-gated.
19
+ * protectedServer.tool("send_email", "Send an email", schema, handler);
20
+ * ```
21
+ *
22
+ * ## How it works
23
+ *
24
+ * `withAttest` monkey-patches `server.tool()` so each registered handler is
25
+ * wrapped with a credential check closure. On every tool call the closure:
26
+ *
27
+ * 1. Extracts the Attest JWT from `extra.authInfo.token` (set by the MCP
28
+ * auth middleware) or `extra.meta?.attest_token`.
29
+ * 2. Verifies the JWT offline against the issuer's JWKS (cached per TTL).
30
+ * 3. Maps the tool name to a Attest scope string via `scopeForTool()`.
31
+ * 4. Confirms the credential's `att_scope` covers the required scope.
32
+ * 5. Calls the Attest revocation endpoint to confirm the JTI is still live.
33
+ * 6. If all checks pass, executes the original handler.
34
+ * 7. If any check fails, returns a structured `attest_violation` error.
35
+ * 8. Fire-and-forgets an audit event to the Attest server regardless of
36
+ * outcome.
37
+ */
38
+ /** Minimal shape of the auth info injected by the MCP auth middleware. */
39
+ interface McpAuthInfo {
40
+ /** Raw bearer token string (the Attest JWT). */
41
+ token: string;
42
+ clientId?: string;
43
+ scopes?: string[];
44
+ expiresAt?: Date;
45
+ extra?: Record<string, unknown>;
46
+ }
47
+ /** Minimal shape of the extra argument passed to every MCP tool handler. */
48
+ interface McpRequestExtra {
49
+ /** Populated by MCP auth middleware from the Authorization header. */
50
+ authInfo?: McpAuthInfo | undefined;
51
+ /** Arbitrary metadata; agents may pass attest_token here explicitly. */
52
+ meta?: Record<string, unknown> | undefined;
53
+ signal?: AbortSignal;
54
+ sessionId?: string;
55
+ requestId?: string | number;
56
+ }
57
+ /**
58
+ * Minimal interface for any object that behaves like an MCP server.
59
+ * `withAttest` accepts anything with a `tool()` method.
60
+ */
61
+ export interface McpServerLike {
62
+ tool: (...args: unknown[]) => unknown;
63
+ [key: string]: unknown;
64
+ }
65
+ /** The decoded Attest JWT claims threaded through the MCP request. */
66
+ export interface AttestContext {
67
+ /** Unique identifier for this credential. */
68
+ jti: string;
69
+ /** Task tree ID shared across the entire delegation chain. */
70
+ att_tid: string;
71
+ /** Delegation depth (0 = root credential). */
72
+ att_depth: number;
73
+ /** Granted permission scopes in "resource:action" form. */
74
+ att_scope: string[];
75
+ /** Human user who initiated the task. */
76
+ att_uid: string;
77
+ /** Raw JWT string. */
78
+ token: string;
79
+ }
80
+ /** Emitted to `onViolation` when a tool call is blocked. */
81
+ export interface ScopeViolationEvent {
82
+ /** MCP tool name that was blocked. */
83
+ toolName: string;
84
+ /** Attest scope required by this tool. */
85
+ requiredScope: string;
86
+ /** Scopes actually present in the credential (empty if no credential). */
87
+ grantedScope: string[];
88
+ /** Violation category. */
89
+ reason: 'no_credential' | 'credential_expired' | 'credential_revoked' | 'scope_violation' | 'invalid_credential' | 'audit_failure';
90
+ /** Credential JTI if available. */
91
+ jti?: string;
92
+ /** Task ID if available. */
93
+ taskId?: string;
94
+ /** ISO timestamp of the violation. */
95
+ timestamp: string;
96
+ }
97
+ /**
98
+ * Options for `withAttest`.
99
+ */
100
+ export interface AttestMcpOptions {
101
+ /**
102
+ * URI of the Attest server used to fetch JWKS and check revocation.
103
+ * @example "https://api.attest.dev"
104
+ */
105
+ issuerUri: string;
106
+ /**
107
+ * How long (in seconds) to cache the JWKS before re-fetching.
108
+ * Longer values reduce latency; shorter values pick up key rotations faster.
109
+ * @default 3600
110
+ */
111
+ jwksCacheTTL?: number;
112
+ /**
113
+ * When `true` (the default), tool calls without a valid Attest credential
114
+ * are blocked and a `attest_violation` error is returned.
115
+ *
116
+ * When `false`, violations are logged via `onViolation` but the tool call
117
+ * proceeds. Useful for gradual rollouts or debugging.
118
+ * @default true
119
+ */
120
+ requireCredential?: boolean;
121
+ /**
122
+ * Called synchronously when any scope check fails.
123
+ * Use this to emit metrics, write logs, or trigger alerts.
124
+ */
125
+ onViolation?: (event: ScopeViolationEvent) => void;
126
+ /**
127
+ * Per-tool scope overrides. Keys are exact MCP tool names; values are
128
+ * Attest scope strings. Overrides the automatic `scopeForTool()` mapping.
129
+ *
130
+ * @example
131
+ * toolScopeMap: {
132
+ * "send_message": "gmail:send",
133
+ * "query_db": "postgres:read",
134
+ * }
135
+ */
136
+ toolScopeMap?: Record<string, string>;
137
+ /**
138
+ * How long (in seconds) to cache revocation status for each JTI.
139
+ * Revocation is eventually consistent; a short TTL (5-30 s) balances
140
+ * latency against revocation lag. Set to 0 to disable caching.
141
+ * @default 10
142
+ */
143
+ revocationCacheTTL?: number;
144
+ /**
145
+ * Called when an audit POST to the Attest server fails.
146
+ * Receives the underlying error and the event payload that was not delivered.
147
+ *
148
+ * Use this to implement a retry queue, write to a fallback log, or page
149
+ * on-call for compliance-critical deployments.
150
+ *
151
+ * If omitted, failures are surfaced via `onViolation` (with
152
+ * `reason: "audit_failure"`) and, if that is also unset, via
153
+ * `console.warn` — audit failures are never silently dropped.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * onAuditError: (err, event) => {
158
+ * retryQueue.push({ event, attempts: 0 });
159
+ * logger.error("audit delivery failed", { err, jti: event.jti });
160
+ * }
161
+ * ```
162
+ */
163
+ onAuditError?: (error: Error, event: AuditPayload) => void;
164
+ }
165
+ /** Structured error payload returned in the tool response body on violation. */
166
+ export interface AttestViolationError {
167
+ error: 'attest_violation';
168
+ reason: ScopeViolationEvent['reason'];
169
+ detail: string;
170
+ jti?: string;
171
+ taskId?: string;
172
+ }
173
+ /**
174
+ * Optional Attest-specific options passed as the final argument to
175
+ * `protectedServer.tool()`. The MCP SDK never sees this object — it is
176
+ * extracted and consumed by the `withAttest` wrapper before forwarding
177
+ * the remaining arguments.
178
+ *
179
+ * @example
180
+ * ```ts
181
+ * protectedServer.tool("gh_create_issue", schema, handler, {
182
+ * requiredScope: "github:write",
183
+ * });
184
+ * ```
185
+ */
186
+ export interface AttestToolOptions {
187
+ /**
188
+ * Explicit Attest scope string required to call this tool.
189
+ * Overrides the automatic `scopeForTool()` mapping.
190
+ * Use this for non-standard tool names or when the auto-mapping would
191
+ * produce a misleading scope.
192
+ *
193
+ * @example "github:write"
194
+ * @example "stripe:charge"
195
+ */
196
+ requiredScope?: string;
197
+ }
198
+ /**
199
+ * Maps an MCP tool name to a Attest scope string using a convention-based
200
+ * approach. The tool name is split on the first underscore: the part before
201
+ * becomes the action; the rest (joined with `:`) becomes the resource.
202
+ *
203
+ * Action aliases:
204
+ * - `create`, `update`, `write`, `put`, `patch` → `write`
205
+ * - `remove`, `destroy` → `delete`
206
+ * - `run`, `invoke` → `execute`
207
+ * - `get`, `fetch`, `list`, `search`, `query` → `read`
208
+ *
209
+ * @example
210
+ * scopeForTool("send_email") // "email:send"
211
+ * scopeForTool("read_file") // "file:read"
212
+ * scopeForTool("delete_calendar_event") // "calendar_event:delete"
213
+ * scopeForTool("create_user") // "user:write"
214
+ * scopeForTool("run_query") // "query:execute"
215
+ */
216
+ export declare function scopeForTool(toolName: string, overrides?: Record<string, string>): string;
217
+ /**
218
+ * The audit event payload sent to `POST /v1/audit` on the Attest server.
219
+ * Passed to `onAuditError` when delivery fails so the caller can retry or
220
+ * write to a fallback log.
221
+ */
222
+ export interface AuditPayload {
223
+ event_type: 'verified' | 'revoked';
224
+ jti: string;
225
+ att_tid?: string | undefined;
226
+ att_uid?: string | undefined;
227
+ agent_id: string;
228
+ scope: string[];
229
+ meta?: Record<string, string> | undefined;
230
+ }
231
+ /**
232
+ * Wraps an MCP server instance and enforces Attest credential checking on
233
+ * every tool call.
234
+ *
235
+ * Returns the **same** server object with its `tool()` method patched in place,
236
+ * typed as the original server type so all other methods remain accessible.
237
+ *
238
+ * @param server Any object with a `tool()` method (e.g. `new McpServer(...)`).
239
+ * @param options Attest enforcement options.
240
+ * @returns The patched server (same reference, same type).
241
+ *
242
+ * @example
243
+ * ```ts
244
+ * import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
245
+ * import { withAttest } from "@attest-dev/sdk/mcp";
246
+ *
247
+ * const server = new McpServer({ name: "my-tools", version: "1.0.0" });
248
+ * const protectedServer = withAttest(server, {
249
+ * issuerUri: "https://api.attest.dev",
250
+ * });
251
+ *
252
+ * protectedServer.tool("send_email", schema, async (args, extra) => {
253
+ * // Only reached when the caller holds a valid credential
254
+ * // with "email:send" in its att_scope.
255
+ * return { content: [{ type: "text", text: "sent!" }] };
256
+ * });
257
+ * ```
258
+ */
259
+ export declare function withAttest<T extends McpServerLike>(server: T, options: AttestMcpOptions): T;
260
+ /**
261
+ * Decodes the Attest JWT in `extra` and returns a typed `AttestContext`
262
+ * without performing any cryptographic verification.
263
+ *
264
+ * Useful inside tool handlers when you want to read the credential's claims
265
+ * (e.g. `att_uid`, `att_tid`) after `withAttest` has already enforced them.
266
+ *
267
+ * @returns `null` when no Attest credential is present.
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * protectedServer.tool("send_email", schema, async (args, extra) => {
272
+ * const ctx = getAttestContext(extra);
273
+ * console.log("acting on behalf of", ctx?.att_uid);
274
+ * ...
275
+ * });
276
+ * ```
277
+ */
278
+ export declare function getAttestContext(extra: McpRequestExtra): AttestContext | null;
279
+ /**
280
+ * Returns the scope registry for a server that has been wrapped with
281
+ * `withAttest` — a map of every registered tool name to its resolved
282
+ * Attest scope string.
283
+ *
284
+ * Use this to build a scope discovery endpoint so credential issuers can
285
+ * query what scopes a server requires without out-of-band coordination.
286
+ *
287
+ * @example
288
+ * ```ts
289
+ * // Express / Hono / any HTTP framework:
290
+ * app.get("/.well-known/attest-scopes", (_req, res) => {
291
+ * res.json({ tools: getAttestScopes(protectedServer) });
292
+ * });
293
+ *
294
+ * // Response:
295
+ * // {
296
+ * // "tools": {
297
+ * // "send_email": "email:send",
298
+ * // "read_file": "file:read",
299
+ * // "gh_create_issue": "github:write"
300
+ * // }
301
+ * // }
302
+ * ```
303
+ *
304
+ * Tools that have not yet been registered (i.e. `server.tool()` hasn't been
305
+ * called for them yet) will not appear in the result. Call this after all
306
+ * tools are registered, not during server startup.
307
+ *
308
+ * @returns A plain `Record<string, string>` safe to serialize directly as JSON.
309
+ */
310
+ export declare function getAttestScopes(server: McpServerLike): Record<string, string>;
311
+ export {};
312
+ //# sourceMappingURL=mcp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}