diff --git a/src/fetch/pyproject.toml b/src/fetch/pyproject.toml index 98b8cfd1..ac417cb2 100644 --- a/src/fetch/pyproject.toml +++ b/src/fetch/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-server-fetch" -version = "0.1.0" +version = "0.1.1" description = "A Model Context Protocol server providing tools to fetch and convert web content for usage by LLMs" readme = "README.md" requires-python = ">=3.10" diff --git a/src/fetch/src/mcp_server_fetch/server.py b/src/fetch/src/mcp_server_fetch/server.py index 3871e993..afcf2ed8 100644 --- a/src/fetch/src/mcp_server_fetch/server.py +++ b/src/fetch/src/mcp_server_fetch/server.py @@ -1,14 +1,17 @@ import markdownify import readabilipy.simple_json +from mcp.shared.exceptions import McpError from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import ( - TextContent, - Tool, + GetPromptResult, Prompt, PromptArgument, - GetPromptResult, PromptMessage, + TextContent, + Tool, + INVALID_PARAMS, + INTERNAL_ERROR, ) from pydantic import BaseModel, Field @@ -25,11 +28,16 @@ def extract_content(html: str) -> str: async def fetch_url(url: str) -> str: - from httpx import AsyncClient + from httpx import AsyncClient, HTTPError async with AsyncClient() as client: - response = await client.get(url) - response.raise_for_status() + try: + response = await client.get(url, follow_redirects=True) + except HTTPError: + raise McpError(INTERNAL_ERROR, f"Failed to fetch {url}") + if response.status_code >= 400: + raise McpError(INTERNAL_ERROR, f"Failed to fetch {url} - status code {response.status_code}") + page_html = response.text return extract_content(page_html) @@ -70,14 +78,32 @@ async def list_prompts() -> list[Prompt]: @server.call_tool() async def call_tool(name, arguments: dict) -> list[TextContent]: - url = arguments["url"] + url = arguments.get("url") + if not url: + raise McpError(INVALID_PARAMS, "URL is required") + content = await fetch_url(url) return [TextContent(type="text", text=f"Contents of {url}:\n{content}")] @server.get_prompt() async def get_prompt(name, arguments: dict) -> GetPromptResult: - url = arguments["url"] - content = await fetch_url(url) + url = arguments.get("url") + if not url: + raise McpError(INVALID_PARAMS, "URL is required") + + try: + content = await fetch_url(url) + # TODO: after SDK bug is addressed, don't catch the exception + except McpError as e: + return GetPromptResult( + description=f"Failed to fetch {url}", + messages=[ + PromptMessage( + role="user", + content=TextContent(type="text", text=str(e)), + ) + ], + ) return GetPromptResult( description=f"Contents of {url}", messages=[ diff --git a/src/fetch/uv.lock b/src/fetch/uv.lock index 7756b36a..e8fc771a 100644 --- a/src/fetch/uv.lock +++ b/src/fetch/uv.lock @@ -328,7 +328,7 @@ wheels = [ [[package]] name = "mcp-server-fetch" -version = "0.1.0" +version = "0.1.1" source = { editable = "." } dependencies = [ { name = "markdownify" },