A collection of multiple separate but related MCP servers on Cloudflare Workers for UT's AI Spark system. Each server runs as a Durable Object with its own tools and URL path, so it is treated as a separate MCP server for all intents and purposes.
- Servers – MCP servers defined with
defineMcpServer/defineTool, each bound to a Durable Object and a route. - Shared – All shared funcionality between servers.
src/shared/mcp-server-creator.tsis where thedefineMcpServerfunction which is how all servers are created.
npm install
npm run devThe Worker runs at http://localhost:8787. Each MCP server is served at its path (e.g. http://localhost:8787/basic-tester, http://localhost:8787/other).
npm run deployUses the spark-plus Worker name in wrangler.jsonc. After deploy, server URLs are https://spark-plus.<your-subdomain>.workers.dev/<server-path>.
- Implement the server in
src/servers/<name>/main.tsusingdefineMcpServeranddefineToolfromsrc/shared/mcp-server-creator.ts. - Register the Durable Object in
wrangler.jsonc: add a migration for the new class and a binding indurable_objects.bindings. - Wire the route in
src/project-source.ts: import the server class and its metadata, add the metadata to theMCP_SERVERSarray, and export the class in theexport { ... }at the bottom.
The request handler in src/project-source.ts matches the request path to each server’s url_prefix and forwards to that server.
This project uses Streamable HTTP, not stdio:
- Run
npm run devso the Worker is athttp://localhost:8787. - Run
npx @modelcontextprotocol/inspector@latestand open the URL it prints. - In the Inspector, choose “Enter URL” (do not use “Run command (stdio)”).
- Enter a server URL, e.g.
http://localhost:8787/basic-tester, then Connect and List tools.
The grants-research server enforces strict Firecrawl usage guardrails:
- Lifetime hard cap: at most
500pages can be fetched across all runs. - Monthly freshness window: snapshots are considered fresh for
30days. - Conservative early refresh: before 30 days, refresh is only allowed when:
- at least
14days have passed since last refresh, and - enough budget remains for a minimum run.
- at least
- Early refresh run cap: at most
2pages per run.
Each tool response exposes budget metadata:
pages_remainingpages_used_this_refresh(for refresh calls)refresh_decision_reasonfresh_until
refresh_decision_reason values:
fresh_cache— skipped because cache is still freshearly_refresh— allowed limited refresh before monthly TTLmonthly_refresh— regular refresh after monthly stalenessbudget_low— skipped because remaining budget is below run thresholdbudget_exhausted— skipped because lifetime budget is depleted
| Command | Purpose |
|---|---|
npm run dev |
Local development |
npm run test |
Budget/TTL policy tests |
npm run cf-typegen |
Regenerate Worker types after binding changes |
npm run type-check |
TypeScript check |
npm run lint |
Lint; npm run lint:fix to fix |
For Cloudflare Workers and product limits, see AGENTS.md.