[MCP] Migrate static engine resources to ResourceTemplate - #48
Merged
Conversation
Replace the 107-entry static resource registry with a single
ResourceTemplate bound to serpapi://engines/{engine_name}. The MCP
client now learns the URI pattern from resources/templates/list and
expands it on demand via resources/read, instead of receiving 107
individual resource entries at handshake time.
Measured: the resources/list metadata payload drops from
~14,445 bytes (107 × ~135 bytes) to ~354 bytes (one template), a
97.5% reduction in MCP handshake cost. Engine JSON files are also
loaded lazily on first read rather than enumerated at server
import.
The template carries the same Annotations block as engines_index
(audience=assistant, priority=0.3) so clients can apply consistent
context-budget policy to both discovery resources.
This was referenced Jun 10, 2026
louzt
force-pushed
the
feat/resource-template-engines
branch
from
June 10, 2026 09:02
46e7e8c to
53a128f
Compare
Contributor
Author
|
Update soon in #48 |
The ResourceTemplate handler returned a successful read with an
{"error": ...} JSON body for unknown or invalid engine names. That
changed the wire contract vs the previous static resources (which
errored on a missing resource) and was inconsistent with extra-path-
segment URIs that still errored. Raise NotFoundError instead so bad
reads produce a proper error response (non-breaking), while keeping the
descriptive messages. Also restore the comment explaining the
json.dumps(json.loads(...)) newline-stripping.
Co-authored-by: Cursor <cursoragent@cursor.com>
Contributor
Author
|
Thanks for the merge! Great catch on raising the NotFoundError exception instead for FastMCP wire contract. It’s been a good experience collaborating with you all to harden this MCP layer over the last few days. I'll be stepping back for now, as the core infrastructure is looking incredibly solid and token-efficient for my and others agentic loops. If you ever need an extra set of eyes onto something infrastructure related or else down the road, you know where to find me here, L-in, etc. Wishing you massive success with Odysseus and the rest of the SerpApi ecosystem! I been watching the wave of commits there and there hardening some skills and so on, thats cool as useful. Cheers. |
louzt
added a commit
to louzt/serpapi-mcp
that referenced
this pull request
Jul 27, 2026
* [MCP] Migrate static engine resources to ResourceTemplate
Replace the 107-entry static resource registry with a single
ResourceTemplate bound to serpapi://engines/{engine_name}. The MCP
client now learns the URI pattern from resources/templates/list and
expands it on demand via resources/read, instead of receiving 107
individual resource entries at handshake time.
Measured: the resources/list metadata payload drops from
~14,445 bytes (107 × ~135 bytes) to ~354 bytes (one template), a
97.5% reduction in MCP handshake cost. Engine JSON files are also
loaded lazily on first read rather than enumerated at server
import.
The template carries the same Annotations block as engines_index
(audience=assistant, priority=0.3) so clients can apply consistent
context-budget policy to both discovery resources.
* Raise NotFoundError for unknown/invalid engines in template handler
The ResourceTemplate handler returned a successful read with an
{"error": ...} JSON body for unknown or invalid engine names. That
changed the wire contract vs the previous static resources (which
errored on a missing resource) and was inconsistent with extra-path-
segment URIs that still errored. Raise NotFoundError instead so bad
reads produce a proper error response (non-breaking), while keeping the
descriptive messages. Also restore the comment explaining the
json.dumps(json.loads(...)) newline-stripping.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Vlad M <vlad@serpapi.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: vm-serpapi <samueljack1900@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
[MCP] Migrate static engine resources to ResourceTemplate
What
Replaces the 107-entry static resource registry with a single
ResourceTemplatebound toserpapi://engines/{engine_name}.Before (
src/server.py):After:
Why
The MCP 2025-11-25 spec defines
ResourceTemplate(RFC 6570 URItemplates) as the canonical way to expose a family of resources
behind a single registry entry. The current code uses
mcp.add_resource(...)107 times in a startup loop, which has threequantifiable problems:
1. Handshake bloat (the SRE angle)
resources/listis sent in response to the first MCPinitializefrom every Host client. With 107 static resources, the response
carries ~14,445 bytes of metadata alone (107 × ~135 bytes per
resource entry — uri, name, description, mimeType). Replacing the
loop with one template entry drops that to ~354 bytes — a
97.5% reduction in MCP handshake cost. Measured locally with
len(json.dumps(template_dict))and an estimated 135 bytes perstatic resource entry. Every Claude Code, Cursor, VSCode Insiders,
and any other MCP host that connects to this server now ships less
data on the wire during initialization.
2. Server CPU at import time
The current code calls
mcp.add_resource(...)107 times at moduleimport. With the template, the loop is gone; the server only reads
the relevant
engines/<engine_name>.jsonfile when an LLM actuallyrequests that specific engine via
resources/readwith URIexpansion. For most sessions (an LLM calls 1–3 engines, not all
107), the server's filesystem read count drops accordingly.
3. Spec compliance and future-proofing
ResourceTemplateis the MCP 2025-11-25 canonical mechanism forparameterized resources. Adopting it:
with a single friendly entry instead of 107 raw URIs.
ecosystem (every modern MCP server exposes parameterizable
resources as templates).
lists, runtime engine registration without server restart) without
another breaking refactor.
4. Consistency with the engines_index PR
The companion PR
(
[MCP] Add resource annotations to engines_index) annotatesengines_indexwithaudience=["assistant"], priority=0.3. ThisPR applies the same annotation profile to the engine template so
clients apply consistent context-budget policy to both discovery
resources. The
engines_indexitself is unchanged: it still listsall 107 engine names, and the URIs it returns are still resolvable
via the template.
Compatibility
serpapi://engines/{engine_name}matchesthe previous static URIs (
serpapi://engines/google,serpapi://engines/walmart, etc.) byte-for-byte. Clients thatread a specific engine via
resources/readwith the concrete URIare unaffected —
mcp.read_resource("serpapi://engines/google")resolves the template and returns the same JSON shape.
ResourceResult(contents=[ResourceContent(content=json.dumps(...), mime_type="application/json")])— the same
ResourceContentenvelope the static resources used,with the same
application/jsonMIME type.mcp.resourcedecorator (which auto-detects{...}URIparameters) and
mcp.types.Annotations.engine_nameand missing files nowreturn structured JSON
{"error": "..."}payloads viaResourceContent, instead of being silently dropped at startup.This is a strict superset of the previous behavior (no previously
working read now returns nothing).
uv format --check(ruff-formatted). The diff is+49/-23insrc/server.py; no other files touched.Validation
End-to-end verified locally with
uv run python:Payload size measurement:
Scope boundary (what this PR does NOT include)
engines_indexresource (companion PR[MCP] Add resource annotations to engines_indexis separate).searchtool or its annotations (covered in[MCP] Add safety annotations to search tool #43).
engines/. The templateconsumes the same on-disk format.
cover this refactor once merged).
protocol is unchanged; only the server's internal resource
registration strategy changes.
_get_engine_files()— it remains useful for theengines_indexresource which still enumerates engines.Open question for maintainers
The companion
[MCP] Add resource annotations to engines_indexPRis staged alongside this one. Recommended merge order:
engines_indexannotations (smaller, isolated,no behavior change).
on the wire).
Happy to split, reorder, or refactor further if the maintainer
prefers a different boundary.