GA /v1 readiness: settle API contract, regenerate SDKs, slim CLI, re-curate MCP (hosted-only)#247
Merged
Conversation
Pre-GA API review decisions (docs/design/ga-api-review.md), Phase-1 contract changes that don't need a store change or the path rename: account/info - I-1 GET /v1/info returns `version` (single-sourced APIVersion const) - A-1/A-2 GET /v1/account → AccountView with identity (user, scope, agent_address for agent-scoped only); whoami now works for both scopes domains - D-4 registerDomain.domain required; D-1 sending_status enum; D-5 deleteDomain ?confirm=DELETE agents - AG-5 createAgent returns full AgentView; AG-1/2 drop slug, email required, shared-domain detected by email domain; AG-6 deleteAgent ?confirm=DELETE (checked after ownership); AG-7 update-agent input enums messages / outbound / hitl - MSG-1 status→read_status (field + filter); MSG-3 to/subject/body required; MSG-9 unified SendResultView (message_id is the e2a id, +provider_message_id /sent_as/method enum/edited; fixes send returning the SES id); MSG-10 RejectRequest; MSG-11 AuthVerdict; MSG-12 auth_headers optional webhooks - WH-1 url/events required; WH-2 events item-enum; WH-3 signing_secret only in CreateWebhookResponse (removed from WebhookView); WH-6 *Request/*Response naming (RotateSecretResponse, TestWebhookResponse, RedeliverEventRequest) A-3 nil→[] sweep (orEmpty + nullable:"false") across all response views + top-level UserExport; nested export rows kept raw (F7). Slice B event rename: email.approved→email.approval_accepted, email.rejected→email.approval_rejected (consts + 6 enum sites + e2e + docs). Tests, contract scenarios, and e2e updated in lockstep. make spec regenerated; spec-check + ./internal/... (-p 1) green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Slice C — path param {address} → {email}
- All per-agent /v1 routes + the chi WebSocket route renamed (route placeholder
+ path: tag + chi.URLParam). Go field name Address kept (internal).
- Suppression recipient /v1/account/suppressions/{address} stays {address}
(it's a recipient address, not the agent identifier).
- spec_test path expectations updated; test URLs use real emails so unaffected.
Slice D — real cursor pagination (+ C-1)
- suppressions (A-5): keyset on (created_at, address). Store ListSuppressions
gains limit + after-key; dep sig + handler updated (fetch limit+1, encode
next_cursor). Auto-grows on every bounce/complaint, so it needed real paging.
- conversations (CV-3): keyset on (last_message_at, conversation_id) via
ConversationListFilter.After* + a HAVING keyset predicate; handler fetches
limit+1 and encodes a filter-identity cursor.
- C-1: ConversationSummaryView timestamps string → time.Time + format date-time
(consistent with every other timestamp; fixes untyped-string SDK gen).
Verified: go build, make spec-check, ./internal/httpapi (-p 1), integration
compile, real-Postgres identity+delivery store tests, and new handler cursor
round-trip tests (TestListSuppressionsPagination / ...BadCursor).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ption) Audit of the implementation against docs/design/ga-api-review.md (48/49 spec assertions passed on first check): - WH-7: bump listWebhookDeliveries limit to default 100 / max 500 (the doc said "single page + large default limit"; the impl still had default 50 / max 100). - Doc reconcile: the lone remaining `Result` schema is `Message.auth` on the raw identity.Message export row (UserExport.messages). identity can't import the httpapi AuthVerdict view (import cycle), so MSG-11 applies to the public message-read views (done) and the export row stays raw — same class as the A-3 nested-export exception. Named explicitly in the F7 exception note. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nomic layer Slice E (SDK regen). Regenerated both generated/ bases from the updated api/openapi.yaml (OpenAPI Generator v7.16.0, deterministic) and re-homed the hand-written ergonomic layer onto the renamed/reshaped types: - Type renames: LimitsView→AccountView, ApproveResultView→SendResultView (approve now returns the unified result), CreateAgentResponse→AgentView, RejectInputBody→RejectRequest, RotateSecretBody→RotateSecretResponse, TestWebhookOutputBody→TestWebhookResponse, RedeliverEventInputBody→ RedeliverEventRequest; webhooks.create→CreateWebhookResponse. - read_status: list filter + ListMessagesParams (TS) / list() kwarg (Python) renamed status→read_status to match the API. - Real pagination: conversations.list + suppressions.list now follow next_cursor (CV-3/A-5) instead of single-page. - Deletes pass ?confirm=DELETE (the typed .delete() is the confirmation; AG-6/D-5); account.delete() drops its confirm arg. - Python enum validators stripped on regen (16 models) — forward-compat preserved. TS 84 tests + Python 131 tests green. generate-sdk-check is clean once committed (deterministic regen). MCP + CLI do NOT yet compile against the reshaped SDK — that's the separately-tracked consumer-port / §6a MCP tool re-curation round. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…es email Hand-written ergonomic layer (both SDKs), after reviewing the generated layers (found strong: TS camelCase/Date/ApiException<ErrorEnvelope>; Python snake_case/var_from/datetime/plain-str enums): - HL-1: rename the agent identifier param address→email across every per-agent method (agents/messages/conversations + listen) in both SDKs — consistent with AG-3 and the generated layer (getAgent(email)). - HL-2: drop the E2A_AGENT_EMAIL listen() env fallback; listen(email) now requires an explicit email (design §9a removed the default-agent env). Tests updated (TS client/ws, Python). Accepted as-is: Python async-only (HL-3); generated TS string enums (TS-1, forward-compat safe via ObjectSerializer passthrough). Deferred: inbound .parse()/.reply() + typed event payloads (F5/S2). TS 84 tests + Python 131 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1, WS-1/2)
Class-by-class review of the hand-written SDK layers surfaced three real gaps,
fixed in both SDKs (or TS-only where Python already had the fix):
- ERR-1 (both): the typed-error status bucket lacked 400, so 11 of the API's 16
distinct 400 codes (confirmation_required, too_many_recipients, invalid_domain,
invalid_slug, reserved_domain, webhook_cap_reached, …) degraded to the bare
E2AError. Map 400 → E2AValidationError.
- WH-SIG-1 (both): verify_webhook_signature threw a raw TypeError/AttributeError
on a missing/non-string X-E2A-Signature header instead of returning false
("never throws" contract). Guard it.
- WS-1 + WS-2 (TS; Python already had both): TS WebSocket injected the raw API
key into ?token= (now encodeURIComponent) and reconnected forever on a fatal
4xx handshake. TS now detects the 4xx (unexpected-response), surfaces a typed
E2AAuthError/E2APermissionError, and stops reconnecting — F6 parity with
Python's _fatal_error_for_status.
Reviewed clean (no change): pagination (AutoPager dual loop-guards), retry
(unsafe-write gating incl. account-delete exclusion). TS 84 + Python 131 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cate CRUD) The CLI isn't a core surface for an agent-first gateway: agents use MCP, programmatic callers use the SDK, operators use the dashboard. The ~12 CRUD/messaging commands duplicated those and broke on every contract change. Confirmed the WebSocket is noMcp (MCP agents poll list_messages / use webhooks; the real-time WS path for local agents is the SDK's client.listen()), so the CLI's only non-duplicative capability is the local dev forward-proxy. Kept: login, listen (with --forward, the `stripe listen --forward-to` pattern), config. Dropped: agents, conversations, domains, events, forward, inbox, labels, pending, read, reply, send, webhooks (+ their tests). Rewrote the dispatcher + README. ~4316 → ~1804 LOC; builds clean, 54 tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e reshaped /v1 web/public/openapi.yaml is a build-time copy of api/openapi.yaml (web's sync-openapi script, run on prebuild); refresh the committed copy so the dashboard API-reference page reflects the GA /v1 contract. Pure copy — byte-identical to api/openapi.yaml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MCP server (hosted Streamable-HTTP at /mcp + stdio self-host) now matches
the GA /v1 contract. Wrapper (client.ts) + tools + tests updated; SDK + CLI
unaffected (all green).
Tool surface:
- Renames: send_email→send_message, get_attachment_data→get_attachment,
approve_pending_message→approve_message, reject_pending_message→reject_message.
- Added: get_domain (poll sending_status).
- Removed: list_webhook_deliveries (folded into the events log).
- create_agent: drop slug/agent_mode/webhook_url → { email, name? }, returns AgentView.
- update_agent: drop agent_mode/webhook_url; add hitl_mode/inbound_policy/
inbound_allowlist.
- whoami: returns the account identity (AccountView: user/scope/agent_address),
no more default-agent auto-resolution.
- Every per-agent tool arg agent_email→email; list_messages status→read_status;
get_message response status→read_status; webhook event names →
email.approval_accepted / email.approval_rejected.
Deviation from §6a (noted): kept list_pending_messages + get_pending_message —
the API has no pending filter on list_messages (read_status is unread/read/all),
so these client-side hitl_status scans are the only way to view the approval
queue. Renamed approve/reject to match the API ops.
E2A_AGENT_EMAIL removed everywhere (config + wrapper + tool docs): an
agent-scoped credential resolves its agent server-side; account callers pass
`email`. Also fixed SDK ListMessagesParams.read_status→readStatus (camelCase
consistency; Python stays read_status).
MCP build clean; 117 MCP tests green. SDK 84, CLI 54, Python 131 — all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…2a.dev/mcp Most users connect via the hosted MCP, so drop the stdio transport entirely and make the hosted Streamable-HTTP server the single surface. Remove stdio: - Delete mcp/src/index.ts (stdio entry) + mcp/tests/events-stdio.test.ts + .github/workflows/publish-mcp.yml (the stdio npm publish). The hosted server runs via Docker (publish-mcp-http.yml → node dist/bin/http.js). - package.json: drop the `e2a-mcp` bin; main → ./dist/bin/http.js. - server.json: drop the npm/stdio `packages` block (+ the removed E2A_AGENT_EMAIL env); keep the streamable-http `remotes` entry (OAuth-first, Bearer API key also accepted). - Examples: remove every stdio variant; the hosted agent.py is now the single example per framework (adk/crewai/langchain/openai-agents); codex config keeps only the hosted block. Full doc sweep of mcp/README + examples (no npx/stdio/ E2A_AGENT_EMAIL left). Hosted endpoint: migrate mcp.e2a.dev → api.e2a.dev/mcp (§6a: api.<host>/mcp, path-routed) across src (allowedHosts default), server.json, plugin.json, tests, examples. (Deploy note: the api.e2a.dev ingress must path-route /mcp to the MCP process.) Design doc left as-is (it intentionally contrasts both hosts). MCP build clean; 114 tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dpoint Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bhook_deliveries
Independent multi-agent review of the MCP tools vs the SDK + API surfaced a
BLOCKER, two MAJORs, and stale comments. Fixes + two new tools (35 total).
New tools:
- get_agent (GET /v1/agents/{email} → AgentView): fetch one agent's full config
(HITL/inbound/sending) without listing all agents — fills a real read gap.
- list_webhook_deliveries (GET /v1/webhooks/{id}/deliveries): RESTORED. The §6a
"folds into the events log" rationale was wrong — the events log is the event
stream, not the per-webhook delivery ledger (status/attempts/last_error). This
also fixes the BLOCKER: test_webhook's description pointed at a non-existent
`list_events {webhook_id}` filter.
Fixes:
- create_webhook + list_events: enumerate all 12 valid event types (were
documenting only 5; agents couldn't discover email.delivered/bounced/
complained/flagged, domain.sending_verified/sending_failed/suppression_added).
- Python SDK messages.list: public kwarg var_from→from_ (PEP-8); translate to the
generated base's var_from internally so the generator's mangled name never
leaks into the public surface. (TS stays `from`; idiomatic per language.)
- Stale code comments: attachments.ts (send_email/approve_pending_message →
send_message/approve_message), http-server.ts resolution-order (dropped the
removed E2A_AGENT_EMAIL step 1).
MCP build clean; 116 tests green (+behavior tests for both new tools). SDK/CLI/
Python all green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…auth from 422)
The create_agent/send auth-rejection steps sent body:{} and expected 401.
With email now required on create_agent (and to/subject/body on send), an
empty body trips Huma schema validation (422) before the per-handler
requirePrincipal auth check (401) runs — so the step saw 422, not 401.
Hold the request otherwise-valid so the auth-rejection steps test auth, not
body validation (standard practice: bad-auth tests send a well-formed request).
Pre-existing 422-before-401 ordering for body endpoints is unchanged; this only
fixes the now-stale scenario bodies.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This was referenced Jun 20, 2026
jiashuoz
added a commit
that referenced
this pull request
Jun 20, 2026
§6a was written as a target; the GA build (PR #247) ratified several deviations. Add an authoritative as-built banner at the top of §6a and fix the two inline claims that were factually false as standalone statements: - arg/path is email/{email}, not address/{address} (AG-3) - E2A_AGENT_EMAIL / E2A_AGENT_ADDRESS removed (no default-agent env) - stdio transport removed — hosted-only at api.e2a.dev/mcp (OAuth + Bearer key) - 35 tools, not 31: kept list_pending_messages/get_pending_message (no pending_approval filter) + list_webhook_deliveries (events log != delivery ledger, so update #6 is rejected); added get_agent - approve/reject are two routes; get_attachment re-fetches the full message - recommended updates: #8 done, #6 rejected; #1/#2/#4/#5/#7 pending, #3 partial - MCP_ALLOWED_HOSTS/MCP_PUBLIC_URL retargeted to api.e2a.dev (done) Doc-only; the drift-gate tool→operationId map remains authoritative for the live surface. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jiashuoz
added a commit
that referenced
this pull request
Jun 20, 2026
…curacy Independent + adversarial review (both READ-ONLY) of #253: - Adversarial: SAFE — proved (in-process + real HTTP) a hidden admin tool can't be invoked (wrapper never runs), fail-closed scope across 18 inputs, no client-controllable scope, session bearer-binding holds. No security findings. - Independent: pass-with-risks. Both converged on the tiers.ts comment referencing a non-existent `assertToolTiersComplete`. Fixes (regression test per finding): - Implement the real guard `assertToolTiersComplete(registered)` in tiers.ts and add a test that collects the TRUE (un-gated) registered tool names via a name-recording fake server and asserts the tier map covers them exactly. This closes a real gap both reviews under-analyzed: an untiered new tool is gated out of EVERY scope and would otherwise vanish silently (the exact-35 list test can't catch it — gating hides it before listing). Fixed the misleading comment. - Add a fail-closed regression test: toolNamesForScope(unrecognized) → runtime. - README: corrected the create_agent row (full email, no slug/mode) and replaced the stale `agent_mode: "cloud"` signature note with the top-level-webhooks reality (the #247-era drift my create_agent edit would otherwise contradict). Nits accepted as-is: registerTool monkey-patch uses an unknown[] cast (loses overload types; functionally correct — args forwarded verbatim). 123 MCP tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jiashuoz
added a commit
that referenced
this pull request
Jun 20, 2026
… HITL approve/reject (#253) * feat(mcp): scope-gate the tool surface by credential tier (§6a #1) A deployed agent no longer carries all 35 tools or holds delete-domain power. The MCP surface is now filtered to the connecting credential's scope: - agent → the 16 runtime/inbox tools - account → runtime + admin (all 35) - tools/tiers.ts (new): the single-source-of-truth tool→tier map (RUNTIME_TOOLS / ADMIN_TOOLS + toolNamesForScope), mirroring §6a's "drift-gate records each tool's tier". - server.ts: buildServer gates registration at one seam (intercepts registerTool, skips tools outside the scope's allowed set). register*Tools stay scope-agnostic. Hidden = unlisted AND uncallable. - client.ts: McpClient gains a `scope` field (default "account"; set per session). - http-server.ts: buildSessionClient resolves scope + the bound agent from whoami (GET /account) instead of the listAgents single-agent guess. agent scope pins whoami.agentAddress; account scope has no default (explicit email per §6a — the single-agent auto-resolve is dropped). whoami non-auth failure fails closed to the runtime tier; 401 → InvalidBearerError. Gating is a decision-space/UX optimization, NOT the security boundary — the backend enforces scope per-handler regardless (CRITICAL-1 fix), so a mis-listed tool is never load-bearing. Tests: account→35, agent→16 (admin hidden), hidden admin tool errors + its wrapper never runs; http session-init rewritten for whoami-based scope/agent resolution (agent pins bound agent; account no default; non-auth fail-closed; 401 rejects). 119 MCP tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(mcp): add tool annotations to every tool (§6a #2) readOnlyHint / destructiveHint / idempotentHint on all 35 tools, so clients can auto-approve reads, flag the destructive deletes, and de-risk retries — and a prerequisite for the Connectors-directory listing. Classification: - readOnlyHint: true — all 17 list_*/get_*/whoami reads. - destructiveHint: true + idempotentHint: true — the 3 CASCADE deletes (delete_agent/domain/webhook). - idempotentHint: true, destructiveHint: false — the 5 idempotent updates (update_agent/webhook, update_message_labels, verify_domain, register_domain). - destructiveHint: false — the 10 non-destructive writes (create/send/reply/ forward/approve/reject, create_webhook, rotate_secret, test_webhook, redeliver_event). Test: lists tools (account scope) and asserts every tool carries annotations with the right hints per category. 120 MCP tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(mcp): reflect scope-gating + annotations (§6a #1/#2 done) - design §6a as-built banner: #1 scope/tier-gating and #2 annotations flipped from PENDING → done (with where: tiers.ts + server.ts seam, whoami signal; note that gating is UX, the backend ceiling per PR #251 is the boundary). - mcp/README: the visible tool set is now scope-dependent (agent ~16 / account 35), every tool carries annotations; fixed the whoami description (account identity, not "agent's full record"). Follow-up flagged (pre-existing #247-era README drift, not this change): the README still references removed `slug`/`agent_mode`/cloud-mode in create_agent. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mcp): address review findings — real tier-drift guard + README accuracy Independent + adversarial review (both READ-ONLY) of #253: - Adversarial: SAFE — proved (in-process + real HTTP) a hidden admin tool can't be invoked (wrapper never runs), fail-closed scope across 18 inputs, no client-controllable scope, session bearer-binding holds. No security findings. - Independent: pass-with-risks. Both converged on the tiers.ts comment referencing a non-existent `assertToolTiersComplete`. Fixes (regression test per finding): - Implement the real guard `assertToolTiersComplete(registered)` in tiers.ts and add a test that collects the TRUE (un-gated) registered tool names via a name-recording fake server and asserts the tier map covers them exactly. This closes a real gap both reviews under-analyzed: an untiered new tool is gated out of EVERY scope and would otherwise vanish silently (the exact-35 list test can't catch it — gating hides it before listing). Fixed the misleading comment. - Add a fail-closed regression test: toolNamesForScope(unrecognized) → runtime. - README: corrected the create_agent row (full email, no slug/mode) and replaced the stale `agent_mode: "cloud"` signature note with the top-level-webhooks reality (the #247-era drift my create_agent edit would otherwise contradict). Nits accepted as-is: registerTool monkey-patch uses an unknown[] cast (loses overload types; functionally correct — args forwarded verbatim). 123 MCP tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(mcp): move approve/reject to admin tier — agents must not self-approve HITL §6a listed approve_message/reject_message in the runtime (agent) tier, but an agent-scoped credential can only ever approve its OWN held outbound — i.e. self-approval, which defeats the human-in-the-loop gate. Approval is an account-owner / human action (or the magic-link browser flow), so these tools are now admin (account-scope) only. - tiers.ts: approve_message/reject_message → ADMIN_TOOLS (runtime 16→14). - tests: agent scope now exposes 14 runtime tools; approve/reject asserted hidden from agent scope (in-process + over real HTTP). - docs: §6a banner correction + mcp/README — approve/reject are account-only; agent scope can send (held) and view its pending queue, not release it. NOTE: this is the MCP-surface half. The load-bearing fix — backend account-scope enforcement on the approve/reject handlers (handleApprove/handleReject currently use resolveOwnedAgent, which permits agent-scope self-approval) — is owned by the HITL + message-screening workstream, not this PR. Until that lands, an e2a_agt_ key could still POST …/approve directly (MCP gating is UX, not the boundary). 123 MCP tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(api): require account scope to approve/reject (close HITL self-approval) The load-bearing half of the approve/reject scope fix (the MCP tier move was the surface half). handleApprove/handleReject now requireAccountScope FIRST — an agent-scoped credential gets 403 forbidden, even on its own bound agent. Previously they used resolveOwnedAgent (which permits agent-scope, pinned to the agent), so the gated agent could approve its OWN held outbound — self-approval that defeats the human-in-the-loop gate. - internal/httpapi/hitl.go: requireAccountScope at the top of both handlers; reuse that principal's user (drops the redundant requireUser). - The human magic-link flow (internal/agent/hitl_magic_api.go, token-gated) is a separate handler and is unaffected — humans still approve via the link. - Test: TestScope_ApproveRejectIsAccountOnly — agent-scoped key POSTing approve/reject on its OWN bound agent gets 403 forbidden (the gate fires before any approve/reject dep). Existing account-scope approve/reject tests still 200 (testServer's legacy Authenticator resolves to account scope). Pairs with the MCP tier move (approve/reject → admin) earlier in this PR: approve/reject are now account-scope end-to-end. Spec unchanged (scope is runtime auth, not in the OpenAPI contract). httpapi + agent suites green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: approve/reject account-scope is now enforced in-PR (not deferred) Sync the two forward-looking comments (tiers.ts + §6a banner) — the backend account-scope enforcement landed in this PR (hitl.go), not a later workstream. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jiashuoz
added a commit
to Amerxu1002/e2accx
that referenced
this pull request
Jul 17, 2026
Two claims in CLAUDE.md were already wrong before this branch, and the org rename made the first one actively harmful. 1. The MCP release section documented `publish-mcp.yml`, which tokencanopy#247 deleted when MCP moved to hosted-only. Renaming the org rewrote that line to point at `tokencanopy/e2a` + `publish-mcp.yml` — a plausible-looking instruction to configure a trusted publisher for a workflow that does not exist and a package deliberately frozen at 0.4.0. Replaced with what actually ships. Confirmed via the signed npm provenance for @e2a/mcp-server@0.4.0, which records `.github/workflows/publish-mcp.yml` @ refs/tags/mcp-v0.4.0. 2. The workspace list omitted `mcp` and `design-system`; root package.json declares all four. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Pre-GA readiness pass over the
/v1API, both SDKs, the CLI, and the MCP server — the goal was to freeze a clean, consistent, forward-compatible public contract. Walked each surface tool-by-tool/endpoint-by-endpoint, applied the agreed fixes, regenerated the SDK bases, and re-curated the MCP tool surface to match.Decisions are logged in
docs/design/ga-api-review.md.API (
/v1)read_status(wasstatus),emailpath/param naming (wasaddress), unifiedSendResultViewfor send+approve (MSG-9;message_idis the e2amsg_id),AccountViewshape + agent-scopedagent_address, real keyset pagination for conversations + suppressions,?confirm=DELETEon destructive ops, enum tightening on agent/webhook fields, struct-tag audit (nullable:"false"for always-present arrays), event renames (email.approval_accepted/approval_rejected).make specregeneratedapi/openapi.yaml;TestSpecGoldenNoDrift+spec-checkgreen.SDKs (TS + Python)
emailparams,listen(email)required).messages.list: public kwargfrom_(PEP-8), translated to the generatedvar_frominternally so the mangled name never leaks.generate-sdk-checkgreen (generated trees byte-identical).CLI
login,listen(with--forward),config. Agent/message/domain/webhook CRUD lives in the MCP tools, SDKs, and dashboard.MCP (hosted-only)
/v1(§6a): renames (send_message,get_attachment,approve_message,reject_message),create_agenttakesemail(dropped slug/agent_mode/webhook_url),update_agentadds hitl_mode/inbound_policy/inbound_allowlist,whoami→AccountView,agent_email→emaileverywhere,read_statusfilter.https://api.e2a.dev/mcp(Streamable-HTTP, OAuth-first; Bearer API key also accepted). Removed the stdio entry/test/publish workflow; examples are hosted-only.E2A_AGENT_EMAILremoved.list_webhook_deliveries(the events log is not the delivery ledger — fixes a danglingtest_webhookreference); enumerated all 12 webhook events; addedget_agent; cleaned stale comments.Verification
spec-check✓,generate-sdk-check✓,make test-unit✓Deploy note
api.e2a.dev/mcprequires theapi.e2a.devingress to path-route/mcpto the MCP process (infra config, not in this repo).Follow-ups (not in this PR)
readOnlyHint/destructiveHint/idempotentHint) — §6a fast-follow.🤖 Generated with Claude Code