@dupecom/botcha-cloudflare 0.21.0 → 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.
- package/README.md +74 -9
- package/dist/agent-auth.d.ts +129 -0
- package/dist/agent-auth.d.ts.map +1 -0
- package/dist/agent-auth.js +210 -0
- package/dist/agents.d.ts +10 -0
- package/dist/agents.d.ts.map +1 -1
- package/dist/agents.js +51 -1
- package/dist/app-gate.d.ts +6 -0
- package/dist/app-gate.d.ts.map +1 -0
- package/dist/app-gate.js +69 -0
- package/dist/apps.d.ts +9 -0
- package/dist/apps.d.ts.map +1 -1
- package/dist/apps.js +26 -0
- package/dist/dashboard/account.d.ts +63 -0
- package/dist/dashboard/account.d.ts.map +1 -0
- package/dist/dashboard/account.js +488 -0
- package/dist/dashboard/api.js +15 -68
- package/dist/dashboard/auth.d.ts.map +1 -1
- package/dist/dashboard/auth.js +14 -14
- package/dist/dashboard/docs.d.ts.map +1 -1
- package/dist/dashboard/docs.js +146 -3
- package/dist/dashboard/layout.d.ts.map +1 -1
- package/dist/dashboard/layout.js +2 -2
- package/dist/dashboard/mcp-setup.d.ts +15 -0
- package/dist/dashboard/mcp-setup.d.ts.map +1 -0
- package/dist/dashboard/mcp-setup.js +391 -0
- package/dist/dashboard/showcase.d.ts +6 -10
- package/dist/dashboard/showcase.d.ts.map +1 -1
- package/dist/dashboard/showcase.js +67 -991
- package/dist/dashboard/whitepaper.d.ts.map +1 -1
- package/dist/dashboard/whitepaper.js +42 -4
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +452 -52
- package/dist/mcp.d.ts +20 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +1290 -0
- package/dist/oauth-agent.d.ts +130 -0
- package/dist/oauth-agent.d.ts.map +1 -0
- package/dist/oauth-agent.js +194 -0
- package/dist/static.d.ts +732 -1
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +646 -2
- package/dist/tap-a2a-routes.d.ts +355 -0
- package/dist/tap-a2a-routes.d.ts.map +1 -0
- package/dist/tap-a2a-routes.js +475 -0
- package/dist/tap-a2a.d.ts +199 -0
- package/dist/tap-a2a.d.ts.map +1 -0
- package/dist/tap-a2a.js +502 -0
- package/dist/tap-agents.d.ts +15 -0
- package/dist/tap-agents.d.ts.map +1 -1
- package/dist/tap-agents.js +31 -1
- package/dist/tap-ans-routes.d.ts +302 -0
- package/dist/tap-ans-routes.d.ts.map +1 -0
- package/dist/tap-ans-routes.js +535 -0
- package/dist/tap-ans.d.ts +241 -0
- package/dist/tap-ans.d.ts.map +1 -0
- package/dist/tap-ans.js +481 -0
- package/dist/tap-delegation-routes.d.ts.map +1 -1
- package/dist/tap-delegation-routes.js +11 -0
- package/dist/tap-did.d.ts +140 -0
- package/dist/tap-did.d.ts.map +1 -0
- package/dist/tap-did.js +262 -0
- package/dist/tap-oidca-routes.d.ts +383 -0
- package/dist/tap-oidca-routes.d.ts.map +1 -0
- package/dist/tap-oidca-routes.js +597 -0
- package/dist/tap-oidca.d.ts +288 -0
- package/dist/tap-oidca.d.ts.map +1 -0
- package/dist/tap-oidca.js +461 -0
- package/dist/tap-routes.d.ts +24 -8
- package/dist/tap-routes.d.ts.map +1 -1
- package/dist/tap-routes.js +169 -23
- package/dist/tap-vc-routes.d.ts +358 -0
- package/dist/tap-vc-routes.d.ts.map +1 -0
- package/dist/tap-vc-routes.js +367 -0
- package/dist/tap-vc.d.ts +125 -0
- package/dist/tap-vc.d.ts.map +1 -0
- package/dist/tap-vc.js +245 -0
- package/dist/tap-x402-routes.d.ts +89 -0
- package/dist/tap-x402-routes.d.ts.map +1 -0
- package/dist/tap-x402-routes.js +579 -0
- package/dist/tap-x402.d.ts +222 -0
- package/dist/tap-x402.d.ts.map +1 -0
- package/dist/tap-x402.js +546 -0
- package/dist/webhooks.d.ts +99 -0
- package/dist/webhooks.d.ts.map +1 -0
- package/dist/webhooks.js +642 -0
- 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
|
+
}
|