diff --git a/mcp/examples/README.md b/mcp/examples/README.md index 7fff545..9ea0be7 100644 --- a/mcp/examples/README.md +++ b/mcp/examples/README.md @@ -5,6 +5,7 @@ End-to-end demos showing how to wire the e2a MCP surface into popular agent fram | Framework | Path | LLM | Stdio variant | Hosted variant | | --- | --- | --- | --- | --- | | LangChain (LangGraph ReAct) | [langchain/](./langchain/) | Anthropic Claude | `agent.py` | `agent_hosted.py` | +| CrewAI | [crewai/](./crewai/) | Anthropic Claude | `agent.py` | `agent_hosted.py` | | Google ADK | [adk/](./adk/) | Google Gemini | `agent.py` | `agent_hosted.py` | | OpenAI Agents SDK | [openai-agents/](./openai-agents/) | OpenAI GPT | `agent.py` | `agent_hosted.py` | | OpenAI Codex CLI | [codex/](./codex/) | Codex (OpenAI) | `[mcp_servers.e2a]` in `config.toml` | `[mcp_servers.e2a-hosted]` in `config.toml` | diff --git a/mcp/examples/crewai/README.md b/mcp/examples/crewai/README.md new file mode 100644 index 0000000..0423b83 --- /dev/null +++ b/mcp/examples/crewai/README.md @@ -0,0 +1,45 @@ +# CrewAI × e2a + +Single-agent CrewAI crew that drives the e2a [MCP server](https://www.npmjs.com/package/@e2a/mcp-server) via [`crewai-tools`](https://github.com/crewAIInc/crewAI-tools)' `MCPServerAdapter`. Picks up the e2a tool surface and uses it to answer natural-language email tasks. + +Two transport options: + +- **`agent.py`** — runs the MCP server locally via `npx -y @e2a/mcp-server` (stdio). Simplest for laptop dev; needs a Node toolchain. +- **`agent_hosted.py`** — talks to the hosted endpoint at `https://mcp.e2a.dev/mcp` (Streamable HTTP). Pick this when deploying to serverless runtimes (Cloud Run, Lambda) where spawning a stdio child process is awkward or impossible, or when you don't want a Node toolchain on the agent host. + +## Prerequisites + +- Python 3.10+ +- An [e2a API key](https://e2a.dev) +- An [Anthropic API key](https://console.anthropic.com/) +- For `agent.py` only: Node 18+ (the script shells out to `npx -y @e2a/mcp-server`) + +## Run (local stdio) + +```bash +pip install -r requirements.txt +export E2A_API_KEY=e2a_… +export E2A_AGENT_EMAIL=your-bot@your-domain.com # optional default inbox +export ANTHROPIC_API_KEY=sk-ant-… + +python agent.py "what's in my inbox?" +python agent.py "reply to the most recent message politely" +``` + +## Run (hosted) + +```bash +pip install -r requirements.txt +export E2A_API_KEY=e2a_… +export ANTHROPIC_API_KEY=sk-ant-… + +python agent_hosted.py "what's in my inbox?" +``` + +If your account has exactly one agent, the hosted endpoint auto-resolves it at session init — no `E2A_AGENT_EMAIL` needed. With multiple agents, pass `agent_email` per tool call. + +## How it works + +`MCPServerAdapter` connects to either a stdio child process (via `StdioServerParameters`) or a Streamable HTTP endpoint (via a dict with `transport: "streamable-http"` and a Bearer header). Inside the `with` block, the adapter yields one CrewAI tool per MCP tool — wired straight into the `Agent` so the crew can call them. + +To swap models, change `"anthropic/claude-sonnet-4-6"` to any [LiteLLM-compatible model string](https://docs.litellm.ai/docs/providers) CrewAI accepts (CrewAI uses LiteLLM under the hood). diff --git a/mcp/examples/crewai/agent.py b/mcp/examples/crewai/agent.py new file mode 100644 index 0000000..faa78db --- /dev/null +++ b/mcp/examples/crewai/agent.py @@ -0,0 +1,79 @@ +"""End-to-end demo: CrewAI agent driving @e2a/mcp-server over stdio. + +Wires the e2a MCP server into a single-agent CrewAI crew so the LLM can +send, read, and reply to email through natural-language prompts. + +Requires: + E2A_API_KEY e2a API key (https://e2a.dev) + E2A_AGENT_EMAIL (optional) default agent inbox + E2A_BASE_URL (optional) self-hosted e2a base URL + ANTHROPIC_API_KEY Anthropic API key + +Run: + pip install -r requirements.txt + python agent.py "what's in my inbox?" +""" + +import os +import sys + +from crewai import Agent, Crew, Process, Task +from crewai_tools import MCPServerAdapter +from mcp import StdioServerParameters + +BACKSTORY = ( + "You manage email through the e2a tools. Call whoami once to find " + "your inbox address. Use list_messages and get_message to read; " + "use reply_to_message (not send_email) when replying to an existing " + "thread so In-Reply-To and References headers are preserved." +) + + +def _e2a_env() -> dict[str, str]: + env = {"E2A_API_KEY": os.environ["E2A_API_KEY"]} + for k in ("E2A_AGENT_EMAIL", "E2A_BASE_URL"): + if k in os.environ: + env[k] = os.environ[k] + return env + + +def main(prompt: str) -> None: + server_params = StdioServerParameters( + command="npx", + args=["-y", "@e2a/mcp-server"], + env=_e2a_env(), + ) + + with MCPServerAdapter(server_params) as e2a_tools: + print( + f"Loaded {len(e2a_tools)} e2a tools: " + f"{', '.join(t.name for t in e2a_tools)}\n" + ) + + agent = Agent( + role="Email Manager", + goal="Handle the operator's email request precisely and concisely.", + backstory=BACKSTORY, + tools=e2a_tools, + llm="anthropic/claude-sonnet-4-6", + allow_delegation=False, + verbose=True, + ) + task = Task( + description=prompt, + expected_output="A clear, concise answer to the user's email-related request.", + agent=agent, + ) + crew = Crew( + agents=[agent], + tasks=[task], + process=Process.sequential, + verbose=False, + ) + result = crew.kickoff() + print(result) + + +if __name__ == "__main__": + prompt = " ".join(sys.argv[1:]) or "what's in my inbox?" + main(prompt) diff --git a/mcp/examples/crewai/agent_hosted.py b/mcp/examples/crewai/agent_hosted.py new file mode 100644 index 0000000..0f59ae1 --- /dev/null +++ b/mcp/examples/crewai/agent_hosted.py @@ -0,0 +1,76 @@ +"""End-to-end demo: CrewAI agent against the hosted e2a MCP server. + +Same shape as `agent.py` but uses the hosted MCP endpoint +(https://mcp.e2a.dev/mcp) over Streamable HTTP instead of spawning +@e2a/mcp-server locally over stdio. Pick this variant when: +- Deploying the CrewAI agent to a serverless runtime (Cloud Run, + Lambda, etc.) where launching a stdio child process per request is + awkward or impossible. +- You don't want a Node toolchain on the agent host. +- You want updates to land without rebuilding the agent's image. + +Requires: + E2A_API_KEY e2a API key (https://e2a.dev) + ANTHROPIC_API_KEY Anthropic API key + +Run: + pip install -r requirements.txt + python agent_hosted.py "what's in my inbox?" +""" + +import os +import sys + +from crewai import Agent, Crew, Process, Task +from crewai_tools import MCPServerAdapter + +BACKSTORY = ( + "You manage email through the e2a tools. Call whoami once to find " + "your inbox address. Use list_messages and get_message to read; " + "use reply_to_message (not send_email) when replying to an existing " + "thread so In-Reply-To and References headers are preserved." +) + + +def main(prompt: str) -> None: + server_params = { + "url": "https://mcp.e2a.dev/mcp", + "transport": "streamable-http", + "headers": { + "Authorization": f"Bearer {os.environ['E2A_API_KEY']}", + }, + } + + with MCPServerAdapter(server_params) as e2a_tools: + print( + f"Loaded {len(e2a_tools)} e2a tools: " + f"{', '.join(t.name for t in e2a_tools)}\n" + ) + + agent = Agent( + role="Email Manager", + goal="Handle the operator's email request precisely and concisely.", + backstory=BACKSTORY, + tools=e2a_tools, + llm="anthropic/claude-sonnet-4-6", + allow_delegation=False, + verbose=True, + ) + task = Task( + description=prompt, + expected_output="A clear, concise answer to the user's email-related request.", + agent=agent, + ) + crew = Crew( + agents=[agent], + tasks=[task], + process=Process.sequential, + verbose=False, + ) + result = crew.kickoff() + print(result) + + +if __name__ == "__main__": + prompt = " ".join(sys.argv[1:]) or "what's in my inbox?" + main(prompt) diff --git a/mcp/examples/crewai/requirements.txt b/mcp/examples/crewai/requirements.txt new file mode 100644 index 0000000..efdd845 --- /dev/null +++ b/mcp/examples/crewai/requirements.txt @@ -0,0 +1,3 @@ +crewai[anthropic]>=0.80.0 +crewai-tools[mcp]>=0.25.0 +email-validator>=2.0