@dupecom/botcha-cloudflare 0.20.2 → 0.23.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.
Files changed (88) hide show
  1. package/README.md +74 -9
  2. package/dist/agent-auth.d.ts +129 -0
  3. package/dist/agent-auth.d.ts.map +1 -0
  4. package/dist/agent-auth.js +210 -0
  5. package/dist/agents.d.ts +10 -0
  6. package/dist/agents.d.ts.map +1 -1
  7. package/dist/agents.js +51 -1
  8. package/dist/app-gate.d.ts +6 -0
  9. package/dist/app-gate.d.ts.map +1 -0
  10. package/dist/app-gate.js +69 -0
  11. package/dist/apps.d.ts +13 -4
  12. package/dist/apps.d.ts.map +1 -1
  13. package/dist/apps.js +30 -4
  14. package/dist/dashboard/account.d.ts +63 -0
  15. package/dist/dashboard/account.d.ts.map +1 -0
  16. package/dist/dashboard/account.js +488 -0
  17. package/dist/dashboard/api.js +15 -68
  18. package/dist/dashboard/auth.d.ts.map +1 -1
  19. package/dist/dashboard/auth.js +14 -14
  20. package/dist/dashboard/docs.d.ts.map +1 -1
  21. package/dist/dashboard/docs.js +146 -3
  22. package/dist/dashboard/layout.d.ts.map +1 -1
  23. package/dist/dashboard/layout.js +2 -2
  24. package/dist/dashboard/mcp-setup.d.ts +15 -0
  25. package/dist/dashboard/mcp-setup.d.ts.map +1 -0
  26. package/dist/dashboard/mcp-setup.js +391 -0
  27. package/dist/dashboard/showcase.d.ts +6 -10
  28. package/dist/dashboard/showcase.d.ts.map +1 -1
  29. package/dist/dashboard/showcase.js +67 -991
  30. package/dist/dashboard/whitepaper.d.ts.map +1 -1
  31. package/dist/dashboard/whitepaper.js +42 -4
  32. package/dist/index.d.ts +5 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +660 -83
  35. package/dist/mcp.d.ts +20 -0
  36. package/dist/mcp.d.ts.map +1 -0
  37. package/dist/mcp.js +1290 -0
  38. package/dist/oauth-agent.d.ts +130 -0
  39. package/dist/oauth-agent.d.ts.map +1 -0
  40. package/dist/oauth-agent.js +194 -0
  41. package/dist/static.d.ts +781 -5
  42. package/dist/static.d.ts.map +1 -1
  43. package/dist/static.js +790 -111
  44. package/dist/tap-a2a-routes.d.ts +355 -0
  45. package/dist/tap-a2a-routes.d.ts.map +1 -0
  46. package/dist/tap-a2a-routes.js +475 -0
  47. package/dist/tap-a2a.d.ts +199 -0
  48. package/dist/tap-a2a.d.ts.map +1 -0
  49. package/dist/tap-a2a.js +502 -0
  50. package/dist/tap-agents.d.ts +15 -0
  51. package/dist/tap-agents.d.ts.map +1 -1
  52. package/dist/tap-agents.js +31 -1
  53. package/dist/tap-ans-routes.d.ts +302 -0
  54. package/dist/tap-ans-routes.d.ts.map +1 -0
  55. package/dist/tap-ans-routes.js +535 -0
  56. package/dist/tap-ans.d.ts +241 -0
  57. package/dist/tap-ans.d.ts.map +1 -0
  58. package/dist/tap-ans.js +481 -0
  59. package/dist/tap-delegation-routes.d.ts.map +1 -1
  60. package/dist/tap-delegation-routes.js +11 -0
  61. package/dist/tap-did.d.ts +140 -0
  62. package/dist/tap-did.d.ts.map +1 -0
  63. package/dist/tap-did.js +262 -0
  64. package/dist/tap-oidca-routes.d.ts +383 -0
  65. package/dist/tap-oidca-routes.d.ts.map +1 -0
  66. package/dist/tap-oidca-routes.js +597 -0
  67. package/dist/tap-oidca.d.ts +288 -0
  68. package/dist/tap-oidca.d.ts.map +1 -0
  69. package/dist/tap-oidca.js +461 -0
  70. package/dist/tap-routes.d.ts +24 -8
  71. package/dist/tap-routes.d.ts.map +1 -1
  72. package/dist/tap-routes.js +169 -23
  73. package/dist/tap-vc-routes.d.ts +358 -0
  74. package/dist/tap-vc-routes.d.ts.map +1 -0
  75. package/dist/tap-vc-routes.js +367 -0
  76. package/dist/tap-vc.d.ts +125 -0
  77. package/dist/tap-vc.d.ts.map +1 -0
  78. package/dist/tap-vc.js +245 -0
  79. package/dist/tap-x402-routes.d.ts +89 -0
  80. package/dist/tap-x402-routes.d.ts.map +1 -0
  81. package/dist/tap-x402-routes.js +579 -0
  82. package/dist/tap-x402.d.ts +222 -0
  83. package/dist/tap-x402.d.ts.map +1 -0
  84. package/dist/tap-x402.js +546 -0
  85. package/dist/webhooks.d.ts +99 -0
  86. package/dist/webhooks.d.ts.map +1 -0
  87. package/dist/webhooks.js +642 -0
  88. package/package.json +3 -1
