@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/README.md +119 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +167 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +312 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +599 -0
- package/dist/mcp.js.map +1 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# @attest-dev/sdk
|
|
2
|
+
|
|
3
|
+
TypeScript SDK for [Attest](https://github.com/attest-dev/attest) — cryptographic credentials for AI agent pipelines.
|
|
4
|
+
|
|
5
|
+
Attest issues RS256-signed JWTs to agents carrying scope, delegation lineage, and task provenance. Every handoff narrows scope, every action is auditable, and the entire task tree can be revoked in one call.
|
|
6
|
+
|
|
7
|
+
> **Beta** — self-host the [Attest server](https://github.com/attest-dev/attest) or point at your own instance. Hosted service coming soon.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @attest-dev/sdk@beta
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { AttestClient } from "@attest-dev/sdk";
|
|
19
|
+
|
|
20
|
+
const client = new AttestClient({
|
|
21
|
+
baseUrl: "http://localhost:8080",
|
|
22
|
+
apiKey: "your-api-key",
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Issue a root credential for an orchestrator agent
|
|
26
|
+
const { token, claims } = await client.issue({
|
|
27
|
+
agent_id: "orchestrator-v1",
|
|
28
|
+
user_id: "usr_alice",
|
|
29
|
+
scope: ["research:read", "gmail:send"],
|
|
30
|
+
instruction: "Research competitors and email the board",
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Delegate a narrowed credential to a sub-agent
|
|
34
|
+
const { token: childToken } = await client.delegate({
|
|
35
|
+
parent_token: token,
|
|
36
|
+
child_agent: "email-agent-v1",
|
|
37
|
+
child_scope: ["gmail:send"], // must be a subset of parent — enforced server-side
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Verify offline (no network call after JWKS is fetched once)
|
|
41
|
+
const jwks = await client.fetchJWKS();
|
|
42
|
+
const result = await client.verify(childToken, jwks);
|
|
43
|
+
console.log(result.valid, result.warnings);
|
|
44
|
+
|
|
45
|
+
// Revoke the entire task tree in one call
|
|
46
|
+
await client.revoke(claims.jti);
|
|
47
|
+
|
|
48
|
+
// Retrieve the tamper-evident audit chain
|
|
49
|
+
const chain = await client.audit(claims.att_tid);
|
|
50
|
+
chain.events.forEach(e => console.log(e.event_type, e.jti, e.created_at));
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## MCP middleware
|
|
54
|
+
|
|
55
|
+
Enforce Attest credentials on every tool call in an MCP server — two lines:
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
59
|
+
import { withAttest } from "@attest-dev/sdk/mcp";
|
|
60
|
+
|
|
61
|
+
const server = new McpServer({ name: "my-tools", version: "1.0.0" });
|
|
62
|
+
const protectedServer = withAttest(server, {
|
|
63
|
+
issuerUri: "http://localhost:8080",
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Register tools exactly as before — every call is now credential-gated
|
|
67
|
+
protectedServer.tool("send_email", schema, handler);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Tool names map to scope strings automatically (`send_email` → `email:send`, `read_file` → `file:read`). Override per tool:
|
|
71
|
+
|
|
72
|
+
```ts
|
|
73
|
+
protectedServer.tool("gh_create_issue", schema, handler, {
|
|
74
|
+
requiredScope: "github:write",
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Expose a discovery endpoint so orchestrators know what scopes to request:
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import { getAttestScopes } from "@attest-dev/sdk/mcp";
|
|
82
|
+
|
|
83
|
+
app.get("/.well-known/attest-scopes", (_req, res) => {
|
|
84
|
+
res.json({ tools: getAttestScopes(protectedServer) });
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
See [`mcp/README.md`](./mcp/README.md) for the full MCP middleware reference.
|
|
89
|
+
|
|
90
|
+
## Scope syntax
|
|
91
|
+
|
|
92
|
+
Scopes follow `resource:action`. Either field may be `*` as a wildcard.
|
|
93
|
+
|
|
94
|
+
| Expression | Meaning |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `gmail:send` | Send via Gmail only |
|
|
97
|
+
| `gmail:*` | All Gmail actions |
|
|
98
|
+
| `*:read` | Read access to any resource |
|
|
99
|
+
| `*:*` | Full access (root credentials only) |
|
|
100
|
+
|
|
101
|
+
Delegation enforces that child scope is a strict subset of parent scope — server-side, cryptographically.
|
|
102
|
+
|
|
103
|
+
## Self-hosting
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
# Clone and start (Docker required)
|
|
107
|
+
git clone https://github.com/attest-dev/attest
|
|
108
|
+
cd attest
|
|
109
|
+
docker compose up
|
|
110
|
+
|
|
111
|
+
# Or run without Docker (ephemeral key, in-memory storage)
|
|
112
|
+
cd server && go run ./cmd/attest
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Server starts on `http://localhost:8080`.
|
|
116
|
+
|
|
117
|
+
## License
|
|
118
|
+
|
|
119
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @attest-dev/sdk — TypeScript client for the Attest credential service.
|
|
3
|
+
*
|
|
4
|
+
* Offline JWT verification uses `jose` (no network call needed after
|
|
5
|
+
* fetchJWKS). All network calls go through the Attest server REST API.
|
|
6
|
+
*/
|
|
7
|
+
import { type JWTPayload } from 'jose';
|
|
8
|
+
/** Standard JWT claims plus every att_* Attest extension. */
|
|
9
|
+
export interface AttestClaims extends JWTPayload {
|
|
10
|
+
/** Task tree ID shared across the entire delegation chain. */
|
|
11
|
+
att_tid: string;
|
|
12
|
+
/** Parent credential jti (absent on root credentials). */
|
|
13
|
+
att_pid?: string;
|
|
14
|
+
/** Delegation depth (0 = root). */
|
|
15
|
+
att_depth: number;
|
|
16
|
+
/** Granted permission scopes in "resource:action" form. */
|
|
17
|
+
att_scope: string[];
|
|
18
|
+
/** SHA-256 hex of the original instruction that initiated the task. */
|
|
19
|
+
att_intent: string;
|
|
20
|
+
/** Ordered jti ancestry list from root to this credential. */
|
|
21
|
+
att_chain: string[];
|
|
22
|
+
/** Human principal who initiated the task. */
|
|
23
|
+
att_uid: string;
|
|
24
|
+
}
|
|
25
|
+
/** A root credential returned by issue(). */
|
|
26
|
+
export interface AttestToken {
|
|
27
|
+
token: string;
|
|
28
|
+
claims: AttestClaims;
|
|
29
|
+
}
|
|
30
|
+
/** A delegated child credential returned by delegate(). */
|
|
31
|
+
export interface DelegatedToken {
|
|
32
|
+
token: string;
|
|
33
|
+
claims: AttestClaims;
|
|
34
|
+
}
|
|
35
|
+
/** Returned by verify(). Valid is false if any check fails. */
|
|
36
|
+
export interface VerifyResult {
|
|
37
|
+
valid: boolean;
|
|
38
|
+
claims?: AttestClaims;
|
|
39
|
+
warnings: string[];
|
|
40
|
+
}
|
|
41
|
+
/** A complete audit trail for a task tree. */
|
|
42
|
+
export interface AuditChain {
|
|
43
|
+
taskId: string;
|
|
44
|
+
events: AuditEvent[];
|
|
45
|
+
}
|
|
46
|
+
/** A single entry in the audit log. */
|
|
47
|
+
export interface AuditEvent {
|
|
48
|
+
id?: number;
|
|
49
|
+
prev_hash: string;
|
|
50
|
+
entry_hash: string;
|
|
51
|
+
event_type: 'issued' | 'delegated' | 'verified' | 'revoked' | 'expired';
|
|
52
|
+
jti: string;
|
|
53
|
+
att_tid: string;
|
|
54
|
+
att_uid: string;
|
|
55
|
+
agent_id: string;
|
|
56
|
+
scope: string[];
|
|
57
|
+
meta?: Record<string, string>;
|
|
58
|
+
created_at: string;
|
|
59
|
+
}
|
|
60
|
+
/** Subset of a JWKS response sufficient for RS256 verification. */
|
|
61
|
+
export interface JWKSResponse {
|
|
62
|
+
keys: JWK[];
|
|
63
|
+
}
|
|
64
|
+
export interface JWK {
|
|
65
|
+
kty: string;
|
|
66
|
+
use?: string;
|
|
67
|
+
alg?: string;
|
|
68
|
+
n: string;
|
|
69
|
+
e: string;
|
|
70
|
+
kid?: string;
|
|
71
|
+
}
|
|
72
|
+
/** Parameters for issuing a root credential. */
|
|
73
|
+
export interface IssueParams {
|
|
74
|
+
agent_id: string;
|
|
75
|
+
user_id: string;
|
|
76
|
+
scope: string[];
|
|
77
|
+
instruction: string;
|
|
78
|
+
ttl_seconds?: number;
|
|
79
|
+
}
|
|
80
|
+
/** Parameters for delegating to a child agent. */
|
|
81
|
+
export interface DelegateParams {
|
|
82
|
+
parent_token: string;
|
|
83
|
+
child_agent: string;
|
|
84
|
+
child_scope: string[];
|
|
85
|
+
ttl_seconds?: number;
|
|
86
|
+
}
|
|
87
|
+
export declare class AttestClient {
|
|
88
|
+
private readonly baseUrl;
|
|
89
|
+
private readonly headers;
|
|
90
|
+
constructor({ baseUrl, apiKey }: {
|
|
91
|
+
baseUrl?: string;
|
|
92
|
+
apiKey: string;
|
|
93
|
+
});
|
|
94
|
+
/** Issue a root credential for the given agent and instruction. */
|
|
95
|
+
issue(params: IssueParams): Promise<AttestToken>;
|
|
96
|
+
/** Delegate a narrowed child credential from a parent token. */
|
|
97
|
+
delegate(params: DelegateParams): Promise<DelegatedToken>;
|
|
98
|
+
/**
|
|
99
|
+
* Offline verification — validates RS256 signature, expiry, chain length
|
|
100
|
+
* (must equal depth + 1), and chain tail (must equal jti).
|
|
101
|
+
*
|
|
102
|
+
* Pass a JWKSResponse previously fetched with fetchJWKS(). No network call
|
|
103
|
+
* is made during verification itself.
|
|
104
|
+
*/
|
|
105
|
+
verify(token: string, jwks: JWKSResponse): Promise<VerifyResult>;
|
|
106
|
+
/** Revoke a credential and cascade to all descendants. */
|
|
107
|
+
revoke(jti: string, revokedBy?: string): Promise<void>;
|
|
108
|
+
/** Fetch the full audit chain for a task tree. */
|
|
109
|
+
audit(taskId: string): Promise<AuditChain>;
|
|
110
|
+
/** Fetch the server's public key set for offline verification. */
|
|
111
|
+
fetchJWKS(): Promise<JWKSResponse>;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Returns true if every entry in childScope is covered by at least one entry
|
|
115
|
+
* in parentScope. Wildcards ("*") match any resource or action.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* isScopeSubset(["gmail:*"], ["gmail:send"]) // true
|
|
119
|
+
* isScopeSubset(["gmail:send"], ["database:delete"]) // false
|
|
120
|
+
*/
|
|
121
|
+
export declare function isScopeSubset(parentScope: string[], childScope: string[]): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Decodes a Attest JWT without verifying the signature.
|
|
124
|
+
* Use verify() for trusted access to claims.
|
|
125
|
+
*/
|
|
126
|
+
export declare function decodeToken(token: string): AttestClaims;
|
|
127
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @attest-dev/sdk — TypeScript client for the Attest credential service.
|
|
4
|
+
*
|
|
5
|
+
* Offline JWT verification uses `jose` (no network call needed after
|
|
6
|
+
* fetchJWKS). All network calls go through the Attest server REST API.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.AttestClient = void 0;
|
|
10
|
+
exports.isScopeSubset = isScopeSubset;
|
|
11
|
+
exports.decodeToken = decodeToken;
|
|
12
|
+
const jose_1 = require("jose");
|
|
13
|
+
// ── AttestClient ─────────────────────────────────────────────────────────────
|
|
14
|
+
class AttestClient {
|
|
15
|
+
baseUrl;
|
|
16
|
+
headers;
|
|
17
|
+
constructor({ baseUrl = 'http://localhost:8080', apiKey }) {
|
|
18
|
+
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
19
|
+
this.headers = {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
Authorization: `Bearer ${apiKey}`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Issue a root credential for the given agent and instruction. */
|
|
25
|
+
async issue(params) {
|
|
26
|
+
const res = await fetch(`${this.baseUrl}/v1/credentials`, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
headers: this.headers,
|
|
29
|
+
body: JSON.stringify(params),
|
|
30
|
+
});
|
|
31
|
+
if (!res.ok)
|
|
32
|
+
await throwFromResponse(res);
|
|
33
|
+
return res.json();
|
|
34
|
+
}
|
|
35
|
+
/** Delegate a narrowed child credential from a parent token. */
|
|
36
|
+
async delegate(params) {
|
|
37
|
+
const res = await fetch(`${this.baseUrl}/v1/credentials/delegate`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: this.headers,
|
|
40
|
+
body: JSON.stringify(params),
|
|
41
|
+
});
|
|
42
|
+
if (!res.ok)
|
|
43
|
+
await throwFromResponse(res);
|
|
44
|
+
return res.json();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Offline verification — validates RS256 signature, expiry, chain length
|
|
48
|
+
* (must equal depth + 1), and chain tail (must equal jti).
|
|
49
|
+
*
|
|
50
|
+
* Pass a JWKSResponse previously fetched with fetchJWKS(). No network call
|
|
51
|
+
* is made during verification itself.
|
|
52
|
+
*/
|
|
53
|
+
async verify(token, jwks) {
|
|
54
|
+
const warnings = [];
|
|
55
|
+
// Build a JWKS key source from the raw key objects by round-tripping
|
|
56
|
+
// through a data URL so jose can parse it without a network request.
|
|
57
|
+
const jwksData = JSON.stringify(jwks);
|
|
58
|
+
const dataUrl = `data:application/json,${encodeURIComponent(jwksData)}`;
|
|
59
|
+
let payload;
|
|
60
|
+
try {
|
|
61
|
+
const keySet = (0, jose_1.createRemoteJWKSet)(new URL(dataUrl));
|
|
62
|
+
const { payload: raw } = await (0, jose_1.jwtVerify)(token, keySet, {
|
|
63
|
+
algorithms: ['RS256'],
|
|
64
|
+
});
|
|
65
|
+
payload = raw;
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
return { valid: false, warnings: [`signature/expiry check failed: ${String(err)}`] };
|
|
69
|
+
}
|
|
70
|
+
// Chain length must equal depth + 1 (root depth=0 → chain=[jti]).
|
|
71
|
+
const expectedLen = (payload.att_depth ?? 0) + 1;
|
|
72
|
+
if (!payload.att_chain || payload.att_chain.length !== expectedLen) {
|
|
73
|
+
warnings.push(`chain length ${payload.att_chain?.length} does not match depth ${payload.att_depth} (expected ${expectedLen})`);
|
|
74
|
+
}
|
|
75
|
+
// Chain tail must match jti.
|
|
76
|
+
const chain = payload.att_chain ?? [];
|
|
77
|
+
if (chain.length > 0 && chain[chain.length - 1] !== payload.jti) {
|
|
78
|
+
warnings.push(`chain tail "${chain[chain.length - 1]}" does not match jti "${payload.jti}"`);
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
valid: warnings.length === 0,
|
|
82
|
+
claims: payload,
|
|
83
|
+
warnings,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** Revoke a credential and cascade to all descendants. */
|
|
87
|
+
async revoke(jti, revokedBy = 'sdk') {
|
|
88
|
+
const res = await fetch(`${this.baseUrl}/v1/credentials/${encodeURIComponent(jti)}`, {
|
|
89
|
+
method: 'DELETE',
|
|
90
|
+
headers: this.headers,
|
|
91
|
+
body: JSON.stringify({ revoked_by: revokedBy }),
|
|
92
|
+
});
|
|
93
|
+
if (!res.ok && res.status !== 204)
|
|
94
|
+
await throwFromResponse(res);
|
|
95
|
+
}
|
|
96
|
+
/** Fetch the full audit chain for a task tree. */
|
|
97
|
+
async audit(taskId) {
|
|
98
|
+
const res = await fetch(`${this.baseUrl}/v1/tasks/${encodeURIComponent(taskId)}/audit`, { headers: this.headers });
|
|
99
|
+
if (!res.ok)
|
|
100
|
+
await throwFromResponse(res);
|
|
101
|
+
const events = (await res.json());
|
|
102
|
+
return { taskId, events };
|
|
103
|
+
}
|
|
104
|
+
/** Fetch the server's public key set for offline verification. */
|
|
105
|
+
async fetchJWKS() {
|
|
106
|
+
const res = await fetch(`${this.baseUrl}/.well-known/jwks.json`, {
|
|
107
|
+
headers: this.headers,
|
|
108
|
+
});
|
|
109
|
+
if (!res.ok)
|
|
110
|
+
await throwFromResponse(res);
|
|
111
|
+
return res.json();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
exports.AttestClient = AttestClient;
|
|
115
|
+
// ── Exported utilities ────────────────────────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Returns true if every entry in childScope is covered by at least one entry
|
|
118
|
+
* in parentScope. Wildcards ("*") match any resource or action.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* isScopeSubset(["gmail:*"], ["gmail:send"]) // true
|
|
122
|
+
* isScopeSubset(["gmail:send"], ["database:delete"]) // false
|
|
123
|
+
*/
|
|
124
|
+
function isScopeSubset(parentScope, childScope) {
|
|
125
|
+
for (const childEntry of childScope) {
|
|
126
|
+
const child = parseScope(childEntry);
|
|
127
|
+
if (!child)
|
|
128
|
+
return false;
|
|
129
|
+
const covered = parentScope.some(parentEntry => {
|
|
130
|
+
const parent = parseScope(parentEntry);
|
|
131
|
+
if (!parent)
|
|
132
|
+
return false;
|
|
133
|
+
const resourceOK = parent.resource === '*' || parent.resource === child.resource;
|
|
134
|
+
const actionOK = parent.action === '*' || parent.action === child.action;
|
|
135
|
+
return resourceOK && actionOK;
|
|
136
|
+
});
|
|
137
|
+
if (!covered)
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Decodes a Attest JWT without verifying the signature.
|
|
144
|
+
* Use verify() for trusted access to claims.
|
|
145
|
+
*/
|
|
146
|
+
function decodeToken(token) {
|
|
147
|
+
return (0, jose_1.decodeJwt)(token);
|
|
148
|
+
}
|
|
149
|
+
function parseScope(s) {
|
|
150
|
+
const parts = s.split(':');
|
|
151
|
+
if (parts.length !== 2 || !parts[0] || !parts[1])
|
|
152
|
+
return null;
|
|
153
|
+
return { resource: parts[0], action: parts[1] };
|
|
154
|
+
}
|
|
155
|
+
async function throwFromResponse(res) {
|
|
156
|
+
let message = `HTTP ${res.status}`;
|
|
157
|
+
try {
|
|
158
|
+
const body = (await res.json());
|
|
159
|
+
if (body.error)
|
|
160
|
+
message += `: ${body.error}`;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// ignore parse failure
|
|
164
|
+
}
|
|
165
|
+
throw new Error(message);
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|