Spaces:
Sleeping
Upload mcp_server.py
Browse filesThe tool surface goes from 12 to 6: search, search_arxiv, search_google_scholar, search_patents, scrap_patent, scrap_patents. The exclusions live in one EXCLUDED_ROUTES list at the top of the file with the reasoning next to each pattern, so it's easy to adjust. MCP_EXPOSE_ALL=1 puts all 12 back without a code change — I verified both paths (6 tools by default, 12 with the flag set).
I also rewrote the server instructions, since the old ones described ops_* tools the model can no longer see. The new text tells the agent that search and search_patents already exhaust every backend internally, so a "not found" is genuine and it should move to the next id rather than hunting for a lower-level tool — which is exactly the loop that produced your JP7904662B2 traceback.
All the /ops/* REST endpoints still work exactly as before; this only changes what an LLM is offered.
One caveat worth knowing: after you redeploy, Claude may keep serving a cached tool list for a few minutes. If you still see the old 12 tools, toggle the connector off and on.
Claude
- mcp_server.py +39 -4
|
@@ -24,6 +24,8 @@ Configuration (all optional, read from the environment):
|
|
| 24 |
is what you want behind the HF Spaces proxy.
|
| 25 |
MCP_ALLOWED_HOSTS Comma-separated Host allow-list. Default "*".
|
| 26 |
MCP_ALLOWED_ORIGINS Comma-separated Origin allow-list. Default "*".
|
|
|
|
|
|
|
| 27 |
"""
|
| 28 |
|
| 29 |
from __future__ import annotations
|
|
@@ -37,12 +39,33 @@ from fastapi.routing import APIRoute
|
|
| 37 |
from fastapi.utils import generate_unique_id
|
| 38 |
|
| 39 |
from fastmcp import FastMCP
|
|
|
|
| 40 |
from fastmcp.utilities.lifespan import combine_lifespans
|
| 41 |
|
| 42 |
logger = logging.getLogger(__name__)
|
| 43 |
|
| 44 |
MCP_SERVER_NAME = "SERPent"
|
| 45 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
MCP_INSTRUCTIONS = """\
|
| 47 |
SERPent gives you web, academic and patent search plus full-text patent retrieval.
|
| 48 |
|
|
@@ -59,9 +82,11 @@ Every search tool accepts a LIST of queries and runs them concurrently, so send
|
|
| 59 |
all your query variations in one call rather than making several calls. Set
|
| 60 |
`n_results` to control how many hits come back per query.
|
| 61 |
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
| 65 |
"""
|
| 66 |
|
| 67 |
|
|
@@ -117,11 +142,21 @@ def _tool_names(app: FastAPI) -> dict[str, str]:
|
|
| 117 |
|
| 118 |
|
| 119 |
def build_mcp_server(app: FastAPI) -> FastMCP:
|
| 120 |
-
"""Build an MCP server exposing
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
mcp = FastMCP.from_fastapi(
|
| 122 |
app=app,
|
| 123 |
name=MCP_SERVER_NAME,
|
| 124 |
mcp_names=_tool_names(app),
|
|
|
|
| 125 |
# Requests are dispatched straight back into this same app in-process.
|
| 126 |
httpx_client_kwargs={"timeout": 300.0},
|
| 127 |
)
|
|
|
|
| 24 |
is what you want behind the HF Spaces proxy.
|
| 25 |
MCP_ALLOWED_HOSTS Comma-separated Host allow-list. Default "*".
|
| 26 |
MCP_ALLOWED_ORIGINS Comma-separated Origin allow-list. Default "*".
|
| 27 |
+
MCP_EXPOSE_ALL "1" to expose every endpoint as a tool instead of the
|
| 28 |
+
curated set (see EXCLUDED_ROUTES below).
|
| 29 |
"""
|
| 30 |
|
| 31 |
from __future__ import annotations
|
|
|
|
| 39 |
from fastapi.utils import generate_unique_id
|
| 40 |
|
| 41 |
from fastmcp import FastMCP
|
| 42 |
+
from fastmcp.server.providers.openapi import MCPType, RouteMap
|
| 43 |
from fastmcp.utilities.lifespan import combine_lifespans
|
| 44 |
|
| 45 |
logger = logging.getLogger(__name__)
|
| 46 |
|
| 47 |
MCP_SERVER_NAME = "SERPent"
|
| 48 |
|
| 49 |
+
# Endpoints kept out of the MCP tool surface. They stay fully available over
|
| 50 |
+
# REST — this only controls what an LLM sees.
|
| 51 |
+
#
|
| 52 |
+
# Fewer, well-differentiated tools measurably improve tool selection, and both
|
| 53 |
+
# groups below are redundant *for an agent*:
|
| 54 |
+
#
|
| 55 |
+
# /ops/* The regular `scrap_patent` / `search_patents` tools already
|
| 56 |
+
# fall back to EPO OPS on their own. Calling the OPS tools
|
| 57 |
+
# directly skips that fallback, so a patent missing from OPS
|
| 58 |
+
# becomes a hard error instead of a Google Patents hit.
|
| 59 |
+
# search_brave These are `search` with the fallback chain removed. `search`
|
| 60 |
+
# search_bing already tries DuckDuckGo, then Brave, then Bing, so exposing
|
| 61 |
+
# search_duck them separately only invites the model to pick a worse path.
|
| 62 |
+
#
|
| 63 |
+
# Set MCP_EXPOSE_ALL=1 to expose everything again, or edit this list.
|
| 64 |
+
EXCLUDED_ROUTES = [
|
| 65 |
+
r"^/ops/.*",
|
| 66 |
+
r"^/serp/search_(brave|bing|duck)$",
|
| 67 |
+
]
|
| 68 |
+
|
| 69 |
MCP_INSTRUCTIONS = """\
|
| 70 |
SERPent gives you web, academic and patent search plus full-text patent retrieval.
|
| 71 |
|
|
|
|
| 82 |
all your query variations in one call rather than making several calls. Set
|
| 83 |
`n_results` to control how many hits come back per query.
|
| 84 |
|
| 85 |
+
`search` and `search_patents` already fall back across several backends
|
| 86 |
+
internally, including the official EPO OPS patent API, so a single call is the
|
| 87 |
+
most thorough option available — there is no lower-level tool to reach for when
|
| 88 |
+
one comes back empty. A patent that returns "not found" is genuinely absent
|
| 89 |
+
from every backend; move on to the next id rather than retrying.
|
| 90 |
"""
|
| 91 |
|
| 92 |
|
|
|
|
| 142 |
|
| 143 |
|
| 144 |
def build_mcp_server(app: FastAPI) -> FastMCP:
|
| 145 |
+
"""Build an MCP server exposing the app's routes as tools."""
|
| 146 |
+
if _env_flag("MCP_EXPOSE_ALL", False):
|
| 147 |
+
route_maps = None
|
| 148 |
+
logger.info("MCP exposing all endpoints (MCP_EXPOSE_ALL=1).")
|
| 149 |
+
else:
|
| 150 |
+
route_maps = [
|
| 151 |
+
RouteMap(pattern=pattern, mcp_type=MCPType.EXCLUDE)
|
| 152 |
+
for pattern in EXCLUDED_ROUTES
|
| 153 |
+
]
|
| 154 |
+
|
| 155 |
mcp = FastMCP.from_fastapi(
|
| 156 |
app=app,
|
| 157 |
name=MCP_SERVER_NAME,
|
| 158 |
mcp_names=_tool_names(app),
|
| 159 |
+
route_maps=route_maps,
|
| 160 |
# Requests are dispatched straight back into this same app in-process.
|
| 161 |
httpx_client_kwargs={"timeout": 300.0},
|
| 162 |
)
|