feat(mcp): single-agent default at session init#98
Merged
Conversation
Test pass against hosted mcp.e2a.dev surfaced friction: every
message-scoped tool (list_messages, get_message, send_email, …)
errored "agentEmail is required" when called without an explicit
agent_email, even though the user had exactly one agent and had
just authorized that agent at OAuth consent. Only `whoami` worked,
because it carries its own inline listAgents() resolution.
The fix hoists whoami's resolution to session-init time. On
isInitializeRequest, if the constructed E2AClient has no agentEmail
(env var unset — the dominant hosted-OAuth case), call listAgents()
once; if the user has exactly one agent, rebuild the client with
that email pinned. Every subsequent tool call in the session sees
a populated client.agentEmail and the SDK's per-call requireEmail()
returns it instead of throwing.
Resolution order (highest precedence first):
1. E2A_AGENT_EMAIL env var (constructor reads it)
2. listAgents() yields exactly one → use it
3. Otherwise leave empty — multi-agent users keep today's UX
(pass explicitly or rely on whoami's resolution)
Failure handling: listAgents() errors are swallowed, session init
proceeds with empty agentEmail. A transient backend hiccup
shouldn't break MCP initialize; worst case the user sees the same
error they'd see today.
clientFactory signature extended with an optional second arg
({ agentEmail }) so tests can observe the resolved email round-
tripping back into the second client construction. Existing tests
that pass a 0-arg factory keep working — the helper only passes
the second arg when an email actually got resolved.
This is the cheapest viable fix: zero backend change, zero schema
change, zero SDK change. A future slice can read session.AgentEmail
from the OAuth grant directly (already persisted in
oauth_access_tokens.request JSONB) to disambiguate multi-agent
accounts — but we have none today.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jiashuoz
added a commit
that referenced
this pull request
May 22, 2026
…ts (#99) * docs(examples): add hosted-MCP variant to ADK, LangChain, OpenAI Agents Each framework example now ships two scripts that exercise the same e2a tool surface: agent.py — stdio: npx -y @e2a/mcp-server, local Node child agent_hosted.py — Streamable HTTP: https://mcp.e2a.dev/mcp + Bearer The hosted variant is the right choice when deploying to serverless runtimes (Cloud Run, Lambda, Vercel) where spawning a stdio child process per request is awkward or impossible — Cloud Run in particular doesn't host stdio MCP servers at all, so an ADK agent deployed there literally can't use agent.py. Verified against the live hosted endpoint: - MCP initialize handshake: 200, session id assigned - tools/list: returns all 18 tools, matches the registry - tools/call list_messages with explicit agent_email: succeeds - tools/call list_messages without agent_email on a multi-agent account: correctly returns "agentEmail is required" (the PR #98 single-agent prefetch is structurally a no-op when listAgents returns >1; a future grant-binding slice would address that) API shapes verified against actual source, not docs: - ADK Python: StreamableHTTPConnectionParams(url, headers, timeout) (google/adk-python src/google/adk/tools/mcp_tool/mcp_session_manager.py) - LangChain: transport="streamable_http" + url + headers (langchain-ai/langchain-mcp-adapters sessions.py) - OpenAI Agents: MCPServerStreamableHttp(params={url, headers}) (openai/openai-agents-python src/agents/mcp/server.py) Also corrects stale tool counts ("all 11 e2a tools" → drop the count; we ship 18) across each per-framework README. Top-level mcp/examples/README.md gets a transport matrix and a stdio-vs-hosted explainer. * fix(examples/adk): import McpToolset from deep path; pin google-adk>=2.0 + mcp google-adk 2.0 stopped re-exporting McpToolset from google.adk.tools.mcp_tool's package __init__ — only the deep path google.adk.tools.mcp_tool.mcp_toolset.McpToolset still works. Bumped requirement to google-adk>=2.0 (was >=1.0) so we don't claim support for a version where the example doesn't import, and added mcp>=1.0 as an explicit dep: google-adk 2.0 declared mcp as optional, but the toolset module imports it unconditionally (`from mcp import SamplingCapability` at mcp_toolset.py:33), so without it both agent.py and agent_hosted.py ImportError before they run. Caught during the build+run smoke for the hosted-variant rollout: neither agent.py nor agent_hosted.py imported on a fresh google-adk 2.0 install. The same fix applies to both since they share the broken import line. With this commit: $ python -c "import agent_hosted; print(agent_hosted.root_agent)" → imports clean $ python -c "...await McpToolset.get_tools(...)..." against the hosted endpoint → returns all 18 e2a tools LangChain and OpenAI Agents examples were unaffected — those import their MCP classes from already-deep paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(examples): apply review findings — version floor, timeouts, README Three findings from the review pass on PR #99: 1. (Low) openai-agents floor was wildly stale: >=0.0.13. The hosted variant uses MCPServerStreamableHttp which first shipped in v0.0.15 (feat: Streamable HTTP support #643, 2025-05-14). A user who pinned the minimum would ImportError before any of our code runs — same failure class as the McpToolset/google-adk 2.0 regression already in this branch. Bumped to >=0.0.15 (verified against the v0.0.15 src/agents/mcp/__init__.py snapshot, which exports both MCPServerStreamableHttp and MCPServerStreamableHttpParams). 2. (Nit) Inconsistent timeouts across hosted variants: ADK timeout=30 LangChain (adapter default, ~undocumented) OpenAI Agents 5s default (tight for cold serverless backends) Aligned all three at 30s: LangChain → "timeout": timedelta(seconds=30) OpenAI → client_session_timeout_seconds=30 Both fields verified against the actual library source (already in this branch's PR description). ADK already at 30s. 3. (Nit) "wait for a future grant-binding feature" appeared 3x across per-example READMEs. Forward-promising language for an unscheduled slice. Tightened to just "pass agent_email per tool call" — drops the implicit promise without dropping useful information. Re-smoked all three hosted variants after the changes: each returns 18 tools against mcp.e2a.dev/mcp. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3 tasks
jiashuoz
added a commit
that referenced
this pull request
May 22, 2026
Prep for publishing to the official MCP Registry (registry.modelcontextprotocol.io). Two artifacts: 1. mcp/package.json: add `mcpName: "io.github.mnexa-ai/mcp-server"`. This is the verification handshake the Registry's publish step uses — it confirms the npm package belongs to the publisher claiming the registry name. The convention `io.github.OWNER/NAME` is required for GitHub-based auth at publish time. Lowercased per the Registry's namespacing rules (our GitHub org is "Mnexa-AI"; the registry name is "io.github.mnexa-ai/..."). Also bumps version 0.3.0 → 0.3.1. Two fixes merged since the 0.3.0 tag — single-agent prefetch (#98) and hosted-MCP example variants (#99) — warrant a publish. 2. mcp/server.json: the Registry's canonical server descriptor. Lists both transports in one entry: - packages[].npm: stdio via @e2a/mcp-server 0.3.1 with the three env vars (E2A_API_KEY required + secret, optional E2A_AGENT_EMAIL, optional E2A_BASE_URL). - remotes[]: streamable-http at https://mcp.e2a.dev/mcp with an `Authorization: Bearer {e2a_api_key}` header that resolves a registry variable. Validated against schema 2025-12-11 — fits maxLength=100 on description, all required fields present. Publish flow (NOT in this PR — requires a maintainer to run): 1. Merge this PR to main. 2. Tag mcp-v0.3.1 and push. publish-mcp.yml fires and publishes @e2a/mcp-server 0.3.1 to npm with the mcpName field baked in. 3. brew install mcp-publisher 4. mcp-publisher login github (interactive) 5. cd mcp && mcp-publisher publish Step 5 reads server.json from the cwd, verifies that the npm package at @e2a/mcp-server@0.3.1 has the matching mcpName field, then writes the listing to registry.modelcontextprotocol.io. After the listing lands, other catalogs (mcp.directory, Glama, smithery) that cross-reference the Registry will auto-pick us up on their next sync. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jiashuoz
added a commit
that referenced
this pull request
May 22, 2026
Prep for publishing to the official MCP Registry (registry.modelcontextprotocol.io). Two artifacts: 1. mcp/package.json: add `mcpName: "io.github.mnexa-ai/mcp-server"`. This is the verification handshake the Registry's publish step uses — it confirms the npm package belongs to the publisher claiming the registry name. The convention `io.github.OWNER/NAME` is required for GitHub-based auth at publish time. Lowercased per the Registry's namespacing rules (our GitHub org is "Mnexa-AI"; the registry name is "io.github.mnexa-ai/..."). Also bumps version 0.3.0 → 0.3.1. Two fixes merged since the 0.3.0 tag — single-agent prefetch (#98) and hosted-MCP example variants (#99) — warrant a publish. 2. mcp/server.json: the Registry's canonical server descriptor. Lists both transports in one entry: - packages[].npm: stdio via @e2a/mcp-server 0.3.1 with the three env vars (E2A_API_KEY required + secret, optional E2A_AGENT_EMAIL, optional E2A_BASE_URL). - remotes[]: streamable-http at https://mcp.e2a.dev/mcp with an `Authorization: Bearer {e2a_api_key}` header that resolves a registry variable. Validated against schema 2025-12-11 — fits maxLength=100 on description, all required fields present. Publish flow (NOT in this PR — requires a maintainer to run): 1. Merge this PR to main. 2. Tag mcp-v0.3.1 and push. publish-mcp.yml fires and publishes @e2a/mcp-server 0.3.1 to npm with the mcpName field baked in. 3. brew install mcp-publisher 4. mcp-publisher login github (interactive) 5. cd mcp && mcp-publisher publish Step 5 reads server.json from the cwd, verifies that the npm package at @e2a/mcp-server@0.3.1 has the matching mcpName field, then writes the listing to registry.modelcontextprotocol.io. After the listing lands, other catalogs (mcp.directory, Glama, smithery) that cross-reference the Registry will auto-pick us up on their next sync. Co-authored-by: Claude Opus 4.7 (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
End-to-end test against the freshly-deployed hosted MCP surfaced friction: every message-scoped tool (
list_messages,get_message,send_email,reply_to_message, …) errored"agentEmail is required"when called without an explicitagent_email, even though the user had exactly one agent and had just authorized that agent at OAuth consent. Onlywhoamiworked, because it carries its own inlinelistAgents()resolution.This hoists
whoami's resolution to session-init time. OnisInitializeRequest, if the constructedE2AClienthas noagentEmail(env var unset — the dominant hosted-OAuth case), the MCP server callslistAgents()once; if the user has exactly one agent, the client is rebuilt with that email pinned. Every subsequent tool call in the session sees a populatedclient.agentEmailand the SDK's per-callrequireEmail()returns it instead of throwing.Resolution order (highest precedence first)
E2A_AGENT_EMAILenv var — constructor populatesagentEmail; operator opted in, don't second-guesslistAgents()yields exactly one agent → use itwhoami's resolution)Scope of change
oauth_access_tokens.requestJSONB viaoauth.Session, but exposing it requires a new endpoint. Deferred.E2AClient.agentEmailstaysreadonly; we just rebuild the instance with the resolved emailhttp-server.ts, 132 lines of tests inhttp.test.tsWhy not the OAuth-grant binding approach?
It's the right shape for multi-agent users (read
session.AgentEmailfrom the bound grant — already persisted, just no API to expose it). But we have zero multi-agent OAuth users today, and that path needs a backend endpoint, auth-middleware refactor to thread session-on-context, and an MCP-side preflight call. YAGNI for the actual reported problem. Filed as a follow-up if a multi-agent OAuth user ever surfaces friction.Failure handling
listAgents()errors are swallowed; session init proceeds with emptyagentEmail. A transient backend hiccup shouldn't break MCP initialize — worst case the user sees the same"agentEmail is required"error they'd see today.Test plan
npx tsc --noEmitcleanmcp-v0.3.1) + prod deploy: end-to-end smoke against hostedmcp.e2a.dev—list_messagessucceeds without explicitagent_email🤖 Generated with Claude Code