From 0158df858244663a6839caddaab9a5cc5831a80c Mon Sep 17 00:00:00 2001 From: ChristopherGS Date: Wed, 4 Jun 2025 09:37:57 +0100 Subject: [PATCH 1/3] mcp working --- 2025-05-16-fastapi-demo/README.md | 20 ++++++- 2025-05-16-fastapi-demo/pyproject.toml | 1 + 2025-05-16-fastapi-demo/src/app.py | 17 ++++++ 2025-05-16-fastapi-demo/src/mcp_agent.py | 71 ++++++++++++++++++++++++ 2025-05-16-fastapi-demo/uv.lock | 8 ++- 5 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 2025-05-16-fastapi-demo/src/mcp_agent.py diff --git a/2025-05-16-fastapi-demo/README.md b/2025-05-16-fastapi-demo/README.md index d063383..0fe8761 100644 --- a/2025-05-16-fastapi-demo/README.md +++ b/2025-05-16-fastapi-demo/README.md @@ -1,9 +1,10 @@ -# FastAPI Demo with Math, Database and PydanticAI +# FastAPI Demo with Math, Database, PydanticAI and MCP This is a FastAPI application that demonstrates: - Mathematical operations (division, Fibonacci) - Database operations with SQLAlchemy - PydanticAI agent integration with Tavily search +- MCP (Model Context Protocol) integration with Playwright MCP server - Logfire observability ## Setup @@ -13,7 +14,12 @@ This is a FastAPI application that demonstrates: uv sync ``` -2. Create a `.env` file in the root directory with the following environment variables: +2. Ensure you have Node.js installed for the MCP filesystem server: + ```bash + node --version # Should be v16 or higher + ``` + +3. Create a `.env` file in the root directory with the following environment variables: ``` OPENAI_API_KEY=your_openai_api_key_here TAVILY_API_KEY=your_tavily_api_key_here @@ -21,7 +27,7 @@ This is a FastAPI application that demonstrates: DATABASE_URL=sqlite:///./test.db ``` -3. Run the application: +4. Run the application: ```bash uv run uvicorn src.app:app --host 0.0.0.0 --port 8000 --reload ``` @@ -34,6 +40,7 @@ This is a FastAPI application that demonstrates: - `GET /items/` - List all items with pagination - `GET /items/{item_id}` - Get a specific item by ID - `POST /agent/query` - Query the PydanticAI agent with a question +- `POST /mcp/query` - Query the MCP-enabled agent with Playwright MCP ## Example Usage @@ -44,6 +51,13 @@ curl -X POST "http://localhost:8000/agent/query" \ -d '{"question": "How do I use PydanticAI tools?"}' ``` +Query the MCP agent with filesystem capabilities: +```bash +curl -X POST "http://localhost:8000/mcp/query" \ + -H "Content-Type: application/json" \ + -d '{"question": "Create a simple Python script that prints hello world and save it to hello.py"}' +``` + Run evals: ```bash PYTHONPATH=. uv run python tests/evals.py diff --git a/2025-05-16-fastapi-demo/pyproject.toml b/2025-05-16-fastapi-demo/pyproject.toml index d6e746a..977e1d6 100644 --- a/2025-05-16-fastapi-demo/pyproject.toml +++ b/2025-05-16-fastapi-demo/pyproject.toml @@ -11,6 +11,7 @@ dependencies = [ "uvicorn>=0.34.2", "pydantic-ai>=0.2.9", "pydantic-ai-slim[tavily]>=0.2.9", + "mcp>=1.9.2", "python-dotenv>=1.1.0", "pydantic-evals>=0.2.9", ] diff --git a/2025-05-16-fastapi-demo/src/app.py b/2025-05-16-fastapi-demo/src/app.py index aa57e7c..05eb959 100644 --- a/2025-05-16-fastapi-demo/src/app.py +++ b/2025-05-16-fastapi-demo/src/app.py @@ -9,6 +9,7 @@ from sqlalchemy.orm import sessionmaker, Session from src.agent import build_agent, answer_question, BotResponse +from src.mcp_agent import answer_mcp_question, MCPBotResponse logfire.configure( service_name='api', @@ -26,6 +27,7 @@ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() logfire.instrument_sqlalchemy(engine) +logfire.instrument_mcp() # Database model @@ -58,6 +60,10 @@ class AgentQuery(BaseModel): question: str +class MCPQuery(BaseModel): + question: str + + # Dependency to get DB session def get_db(): db = SessionLocal() @@ -146,6 +152,17 @@ async def query_agent(query: AgentQuery, agent: Agent[None, BotResponse] = Depen return response +@app.post("/mcp/query", response_model=MCPBotResponse) +async def query_mcp_agent(query: MCPQuery): + """ + Queries the MCP-enabled PydanticAI agent with browser automation capabilities. + """ + logfire.info(f"Querying MCP agent with question: {query.question}") + response = await answer_mcp_question(query.question) + return response + + + if __name__ == '__main__': # Fixed double asterisks import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000) # Added this line to complete the if block \ No newline at end of file diff --git a/2025-05-16-fastapi-demo/src/mcp_agent.py b/2025-05-16-fastapi-demo/src/mcp_agent.py new file mode 100644 index 0000000..923cc3f --- /dev/null +++ b/2025-05-16-fastapi-demo/src/mcp_agent.py @@ -0,0 +1,71 @@ +import asyncio +from pathlib import Path +from textwrap import dedent +from typing import Annotated + +import logfire +from dotenv import load_dotenv +from pydantic import BaseModel, Field +from pydantic_ai import Agent +from pydantic_ai.mcp import MCPServerStdio + +ROOT_DIR = Path(__file__).parent.parent +load_dotenv(dotenv_path=ROOT_DIR / ".env") + +# Configure logfire instrumentation +logfire.configure(scrubbing=False, service_name='playwright-browser') +logfire.instrument_mcp() +logfire.instrument_pydantic_ai() + +class MCPBotResponse(BaseModel): + answer: str + reasoning: str + websites_accessed: list[str] = [] + confidence_percentage: Annotated[int, Field(ge=0, le=100)] + +SYSTEM_PROMPT = dedent( + """ + You're a helpful AI assistant with access to browser automation capabilities through Playwright. + You can navigate to websites, interact with web pages, take screenshots, and extract information. + + When working with web pages: + - Be thorough in your web navigation and information extraction + - Take screenshots when helpful for verification + - Extract relevant information clearly and accurately + - Explain what you're doing with the browser + - Be mindful of website terms of service and respectful browsing practices + + Give a confidence percentage for your answer, from 0 to 100. + List any websites you accessed in the websites_accessed field. + """ +) + +# Set up Playwright MCP server +browser_mcp = MCPServerStdio('npx', args=['-Y', '@playwright/mcp@latest']) + +# Create the agent with MCP server integration +agent = Agent( + 'openai:gpt-4o', + output_type=MCPBotResponse, + system_prompt=SYSTEM_PROMPT, + mcp_servers=[browser_mcp], + instrument=True, +) + +async def answer_mcp_question(question: str) -> MCPBotResponse: + """Run a question through the MCP-enabled browser agent.""" + async with agent.run_mcp_servers(): + result = await agent.run(user_prompt=question) + return result.output + +async def main(): + """Example usage of the browser agent.""" + async with agent.run_mcp_servers(): + result = await agent.run( + 'Navigate to pydantic.dev and get information about their latest blog post or announcement. ' + 'Summarize what you find.' + ) + print(result.output) + +if __name__ == '__main__': + asyncio.run(main()) \ No newline at end of file diff --git a/2025-05-16-fastapi-demo/uv.lock b/2025-05-16-fastapi-demo/uv.lock index 55f2544..088be10 100644 --- a/2025-05-16-fastapi-demo/uv.lock +++ b/2025-05-16-fastapi-demo/uv.lock @@ -8,6 +8,7 @@ source = { virtual = "." } dependencies = [ { name = "fastapi" }, { name = "logfire", extra = ["fastapi", "sqlalchemy"] }, + { name = "mcp" }, { name = "opentelemetry-api" }, { name = "pydantic-ai" }, { name = "pydantic-ai-slim", extra = ["tavily"] }, @@ -26,6 +27,7 @@ dev = [ requires-dist = [ { name = "fastapi", specifier = ">=0.115.12" }, { name = "logfire", extras = ["fastapi", "sqlalchemy"], specifier = ">=3.14.0" }, + { name = "mcp", specifier = ">=1.9.2" }, { name = "opentelemetry-api", specifier = ">=1.20.0" }, { name = "pydantic-ai", specifier = ">=0.2.9" }, { name = "pydantic-ai-slim", extras = ["tavily"], specifier = ">=0.2.9" }, @@ -602,7 +604,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.9.1" +version = "1.9.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -615,9 +617,9 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/bc/54aec2c334698cc575ca3b3481eed627125fb66544152fa1af927b1a495c/mcp-1.9.1.tar.gz", hash = "sha256:19879cd6dde3d763297617242888c2f695a95dfa854386a6a68676a646ce75e4", size = 316247 } +sdist = { url = "https://files.pythonhosted.org/packages/ea/03/77c49cce3ace96e6787af624611b627b2828f0dca0f8df6f330a10eea51e/mcp-1.9.2.tar.gz", hash = "sha256:3c7651c053d635fd235990a12e84509fe32780cd359a5bbef352e20d4d963c05", size = 333066 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/c0/4ac795585a22a0a2d09cd2b1187b0252d2afcdebd01e10a68bbac4d34890/mcp-1.9.1-py3-none-any.whl", hash = "sha256:2900ded8ffafc3c8a7bfcfe8bc5204037e988e753ec398f371663e6a06ecd9a9", size = 130261 }, + { url = "https://files.pythonhosted.org/packages/5d/a6/8f5ee9da9f67c0fd8933f63d6105f02eabdac8a8c0926728368ffbb6744d/mcp-1.9.2-py3-none-any.whl", hash = "sha256:bc29f7fd67d157fef378f89a4210384f5fecf1168d0feb12d22929818723f978", size = 131083 }, ] [[package]] From 61edf8f5c73bbe3f6613d1f94d3d09fcd8246161 Mon Sep 17 00:00:00 2001 From: ChristopherGS Date: Wed, 4 Jun 2025 15:20:51 +0100 Subject: [PATCH 2/3] add linear mcp --- 2025-05-16-fastapi-demo/src/mcp_agent.py | 38 +++++++++++++++--------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/2025-05-16-fastapi-demo/src/mcp_agent.py b/2025-05-16-fastapi-demo/src/mcp_agent.py index 923cc3f..f0c04f3 100644 --- a/2025-05-16-fastapi-demo/src/mcp_agent.py +++ b/2025-05-16-fastapi-demo/src/mcp_agent.py @@ -20,35 +20,44 @@ class MCPBotResponse(BaseModel): answer: str reasoning: str - websites_accessed: list[str] = [] + services_used: list[str] = [] confidence_percentage: Annotated[int, Field(ge=0, le=100)] SYSTEM_PROMPT = dedent( """ - You're a helpful AI assistant with access to browser automation capabilities through Playwright. - You can navigate to websites, interact with web pages, take screenshots, and extract information. + You're a helpful AI assistant with access to browser automation and Linear project management capabilities. - When working with web pages: - - Be thorough in your web navigation and information extraction + Browser capabilities (via Playwright): + - Navigate to websites, interact with web pages, take screenshots, and extract information + - Be thorough in web navigation and information extraction - Take screenshots when helpful for verification - Extract relevant information clearly and accurately - - Explain what you're doing with the browser + + Linear capabilities: + - Find, create, and update Linear issues, projects, and comments + - Access Linear workspace data and project management information + - Help with issue tracking and project organization + + When working with these services: + - Explain what you're doing clearly - Be mindful of website terms of service and respectful browsing practices + - Follow best practices for project management workflows Give a confidence percentage for your answer, from 0 to 100. - List any websites you accessed in the websites_accessed field. + List any services you used (e.g., "playwright", "linear") in the services_used field. """ ) -# Set up Playwright MCP server -browser_mcp = MCPServerStdio('npx', args=['-Y', '@playwright/mcp@latest']) +# Set up MCP servers +browser_mcp = MCPServerStdio('npx', args=['-Y', '@playwright/mcp@latest'], tool_prefix='browser') +linear_mcp = MCPServerStdio('npx', args=['-y', 'mcp-remote', 'https://mcp.linear.app/sse'], tool_prefix='linear') -# Create the agent with MCP server integration +# Create the agent with both MCP servers agent = Agent( 'openai:gpt-4o', output_type=MCPBotResponse, system_prompt=SYSTEM_PROMPT, - mcp_servers=[browser_mcp], + mcp_servers=[browser_mcp, linear_mcp], instrument=True, ) @@ -59,11 +68,12 @@ async def answer_mcp_question(question: str) -> MCPBotResponse: return result.output async def main(): - """Example usage of the browser agent.""" + """Example usage of the browser and Linear agent.""" async with agent.run_mcp_servers(): result = await agent.run( - 'Navigate to pydantic.dev and get information about their latest blog post or announcement. ' - 'Summarize what you find.' + 'Help me with project management: First, check what Linear workspaces and projects are available. ' + 'Then navigate to pydantic.dev to get information about their latest announcement and ' + 'create a Linear issue to track following up on any interesting developments you find.' ) print(result.output) From f2cfd1ac55f52b2f2f344d4b94cc2496fb3f6aba Mon Sep 17 00:00:00 2001 From: ChristopherGS Date: Wed, 4 Jun 2025 15:33:53 +0100 Subject: [PATCH 3/3] attempt to use logfire mcp - failing --- 2025-05-16-fastapi-demo/pyproject.toml | 1 + 2025-05-16-fastapi-demo/src/mcp_agent.py | 42 +++++++++++----------- 2025-05-16-fastapi-demo/uv.lock | 45 ++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/2025-05-16-fastapi-demo/pyproject.toml b/2025-05-16-fastapi-demo/pyproject.toml index 977e1d6..a32f6f5 100644 --- a/2025-05-16-fastapi-demo/pyproject.toml +++ b/2025-05-16-fastapi-demo/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "mcp>=1.9.2", "python-dotenv>=1.1.0", "pydantic-evals>=0.2.9", + "logfire-mcp>=0.0.11", ] [tool.ruff] diff --git a/2025-05-16-fastapi-demo/src/mcp_agent.py b/2025-05-16-fastapi-demo/src/mcp_agent.py index f0c04f3..46b152c 100644 --- a/2025-05-16-fastapi-demo/src/mcp_agent.py +++ b/2025-05-16-fastapi-demo/src/mcp_agent.py @@ -25,7 +25,7 @@ class MCPBotResponse(BaseModel): SYSTEM_PROMPT = dedent( """ - You're a helpful AI assistant with access to browser automation and Linear project management capabilities. + You're a helpful AI assistant with access to browser automation and Logfire telemetry analysis capabilities. Browser capabilities (via Playwright): - Navigate to websites, interact with web pages, take screenshots, and extract information @@ -33,49 +33,51 @@ class MCPBotResponse(BaseModel): - Take screenshots when helpful for verification - Extract relevant information clearly and accurately - Linear capabilities: - - Find, create, and update Linear issues, projects, and comments - - Access Linear workspace data and project management information - - Help with issue tracking and project organization + Logfire capabilities: + - Find and analyze exceptions in OpenTelemetry traces grouped by file + - Get detailed trace information about exceptions in specific files + - Run custom SQL queries on traces and metrics data + - Access OpenTelemetry schema information for query building + - Analyze application performance and error patterns When working with these services: - Explain what you're doing clearly - Be mindful of website terms of service and respectful browsing practices - - Follow best practices for project management workflows + - Use appropriate time ranges for telemetry queries (max 7 days) + - Help identify patterns in application behavior and errors Give a confidence percentage for your answer, from 0 to 100. - List any services you used (e.g., "playwright", "linear") in the services_used field. + List any services you used (e.g., "playwright", "logfire") in the services_used field. """ ) -# Set up MCP servers -browser_mcp = MCPServerStdio('npx', args=['-Y', '@playwright/mcp@latest'], tool_prefix='browser') -linear_mcp = MCPServerStdio('npx', args=['-y', 'mcp-remote', 'https://mcp.linear.app/sse'], tool_prefix='linear') +# Set up MCP servers with correct command syntax +browser_mcp = MCPServerStdio('npx', args=['--yes', '@playwright/mcp@latest'], tool_prefix='browser') +logfire_mcp = MCPServerStdio('uvx', args=['logfire-mcp'], tool_prefix='logfire') # Create the agent with both MCP servers agent = Agent( 'openai:gpt-4o', output_type=MCPBotResponse, system_prompt=SYSTEM_PROMPT, - mcp_servers=[browser_mcp, linear_mcp], + mcp_servers=[browser_mcp, logfire_mcp], instrument=True, ) async def answer_mcp_question(question: str) -> MCPBotResponse: - """Run a question through the MCP-enabled browser agent.""" + """Run a question through the MCP-enabled agent.""" async with agent.run_mcp_servers(): result = await agent.run(user_prompt=question) return result.output async def main(): - """Example usage of the browser and Linear agent.""" - async with agent.run_mcp_servers(): - result = await agent.run( - 'Help me with project management: First, check what Linear workspaces and projects are available. ' - 'Then navigate to pydantic.dev to get information about their latest announcement and ' - 'create a Linear issue to track following up on any interesting developments you find.' - ) - print(result.output) + """Example usage of the browser and Logfire telemetry agent.""" + question = ('Help me analyze my application: First, check for any exceptions in traces from the last hour using Logfire. ' + 'Then navigate to the Logfire documentation to get information about best practices for error monitoring. ' + 'Finally, provide recommendations based on what you find.') + + result = await answer_mcp_question(question) + print(result) if __name__ == '__main__': asyncio.run(main()) \ No newline at end of file diff --git a/2025-05-16-fastapi-demo/uv.lock b/2025-05-16-fastapi-demo/uv.lock index 088be10..8a786d7 100644 --- a/2025-05-16-fastapi-demo/uv.lock +++ b/2025-05-16-fastapi-demo/uv.lock @@ -8,6 +8,7 @@ source = { virtual = "." } dependencies = [ { name = "fastapi" }, { name = "logfire", extra = ["fastapi", "sqlalchemy"] }, + { name = "logfire-mcp" }, { name = "mcp" }, { name = "opentelemetry-api" }, { name = "pydantic-ai" }, @@ -27,6 +28,7 @@ dev = [ requires-dist = [ { name = "fastapi", specifier = ">=0.115.12" }, { name = "logfire", extras = ["fastapi", "sqlalchemy"], specifier = ">=3.14.0" }, + { name = "logfire-mcp", specifier = ">=0.0.11" }, { name = "mcp", specifier = ">=1.9.2" }, { name = "opentelemetry-api", specifier = ">=1.20.0" }, { name = "pydantic-ai", specifier = ">=0.2.9" }, @@ -590,6 +592,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ee/a4/8200b279a44990ad9d4233f05c2bc4029ba02f25de51fee61f51bc5c5a98/logfire_api-3.16.1-py3-none-any.whl", hash = "sha256:da0d232fffadded58339b91a5a1b5f45c4bd05a62e9241c973de9c5bebe34521", size = 80121 }, ] +[[package]] +name = "logfire-mcp" +version = "0.0.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "logfire" }, + { name = "mcp", extra = ["cli"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/68/4c7e33efe11a21aba78c9ce4fbdd06f7f40d074ea62ece86b5057129aa7c/logfire_mcp-0.0.11.tar.gz", hash = "sha256:3e4148c76268daa874013ea788a1f7a6fe9296713243bf3e428e690baa795b9a", size = 26834 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/c5/5892c5a45ff631d2702360a81e7be8c6fbb5586d073a56dbf3e459cdee6f/logfire_mcp-0.0.11-py3-none-any.whl", hash = "sha256:8faa91bfd204bd34c0d31be1948fb2464d26b0112a60a4d7a10df9431ac255b6", size = 6757 }, +] + [[package]] name = "markdown-it-py" version = "3.0.0" @@ -622,6 +637,12 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/a6/8f5ee9da9f67c0fd8933f63d6105f02eabdac8a8c0926728368ffbb6744d/mcp-1.9.2-py3-none-any.whl", hash = "sha256:bc29f7fd67d157fef378f89a4210384f5fecf1168d0feb12d22929818723f978", size = 131083 }, ] +[package.optional-dependencies] +cli = [ + { name = "python-dotenv" }, + { name = "typer" }, +] + [[package]] name = "mdurl" version = "0.1.2" @@ -1195,6 +1216,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/18/17/22bf8155aa0ea2305eefa3a6402e040df7ebe512d1310165eda1e233c3f8/s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", size = 85152 }, ] +[[package]] +name = "shellingham" +version = "1.5.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, +] + [[package]] name = "six" version = "1.17.0" @@ -1328,6 +1358,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, ] +[[package]] +name = "typer" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "rich" }, + { name = "shellingham" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c5/8c/7d682431efca5fd290017663ea4588bf6f2c6aad085c7f108c5dbc316e70/typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b", size = 102625 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/42/3efaf858001d2c2913de7f354563e3a3a2f0decae3efe98427125a8f441e/typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855", size = 46317 }, +] + [[package]] name = "types-requests" version = "2.32.0.20250515"