From d818588698a5ddb0a90a4e29033e17933f11d973 Mon Sep 17 00:00:00 2001 From: Hanieh Zahiremami Date: Thu, 30 Oct 2025 17:24:07 -0400 Subject: [PATCH 1/5] Add MCP Agent Cloud deployment config for fetch server --- src/fetch/README.md | 10 ++++++++++ src/fetch/main.py | 11 +++++++++++ src/fetch/mcp_agent.config.yaml | 15 +++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 src/fetch/main.py create mode 100644 src/fetch/mcp_agent.config.yaml diff --git a/src/fetch/README.md b/src/fetch/README.md index 1bf12a1565..88c44175e9 100644 --- a/src/fetch/README.md +++ b/src/fetch/README.md @@ -168,6 +168,16 @@ This can be customized by adding the argument `--user-agent=YourUserAgent` to th The server can be configured to use a proxy by using the `--proxy-url` argument. +## Deployment to MCP Agent Cloud + +The repository ships with a ready-to-use deployment config for [MCP Agent Cloud](https://docs.mcp-agent.com/cloud/use-cases/deploy-mcp-servers). To publish this server: + +1. Authenticate once with `mcp-agent login`. +2. From `servers/src/fetch`, deploy with `mcp-agent deploy fetch-server --app-description "Fetch MCP server"`. +3. Inspect the live endpoint using `mcp-agent cloud servers describe fetch-server`, then install it into a client (for example `mcp-agent install https://.deployments.mcp-agent.com/sse --client cursor`). + +The deployment uses `mcp_agent.config.yaml` (same directory) which runs `uv run python -m mcp_server_fetch`. The bundler expects a `main.py` containing an `MCPApp` definition—this repository includes that stub so you can deploy without additional wiring. To pass optional flags (such as `--ignore-robots-txt`, `--user-agent`, or `--proxy-url`), edit `mcp_agent.config.yaml` before re-running `mcp-agent deploy`. + ## Windows Configuration If you're experiencing timeout issues on Windows, you may need to set the `PYTHONIOENCODING` environment variable to ensure proper character encoding: diff --git a/src/fetch/main.py b/src/fetch/main.py new file mode 100644 index 0000000000..0a0fcf6dd0 --- /dev/null +++ b/src/fetch/main.py @@ -0,0 +1,11 @@ +"""MCP Agent Cloud entrypoint for the Fetch MCP server.""" + +from mcp_agent.app import MCPApp + +# The MCPApp reads settings from mcp_agent.config.yaml in this directory. +# Deployment tooling requires defining the application object at module import. +app = MCPApp( + name="fetch-server", + description="Expose the fetch MCP server via MCP Agent Cloud.", +) + diff --git a/src/fetch/mcp_agent.config.yaml b/src/fetch/mcp_agent.config.yaml new file mode 100644 index 0000000000..78aab564a6 --- /dev/null +++ b/src/fetch/mcp_agent.config.yaml @@ -0,0 +1,15 @@ +name: fetch-server +execution_engine: asyncio +mcp: + servers: + fetch: + command: uv + args: + - run + - python + - -m + - mcp_server_fetch +logger: + transports: + - console + level: info From c5b0d90739df1d72a33c670a771ce30fbcefc89a Mon Sep 17 00:00:00 2001 From: Hanieh Zahiremami Date: Thu, 30 Oct 2025 19:17:14 -0400 Subject: [PATCH 2/5] Minimal MCPApp wrapper for Fetch: expose single proxy tool and trim config --- src/fetch/main.py | 72 +++++++++++++++++++++++++++++++-- src/fetch/mcp_agent.config.yaml | 20 +++++---- 2 files changed, 77 insertions(+), 15 deletions(-) diff --git a/src/fetch/main.py b/src/fetch/main.py index 0a0fcf6dd0..4192a8944e 100644 --- a/src/fetch/main.py +++ b/src/fetch/main.py @@ -1,11 +1,75 @@ -"""MCP Agent Cloud entrypoint for the Fetch MCP server.""" +""" +Minimal MCPApp wrapper to expose ONLY the Fetch MCP server over SSE, +following the mcp-agent cloud example structure. +""" +import asyncio +import os + +from mcp.server.fastmcp import FastMCP from mcp_agent.app import MCPApp +from mcp_agent.server.app_server import create_mcp_server_for_app +from mcp_agent.core.context import Context as AppContext +from typing import Optional -# The MCPApp reads settings from mcp_agent.config.yaml in this directory. -# Deployment tooling requires defining the application object at module import. +# Minimal FastMCP server descriptor +mcp = FastMCP( + name="fetch-server", + instructions="Fetch MCP server hosted via mcp-agent SSE.", +) + +# Minimal MCPApp required by the cloud bundler app = MCPApp( name="fetch-server", - description="Expose the fetch MCP server via MCP Agent Cloud.", + description="Expose the Fetch MCP server over SSE.", + mcp=mcp, ) + +@app.tool(name="fetch") +async def fetch_proxy( + url: str, + max_length: int = 5000, + start_index: int = 0, + raw: bool = False, + app_ctx: Optional[AppContext] = None, +) -> str: + """Proxy to the child Fetch MCP server's `fetch` tool. + + Mirrors the parameters of mcp-server-fetch so clients can call `fetch` + directly on this app's SSE endpoint. + """ + context = app_ctx or app.context + # Connect to the configured child server named "fetch" + agent = await context.get_agent(server_names=["fetch"]) # obtains a connected Agent + async with agent: + result = await agent.call_tool( + name="fetch", + arguments={ + "url": url, + "max_length": max_length, + "start_index": start_index, + "raw": raw, + }, + ) + # result is a ToolResult; return text content if present + # Fallback to JSON dump if other content types are returned + for c in result.content: + if getattr(c, "type", None) == "text" and hasattr(c, "text"): + return c.text + return str(result.model_dump()) + + +async def main() -> None: + async with app.run() as agent_app: + server = create_mcp_server_for_app(agent_app) + # Allow overriding bind address in local dev + host = os.environ.get("HOST", "0.0.0.0") + port = int(os.environ.get("PORT", "8000")) + server.settings.host = host + server.settings.port = port + await server.run_sse_async() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/src/fetch/mcp_agent.config.yaml b/src/fetch/mcp_agent.config.yaml index 78aab564a6..69b716ec0a 100644 --- a/src/fetch/mcp_agent.config.yaml +++ b/src/fetch/mcp_agent.config.yaml @@ -1,15 +1,13 @@ -name: fetch-server +$schema: ../../schema/mcp-agent.config.schema.json + execution_engine: asyncio +logger: + transports: [console] + level: debug + mcp: servers: fetch: - command: uv - args: - - run - - python - - -m - - mcp_server_fetch -logger: - transports: - - console - level: info + command: "uvx" + args: ["mcp-server-fetch"] + description: "Fetch content at URLs from the web" From f4e37990f8b121eefcf9e6f695c31d599d729840 Mon Sep 17 00:00:00 2001 From: Hanieh Zahiremami Date: Fri, 31 Oct 2025 13:42:10 -0400 Subject: [PATCH 3/5] Integrate MCP app wrapper and cloud config updates --- src/fetch/inspector.mcp.json | 10 + src/fetch/main.py | 366 +++++++++++++++++++++++++++----- src/fetch/mcp_agent.config.yaml | 15 +- 3 files changed, 328 insertions(+), 63 deletions(-) create mode 100644 src/fetch/inspector.mcp.json diff --git a/src/fetch/inspector.mcp.json b/src/fetch/inspector.mcp.json new file mode 100644 index 0000000000..29ac7e2512 --- /dev/null +++ b/src/fetch/inspector.mcp.json @@ -0,0 +1,10 @@ +{ + "mcpServers": { + "fetch": { + "command": "uvx", + "args": ["mcp-server-fetch"], + "transport": "stdio", + "description": "Fetch MCP server for local Inspector testing" + } + } +} diff --git a/src/fetch/main.py b/src/fetch/main.py index 4192a8944e..16769534ff 100644 --- a/src/fetch/main.py +++ b/src/fetch/main.py @@ -1,75 +1,327 @@ -""" -Minimal MCPApp wrapper to expose ONLY the Fetch MCP server over SSE, -following the mcp-agent cloud example structure. -""" +from __future__ import annotations -import asyncio -import os +from pathlib import Path +from typing import Annotated, Tuple +from urllib.parse import urlparse, urlunparse -from mcp.server.fastmcp import FastMCP from mcp_agent.app import MCPApp -from mcp_agent.server.app_server import create_mcp_server_for_app -from mcp_agent.core.context import Context as AppContext -from typing import Optional - -# Minimal FastMCP server descriptor -mcp = FastMCP( - name="fetch-server", - instructions="Fetch MCP server hosted via mcp-agent SSE.", +from mcp.shared.exceptions import McpError +from mcp.server import Server +from mcp.server.stdio import stdio_server +from mcp.types import ( + ErrorData, + GetPromptResult, + Prompt, + PromptArgument, + PromptMessage, + TextContent, + Tool, + INVALID_PARAMS, + INTERNAL_ERROR, ) +from pydantic import AnyUrl, BaseModel, Field -# Minimal MCPApp required by the cloud bundler +CONFIG_PATH = (Path(__file__).parent / "mcp_agent.config.yaml").resolve() app = MCPApp( name="fetch-server", - description="Expose the Fetch MCP server over SSE.", - mcp=mcp, + description="Fetch MCP server", + settings=str(CONFIG_PATH), ) +DEFAULT_USER_AGENT_AUTONOMOUS = "ModelContextProtocol/1.0 (Autonomous; +https://github.com/modelcontextprotocol/servers)" +DEFAULT_USER_AGENT_MANUAL = "ModelContextProtocol/1.0 (User-Specified; +https://github.com/modelcontextprotocol/servers)" + + +def extract_content_from_html(html: str) -> str: + from readabilipy.simple_json import simple_json_from_html_string + import markdownify + + ret = simple_json_from_html_string(html, use_readability=True) + if not ret["content"]: + return "Page failed to be simplified from HTML" + return markdownify.markdownify(ret["content"], heading_style=markdownify.ATX) + + +def get_robots_txt_url(url: str) -> str: + parsed = urlparse(url) + return urlunparse((parsed.scheme, parsed.netloc, "/robots.txt", "", "", "")) + + +async def check_may_autonomously_fetch_url( + url: str, user_agent: str, proxy_url: str | None = None +) -> None: + from httpx import AsyncClient, HTTPError + from protego import Protego + + robot_txt_url = get_robots_txt_url(url) + + async with AsyncClient(proxies=proxy_url) as client: + try: + response = await client.get( + robot_txt_url, + follow_redirects=True, + headers={"User-Agent": user_agent}, + ) + except HTTPError: + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message=f"Failed to fetch robots.txt {robot_txt_url} due to a connection issue", + ) + ) + if response.status_code in (401, 403): + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message=( + "When fetching robots.txt" + f" ({robot_txt_url}), received status {response.status_code} so assuming that" + " autonomous fetching is not allowed, the user can try manually fetching by" + " using the fetch prompt" + ), + ) + ) + if 400 <= response.status_code < 500: + return + robot_txt = response.text + + processed_robot_txt = "\n".join( + line for line in robot_txt.splitlines() if not line.strip().startswith("#") + ) + robot_parser = Protego.parse(processed_robot_txt) + if not robot_parser.can_fetch(str(url), user_agent): + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message=( + "The sites robots.txt" + f" ({robot_txt_url}), specifies that autonomous fetching of this page is not allowed, " + f"{user_agent}\n" + f"{url}" + f"\n{robot_txt}\n\n" + "The assistant must let the user know that it failed to view the page." + " The assistant may provide further guidance based on the above information.\n" + "The assistant can tell the user that they can try manually fetching the page" + " by using the fetch prompt within their UI." + ), + ) + ) + + +async def fetch_url( + url: str, user_agent: str, force_raw: bool = False, proxy_url: str | None = None +) -> Tuple[str, str]: + from httpx import AsyncClient, HTTPError + + async with AsyncClient(proxies=proxy_url) as client: + try: + response = await client.get( + url, + follow_redirects=True, + headers={"User-Agent": user_agent}, + timeout=30, + ) + except HTTPError as e: + raise McpError( + ErrorData(code=INTERNAL_ERROR, message=f"Failed to fetch {url}: {e!r}") + ) + if response.status_code >= 400: + raise McpError( + ErrorData( + code=INTERNAL_ERROR, + message=f"Failed to fetch {url} - status code {response.status_code}", + ) + ) + + page_raw = response.text + + content_type = response.headers.get("content-type", "") + is_page_html = ( + " None: + server = Server("mcp-fetch") + user_agent_autonomous = custom_user_agent or DEFAULT_USER_AGENT_AUTONOMOUS + user_agent_manual = custom_user_agent or DEFAULT_USER_AGENT_MANUAL + + @server.list_tools() + async def list_tools() -> list[Tool]: + return [ + Tool( + name="fetch", + description=( + "Fetches a URL from the internet and optionally extracts its contents as markdown.\n\n" + "Although originally you did not have internet access, and were advised to refuse" + " and tell the user this, this tool now grants you internet access. Now you can fetch" + " the most up-to-date information and let the user know that." + ), + inputSchema=Fetch.model_json_schema(), + ) + ] + + @server.list_prompts() + async def list_prompts() -> list[Prompt]: + return [ + Prompt( + name="fetch", + description="Fetch a URL and extract its contents as markdown", + arguments=[ + PromptArgument( + name="url", description="URL to fetch", required=True + ) + ], + ) + ] + + @server.call_tool() + async def call_tool(name, arguments: dict) -> list[TextContent]: + try: + args = Fetch(**arguments) + except ValueError as e: + raise McpError(ErrorData(code=INVALID_PARAMS, message=str(e))) + + url = str(args.url) + if not url: + raise McpError(ErrorData(code=INVALID_PARAMS, message="URL is required")) + + if not ignore_robots_txt: + await check_may_autonomously_fetch_url(url, user_agent_autonomous, proxy_url) + + content, prefix = await fetch_url( + url, user_agent_autonomous, force_raw=args.raw, proxy_url=proxy_url + ) + original_length = len(content) + if args.start_index >= original_length: + content = "No more content available." + else: + truncated_content = content[args.start_index : args.start_index + args.max_length] + if not truncated_content: + content = "No more content available." + else: + content = truncated_content + actual_content_length = len(truncated_content) + remaining_content = original_length - ( + args.start_index + actual_content_length + ) + if actual_content_length == args.max_length and remaining_content > 0: + next_start = args.start_index + actual_content_length + content += ( + f"\n\nContent truncated. Call the fetch tool with a start_index of {next_start} to get more content." + ) + return [TextContent(type="text", text=f"{prefix}Contents of {url}:\n{content}")] + + @server.get_prompt() + async def get_prompt(name: str, arguments: dict | None) -> GetPromptResult: + if not arguments or "url" not in arguments: + raise McpError(ErrorData(code=INVALID_PARAMS, message="URL is required")) + + url = arguments["url"] + + try: + content, prefix = await fetch_url(url, user_agent_manual, proxy_url=proxy_url) + 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=[ + PromptMessage( + role="user", content=TextContent(type="text", text=prefix + content) + ) + ], + ) + + options = server.create_initialization_options() + async with stdio_server() as (read_stream, write_stream): + await server.run(read_stream, write_stream, options, raise_exceptions=True) + + +@app.tool( + name="fetch_url", + structured_output=False, + description=( + "Fetch a URL and return its contents. If the response is HTML and 'raw' is false," + " the content is simplified to markdown. Supports truncation via max_length and" + " pagination via start_index." + ), +) +async def app_fetch_url( url: str, max_length: int = 5000, start_index: int = 0, raw: bool = False, - app_ctx: Optional[AppContext] = None, ) -> str: - """Proxy to the child Fetch MCP server's `fetch` tool. - - Mirrors the parameters of mcp-server-fetch so clients can call `fetch` - directly on this app's SSE endpoint. - """ - context = app_ctx or app.context - # Connect to the configured child server named "fetch" - agent = await context.get_agent(server_names=["fetch"]) # obtains a connected Agent - async with agent: - result = await agent.call_tool( - name="fetch", - arguments={ - "url": url, - "max_length": max_length, - "start_index": start_index, - "raw": raw, - }, - ) - # result is a ToolResult; return text content if present - # Fallback to JSON dump if other content types are returned - for c in result.content: - if getattr(c, "type", None) == "text" and hasattr(c, "text"): - return c.text - return str(result.model_dump()) - - -async def main() -> None: - async with app.run() as agent_app: - server = create_mcp_server_for_app(agent_app) - # Allow overriding bind address in local dev - host = os.environ.get("HOST", "0.0.0.0") - port = int(os.environ.get("PORT", "8000")) - server.settings.host = host - server.settings.port = port - await server.run_sse_async() + content, prefix = await fetch_url(url, DEFAULT_USER_AGENT_MANUAL, force_raw=raw) + original_length = len(content) + if start_index >= original_length: + content = "No more content available." + else: + truncated_content = content[start_index : start_index + max_length] + if not truncated_content: + content = "No more content available." + else: + content = truncated_content + actual_content_length = len(truncated_content) + remaining_content = original_length - (start_index + actual_content_length) + if actual_content_length == max_length and remaining_content > 0: + next_start = start_index + actual_content_length + content += ( + f"\n\nContent truncated. Call again with start_index={next_start} to get more content." + ) + return f"{prefix}Contents of {url}:\n{content}" if __name__ == "__main__": - asyncio.run(main()) + import asyncio + asyncio.run(serve()) + \ No newline at end of file diff --git a/src/fetch/mcp_agent.config.yaml b/src/fetch/mcp_agent.config.yaml index 69b716ec0a..2ff55bfffd 100644 --- a/src/fetch/mcp_agent.config.yaml +++ b/src/fetch/mcp_agent.config.yaml @@ -1,13 +1,16 @@ $schema: ../../schema/mcp-agent.config.schema.json - execution_engine: asyncio logger: - transports: [console] + transports: + - console level: debug - mcp: servers: fetch: - command: "uvx" - args: ["mcp-server-fetch"] - description: "Fetch content at URLs from the web" + command: uvx + args: + - mcp-server-fetch + description: Fetch content at URLs from the web + fetch-remote: + transport: sse + url: https://xxucyrqrp9xazl7kat535fkhnugne7h.deployments.mcp-agent.com/sse From 0c365f6d1edeecc14b433301d05733db470de64b Mon Sep 17 00:00:00 2001 From: Hanieh Zahiremami Date: Fri, 31 Oct 2025 14:38:07 -0400 Subject: [PATCH 4/5] Align fetch server cloud deployment --- src/fetch/main.py | 52 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/fetch/main.py b/src/fetch/main.py index 16769534ff..fb4604f614 100644 --- a/src/fetch/main.py +++ b/src/fetch/main.py @@ -5,6 +5,7 @@ from urllib.parse import urlparse, urlunparse from mcp_agent.app import MCPApp +from mcp.server.fastmcp import FastMCP from mcp.shared.exceptions import McpError from mcp.server import Server from mcp.server.stdio import stdio_server @@ -22,10 +23,17 @@ from pydantic import AnyUrl, BaseModel, Field CONFIG_PATH = (Path(__file__).parent / "mcp_agent.config.yaml").resolve() + +mcp = FastMCP( + name="fetch", + instructions="Fetch URLs and return markdown content.", +) + app = MCPApp( name="fetch-server", description="Fetch MCP server", settings=str(CONFIG_PATH), + mcp=mcp, ) DEFAULT_USER_AGENT_AUTONOMOUS = "ModelContextProtocol/1.0 (Autonomous; +https://github.com/modelcontextprotocol/servers)" @@ -33,16 +41,34 @@ def extract_content_from_html(html: str) -> str: - from readabilipy.simple_json import simple_json_from_html_string - import markdownify + try: + from readabilipy.simple_json import simple_json_from_html_string + except ImportError: + simple_json_from_html_string = None + + try: + import markdownify + except ImportError: + markdownify = None # type: ignore + + if simple_json_from_html_string: + ret = simple_json_from_html_string(html, use_readability=True) + if ret.get("content"): + if markdownify: + return markdownify.markdownify( + ret["content"], + heading_style=markdownify.ATX, + ) + return ret["content"] - ret = simple_json_from_html_string(html, use_readability=True) - if not ret["content"]: - return "Page failed to be simplified from HTML" - return markdownify.markdownify(ret["content"], heading_style=markdownify.ATX) + if markdownify: + return markdownify.markdownify(html, heading_style=markdownify.ATX) + + return html def get_robots_txt_url(url: str) -> str: + url = url.strip() parsed = urlparse(url) return urlunparse((parsed.scheme, parsed.netloc, "/robots.txt", "", "", "")) @@ -50,12 +76,13 @@ def get_robots_txt_url(url: str) -> str: async def check_may_autonomously_fetch_url( url: str, user_agent: str, proxy_url: str | None = None ) -> None: + url = url.strip() from httpx import AsyncClient, HTTPError from protego import Protego robot_txt_url = get_robots_txt_url(url) - async with AsyncClient(proxies=proxy_url) as client: + async with AsyncClient() as client: try: response = await client.get( robot_txt_url, @@ -111,9 +138,14 @@ async def check_may_autonomously_fetch_url( async def fetch_url( url: str, user_agent: str, force_raw: bool = False, proxy_url: str | None = None ) -> Tuple[str, str]: + url = url.strip() + if not url: + raise McpError( + ErrorData(code=INVALID_PARAMS, message="URL is required after trimming") + ) from httpx import AsyncClient, HTTPError - async with AsyncClient(proxies=proxy_url) as client: + async with AsyncClient() as client: try: response = await client.get( url, @@ -286,7 +318,7 @@ async def get_prompt(name: str, arguments: dict | None) -> GetPromptResult: await server.run(read_stream, write_stream, options, raise_exceptions=True) -@app.tool( +@mcp.tool( name="fetch_url", structured_output=False, description=( @@ -324,4 +356,4 @@ async def app_fetch_url( if __name__ == "__main__": import asyncio asyncio.run(serve()) - \ No newline at end of file + From 2f9a5e0748191eb4d6f9f011e2465cd4e5d916de Mon Sep 17 00:00:00 2001 From: Hanieh Zahiremami Date: Fri, 31 Oct 2025 14:39:28 -0400 Subject: [PATCH 5/5] Document hosted fetch server endpoint --- src/fetch/README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/fetch/README.md b/src/fetch/README.md index 88c44175e9..12908d1948 100644 --- a/src/fetch/README.md +++ b/src/fetch/README.md @@ -176,7 +176,25 @@ The repository ships with a ready-to-use deployment config for [MCP Agent Cloud] 2. From `servers/src/fetch`, deploy with `mcp-agent deploy fetch-server --app-description "Fetch MCP server"`. 3. Inspect the live endpoint using `mcp-agent cloud servers describe fetch-server`, then install it into a client (for example `mcp-agent install https://.deployments.mcp-agent.com/sse --client cursor`). -The deployment uses `mcp_agent.config.yaml` (same directory) which runs `uv run python -m mcp_server_fetch`. The bundler expects a `main.py` containing an `MCPApp` definition—this repository includes that stub so you can deploy without additional wiring. To pass optional flags (such as `--ignore-robots-txt`, `--user-agent`, or `--proxy-url`), edit `mcp_agent.config.yaml` before re-running `mcp-agent deploy`. +The deployment uses `mcp_agent.config.yaml` (same directory), mirroring the [mcp-agent cloud example](https://github.com/lastmile-ai/mcp-agent/tree/main/examples/cloud/mcp). It launches the stdio fetch server via `uvx mcp-server-fetch`, and `main.py` wires the tool into the cloud runtime using `FastMCP`. + +**Hosted instance** + +- App URL: `https://xxucyrqrp9xazl7kat535fkhnugne7h.deployments.mcp-agent.com` +- SSE endpoint: `https://xxucyrqrp9xazl7kat535fkhnugne7h.deployments.mcp-agent.com/sse` +- Tool name: `fetch_url` + +To connect with Inspector: + +```bash +killall node 2>/dev/null || true +npx --yes @modelcontextprotocol/inspector \ + --transport sse \ + --server-url https://xxucyrqrp9xazl7kat535fkhnugne7h.deployments.mcp-agent.com/sse \ + --header "Authorization: Bearer " +``` + +If `readabilipy` or `markdownify` are unavailable in the environment, the server gracefully falls back to returning raw HTML. To pass optional CLI flags (such as `--ignore-robots-txt`, `--user-agent`, or `--proxy-url`), edit `mcp_agent.config.yaml` before re-running `mcp-agent cloud deploy`. ## Windows Configuration