Skip to content

Commit 17acb1b

Browse files
committed
feat(auth): add AgentPass trust verification provider
Adds AgentPassProvider as a new auth provider that verifies agent identity and behavioural trust scores via AgentPass (agentpass.co.uk). Features: - Trust level verification (L0-L4) with configurable minimum - Numeric trust score threshold (0-100) - AML sanctions screening status check (75K+ UK HMT + OFAC entries) - ECDSA identity verification status - Response caching with configurable TTL - Graceful degradation (onMissing: allow/reject) - AgentPass Passport ID header support Follows existing AuthProvider pattern -- drop-in compatible, no breaking changes, zero new dependencies. Resolves QuantGeekDev#175
1 parent 57c9679 commit 17acb1b

File tree

2 files changed

+269
-0
lines changed

2 files changed

+269
-0
lines changed

src/auth/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./providers/jwt.js";
33
export * from "./providers/apikey.js";
44
export * from "./providers/oauth.js";
55
export * from "./providers/satp.js";
6+
export * from "./providers/agentpass.js";
67

78
export type { AuthProvider, AuthConfig, AuthResult } from "./types.js";
89
export type { JWTConfig } from "./providers/jwt.js";

src/auth/providers/agentpass.ts

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
import { IncomingMessage } from "node:http";
2+
import { AuthProvider, AuthResult } from "../types.js";
3+
4+
/**
5+
* Trust verification result from AgentPass
6+
*/
7+
export interface AgentPassTrustResult {
8+
/** Agent handle (e.g. payment-bot.cybersecai.agentpass) */
9+
agent: string;
10+
/** Trust level (L0-L4) */
11+
trustLevel: string;
12+
/** Numeric trust score (0-100) */
13+
trustScore: number;
14+
/** Whether the agent has been identity-verified via ECDSA challenge-response */
15+
identityVerified: boolean;
16+
/** AML sanctions screening status */
17+
sanctionsStatus: string;
18+
/** Number of completed transactions */
19+
transactionCount: number;
20+
/** Agent registration timestamp */
21+
registeredAt?: string;
22+
}
23+
24+
/**
25+
* Configuration for AgentPass trust verification
26+
*/
27+
export interface AgentPassConfig {
28+
/**
29+
* AgentPass API base URL
30+
* @default "https://agentpass.co.uk"
31+
*/
32+
apiUrl?: string;
33+
34+
/**
35+
* Minimum trust score required (0-100)
36+
* Set to 0 to allow all agents but still annotate requests with trust data
37+
* @default 0
38+
*/
39+
minTrustScore?: number;
40+
41+
/**
42+
* Minimum trust level required (L0-L4)
43+
* L0: Untrusted, L1: Basic, L2: Verified, L3: Trusted, L4: Certified
44+
* @default "L0"
45+
*/
46+
minTrustLevel?: "L0" | "L1" | "L2" | "L3" | "L4";
47+
48+
/**
49+
* Require clean AML sanctions screening
50+
* When true, agents with sanctions violations are rejected
51+
* @default false
52+
*/
53+
requireCleanSanctions?: boolean;
54+
55+
/**
56+
* Header name for agent identity
57+
* @default "x-agent-id"
58+
*/
59+
agentIdHeader?: string;
60+
61+
/**
62+
* Behavior when agent identity is missing from request
63+
* - "reject": Return 401
64+
* - "allow": Continue without trust data
65+
* @default "allow"
66+
*/
67+
onMissing?: "reject" | "allow";
68+
69+
/**
70+
* Cache TTL in milliseconds for trust score lookups
71+
* @default 300000 (5 minutes)
72+
*/
73+
cacheTtlMs?: number;
74+
}
75+
76+
const TRUST_LEVEL_ORDER = ["L0", "L1", "L2", "L3", "L4"];
77+
78+
/**
79+
* AgentPass Trust Provider
80+
*
81+
* Verifies agent identity and trust scores via AgentPass -- the pre-payment
82+
* trust gateway for AI agents. Checks identity verification, behavioural
83+
* trust scoring (L0-L4), and AML sanctions status before allowing requests.
84+
*
85+
* AgentPass screens agents through cryptographic identity (ECDSA P-256),
86+
* behavioural trust scoring, and 75,784-entry AML sanctions databases
87+
* (UK HMT + OFAC SDN). Trust scores are publicly queryable.
88+
*
89+
* @see https://agentpass.co.uk
90+
* @see https://datatracker.ietf.org/doc/draft-sharif-agent-payment-trust/
91+
*
92+
* @example
93+
* ```typescript
94+
* import { MCPServer, AgentPassProvider } from "mcp-framework";
95+
*
96+
* const server = new MCPServer({
97+
* auth: {
98+
* provider: new AgentPassProvider({
99+
* minTrustScore: 50,
100+
* minTrustLevel: "L2",
101+
* requireCleanSanctions: true,
102+
* }),
103+
* },
104+
* });
105+
* ```
106+
*/
107+
export class AgentPassProvider implements AuthProvider {
108+
private config: Required<AgentPassConfig>;
109+
private cache: Map<string, { result: AgentPassTrustResult; expiry: number }> =
110+
new Map();
111+
112+
constructor(config: AgentPassConfig = {}) {
113+
this.config = {
114+
apiUrl: config.apiUrl ?? "https://agentpass.co.uk",
115+
minTrustScore: config.minTrustScore ?? 0,
116+
minTrustLevel: config.minTrustLevel ?? "L0",
117+
requireCleanSanctions: config.requireCleanSanctions ?? false,
118+
agentIdHeader: config.agentIdHeader ?? "x-agent-id",
119+
onMissing: config.onMissing ?? "allow",
120+
cacheTtlMs: config.cacheTtlMs ?? 300_000,
121+
};
122+
}
123+
124+
async authenticate(req: IncomingMessage): Promise<boolean | AuthResult> {
125+
const agentId = this.extractAgentId(req);
126+
127+
if (!agentId) {
128+
return this.config.onMissing === "allow"
129+
? { data: { agentTrust: null } }
130+
: false;
131+
}
132+
133+
const trust = await this.queryTrust(agentId);
134+
135+
if (!trust) {
136+
return this.config.onMissing === "allow"
137+
? { data: { agentTrust: null } }
138+
: false;
139+
}
140+
141+
// Check minimum trust score
142+
if (trust.trustScore < this.config.minTrustScore) {
143+
return false;
144+
}
145+
146+
// Check minimum trust level
147+
const agentLevelIndex = TRUST_LEVEL_ORDER.indexOf(trust.trustLevel);
148+
const requiredLevelIndex = TRUST_LEVEL_ORDER.indexOf(
149+
this.config.minTrustLevel
150+
);
151+
if (agentLevelIndex < requiredLevelIndex) {
152+
return false;
153+
}
154+
155+
// Check sanctions status
156+
if (
157+
this.config.requireCleanSanctions &&
158+
trust.sanctionsStatus !== "CLEAR"
159+
) {
160+
return false;
161+
}
162+
163+
return {
164+
data: {
165+
agentTrust: trust,
166+
},
167+
};
168+
}
169+
170+
getAuthError(): {
171+
status: number;
172+
message: string;
173+
headers?: Record<string, string>;
174+
} {
175+
return {
176+
status: 403,
177+
message: "Agent trust verification failed",
178+
headers: {
179+
"X-Trust-Required": `min-score=${this.config.minTrustScore},min-level=${this.config.minTrustLevel}`,
180+
"X-Trust-Info": "https://agentpass.co.uk",
181+
},
182+
};
183+
}
184+
185+
/**
186+
* Extract agent ID from request headers
187+
*/
188+
private extractAgentId(req: IncomingMessage): string | null {
189+
// Check custom header first
190+
const headerValue = req.headers[this.config.agentIdHeader];
191+
if (headerValue) {
192+
return Array.isArray(headerValue) ? headerValue[0] : headerValue;
193+
}
194+
195+
// Check Authorization header for agent token
196+
const auth = req.headers.authorization;
197+
if (auth?.startsWith("Agent ")) {
198+
return auth.slice(6).trim();
199+
}
200+
201+
// Check AgentPass passport ID header
202+
const passportId = req.headers["x-agent-passport-id"];
203+
if (passportId) {
204+
return Array.isArray(passportId) ? passportId[0] : passportId;
205+
}
206+
207+
return null;
208+
}
209+
210+
/**
211+
* Query AgentPass public trust API with caching
212+
*/
213+
private async queryTrust(
214+
agentId: string
215+
): Promise<AgentPassTrustResult | null> {
216+
// Check cache
217+
const cached = this.cache.get(agentId);
218+
if (cached && cached.expiry > Date.now()) {
219+
return cached.result;
220+
}
221+
222+
try {
223+
const response = await fetch(
224+
`${this.config.apiUrl}/api/trust/${encodeURIComponent(agentId)}`,
225+
{
226+
method: "GET",
227+
headers: {
228+
Accept: "application/json",
229+
"User-Agent": "mcp-framework-agentpass/1.0",
230+
},
231+
signal: AbortSignal.timeout(5000),
232+
}
233+
);
234+
235+
if (!response.ok) {
236+
return null;
237+
}
238+
239+
const data = (await response.json()) as Record<string, unknown>;
240+
const result: AgentPassTrustResult = {
241+
agent: (data.agent as string) ?? agentId,
242+
trustLevel: (data.trustLevel as string) ?? "L0",
243+
trustScore: (data.trustScore as number) ?? 0,
244+
identityVerified: (data.identityVerified as boolean) ?? false,
245+
sanctionsStatus: (data.sanctionsStatus as string) ?? "UNKNOWN",
246+
transactionCount: (data.transactionCount as number) ?? 0,
247+
registeredAt: data.registeredAt as string | undefined,
248+
};
249+
250+
// Cache result
251+
this.cache.set(agentId, {
252+
result,
253+
expiry: Date.now() + this.config.cacheTtlMs,
254+
});
255+
256+
return result;
257+
} catch {
258+
return null;
259+
}
260+
}
261+
262+
/**
263+
* Clear the trust score cache
264+
*/
265+
clearCache(): void {
266+
this.cache.clear();
267+
}
268+
}

0 commit comments

Comments
 (0)