Merge pull request #816 from shiquda/main

feat(fetch): add support for using proxy for requests
This commit is contained in:
Ola Hungerford 2025-03-30 08:35:05 -07:00 • committed by GitHub
commit e181222154
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 19 additions and 10 deletions

View file

@ -107,6 +107,10 @@ ModelContextProtocol/1.0 (User-Specified; +https://github.com/modelcontextprotoc
This can be customized by adding the argument `--user-agent=YourUserAgent` to the `args` list in the configuration. This can be customized by adding the argument `--user-agent=YourUserAgent` to the `args` list in the configuration.
### Customization - Proxy
The server can be configured to use a proxy by using the `--proxy-url` argument.
## Debugging ## Debugging
You can use the MCP inspector to debug the server. For uvx installations: You can use the MCP inspector to debug the server. For uvx installations:

View file

@ -1,6 +1,6 @@
[project] [project]
name = "mcp-server-fetch" name = "mcp-server-fetch"
version = "0.6.2" version = "0.6.3"
description = "A Model Context Protocol server providing tools to fetch and convert web content for usage by LLMs" description = "A Model Context Protocol server providing tools to fetch and convert web content for usage by LLMs"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
@ -16,6 +16,7 @@ classifiers = [
"Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.10",
] ]
dependencies = [ dependencies = [
"httpx<0.28",
"markdownify>=0.13.1", "markdownify>=0.13.1",
"mcp>=1.1.3", "mcp>=1.1.3",
"protego>=0.3.1", "protego>=0.3.1",

View file

@ -15,9 +15,10 @@ def main():
action="store_true", action="store_true",
help="Ignore robots.txt restrictions", help="Ignore robots.txt restrictions",
) )
parser.add_argument("--proxy-url", type=str, help="Proxy URL to use for requests")
args = parser.parse_args() args = parser.parse_args()
asyncio.run(serve(args.user_agent, args.ignore_robots_txt)) asyncio.run(serve(args.user_agent, args.ignore_robots_txt, args.proxy_url))
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -63,7 +63,7 @@ def get_robots_txt_url(url: str) -> str:
return robots_url return robots_url
async def check_may_autonomously_fetch_url(url: str, user_agent: str) -> None: async def check_may_autonomously_fetch_url(url: str, user_agent: str, proxy_url: str | None = None) -> None:
""" """
Check if the URL can be fetched by the user agent according to the robots.txt file. Check if the URL can be fetched by the user agent according to the robots.txt file.
Raises a McpError if not. Raises a McpError if not.
@ -72,7 +72,7 @@ async def check_may_autonomously_fetch_url(url: str, user_agent: str) -> None:
robot_txt_url = get_robots_txt_url(url) robot_txt_url = get_robots_txt_url(url)
async with AsyncClient() as client: async with AsyncClient(proxies=proxy_url) as client:
try: try:
response = await client.get( response = await client.get(
robot_txt_url, robot_txt_url,
@ -109,14 +109,14 @@ async def check_may_autonomously_fetch_url(url: str, user_agent: str) -> None:
async def fetch_url( async def fetch_url(
url: str, user_agent: str, force_raw: bool = False url: str, user_agent: str, force_raw: bool = False, proxy_url: str | None = None
) -> Tuple[str, str]: ) -> Tuple[str, str]:
""" """
Fetch the URL and return the content in a form ready for the LLM, as well as a prefix string with status information. Fetch the URL and return the content in a form ready for the LLM, as well as a prefix string with status information.
""" """
from httpx import AsyncClient, HTTPError from httpx import AsyncClient, HTTPError
async with AsyncClient() as client: async with AsyncClient(proxies=proxy_url) as client:
try: try:
response = await client.get( response = await client.get(
url, url,
@ -179,13 +179,16 @@ class Fetch(BaseModel):
async def serve( async def serve(
custom_user_agent: str | None = None, ignore_robots_txt: bool = False custom_user_agent: str | None = None,
ignore_robots_txt: bool = False,
proxy_url: str | None = None,
) -> None: ) -> None:
"""Run the fetch MCP server. """Run the fetch MCP server.
Args: Args:
custom_user_agent: Optional custom User-Agent string to use for requests custom_user_agent: Optional custom User-Agent string to use for requests
ignore_robots_txt: Whether to ignore robots.txt restrictions ignore_robots_txt: Whether to ignore robots.txt restrictions
proxy_url: Optional proxy URL to use for requests
""" """
server = Server("mcp-fetch") server = Server("mcp-fetch")
user_agent_autonomous = custom_user_agent or DEFAULT_USER_AGENT_AUTONOMOUS user_agent_autonomous = custom_user_agent or DEFAULT_USER_AGENT_AUTONOMOUS
@ -229,10 +232,10 @@ Although originally you did not have internet access, and were advised to refuse
raise McpError(ErrorData(code=INVALID_PARAMS, message="URL is required")) raise McpError(ErrorData(code=INVALID_PARAMS, message="URL is required"))
if not ignore_robots_txt: if not ignore_robots_txt:
await check_may_autonomously_fetch_url(url, user_agent_autonomous) await check_may_autonomously_fetch_url(url, user_agent_autonomous, proxy_url)
content, prefix = await fetch_url( content, prefix = await fetch_url(
url, user_agent_autonomous, force_raw=args.raw url, user_agent_autonomous, force_raw=args.raw, proxy_url=proxy_url
) )
original_length = len(content) original_length = len(content)
if args.start_index >= original_length: if args.start_index >= original_length:
@ -259,7 +262,7 @@ Although originally you did not have internet access, and were advised to refuse
url = arguments["url"] url = arguments["url"]
try: try:
content, prefix = await fetch_url(url, user_agent_manual) content, prefix = await fetch_url(url, user_agent_manual, proxy_url=proxy_url)
# TODO: after SDK bug is addressed, don't catch the exception # TODO: after SDK bug is addressed, don't catch the exception
except McpError as e: except McpError as e:
return GetPromptResult( return GetPromptResult(