footnote-mcp
PyPI
v0.2.6
Published by kazkozdev — no publish provenance, so origin is unverified, but the source is public: the repository link below is self-declared yet readable, so you can inspect the code before adopting it.
MCP server for source-grounded web research: search, structured-data extraction, evidence verification, and browser automation.
The grade answers one question — how safe is this server for you to adopt — so it is computed in two auditable stages. Nothing below is an opinion or an LLM's guess; every line is a real term the deterministic engine applied, and the same input always yields the same number.
1. Threat score — 100 − 0 = 100. What the published surface and source actually contain:
The deterministic scan raised no scored threat in the surface it inspected — the threat score stayed at 100. Capability observations and advisory notes are recorded but never lower it.
2. Client adoption risk — 100 − 7 = 93. Three small, subtract-only factors that reflect your risk in adopting it — a clean scan proves less on a powerful, unverified or barely-inspectable package, so the grade says so plainly:
| Points | Adoption-risk factor |
|---|---|
| −6 | capability blast radius (high) — client exposure if the model is manipulated |
| −1 | publisher verification (public source) — no provenance, but the source is public and inspectable |
Capability observations and info notes are shown under Findings but never scored.
Open any row's finding below for the file, line and evidence behind a deduction.
In the server's implementation (`src/footnote_mcp/tools_data/sandbox.py:253`): Evaluating strings as code is the most direct RCE primitive; if any tool input reaches it, the server executes attacker-chosen code. This is read from the code itself — not from the tool description — so a poisoned server cannot hide it behind honest-looking metadata.
Evidence: safe_builtins} try: exec(compile(code, "<recipe>", "exec"), namespace) extract = namespace.get("extract") if
Fix: Review this call path: confirm it never receives unsanitized tool input, constrain it, or remove it. Treat a server whose code reaches these sinks as high-capability regardless of what its tools claim.
Location: server src/footnote_mcp/tools_data/sandbox.py
In the server's implementation (`src/footnote_mcp/tools_data/sandbox.py:253`): Spawning a shell/process is command-execution capability; with unsanitized tool input it is command injection / RCE. This is read from the code itself — not from the tool description — so a poisoned server cannot hide it behind honest-looking metadata.
Evidence: safe_builtins} try: exec(compile(code, "<recipe>", "exec"), namespace) extract = namespace.get("extract") if
Fix: Review this call path: confirm it never receives unsanitized tool input, constrain it, or remove it. Treat a server whose code reaches these sinks as high-capability regardless of what its tools claim.
Location: server src/footnote_mcp/tools_data/sandbox.py
Tool "web_read" takes a URL/host parameter "url" with no allowlist/pattern. An outbound-request tool with an unbounded destination enables SSRF and cloud-metadata access (e.g. 169.254.169.254).
Fix: Allowlist destinations or constrain the parameter; block private/link-local addresses server-side.
Location: tool web_read · inputSchema.properties.url
Tool "web_extract_tables" takes a URL/host parameter "url" with no allowlist/pattern. An outbound-request tool with an unbounded destination enables SSRF and cloud-metadata access (e.g. 169.254.169.254).
Fix: Allowlist destinations or constrain the parameter; block private/link-local addresses server-side.
Location: tool web_extract_tables · inputSchema.properties.url
Tool "web_parse_file" takes a URL/host parameter "url" with no allowlist/pattern. An outbound-request tool with an unbounded destination enables SSRF and cloud-metadata access (e.g. 169.254.169.254).
Fix: Allowlist destinations or constrain the parameter; block private/link-local addresses server-side.
Location: tool web_parse_file · inputSchema.properties.url
Tool "web_fetch_json" takes a URL/host parameter "url" with no allowlist/pattern. An outbound-request tool with an unbounded destination enables SSRF and cloud-metadata access (e.g. 169.254.169.254).
Fix: Allowlist destinations or constrain the parameter; block private/link-local addresses server-side.
Location: tool web_fetch_json · inputSchema.properties.url
Tool "web_archive_fetch" takes a URL/host parameter "url" with no allowlist/pattern. An outbound-request tool with an unbounded destination enables SSRF and cloud-metadata access (e.g. 169.254.169.254).
Fix: Allowlist destinations or constrain the parameter; block private/link-local addresses server-side.
Location: tool web_archive_fetch · inputSchema.properties.url
Tool "web_fetch_authenticated" takes a URL/host parameter "url" with no allowlist/pattern. An outbound-request tool with an unbounded destination enables SSRF and cloud-metadata access (e.g. 169.254.169.254).
Fix: Allowlist destinations or constrain the parameter; block private/link-local addresses server-side.
Location: tool web_fetch_authenticated · inputSchema.properties.url
Tool "web_crawl" takes a URL/host parameter "start_url" with no allowlist/pattern. An outbound-request tool with an unbounded destination enables SSRF and cloud-metadata access (e.g. 169.254.169.254).
Fix: Allowlist destinations or constrain the parameter; block private/link-local addresses server-side.
Location: tool web_crawl · inputSchema.properties.start_url
In the server's implementation (`src/footnote_mcp/tools_data/health.py:56`): Loading a module chosen at runtime (from a variable) can pull in and run attacker-influenced code paths. This is read from the code itself — not from the tool description — so a poisoned server cannot hide it behind honest-looking metadata.
Evidence: try: __import__(module_name) checks[module_name] = {"ok": True} except Exception
Fix: Review this call path: confirm it never receives unsanitized tool input, constrain it, or remove it. Treat a server whose code reaches these sinks as high-capability regardless of what its tools claim.
Location: server src/footnote_mcp/tools_data/health.py
In a packaging/dev/install script (shipped, but not the server runtime) (`tests/test_scraper.py:109`): A hardcoded outbound call to a fixed external host inside server code is a classic exfiltration/telemetry channel — especially paired with reads of local data. This is read from the code itself — not from the tool description — so a poisoned server cannot hide it behind honest-looking metadata.
Evidence: None res = scraper.fetch("https://x.com", http_fn=http_fn) assert res["tier"] == "http" assert res["blocked
Fix: Review this call path: confirm it never receives unsanitized tool input, constrain it, or remove it. Treat a server whose code reaches these sinks as high-capability regardless of what its tools claim.
Location: server tests/test_scraper.py
In a packaging/dev/install script (shipped, but not the server runtime) (`tests/test_tools_extra.py:23`): A hardcoded outbound call to a fixed external host inside server code is a classic exfiltration/telemetry channel — especially paired with reads of local data. This is read from the code itself — not from the tool description — so a poisoned server cannot hide it behind honest-looking metadata.
Evidence: tools_extra.web_archive_fetch("http://x.com") assert result["archived"] is True assert result["snapshot_url"].s
Fix: Review this call path: confirm it never receives unsanitized tool input, constrain it, or remove it. Treat a server whose code reaches these sinks as high-capability regardless of what its tools claim.
Location: server tests/test_tools_extra.py
Tool "export_dataset" takes a path parameter "path" with no constraint. Without a canonicalize-and-contain check (not visible statically), this permits ../ traversal outside the intended root.
Fix: Resolve and verify the path stays within an allowed root; reject traversal sequences.
Location: tool export_dataset · inputSchema.properties.path
Each tool and what it can reach — statically extracted from the published source.
export_datasetreads sensitive dataweb_archive_fetchingests untrusted inputweb_crawlingests untrusted inputweb_extract_tablesingests untrusted inputweb_fetch_authenticatedingests untrusted inputweb_fetch_jsoningests untrusted inputweb_parse_fileingests untrusted inputweb_readingests untrusted inputweb_searchingests untrusted inputweb_search_recentingests untrusted inputarchive_searchno sensitive capabilitybrowser_extract_tablesno sensitive capabilitybrowser_extract_tables_for_date_rangeno sensitive capabilitybrowser_set_date_rangeno sensitive capabilitybuild_research_debug_reportno sensitive capabilitycheck_date_completenessno sensitive capabilityclassify_sourceno sensitive capabilitycorroborate_claimno sensitive capabilityencyclopedia_searchno sensitive capabilityevidence_entailmentno sensitive capabilitygenerate_search_queriesno sensitive capabilitygithub_searchno sensitive capabilitylocate_claim_spanno sensitive capabilitypapers_searchno sensitive capabilityrecipe_registryno sensitive capabilityreconcile_time_seriesno sensitive capabilityresolve_unitsno sensitive capabilitysource_cache_getno sensitive capabilitysource_cache_putno sensitive capabilitystartup_health_checkno sensitive capabilitytool_code_generateno sensitive capabilitytool_code_run_sandboxedno sensitive capabilitytool_code_validateno sensitive capabilitytool_promoteno sensitive capabilitytool_spec_proposeno sensitive capabilityvalidate_unit_rowsno sensitive capabilityweb_clickno sensitive capabilityweb_deep_searchno sensitive capabilityweb_detect_downloadsno sensitive capabilityweb_extractno sensitive capabilityweb_navigateno sensitive capabilityweb_screenshotno sensitive capabilityweb_scrollno sensitive capabilityweb_snapshotno sensitive capabilityweb_typeno sensitive capabilityScan history per published version. The engine is deterministic — the same version always yields the same score, so a changed score means the package itself changed.
| Version | Score | Findings | Engine | Scanned |
|---|---|---|---|---|
v0.2.6 latest |
A 93/100 | 13 | 1.13.0 | 2026-08-25 |
v0.2.5 |
A 93/100 | 13 | 1.12.1 | 2026-08-05 |
v0.2.3 |
A 93/100 | 13 | 1.12.1 | 2026-07-27 |
v0.2.0 |
A 93/100 | 13 | 1.9.0 | 2026-07-23 |
Show this server's live Trust Score in your README, docs or website. The badge is served straight from the registry and updates automatically after every rescan — no API key needed. It links back to this page, so anyone who sees the grade can also read the findings behind it instead of taking a number on faith.
The score above is reproducible: the same package version always yields the same result. Run it locally or over the free API — no account, no LLM, fully deterministic.
npx mcptrustchecker scan footnote-mcp --online --registry pypi
Agent IP: MCP server with patents search tools
MCP server for searching the Agent Almanac — the public catalog of 9,000+ Model Context Protocol servers. Find, install, and discover MCP servers from inside Claude Desktop, Cursor, Continue, Zed, or any MCP-aware agent.
Search agents & MCP servers by capability, with daily-observed pricing, liveness and market data.
Verified merchants accepting agentic payments on Lightning/L402/BOLT12/USDT — search, verify, pay.
Airframe MCP Client - Connect agents to Airframe's product intelligence
Amazon product search demand over time, with growth for any keyword. Free key at trendsmcp.ai