package/dist/mcp.js ADDED
@@ -0,0 +1,1290 @@
1
+ /**
2
+ * BOTCHA MCP Server
3
+ *
4
+ * Implements the Model Context Protocol (MCP) 2025-03-26 Streamable HTTP transport.
5
+ * Exposes BOTCHA documentation, API reference, and code examples as MCP tools.
6
+ *
7
+ * Endpoint: POST /mcp
8
+ * Discovery: GET /.well-known/mcp.json
9
+ *
10
+ * Tools:
11
+ * list_features — list all BOTCHA features
12
+ * get_feature — detailed info on a feature
13
+ * search_docs — keyword search across all docs
14
+ * list_endpoints — all API endpoints grouped by category
15
+ * get_endpoint — details for a specific endpoint
16
+ * get_example — code example for a feature (TypeScript / Python / curl)
17
+ */
18
+ // ============ KNOWLEDGE BASE ============
19
+ const FEATURES = {
20
+ challenges: {
21
+ name: 'Challenge Verification',
22
+ category: 'Core',
23
+ summary: 'Computational challenges only AI agents can solve — SHA-256 hashes in <500ms.',
24
+ detail: `BOTCHA challenges prove you are a machine, not a human. Four types are available:
25
+
26
+ • Speed: Compute SHA-256 of 5 random numbers, return the first 8 hex chars each — all within 500ms.
27
+ RTT-aware: timeout = 500ms + (2 × RTT) + 100ms buffer. Capped at 5 seconds.
28
+ • Reasoning: Answer 3 questions drawn from 6 categories (math, code, logic, wordplay, common-sense,
29
+ analogy). 45+ parameterized generators, never the same question twice. 30s limit.
30
+ • Hybrid (default): Both speed AND reasoning must pass. Strongest proof.
31
+ • Compute: Heavy computation — generate primes, concatenate with salt, hash. Scales easy→hard.
32
+
33
+ All challenges are single-use (deleted on first attempt) and timestamp-validated (±30s).`,
34
+ endpoints: [
35
+ 'GET /v1/challenges',
36
+ 'POST /v1/challenges/:id/verify',
37
+ 'GET /v1/reasoning',
38
+ 'POST /v1/reasoning',
39
+ 'GET /v1/hybrid',
40
+ 'POST /v1/hybrid',
41
+ ],
42
+ },
43
+ tokens: {
44
+ name: 'JWT Tokens',
45
+ category: 'Core',
46
+ summary: 'ES256 JWTs: 1-hour access token + 1-hour refresh token, with revocation and audience scoping.',
47
+ detail: `After solving a challenge, agents receive:
48
+ • access_token — ES256 JWT, 1 hour, use as Bearer in Authorization header
49
+ • refresh_token — ES256 JWT, 1 hour, exchange for a new access token without re-solving
50
+ • human_link — a /go/:code URL to give to a human operator for browser access
51
+
52
+ Token features:
53
+ • ES256 (ECDSA P-256) — asymmetric signing, verify via GET /.well-known/jwks (no shared secret needed)
54
+ • HS256 still supported for backward compatibility
55
+ • Audience (aud) scoping — token for api.stripe.com rejected by api.github.com
56
+ • IP binding — solve on machine A, only works on machine A (optional)
57
+ • JTI — unique ID per token for revocation and audit
58
+ • Remote validation — POST /v1/token/validate without needing the signing secret
59
+
60
+ Revocation is KV-backed with fail-open on infrastructure errors.`,
61
+ endpoints: [
62
+ 'GET /v1/token',
63
+ 'POST /v1/token/verify',
64
+ 'POST /v1/token/refresh',
65
+ 'POST /v1/token/revoke',
66
+ 'POST /v1/token/validate',
67
+ 'GET /.well-known/jwks',
68
+ ],
69
+ },
70
+ apps: {
71
+ name: 'Multi-Tenant Apps',
72
+ category: 'Core',
73
+ summary: 'Create isolated apps with unique credentials, per-app rate limits, and email-tied accounts.',
74
+ detail: `Every API call requires a registered app (app_id). Apps provide:
75
+ • Isolation — each app has its own rate limit bucket, token scoping, and analytics
76
+ • Email — required at creation; verified with a 6-digit code
77
+ • Secret — shown ONCE at creation, used for email verification and secret rotation
78
+ • Recovery — lost your secret? POST /v1/auth/recover emails a device code
79
+
80
+ App lifecycle:
81
+ 1. POST /v1/apps {"email": "..."} → app_id + app_secret (save the secret!)
82
+ 2. POST /v1/apps/:id/verify-email {"code": "123456"} → enables recovery
83
+ 3. Use app_id on all API calls via ?app_id=, X-App-Id header, or JWT claim`,
84
+ endpoints: [
85
+ 'POST /v1/apps',
86
+ 'GET /v1/apps/:id',
87
+ 'POST /v1/apps/:id/verify-email',
88
+ 'POST /v1/apps/:id/resend-verification',
89
+ 'POST /v1/apps/:id/rotate-secret',
90
+ 'POST /v1/auth/recover',
91
+ ],
92
+ },
93
+ agents: {
94
+ name: 'Agent Registry',
95
+ category: 'Identity',
96
+ summary: 'Register persistent agent identities with names, operators, and version tracking.',
97
+ detail: `Register your agent to get a persistent agent_id that survives across sessions.
98
+
99
+ Registration fields:
100
+ • name — human-readable agent name
101
+ • operator — organization operating the agent
102
+ • version — optional semver
103
+
104
+ The agent_id is the foundation for TAP, delegation, attestation, and reputation.
105
+ Public GET /v1/agents/:id lets anyone look up an agent without auth.`,
106
+ endpoints: [
107
+ 'POST /v1/agents/register',
108
+ 'GET /v1/agents/:id',
109
+ 'GET /v1/agents',
110
+ ],
111
+ },
112
+ tap: {
113
+ name: 'TAP (Trusted Agent Protocol)',
114
+ category: 'Identity',
115
+ summary: 'Cryptographic agent identity using HTTP Message Signatures (RFC 9421) with capability scoping and intent sessions.',
116
+ detail: `TAP proves you are a specific, trusted bot — not just any bot.
117
+
118
+ Based on Visa's Trusted Agent Protocol (https://developer.visa.com/capabilities/trusted-agent-protocol/overview).
119
+
120
+ Features:
121
+ • Public key registration — Ed25519 (recommended), ECDSA P-256, or RSA-PSS
122
+ • RFC 9421 request signing — signature-input + signature headers
123
+ • Capability scoping — declare what the agent can do: browse, search, compare, purchase, audit
124
+ • Intent sessions — time-limited sessions validated against registered capabilities
125
+ • Trust levels — basic, verified, enterprise
126
+ • Layer 2 (Consumer Recognition) — OIDC ID tokens with obfuscated consumer identity
127
+ • Layer 3 (Payment Container) — card metadata, credential hash, encrypted payment payloads
128
+
129
+ Signing headers example:
130
+ x-tap-agent-id: agent_6ddfd9f10cfd8dfc
131
+ x-tap-intent: {"action":"browse","resource":"products"}
132
+ signature-input: sig1=("@method" "@path" "x-tap-agent-id");alg="ecdsa-p256-sha256"
133
+ signature: sig1=:BASE64:`,
134
+ endpoints: [
135
+ 'POST /v1/agents/register/tap',
136
+ 'GET /v1/agents/:id/tap',
137
+ 'GET /v1/agents/tap',
138
+ 'POST /v1/sessions/tap',
139
+ 'GET /v1/sessions/:id/tap',
140
+ 'POST /v1/agents/:id/tap/rotate-key',
141
+ 'GET /.well-known/jwks',
142
+ 'GET /v1/keys',
143
+ 'GET /v1/keys/:keyId',
144
+ ],
145
+ spec: 'https://www.rfc-editor.org/rfc/rfc9421',
146
+ },
147
+ delegation: {
148
+ name: 'Delegation Chains',
149
+ category: 'Identity',
150
+ summary: '"User X authorized Agent Y to do Z until T." Signed chains with cascade revocation.',
151
+ detail: `Delegation encodes: "Agent A authorizes Agent B to perform capabilities C until time T."
152
+
153
+ Rules:
154
+ • Capabilities can only NARROW, never expand — a grantee cannot exceed the grantor's capabilities
155
+ • Chain depth capped at 3 (max 10) — prevents infinite delegation trees
156
+ • Revoking any link cascades to ALL sub-delegations automatically
157
+ • Sub-delegations cannot outlive their parent
158
+ • Cycle detection prevents circular chains
159
+
160
+ Capabilities use action:resource format: {"action": "browse", "resource": "products"}
161
+
162
+ POST /v1/verify/delegation verifies the entire chain in one call and returns effective_capabilities.`,
163
+ endpoints: [
164
+ 'POST /v1/delegations',
165
+ 'GET /v1/delegations/:id',
166
+ 'GET /v1/delegations',
167
+ 'POST /v1/delegations/:id/revoke',
168
+ 'POST /v1/verify/delegation',
169
+ ],
170
+ },
171
+ attestation: {
172
+ name: 'Capability Attestation',
173
+ category: 'Identity',
174
+ summary: 'Signed action:resource permission tokens with explicit deny rules and wildcard patterns.',
175
+ detail: `Attestation tokens encode exactly what an agent CAN and CANNOT do.
176
+
177
+ Permission model:
178
+ • can: ["read:invoices", "browse:*"] — allow rules, wildcards supported
179
+ • cannot: ["purchase:*"] — explicit deny rules, ALWAYS take precedence over can
180
+ • Bare actions expand: "browse" → "browse:*"
181
+ • Patterns: *:* (all), read:* (any resource), *:invoices (any action on invoices)
182
+
183
+ The token is a signed JWT. Present it as X-Botcha-Attestation header.
184
+
185
+ Enforcement: use requireCapability('read:invoices') middleware on Hono routes.
186
+ Link to delegation chains via delegation_id for full audit trail.`,
187
+ endpoints: [
188
+ 'POST /v1/attestations',
189
+ 'GET /v1/attestations/:id',
190
+ 'GET /v1/attestations',
191
+ 'POST /v1/attestations/:id/revoke',
192
+ 'POST /v1/verify/attestation',
193
+ ],
194
+ },
195
+ reputation: {
196
+ name: 'Agent Reputation',
197
+ category: 'Identity',
198
+ summary: 'Score-based reputation (0-1000, 5 tiers) tracking 18 action types across 6 categories.',
199
+ detail: `Reputation is the "credit score" for AI agents.
200
+
201
+ Scoring:
202
+ • Base: 500 (neutral, no history)
203
+ • Range: 0–1000
204
+ • Tiers: untrusted (0-199), low (200-399), neutral (400-599), good (600-799), excellent (800-1000)
205
+ • Decay: scores trend toward 500 without activity (mean reversion)
206
+ • Deny always wins: abuse events (-50) are heavily weighted
207
+
208
+ Categories and actions:
209
+ • verification: challenge_solved (+5), tap_session_created (+3), key_rotation (+2)
210
+ • commerce: purchase_completed (+10), invoice_paid (+8), payment_failed (-15)
211
+ • compliance: policy_violation (-30), rate_limit_exceeded (-5), suspicious_activity (-50)
212
+ • social: endorsement_received (+20), endorsement_given (+5)
213
+ • security: key_compromise (-100), unauthorized_access (-50)
214
+ • governance: delegation_granted (+3), delegation_revoked (-2)
215
+
216
+ High reputation unlocks elevated rate limits, faster verification paths, and access to sensitive endpoints.`,
217
+ endpoints: [
218
+ 'GET /v1/reputation/:agent_id',
219
+ 'POST /v1/reputation/events',
220
+ 'GET /v1/reputation/:agent_id/events',
221
+ 'POST /v1/reputation/:agent_id/reset',
222
+ ],
223
+ },
224
+ webhooks: {
225
+ name: 'Webhooks',
226
+ category: 'Platform',
227
+ summary: 'Per-app webhook endpoints receiving HMAC-SHA256 signed event deliveries.',
228
+ detail: `Register webhooks to receive signed HTTP POST event deliveries.
229
+
230
+ Supported events:
231
+ • agent.tap.registered — new TAP agent registered
232
+ • token.created — new access token issued
233
+ • token.revoked — token explicitly revoked
234
+ • tap.session.created — new TAP session started
235
+ • delegation.created — new delegation chain created
236
+ • delegation.revoked — delegation revoked (with cascade info)
237
+
238
+ Signature verification:
239
+ const sig = crypto.createHmac('sha256', signingSecret).update(body).digest('hex');
240
+ const valid = crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(incomingSignature));
241
+
242
+ The signing secret is shown ONCE at webhook creation — save it.
243
+ POST /v1/webhooks/:id/test sends a test payload to verify your endpoint is working.`,
244
+ endpoints: [
245
+ 'POST /v1/webhooks',
246
+ 'GET /v1/webhooks',
247
+ 'GET /v1/webhooks/:id',
248
+ 'PUT /v1/webhooks/:id',
249
+ 'DELETE /v1/webhooks/:id',
250
+ 'POST /v1/webhooks/:id/test',
251
+ 'GET /v1/webhooks/:id/deliveries',
252
+ ],
253
+ },
254
+ x402: {
255
+ name: 'x402 Payment Gating',
256
+ category: 'Protocols',
257
+ summary: 'HTTP 402 micropayment flow — pay $0.001 USDC on Base instead of solving a challenge.',
258
+ detail: `x402 is an HTTP payment protocol (https://x402.org/). BOTCHA supports it as an alternative
259
+ to challenge-solving — agents can pay instead of compute.
260
+
261
+ Flow:
262
+ 1. GET /v1/x402/challenge — receives 402 Payment Required with payment terms
263
+ Response: { amount: "0.001", currency: "USDC", chain: "base", recipient: "0xBOTCHA..." }
264
+ 2. Agent pays $0.001 USDC on Base to the recipient address
265
+ 3. Retry the same request with X-Payment: <payment_proof> header
266
+ 4. Receive 200 with access_token — no puzzle solved
267
+
268
+ Also available: a demo endpoint GET /agent-only/x402 that requires BOTH a BOTCHA token AND x402 payment.`,
269
+ endpoints: [
270
+ 'GET /v1/x402/info',
271
+ 'GET /v1/x402/challenge',
272
+ 'POST /v1/x402/verify-payment',
273
+ 'POST /v1/x402/webhook',
274
+ 'GET /agent-only/x402',
275
+ ],
276
+ spec: 'https://x402.org/',
277
+ },
278
+ ans: {
279
+ name: 'Agent Name Service (ANS)',
280
+ category: 'Protocols',
281
+ summary: 'DNS-based agent identity lookup with BOTCHA-issued ownership badges (GoDaddy ANS standard).',
282
+ detail: `ANS gives agents human-readable names like "my-agent.agents" that resolve via DNS TXT records
283
+ to endpoint URLs and identity metadata.
284
+
285
+ BOTCHA acts as a verification layer: agents prove they own their DNS name and receive a
286
+ BOTCHA-signed badge JWT that can be presented to any party.
287
+
288
+ Ownership proof flow:
289
+ 1. GET /v1/ans/nonce/:name — get a one-time nonce (requires Bearer token)
290
+ 2. Sign the nonce with your agent's private key
291
+ 3. POST /v1/ans/verify with name, agent_url, nonce, and proof
292
+ 4. Receive a BOTCHA-signed badge JWT
293
+
294
+ BOTCHA publishes its own ANS identity at GET /v1/ans/botcha.`,
295
+ endpoints: [
296
+ 'GET /v1/ans/botcha',
297
+ 'GET /v1/ans/resolve/:name',
298
+ 'GET /v1/ans/resolve/lookup',
299
+ 'GET /v1/ans/discover',
300
+ 'GET /v1/ans/nonce/:name',
301
+ 'POST /v1/ans/verify',
302
+ ],
303
+ spec: 'https://www.godaddy.com/engineering/2024/12/16/agent-name-service/',
304
+ },
305
+ 'did-vc': {
306
+ name: 'DID / Verifiable Credentials',
307
+ category: 'Protocols',
308
+ summary: 'BOTCHA as a W3C DID issuer (did:web:botcha.ai) — portable VC JWTs verifiable without contacting BOTCHA.',
309
+ detail: `BOTCHA is a W3C DID/VC issuer. After solving a challenge, request a W3C Verifiable Credential
310
+ JWT signed with BOTCHA's private key. Any party can verify it offline using the public JWKS.
311
+
312
+ DID Document: GET /.well-known/did.json → did:web:botcha.ai
313
+
314
+ Issuance flow:
315
+ 1. Solve a challenge → receive Bearer token
316
+ 2. POST /v1/credentials/issue with subject and credential type
317
+ 3. Receive a VC JWT
318
+ 4. Present the VC JWT to any relying party
319
+ 5. Relying party verifies via POST /v1/credentials/verify (or local JWK verification)
320
+
321
+ Supported credential types: VerifiableCredential, BotchaVerification
322
+ VC is signed with ES256 (ECDSA P-256).
323
+
324
+ Also: GET /v1/dids/:did/resolve resolves any did:web DID (not just BOTCHA's).`,
325
+ endpoints: [
326
+ 'GET /.well-known/did.json',
327
+ 'GET /.well-known/jwks',
328
+ 'GET /.well-known/jwks.json',
329
+ 'POST /v1/credentials/issue',
330
+ 'POST /v1/credentials/verify',
331
+ 'GET /v1/dids/:did/resolve',
332
+ ],
333
+ spec: 'https://www.w3.org/TR/did-core/',
334
+ },
335
+ a2a: {
336
+ name: 'A2A Agent Card Attestation',
337
+ category: 'Protocols',
338
+ summary: 'BOTCHA as a trust seal issuer for Google A2A Agent Cards — tamper-evident, offline-verifiable.',
339
+ detail: `The Google A2A protocol defines a standard JSON Agent Card published at /.well-known/agent.json.
340
+ BOTCHA attests these cards by producing a tamper-evident hash+signature bundle.
341
+
342
+ Attestation:
343
+ 1. POST /v1/a2a/attest with the agent's card + duration + trust_level (requires Bearer)
344
+ 2. Receive a trust seal token (JWT) and an attested_card with botcha_attestation extension
345
+ 3. Embed the seal in the card's extensions field
346
+ 4. Anyone can verify offline via POST /v1/a2a/verify-card — no BOTCHA round-trip needed
347
+
348
+ Trust levels: unverified (default), verified, enterprise
349
+
350
+ BOTCHA publishes its own A2A card at GET /.well-known/agent.json.
351
+ Browse all attested cards at GET /v1/a2a/cards.`,
352
+ endpoints: [
353
+ 'GET /.well-known/agent.json',
354
+ 'GET /v1/a2a/agent-card',
355
+ 'POST /v1/a2a/attest',
356
+ 'POST /v1/a2a/verify-card',
357
+ 'POST /v1/a2a/verify-agent',
358
+ 'GET /v1/a2a/trust-level/:agent_url',
359
+ 'GET /v1/a2a/cards',
360
+ 'GET /v1/a2a/cards/:id',
361
+ ],
362
+ spec: 'https://google.github.io/A2A/',
363
+ },
364
+ 'oidc-a': {
365
+ name: 'OIDC-A Attestation',
366
+ category: 'Protocols',
367
+ summary: 'Entity Attestation Tokens (EAT/RFC 9334) and OIDC-A claims for enterprise agent auth chains.',
368
+ detail: `OIDC-A bridges human identity systems with agent identity systems.
369
+ Chain: human → enterprise IdP → BOTCHA → agent.
370
+
371
+ Three capabilities:
372
+
373
+ 1. Entity Attestation Tokens (EAT / RFC 9334):
374
+ POST /v1/attestation/eat — signed JWT attesting agent provenance, verification method, model identity
375
+ Fields: agent_model, ttl_seconds, verification_method, nonce
376
+
377
+ 2. OIDC-A Agent Claims:
378
+ POST /v1/attestation/oidc-agent-claims — OIDC claims block JWT for OAuth2 token responses
379
+ Fields: agent_model, agent_version, agent_capabilities, agent_operator,
380
+ human_oversight_required, task_id, task_purpose
381
+
382
+ 3. Agent Grant Flow (OAuth2-style):
383
+ POST /v1/auth/agent-grant — initiate; if human_oversight_required=true, returns oversight_url
384
+ GET /v1/auth/agent-grant/:id/status — poll status
385
+ POST /v1/auth/agent-grant/:id/resolve {"decision": "approved"} — human approves
386
+
387
+ Also: GET /v1/oidc/userinfo — OIDC-A UserInfo endpoint (returns agent claims for authenticated agent)
388
+ GET /.well-known/oauth-authorization-server — OIDC discovery document`,
389
+ endpoints: [
390
+ 'GET /.well-known/oauth-authorization-server',
391
+ 'POST /v1/attestation/eat',
392
+ 'POST /v1/attestation/oidc-agent-claims',
393
+ 'POST /v1/auth/agent-grant',
394
+ 'GET /v1/auth/agent-grant/:id/status',
395
+ 'POST /v1/auth/agent-grant/:id/resolve',
396
+ 'GET /v1/oidc/userinfo',
397
+ ],
398
+ spec: 'https://www.rfc-editor.org/rfc/rfc9334',
399
+ },
400
+ dashboard: {
401
+ name: 'Dashboard & Auth',
402
+ category: 'Platform',
403
+ summary: 'Agent-first dashboard with per-app analytics. Agents solve challenges; humans use device codes.',
404
+ detail: `The metrics dashboard at /dashboard shows per-app analytics.
405
+
406
+ Agent-first auth — no password form:
407
+ • Agent Direct: POST /v1/auth/dashboard → session token
408
+ • Device Code: POST /v1/auth/device-code (agent solves), POST /v1/auth/device-code/verify
409
+ → returns BOTCHA-XXXX code → human enters at /dashboard/code → instant browser session
410
+ • Legacy: app_id + app_secret login at /dashboard/login
411
+
412
+ Dashboard shows: challenges generated, verifications, success rate, avg solve time,
413
+ request volume charts, challenge type breakdown, p50/p95 solve times, errors, geo distribution.
414
+ Time filters: 1h, 24h, 7d, 30d.`,
415
+ endpoints: [
416
+ 'POST /v1/auth/device-code',
417
+ 'POST /v1/auth/device-code/verify',
418
+ 'GET /dashboard',
419
+ ],
420
+ },
421
+ sdks: {
422
+ name: 'SDKs & Middleware',
423
+ category: 'Platform',
424
+ summary: 'TypeScript (npm), Python (PyPI), CLI, LangChain. Server-side middleware for Express, Hono, FastAPI, Django.',
425
+ detail: `Client SDKs (for agents):
426
+ • TypeScript: npm install @dupecom/botcha
427
+ BotchaClient — drop-in fetch replacement, auto-solves challenges on 403/401
428
+ • Python: pip install botcha
429
+ BotchaClient (async context manager) — same auto-solve behavior
430
+ • LangChain: npm install @dupecom/botcha-langchain
431
+ • CLI: npm install -g @dupecom/botcha-cli
432
+
433
+ Server middleware (for API providers):
434
+ • Express: npm install @dupecom/botcha-verify
435
+ botchaVerify({ jwksUrl: 'https://botcha.ai/.well-known/jwks' })
436
+ • FastAPI: pip install botcha-verify
437
+ BotchaVerify(jwks_url='https://botcha.ai/.well-known/jwks')
438
+ • Hono: built into @dupecom/botcha-verify
439
+ • Django: pip install botcha-verify, BotchaMiddleware
440
+
441
+ Verification uses ES256 asymmetric tokens via JWKS — no shared secret needed.
442
+ HS256 (shared secret) still supported for backward compatibility.`,
443
+ endpoints: [],
444
+ },
445
+ discovery: {
446
+ name: 'Discovery',
447
+ category: 'Platform',
448
+ summary: 'ai.txt, OpenAPI 3.1, AI Plugin manifest, DID Document, A2A card, MCP server.',
449
+ detail: `BOTCHA is auto-discoverable by AI agents through multiple standards:
450
+
451
+ • GET /ai.txt — structured discovery file for AI agents
452
+ • GET /openapi.json — OpenAPI 3.1.0 specification
453
+ • GET /.well-known/ai-plugin.json — AI plugin manifest
454
+ • GET /.well-known/did.json — W3C DID Document
455
+ • GET /.well-known/agent.json — Google A2A Agent Card
456
+ • GET /.well-known/jwks — JWK Set for token verification
457
+ • GET /.well-known/oauth-authorization-server — OIDC discovery
458
+ • GET /.well-known/mcp.json — MCP server discovery (this server!)
459
+ • POST /mcp — MCP server (Model Context Protocol, 2025-03-26 Streamable HTTP)
460
+
461
+ Every response includes X-Botcha-* headers:
462
+ X-Botcha-Version: 0.22.0
463
+ X-Botcha-Enabled: true
464
+ X-Botcha-Methods: hybrid-challenge,speed-challenge,...
465
+ X-Botcha-Docs: https://botcha.ai/openapi.json`,
466
+ endpoints: [
467
+ 'GET /ai.txt',
468
+ 'GET /openapi.json',
469
+ 'GET /.well-known/ai-plugin.json',
470
+ 'GET /.well-known/did.json',
471
+ 'GET /.well-known/agent.json',
472
+ 'GET /.well-known/jwks',
473
+ 'GET /.well-known/oauth-authorization-server',
474
+ 'GET /.well-known/mcp.json',
475
+ 'POST /mcp',
476
+ ],
477
+ },
478
+ };
479
+ // ============ ENDPOINT INDEX ============
480
+ // Flat lookup by path for get_endpoint tool
481
+ const ENDPOINT_DETAILS = {
482
+ 'GET /v1/challenges': {
483
+ method: 'GET', path: '/v1/challenges', auth: 'app_id required',
484
+ description: 'Generate a challenge. Default type is hybrid (speed + reasoning).',
485
+ params: '?type=hybrid|speed|standard — challenge type\n?ts=<ms> — client timestamp for RTT compensation\n?app_id=<id> — required app ID',
486
+ response: '{ success, type, challenge: { id, speed: { problems, timeLimit }, reasoning: { questions, timeLimit } }, verify_endpoint }',
487
+ },
488
+ 'POST /v1/challenges/:id/verify': {
489
+ method: 'POST', path: '/v1/challenges/:id/verify', auth: 'app_id required',
490
+ description: 'Submit challenge solution. Challenge is deleted on first attempt (single-use).',
491
+ body: 'Hybrid: { type: "hybrid", speed_answers: ["8hex",...], reasoning_answers: {"q-id": "answer"} }\nSpeed: { type: "speed", answers: ["8hex",...] }',
492
+ response: '{ success, message, speed: { valid, solveTimeMs }, reasoning: { valid, score } }',
493
+ },
494
+ 'GET /v1/token': {
495
+ method: 'GET', path: '/v1/token', auth: 'app_id required',
496
+ description: 'Get a speed challenge to solve in exchange for a JWT token pair.',
497
+ params: '?ts=<ms> — RTT compensation\n?audience=<url> — scope token to a service\n?app_id=<id> — required',
498
+ },
499
+ 'POST /v1/token/verify': {
500
+ method: 'POST', path: '/v1/token/verify', auth: 'app_id required',
501
+ description: 'Submit challenge solution, receive access_token + refresh_token + human_link.',
502
+ body: '{ id: "<challenge_id>", answers: ["hash1",...], audience?: "<url>", bind_ip?: true }',
503
+ response: '{ success, access_token, expires_in: 3600, refresh_token, refresh_expires_in: 3600, human_link, human_code, solveTimeMs }',
504
+ },
505
+ 'POST /v1/token/refresh': {
506
+ method: 'POST', path: '/v1/token/refresh', auth: 'none',
507
+ description: 'Exchange a refresh_token for a new access_token.',
508
+ body: '{ refresh_token: "<token>" }',
509
+ response: '{ success, access_token, expires_in: 3600 }',
510
+ },
511
+ 'POST /v1/token/revoke': {
512
+ method: 'POST', path: '/v1/token/revoke', auth: 'none',
513
+ description: 'Immediately revoke any BOTCHA token (access or refresh).',
514
+ body: '{ token: "<jwt>" }',
515
+ },
516
+ 'POST /v1/token/validate': {
517
+ method: 'POST', path: '/v1/token/validate', auth: 'none',
518
+ description: 'Validate any BOTCHA token without needing the signing secret. Supports ES256 and HS256.',
519
+ body: '{ token: "<jwt>" }',
520
+ response: '{ valid: true, payload: { sub, type, aud, exp } } or { valid: false, error: "..." }',
521
+ },
522
+ 'POST /v1/apps': {
523
+ method: 'POST', path: '/v1/apps', auth: 'none',
524
+ description: 'Create a new app. Email required. App secret shown ONCE — save it.',
525
+ body: '{ email: "human@example.com", name?: "My App" }',
526
+ response: '{ success, app_id, app_secret, email, email_verified: false }',
527
+ },
528
+ 'POST /v1/agents/register': {
529
+ method: 'POST', path: '/v1/agents/register', auth: 'app_id required',
530
+ description: 'Register a new agent identity. Returns a persistent agent_id.',
531
+ body: '{ name: "my-agent", operator: "Acme Corp", version?: "1.0.0" }',
532
+ response: '{ agent_id, app_id, name, operator, version, created_at }',
533
+ },
534
+ 'POST /v1/agents/register/tap': {
535
+ method: 'POST', path: '/v1/agents/register/tap', auth: 'app_id required',
536
+ description: 'Register a TAP agent with a public key and capability scoping.',
537
+ body: '{ name, operator?, version?, public_key, signature_algorithm: "ed25519"|"ecdsa-p256-sha256"|"rsa-pss-sha256", capabilities: [{action, resource, constraints?}], trust_level: "basic"|"verified"|"enterprise" }',
538
+ },
539
+ 'POST /v1/sessions/tap': {
540
+ method: 'POST', path: '/v1/sessions/tap', auth: 'app_id required',
541
+ description: 'Create a TAP session with intent declaration. Validates intent against registered capabilities.',
542
+ body: '{ agent_id, user_context, intent: { action, resource, duration? } }',
543
+ response: '{ session_id, agent_id, intent, expires_at, status: "active" }',
544
+ },
545
+ 'POST /v1/delegations': {
546
+ method: 'POST', path: '/v1/delegations', auth: 'Bearer token',
547
+ description: 'Create a delegation from grantor agent to grantee agent. Capabilities can only narrow.',
548
+ body: '{ grantor_id, grantee_id, capabilities: [{action, resource}], ttl: 3600, parent_delegation_id? }',
549
+ response: '{ delegation_id, grantor_id, grantee_id, capabilities, expires_at, status: "active" }',
550
+ },
551
+ 'POST /v1/delegations/:id/revoke': {
552
+ method: 'POST', path: '/v1/delegations/:id/revoke', auth: 'Bearer token',
553
+ description: 'Revoke a delegation. Cascades to all child delegations.',
554
+ body: '{ reason?: "string" }',
555
+ },
556
+ 'POST /v1/verify/delegation': {
557
+ method: 'POST', path: '/v1/verify/delegation', auth: 'Bearer token',
558
+ description: 'Verify the entire delegation chain and return effective capabilities.',
559
+ body: '{ delegation_id }',
560
+ response: '{ valid, effective_capabilities, chain: [...], depth }',
561
+ },
562
+ 'POST /v1/attestations': {
563
+ method: 'POST', path: '/v1/attestations', auth: 'Bearer token',
564
+ description: 'Issue an attestation token with can/cannot capability rules.',
565
+ body: '{ agent_id, can: ["read:invoices", "browse:*"], cannot?: ["purchase:*"], ttl: 3600, delegation_id? }',
566
+ response: '{ attestation_id, token, agent_id, can, cannot, expires_at }',
567
+ },
568
+ 'POST /v1/verify/attestation': {
569
+ method: 'POST', path: '/v1/verify/attestation', auth: 'Bearer token',
570
+ description: 'Verify an attestation token and check if a specific capability is allowed.',
571
+ body: '{ token, action, resource }',
572
+ response: '{ valid, allowed, reason? }',
573
+ },
574
+ 'POST /v1/reputation/events': {
575
+ method: 'POST', path: '/v1/reputation/events', auth: 'Bearer token',
576
+ description: 'Record a reputation event for an agent.',
577
+ body: '{ agent_id, category: "verification"|"commerce"|"compliance"|"social"|"security"|"governance", action: "<action_name>", metadata?: {}, source_agent_id? }',
578
+ },
579
+ 'POST /v1/webhooks': {
580
+ method: 'POST', path: '/v1/webhooks', auth: 'app_id + Bearer token',
581
+ description: 'Register a webhook endpoint. Signing secret shown ONCE.',
582
+ body: '{ url: "https://...", events: ["token.created", ...] }',
583
+ response: '{ webhook_id, url, signing_secret, events, enabled: true }',
584
+ },
585
+ 'POST /v1/credentials/issue': {
586
+ method: 'POST', path: '/v1/credentials/issue', auth: 'Bearer token',
587
+ description: 'Issue a W3C Verifiable Credential JWT signed with BOTCHA\'s ES256 key.',
588
+ body: '{ subject: {}, type?: ["VerifiableCredential", "BotchaVerification"], ttl_seconds?: 3600 }',
589
+ response: '{ vc: "eyJ...", expires_at }',
590
+ },
591
+ 'POST /v1/credentials/verify': {
592
+ method: 'POST', path: '/v1/credentials/verify', auth: 'public',
593
+ description: 'Verify any BOTCHA-issued VC JWT. Public — no auth required.',
594
+ body: '{ vc: "eyJ..." }',
595
+ response: '{ valid, payload: { iss: "did:web:botcha.ai", sub, vc: { type, credentialSubject } } }',
596
+ },
597
+ 'POST /v1/a2a/attest': {
598
+ method: 'POST', path: '/v1/a2a/attest', auth: 'Bearer token',
599
+ description: 'Attest an A2A Agent Card. Returns a tamper-evident BOTCHA trust seal.',
600
+ body: '{ card: { name, url, version, capabilities, skills }, duration_seconds?: 86400, trust_level?: "verified" }',
601
+ response: '{ success, attestation: { attestation_id, trust_level, token }, attested_card: { ...card, extensions: { botcha_attestation: { token, card_hash } } } }',
602
+ },
603
+ 'POST /v1/attestation/eat': {
604
+ method: 'POST', path: '/v1/attestation/eat', auth: 'Bearer token',
605
+ description: 'Issue an Entity Attestation Token (EAT / RFC 9334).',
606
+ body: '{ agent_model?: "gpt-5", ttl_seconds?: 900, verification_method?: "speed-challenge", nonce? }',
607
+ response: '{ token: "eyJ...", expires_at }',
608
+ },
609
+ 'POST /v1/attestation/oidc-agent-claims': {
610
+ method: 'POST', path: '/v1/attestation/oidc-agent-claims', auth: 'Bearer token',
611
+ description: 'Issue an OIDC-A agent claims block JWT for inclusion in OAuth2 token responses.',
612
+ body: '{ agent_model?, agent_version?, agent_capabilities?: ["agent:tool-use"], agent_operator?, human_oversight_required?: false, task_id?, task_purpose?, nonce? }',
613
+ },
614
+ 'POST /v1/auth/agent-grant': {
615
+ method: 'POST', path: '/v1/auth/agent-grant', auth: 'Bearer token',
616
+ description: 'Initiate an OAuth2-style agent grant. Returns oversight_url if human_oversight_required=true.',
617
+ body: '{ scope: "agent:read openid", human_oversight_required?: true, agent_model?, agent_operator?, task_purpose? }',
618
+ response: '{ grant_id, token, status: "pending"|"approved", oversight_url? }',
619
+ },
620
+ };
621
+ // ============ CODE EXAMPLES ============
622
+ const EXAMPLES = {
623
+ challenges: {
624
+ typescript: `import { BotchaClient } from '@dupecom/botcha';
625
+
626
+ const client = new BotchaClient({ appId: 'app_...' });
627
+
628
+ // Auto-solve: challenges handled automatically
629
+ const response = await client.fetch('https://api.example.com/agent-only');
630
+ const data = await response.json();`,
631
+ python: `from botcha import BotchaClient
632
+
633
+ async with BotchaClient(app_id="app_...") as client:
634
+ # Auto-solve: challenges handled automatically
635
+ response = await client.fetch("https://api.example.com/agent-only")
636
+ data = response.json()`,
637
+ curl: `# 1. Get challenge
638
+ curl "https://botcha.ai/v1/challenges?app_id=app_..."
639
+
640
+ # 2. Solve: SHA-256 of each number, first 8 hex chars
641
+ echo -n "42" | sha256sum | cut -c1-8
642
+
643
+ # 3. Verify
644
+ curl -X POST "https://botcha.ai/v1/challenges/{id}/verify" \\
645
+ -H "Content-Type: application/json" \\
646
+ -d '{"type":"hybrid","speed_answers":["73475cb4",...],"reasoning_answers":{"q1":"answer"}}'`,
647
+ },
648
+ tokens: {
649
+ typescript: `import { BotchaClient } from '@dupecom/botcha';
650
+
651
+ const client = new BotchaClient({
652
+ appId: 'app_...',
653
+ audience: 'https://api.example.com', // scope token to this service
654
+ });
655
+
656
+ // Get token explicitly
657
+ const token = await client.getToken();
658
+
659
+ // Or use fetch — auto-handles challenge → token → refresh → retry
660
+ const response = await client.fetch('https://api.example.com/protected');`,
661
+ python: `from botcha import BotchaClient
662
+
663
+ async with BotchaClient(app_id="app_...", audience="https://api.example.com") as client:
664
+ token = await client.get_token()
665
+ response = await client.fetch("https://api.example.com/protected")`,
666
+ curl: `# Get challenge for token flow
667
+ curl "https://botcha.ai/v1/token?app_id=app_..."
668
+
669
+ # Submit solution, get JWT
670
+ curl -X POST https://botcha.ai/v1/token/verify \\
671
+ -H "Content-Type: application/json" \\
672
+ -d '{"id":"<challenge_id>","answers":["hash1","hash2","hash3","hash4","hash5"]}'
673
+
674
+ # Use token
675
+ curl https://botcha.ai/agent-only \\
676
+ -H "Authorization: Bearer <access_token>"`,
677
+ },
678
+ tap: {
679
+ typescript: `import { BotchaClient } from '@dupecom/botcha';
680
+
681
+ const client = new BotchaClient({ appId: 'app_...' });
682
+
683
+ // Register TAP agent with public key
684
+ const agent = await client.registerTAPAgent({
685
+ name: 'shopping-agent',
686
+ operator: 'Acme Corp',
687
+ capabilities: [{ action: 'browse', scope: ['products'] }],
688
+ trust_level: 'verified',
689
+ });
690
+
691
+ // Create intent-scoped session
692
+ const session = await client.createTAPSession({
693
+ agent_id: agent.agent_id,
694
+ user_context: 'user-hash',
695
+ intent: { action: 'browse', resource: 'products', duration: 3600 },
696
+ });`,
697
+ python: `from botcha import BotchaClient
698
+
699
+ async with BotchaClient(app_id="app_...") as client:
700
+ agent = await client.register_tap_agent(
701
+ name="shopping-agent",
702
+ operator="Acme Corp",
703
+ capabilities=[{"action": "browse", "scope": ["products"]}],
704
+ trust_level="verified",
705
+ )
706
+ session = await client.create_tap_session(
707
+ agent_id=agent.agent_id,
708
+ user_context="user-hash",
709
+ intent={"action": "browse", "resource": "products", "duration": 3600},
710
+ )`,
711
+ curl: `# Register TAP agent
712
+ curl -X POST "https://botcha.ai/v1/agents/register/tap?app_id=app_..." \\
713
+ -H "Content-Type: application/json" \\
714
+ -d '{
715
+ "name": "shopping-agent",
716
+ "public_key": "-----BEGIN PUBLIC KEY-----\\n...\\n-----END PUBLIC KEY-----",
717
+ "signature_algorithm": "ecdsa-p256-sha256",
718
+ "capabilities": [{"action": "browse", "resource": "products"}],
719
+ "trust_level": "verified"
720
+ }'`,
721
+ },
722
+ delegation: {
723
+ typescript: `import { BotchaClient } from '@dupecom/botcha';
724
+
725
+ const client = new BotchaClient({ appId: 'app_...' });
726
+
727
+ // Agent A delegates browse:products to Agent B
728
+ const delegation = await client.createDelegation({
729
+ grantor_id: 'agent_aaa',
730
+ grantee_id: 'agent_bbb',
731
+ capabilities: [{ action: 'browse', resource: 'products' }],
732
+ duration_seconds: 3600,
733
+ });
734
+
735
+ // Verify the chain
736
+ const chain = await client.verifyDelegationChain(delegation.delegation_id);
737
+ console.log(chain.effective_capabilities);
738
+
739
+ // Revoke (cascades to sub-delegations)
740
+ await client.revokeDelegation(delegation.delegation_id, 'Session ended');`,
741
+ python: `from botcha import BotchaClient
742
+
743
+ async with BotchaClient(app_id="app_...") as client:
744
+ delegation = await client.create_delegation(
745
+ grantor_id="agent_aaa",
746
+ grantee_id="agent_bbb",
747
+ capabilities=[{"action": "browse", "resource": "products"}],
748
+ ttl=3600,
749
+ )
750
+ chain = await client.verify_delegation_chain(delegation.delegation_id)
751
+ await client.revoke_delegation(delegation.delegation_id, reason="Session ended")`,
752
+ curl: `curl -X POST https://botcha.ai/v1/delegations \\
753
+ -H "Authorization: Bearer <token>" \\
754
+ -H "Content-Type: application/json" \\
755
+ -d '{
756
+ "grantor_id": "agent_aaa",
757
+ "grantee_id": "agent_bbb",
758
+ "capabilities": [{"action": "browse", "resource": "products"}],
759
+ "ttl": 3600
760
+ }'`,
761
+ },
762
+ attestation: {
763
+ typescript: `import { BotchaClient } from '@dupecom/botcha';
764
+
765
+ const client = new BotchaClient({ appId: 'app_...' });
766
+
767
+ const att = await client.issueAttestation({
768
+ agent_id: 'agent_abc123',
769
+ can: ['read:invoices', 'browse:*'],
770
+ cannot: ['purchase:*'],
771
+ duration_seconds: 3600,
772
+ });
773
+
774
+ // Present token as header: X-Botcha-Attestation: <att.token>
775
+
776
+ // Verify capability
777
+ const check = await client.verifyAttestation(att.token, 'read', 'invoices');
778
+ console.log(check.allowed); // true`,
779
+ python: `from botcha import BotchaClient
780
+
781
+ async with BotchaClient(app_id="app_...") as client:
782
+ att = await client.issue_attestation(
783
+ agent_id="agent_abc123",
784
+ can=["read:invoices", "browse:*"],
785
+ cannot=["purchase:*"],
786
+ ttl=3600,
787
+ )
788
+ check = await client.verify_attestation(att.token, "read", "invoices")
789
+ print(check.allowed) # True`,
790
+ curl: `curl -X POST https://botcha.ai/v1/attestations \\
791
+ -H "Authorization: Bearer <token>" \\
792
+ -H "Content-Type: application/json" \\
793
+ -d '{
794
+ "agent_id": "agent_abc123",
795
+ "can": ["read:invoices", "browse:*"],
796
+ "cannot": ["purchase:*"],
797
+ "ttl": 3600
798
+ }'`,
799
+ },
800
+ 'did-vc': {
801
+ typescript: `import { BotchaClient } from '@dupecom/botcha';
802
+
803
+ const client = new BotchaClient({ appId: 'app_...' });
804
+
805
+ // Issue a W3C Verifiable Credential
806
+ const result = await client.issueCredential({
807
+ subject: { agentType: 'llm', operator: 'Acme Corp' },
808
+ credentialType: ['VerifiableCredential', 'BotchaVerification'],
809
+ ttlSeconds: 3600,
810
+ });
811
+ console.log(result.vc); // eyJ... — portable JWT
812
+
813
+ // Anyone can verify offline
814
+ const verified = await client.verifyCredential(result.vc);
815
+ console.log(verified.valid); // true
816
+ console.log(verified.payload.iss); // did:web:botcha.ai`,
817
+ python: `from botcha import BotchaClient
818
+
819
+ async with BotchaClient() as client:
820
+ result = await client.issue_credential(
821
+ subject={"agentType": "llm", "operator": "Acme Corp"},
822
+ credential_type=["VerifiableCredential", "BotchaVerification"],
823
+ ttl_seconds=3600,
824
+ )
825
+ verified = await client.verify_credential(result.vc)
826
+ print(verified.valid) # True
827
+ print(verified.payload["iss"]) # did:web:botcha.ai`,
828
+ curl: `# Issue VC
829
+ curl -X POST https://botcha.ai/v1/credentials/issue \\
830
+ -H "Authorization: Bearer <token>" \\
831
+ -H "Content-Type: application/json" \\
832
+ -d '{"subject":{"agentType":"llm"},"type":["VerifiableCredential","BotchaVerification"]}'
833
+
834
+ # Verify VC (public, no auth needed)
835
+ curl -X POST https://botcha.ai/v1/credentials/verify \\
836
+ -H "Content-Type: application/json" \\
837
+ -d '{"vc":"eyJ..."}'`,
838
+ },
839
+ a2a: {
840
+ typescript: `import { BotchaClient } from '@dupecom/botcha';
841
+
842
+ const client = new BotchaClient({ appId: 'app_...' });
843
+
844
+ // Attest your A2A Agent Card
845
+ const result = await client.attestAgentCard({
846
+ card: {
847
+ name: 'My Commerce Agent',
848
+ url: 'https://myagent.example',
849
+ version: '1.0.0',
850
+ capabilities: { streaming: false },
851
+ skills: [{ id: 'browse', name: 'Browse' }],
852
+ },
853
+ trust_level: 'verified',
854
+ });
855
+
856
+ // Verify any attested card
857
+ const check = await client.verifyAgentCard(result.attested_card);
858
+ console.log(check.valid); // true`,
859
+ python: `from botcha import BotchaClient
860
+
861
+ async with BotchaClient() as client:
862
+ result = await client.attest_agent_card(
863
+ card={"name": "My Agent", "url": "https://myagent.example", "version": "1.0.0",
864
+ "capabilities": {"streaming": False}, "skills": [{"id": "browse", "name": "Browse"}]},
865
+ trust_level="verified",
866
+ )
867
+ check = await client.verify_agent_card(result.attested_card)
868
+ print(check.valid) # True`,
869
+ curl: `curl -X POST https://botcha.ai/v1/a2a/attest \\
870
+ -H "Authorization: Bearer <token>" \\
871
+ -H "Content-Type: application/json" \\
872
+ -d '{
873
+ "card": {"name":"My Agent","url":"https://myagent.example","version":"1.0.0",
874
+ "capabilities":{"streaming":false},"skills":[{"id":"browse","name":"Browse"}]},
875
+ "trust_level": "verified"
876
+ }'`,
877
+ },
878
+ 'oidc-a': {
879
+ typescript: `import { BotchaClient } from '@dupecom/botcha';
880
+
881
+ const client = new BotchaClient({ appId: 'app_...' });
882
+
883
+ // Issue Entity Attestation Token (EAT / RFC 9334)
884
+ const eat = await client.issueEAT({
885
+ agent_model: 'gpt-5',
886
+ ttl_seconds: 900,
887
+ verification_method: 'speed-challenge',
888
+ });
889
+
890
+ // Start an agent grant with human oversight
891
+ const grant = await client.createAgentGrant({
892
+ scope: 'agent:read openid',
893
+ human_oversight_required: true,
894
+ task_purpose: 'invoice reconciliation',
895
+ });
896
+ if (grant.oversight_url) {
897
+ console.log('Human approval needed:', grant.oversight_url);
898
+ }`,
899
+ python: `from botcha import BotchaClient
900
+
901
+ async with BotchaClient() as client:
902
+ eat = await client.issue_eat(agent_model="gpt-5", ttl_seconds=900)
903
+ grant = await client.create_agent_grant(
904
+ scope="agent:read openid",
905
+ human_oversight_required=True,
906
+ task_purpose="invoice reconciliation",
907
+ )
908
+ if grant.oversight_url:
909
+ print(f"Human approval needed: {grant.oversight_url}")`,
910
+ curl: `# Issue EAT
911
+ curl -X POST https://botcha.ai/v1/attestation/eat \\
912
+ -H "Authorization: Bearer <token>" \\
913
+ -H "Content-Type: application/json" \\
914
+ -d '{"agent_model":"gpt-5","ttl_seconds":900,"verification_method":"speed-challenge"}'
915
+
916
+ # Start agent grant
917
+ curl -X POST https://botcha.ai/v1/auth/agent-grant \\
918
+ -H "Authorization: Bearer <token>" \\
919
+ -H "Content-Type: application/json" \\
920
+ -d '{"scope":"agent:read openid","human_oversight_required":true,"task_purpose":"invoice reconciliation"}'`,
921
+ },
922
+ x402: {
923
+ typescript: `import { BotchaClient } from '@dupecom/botcha';
924
+
925
+ const client = new BotchaClient({ appId: 'app_...' });
926
+
927
+ // Get payment terms
928
+ const info = await client.getX402Info();
929
+ // info: { amount: "0.001", currency: "USDC", chain: "base", recipient: "0x..." }
930
+
931
+ // After paying on-chain, get the token
932
+ const result = await client.getX402Challenge(paymentProof);
933
+ // result.access_token — no challenge solved!`,
934
+ python: `from botcha import BotchaClient
935
+
936
+ async with BotchaClient(app_id="app_...") as client:
937
+ info = await client.get_x402_info()
938
+ # Pay on-chain, then:
939
+ result = await client.get_x402_challenge(payment_proof=payment_proof)`,
940
+ curl: `# Step 1: Get payment terms (returns 402)
941
+ curl https://botcha.ai/v1/x402/challenge
942
+
943
+ # Step 2: Pay on Base, then retry with payment proof
944
+ curl https://botcha.ai/v1/x402/challenge \\
945
+ -H "X-Payment: <payment_proof>"`,
946
+ },
947
+ };
948
+ // ============ TOOL DEFINITIONS ============
949
+ const MCP_TOOLS = [
950
+ {
951
+ name: 'list_features',
952
+ description: 'List all BOTCHA features with a brief summary of each. Use this to discover what BOTCHA can do, or to get an overview before diving into a specific feature.',
953
+ inputSchema: { type: 'object', properties: {
954
+ category: {
955
+ type: 'string',
956
+ description: 'Filter by category: Core, Identity, Protocols, or Platform. Omit for all.',
957
+ enum: ['Core', 'Identity', 'Protocols', 'Platform'],
958
+ },
959
+ } },
960
+ },
961
+ {
962
+ name: 'get_feature',
963
+ description: 'Get detailed documentation for a specific BOTCHA feature, including how it works, what endpoints it uses, and any relevant specifications.',
964
+ inputSchema: { type: 'object', properties: {
965
+ feature: {
966
+ type: 'string',
967
+ description: 'Feature name: challenges, tokens, apps, agents, tap, delegation, attestation, reputation, webhooks, x402, ans, did-vc, a2a, oidc-a, dashboard, sdks, or discovery',
968
+ },
969
+ }, required: ['feature'] },
970
+ },
971
+ {
972
+ name: 'search_docs',
973
+ description: 'Search across all BOTCHA documentation by keyword. Useful when you\'re not sure which feature covers your use case.',
974
+ inputSchema: { type: 'object', properties: {
975
+ query: {
976
+ type: 'string',
977
+ description: 'Search terms — e.g. "how do I verify a token", "delegation cascade", "USDC payment", "RFC 9421"',
978
+ },
979
+ }, required: ['query'] },
980
+ },
981
+ {
982
+ name: 'list_endpoints',
983
+ description: 'List all BOTCHA API endpoints grouped by category. Returns method, path, and a short description for each.',
984
+ inputSchema: { type: 'object', properties: {
985
+ category: {
986
+ type: 'string',
987
+ description: 'Filter by feature category. Omit for all endpoints.',
988
+ enum: ['Core', 'Identity', 'Protocols', 'Platform'],
989
+ },
990
+ } },
991
+ },
992
+ {
993
+ name: 'get_endpoint',
994
+ description: 'Get detailed documentation for a specific API endpoint: description, authentication, request body/params, and response shape.',
995
+ inputSchema: { type: 'object', properties: {
996
+ path: {
997
+ type: 'string',
998
+ description: 'Endpoint path, e.g. "POST /v1/token/verify" or "/v1/delegations" or "GET /v1/credentials/issue"',
999
+ },
1000
+ }, required: ['path'] },
1001
+ },
1002
+ {
1003
+ name: 'get_example',
1004
+ description: 'Get a working code example for a BOTCHA feature in TypeScript, Python, or curl.',
1005
+ inputSchema: { type: 'object', properties: {
1006
+ feature: {
1007
+ type: 'string',
1008
+ description: 'Feature name: challenges, tokens, tap, delegation, attestation, did-vc, a2a, oidc-a, or x402',
1009
+ },
1010
+ language: {
1011
+ type: 'string',
1012
+ description: 'Language for the example',
1013
+ enum: ['typescript', 'python', 'curl'],
1014
+ },
1015
+ }, required: ['feature', 'language'] },
1016
+ },
1017
+ ];
1018
+ // ============ TOOL IMPLEMENTATIONS ============
1019
+ function toolListFeatures(params) {
1020
+ const category = params.category;
1021
+ const entries = Object.entries(FEATURES)
1022
+ .filter(([, f]) => !category || f.category === category)
1023
+ .map(([key, f]) => ({
1024
+ id: key,
1025
+ name: f.name,
1026
+ category: f.category,
1027
+ summary: f.summary,
1028
+ ...(f.spec ? { spec: f.spec } : {}),
1029
+ }));
1030
+ return {
1031
+ count: entries.length,
1032
+ features: entries,
1033
+ tip: 'Use get_feature("<id>") for detailed docs, get_example("<id>", "<lang>") for code.',
1034
+ };
1035
+ }
1036
+ function toolGetFeature(params) {
1037
+ const key = params.feature?.toLowerCase().replace(/ /g, '-');
1038
+ const feature = FEATURES[key];
1039
+ if (!feature) {
1040
+ const available = Object.keys(FEATURES).join(', ');
1041
+ return { error: `Feature "${params.feature}" not found. Available: ${available}` };
1042
+ }
1043
+ return {
1044
+ name: feature.name,
1045
+ category: feature.category,
1046
+ summary: feature.summary,
1047
+ detail: feature.detail,
1048
+ endpoints: feature.endpoints,
1049
+ ...(feature.spec ? { spec: feature.spec } : {}),
1050
+ tip: `Use get_example("${key}", "typescript"|"python"|"curl") for code examples.`,
1051
+ };
1052
+ }
1053
+ function toolSearchDocs(params) {
1054
+ const query = (params.query || '').toLowerCase();
1055
+ const terms = query.split(/\s+/).filter(Boolean);
1056
+ const results = [];
1057
+ for (const [key, feature] of Object.entries(FEATURES)) {
1058
+ const searchText = [
1059
+ feature.name, feature.category, feature.summary, feature.detail,
1060
+ ...feature.endpoints, feature.spec ?? '',
1061
+ ].join(' ').toLowerCase();
1062
+ const relevance = terms.reduce((score, term) => {
1063
+ const matches = (searchText.match(new RegExp(term, 'g')) || []).length;
1064
+ return score + matches;
1065
+ }, 0);
1066
+ if (relevance > 0) {
1067
+ // Find the most relevant excerpt from detail
1068
+ const detailLower = feature.detail.toLowerCase();
1069
+ let bestIdx = -1;
1070
+ let bestScore = 0;
1071
+ for (const term of terms) {
1072
+ const idx = detailLower.indexOf(term);
1073
+ if (idx !== -1 && idx > bestScore) {
1074
+ bestIdx = idx;
1075
+ bestScore = idx;
1076
+ }
1077
+ }
1078
+ const excerptStart = Math.max(0, bestIdx - 40);
1079
+ const excerpt = feature.detail.slice(excerptStart, excerptStart + 200).trim();
1080
+ results.push({ feature: key, name: feature.name, category: feature.category, relevance, excerpt });
1081
+ }
1082
+ }
1083
+ // Check endpoint index too
1084
+ const endpointMatches = [];
1085
+ for (const [key, ep] of Object.entries(ENDPOINT_DETAILS)) {
1086
+ const text = [key, ep.description, ep.body ?? '', ep.response ?? ''].join(' ').toLowerCase();
1087
+ if (terms.some(t => text.includes(t)))
1088
+ endpointMatches.push(key);
1089
+ }
1090
+ results.sort((a, b) => b.relevance - a.relevance);
1091
+ if (results.length === 0 && endpointMatches.length === 0) {
1092
+ return { message: `No results for "${params.query}". Try terms like: challenge, token, delegation, attestation, reputation, webhook, x402, ANS, DID, VC, A2A, OIDC, TAP` };
1093
+ }
1094
+ return {
1095
+ query: params.query,
1096
+ features: results.slice(0, 5).map(r => ({ feature: r.feature, name: r.name, category: r.category, excerpt: r.excerpt })),
1097
+ matching_endpoints: endpointMatches.slice(0, 10),
1098
+ tip: 'Use get_feature() or get_endpoint() for full details on any result.',
1099
+ };
1100
+ }
1101
+ function toolListEndpoints(params) {
1102
+ const category = params.category;
1103
+ const groups = {};
1104
+ for (const [, feature] of Object.entries(FEATURES)) {
1105
+ if (category && feature.category !== category)
1106
+ continue;
1107
+ if (feature.endpoints.length === 0)
1108
+ continue;
1109
+ const key = `${feature.category}: ${feature.name}`;
1110
+ groups[key] = feature.endpoints.map(ep => {
1111
+ const [method, path] = ep.split(' ');
1112
+ const detail = ENDPOINT_DETAILS[ep];
1113
+ return { method, path, description: detail?.description ?? '' };
1114
+ });
1115
+ }
1116
+ const totalCount = Object.values(groups).reduce((n, arr) => n + arr.length, 0);
1117
+ return { total: totalCount, groups };
1118
+ }
1119
+ function toolGetEndpoint(params) {
1120
+ const query = (params.path || '').trim().toUpperCase();
1121
+ // Try exact match first (normalize)
1122
+ for (const [key, ep] of Object.entries(ENDPOINT_DETAILS)) {
1123
+ if (key.toUpperCase() === query || key.toUpperCase().endsWith(query) ||
1124
+ query.includes(ep.path.toUpperCase())) {
1125
+ return {
1126
+ method: ep.method,
1127
+ path: ep.path,
1128
+ auth: ep.auth,
1129
+ description: ep.description,
1130
+ ...(ep.params ? { params: ep.params } : {}),
1131
+ ...(ep.body ? { requestBody: ep.body } : {}),
1132
+ ...(ep.response ? { response: ep.response } : {}),
1133
+ };
1134
+ }
1135
+ }
1136
+ // Fuzzy: check if path fragment matches
1137
+ const pathQuery = params.path.replace(/^(GET|POST|PUT|DELETE|PATCH)\s+/i, '').trim();
1138
+ for (const [key, ep] of Object.entries(ENDPOINT_DETAILS)) {
1139
+ if (ep.path.includes(pathQuery) || pathQuery.includes(ep.path)) {
1140
+ return {
1141
+ method: ep.method,
1142
+ path: ep.path,
1143
+ auth: ep.auth,
1144
+ description: ep.description,
1145
+ ...(ep.params ? { params: ep.params } : {}),
1146
+ ...(ep.body ? { requestBody: ep.body } : {}),
1147
+ ...(ep.response ? { response: ep.response } : {}),
1148
+ };
1149
+ }
1150
+ }
1151
+ const available = Object.keys(ENDPOINT_DETAILS).slice(0, 15).join('\n ');
1152
+ return { error: `Endpoint "${params.path}" not found. Some available endpoints:\n ${available}\n\nUse list_endpoints() to browse all.` };
1153
+ }
1154
+ function toolGetExample(params) {
1155
+ const key = params.feature?.toLowerCase().replace(/ /g, '-');
1156
+ const lang = params.language?.toLowerCase();
1157
+ const featureExamples = EXAMPLES[key];
1158
+ if (!featureExamples) {
1159
+ const available = Object.keys(EXAMPLES).join(', ');
1160
+ return { error: `No examples for "${params.feature}". Available: ${available}` };
1161
+ }
1162
+ if (!['typescript', 'python', 'curl'].includes(lang)) {
1163
+ return { error: 'language must be "typescript", "python", or "curl"' };
1164
+ }
1165
+ const code = featureExamples[lang];
1166
+ if (!code) {
1167
+ return { error: `No ${lang} example for "${key}"` };
1168
+ }
1169
+ const feature = FEATURES[key];
1170
+ return {
1171
+ feature: key,
1172
+ language: lang,
1173
+ ...(lang === 'typescript' ? { install: 'npm install @dupecom/botcha' } : {}),
1174
+ ...(lang === 'python' ? { install: 'pip install botcha' } : {}),
1175
+ code,
1176
+ docs: feature ? `https://botcha.ai/docs#${key}` : undefined,
1177
+ };
1178
+ }
1179
+ // ============ JSON-RPC DISPATCHER ============
1180
+ function jsonRpcError(id, code, message) {
1181
+ return { jsonrpc: '2.0', id, error: { code, message } };
1182
+ }
1183
+ function jsonRpcResult(id, result) {
1184
+ return { jsonrpc: '2.0', id, result };
1185
+ }
1186
+ function handleMethod(req, version) {
1187
+ const { id, method, params = {} } = req;
1188
+ switch (method) {
1189
+ case 'initialize':
1190
+ return jsonRpcResult(id, {
1191
+ protocolVersion: '2025-03-26',
1192
+ capabilities: { tools: {} },
1193
+ serverInfo: {
1194
+ name: 'BOTCHA Documentation',
1195
+ version,
1196
+ description: 'Ask questions about BOTCHA features, API endpoints, and integrations. BOTCHA is the identity layer for AI agents.',
1197
+ },
1198
+ instructions: 'Use list_features() to discover what BOTCHA supports, get_feature("<name>") for detailed docs, get_example("<name>", "<lang>") for code, search_docs("<query>") to find relevant sections, and list_endpoints() or get_endpoint("<path>") for API reference.',
1199
+ });
1200
+ case 'notifications/initialized':
1201
+ return { jsonrpc: '2.0', id: null, result: {} };
1202
+ case 'tools/list':
1203
+ return jsonRpcResult(id, { tools: MCP_TOOLS });
1204
+ case 'tools/call': {
1205
+ const name = params.name;
1206
+ const toolParams = (params.arguments ?? {});
1207
+ switch (name) {
1208
+ case 'list_features': return jsonRpcResult(id, { content: [{ type: 'text', text: JSON.stringify(toolListFeatures(toolParams), null, 2) }] });
1209
+ case 'get_feature': return jsonRpcResult(id, { content: [{ type: 'text', text: JSON.stringify(toolGetFeature(toolParams), null, 2) }] });
1210
+ case 'search_docs': return jsonRpcResult(id, { content: [{ type: 'text', text: JSON.stringify(toolSearchDocs(toolParams), null, 2) }] });
1211
+ case 'list_endpoints': return jsonRpcResult(id, { content: [{ type: 'text', text: JSON.stringify(toolListEndpoints(toolParams), null, 2) }] });
1212
+ case 'get_endpoint': return jsonRpcResult(id, { content: [{ type: 'text', text: JSON.stringify(toolGetEndpoint(toolParams), null, 2) }] });
1213
+ case 'get_example': return jsonRpcResult(id, { content: [{ type: 'text', text: JSON.stringify(toolGetExample(toolParams), null, 2) }] });
1214
+ default:
1215
+ return jsonRpcError(id, -32601, `Unknown tool: ${name}`);
1216
+ }
1217
+ }
1218
+ case 'ping':
1219
+ return jsonRpcResult(id, {});
1220
+ default:
1221
+ return jsonRpcError(id, -32601, `Method not found: ${method}`);
1222
+ }
1223
+ }
1224
+ // ============ HTTP HANDLER ============
1225
+ export async function handleMCPRequest(request, version) {
1226
+ const corsHeaders = {
1227
+ 'Access-Control-Allow-Origin': '*',
1228
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
1229
+ 'Access-Control-Allow-Headers': 'Content-Type, Mcp-Session-Id',
1230
+ };
1231
+ if (request.method === 'OPTIONS') {
1232
+ return new Response(null, { status: 204, headers: corsHeaders });
1233
+ }
1234
+ if (request.method === 'GET') {
1235
+ // Streamable HTTP: GET returns server info / healthcheck
1236
+ return new Response(JSON.stringify({
1237
+ name: 'BOTCHA Documentation',
1238
+ version,
1239
+ protocol: 'MCP 2025-03-26 Streamable HTTP',
1240
+ endpoint: 'POST /mcp',
1241
+ tools: MCP_TOOLS.map(t => ({ name: t.name, description: t.description })),
1242
+ }, null, 2), {
1243
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
1244
+ });
1245
+ }
1246
+ if (request.method !== 'POST') {
1247
+ return new Response('Method Not Allowed', { status: 405, headers: corsHeaders });
1248
+ }
1249
+ let body;
1250
+ try {
1251
+ body = await request.json();
1252
+ }
1253
+ catch {
1254
+ return new Response(JSON.stringify(jsonRpcError(null, -32700, 'Parse error — invalid JSON')), { status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } });
1255
+ }
1256
+ // Support both single request and batch array
1257
+ const isBatch = Array.isArray(body);
1258
+ const requests = isBatch ? body : [body];
1259
+ const responses = requests
1260
+ .map(req => {
1261
+ if (!req || req.jsonrpc !== '2.0' || !req.method) {
1262
+ return jsonRpcError(req?.id ?? null, -32600, 'Invalid JSON-RPC 2.0 request');
1263
+ }
1264
+ return handleMethod(req, version);
1265
+ })
1266
+ // Notifications (id === undefined/null in request with no expected response) are filtered out
1267
+ .filter(r => r.id !== null || r.error);
1268
+ const responseBody = isBatch ? responses : responses[0];
1269
+ return new Response(JSON.stringify(responseBody), {
1270
+ headers: { ...corsHeaders, 'Content-Type': 'application/json' },
1271
+ });
1272
+ }
1273
+ // ============ DISCOVERY HANDLER ============
1274
+ export function handleMCPDiscovery(version) {
1275
+ return new Response(JSON.stringify({
1276
+ mcpVersion: '2025-03-26',
1277
+ name: 'BOTCHA Documentation',
1278
+ description: 'Ask questions about BOTCHA features, API endpoints, and integrations for AI agents.',
1279
+ version,
1280
+ endpoint: 'https://botcha.ai/mcp',
1281
+ transport: 'streamable-http',
1282
+ tools: MCP_TOOLS.map(t => ({ name: t.name, description: t.description })),
1283
+ contact: 'https://botcha.ai',
1284
+ }, null, 2), {
1285
+ headers: {
1286
+ 'Content-Type': 'application/json',
1287
+ 'Access-Control-Allow-Origin': '*',
1288
+ },
1289
+ });
1290
+ }