Skip to content

v1.2.0 — FastMCP 3 upgrade + observability rebuild

Latest

Choose a tag to compare

@damionrashford damionrashford released this 17 Apr 08:07
· 25 commits to main since this release
8d9b704

Highlights

FastMCP 2 → 3 upgrade

  • Bumped fastmcp from 2.14.3 to 3.2.4
  • Dropped removed constructor kwargs (include_fastmcp_meta, on_duplicate_tools)
  • Converted all 4 prompts from raw {"role","content"} dicts to typed fastmcp.prompts.Message
  • Deleted dead MCPPaginationManager (FastMCP 3 handles list-pagination natively via list_page_size, not needed for a 10-tool server)

Middleware stack rebuild

  • Swapped custom Timing / Logging / ErrorHandling for FastMCP built-ins
  • Added SlidingWindowRateLimitingMiddleware (100 req / session / min)
  • Added ResponseCachingMiddleware scoped to URL-fetching tools only (see bug fix below)
  • Added ResponseLimitingMiddleware (1 MB cap on content_operations, map_website, document_analysis)
  • Added PingMiddleware (30 s keepalive for streaming HTTP)
  • Kept custom SecurityMiddleware + PerformanceMonitoringMiddleware

Tools features

  • ToolAnnotations on all 10 tools: 9 read-only get readOnlyHint=True, openWorldHint=True; research_memory is correctly flagged readOnlyHint=False, destructiveHint=True, openWorldHint=False
  • Per-tool timeout= values (15 s for local-only tools up to 180 s for research_topic entity mode + document_analysis)
  • ToolResult dual-channel output on content_operations score and find_conflicts — humans see markdown, agents parse structured_content
  • mask_error_details=True on the server; ValueError → ToolError conversions in analysis.py so legit validation messages still reach clients unmasked
  • ctx.report_progress() at phase boundaries in research_topic entity mode, map_website, content_operations find_conflicts

Critical bug squashed

ResponseCachingMiddleware defaulted to caching tools/list, resources/list, prompts/list, read_resource, and get_prompt alongside call_tool. That froze tool schemas on the client side — when annotations, timeouts, or params changed, clients still saw the old schema until the 5-minute TTL expired. The tool list cache also caused cross-test pollution during development (an earlier inline_tool test stuck around as a ghost tool for the rest of the run). All list/read/get surfaces are now cache-disabled; only call_tool stays cached, and only for content_operations + document_analysis whose output is a near-pure function of the URL.

Dead code purge

Audited every .py under src/core/ against its actual callers. Removed:

  • src/core/bypass/ — paywall/proxy helpers, never called
  • src/core/search/core/main.py — orphaned, broken import to nonexistent google scraper
  • src/core/search/core/engine.pyMultiEngineSearch, superseded by multi_engines.py
  • src/core/scientific/academic_search.py — wrapper classes, tools bypass them
  • src/core/content/cleaners.py — 4 cleaner classes, unused
  • src/core/content/parsers.py — 3 parser classes, unused
  • src/core/fetch/{batch,enhanced,resource}.py — never called
  • GenericContentExtractor, GoogleSpecificExtractor — unused subclasses
  • 5-class Fetcher hierarchy in fetch/base.py — kept only base_fetch_url + stream_fetch
  • CacheManager forwarding wrapper in cache/__init__.py
  • 4 unused traverse convenience wrappers
  • 5 never-called metrics functions
  • Multiple stale __init__.py re-exports (GoogleTrendsAPI, ContentProcessor, LLMsTxtGenerator, …)

Three real bugs fixed along the way

  1. metrics.get_all_metrics crashed the /metrics endpoint — it was sync but called asyncio.run(cache_manager.get_stats()) inside; asyncio.run() can't be called from a running event loop. Made it async, use await.
  2. easyocr.image_to_string() doesn't existpdf/analyzer.py::_analyze_image called a nonexistent function. Real API is Reader([...]).readtext(image). Fixed with detail=0, paragraph=True.
  3. _ocr_pdf_pages was a stub returning "" — but ocr_used: True was being reported anyway. Removed the stub call, report honest ocr_used: False until pdf2image is wired up.

Also

  • Added pre-commit>=3.5.0 to dev deps (CI's uv run pre-commit was failing with "binary not found")
  • Renamed test_rejects_localhost_origin_by_defaulttest_allows_localhost_for_local_development to match the actual CORS policy
  • Marked test_reddit_search as skipif CI (Cloudflare blocks GitHub Actions IP ranges; source is resilient locally)
  • New tests/test_inmemory_transport.py — sub-1s MCP-protocol smoke tests that catch schema/prompt/middleware regressions

71/71 tests pass, 5/5 CI checks green on the merge commit.