google-surf-mcp
This server provides Google search and web content extraction without requiring an API key, using a persistent Chrome profile with stealth capabilities.
search(query, limit?)– Run a single Google search and get title, URL, and snippet for up to 20 results.search_parallel(queries[], limit?)– Execute up to 10 queries simultaneously using a pool of 4 concurrent workers, returning title/URL/snippet per result.extract(url, max_chars?)– Fetch a URL and return clean article content as Markdown (via Mozilla Readability), with optional truncation up to 50,000 characters.search_extract(query, limit?, max_chars?)– Combines search and extraction in one call: performs a Google search and extracts full article Markdown from each result page in parallel (up to 10 results, up to 20,000 chars each), replacing the typical two-step search + fetch workflow.
Key highlights:
No API key, proxies, or external solvers required
Automatic CAPTCHA recovery: opens a visible Chrome window for a human to solve, then retries automatically
Blocks images/media/fonts for faster performance
Designed for local use with a persistent, warm Chrome profile
Provides tools for searching Google and extracting web content without API keys, including search, parallel search, URL extraction, and combined search+extract.
google-surf-mcp
English | Korean
Web searches, papers, and GitHub repositories are stored as PKM, ontology, and lineage. The view above is generated with
project_memory(action="export", export_format="html", export_view="graph", all_projects=true).
"Turn Google Search, Papers, and Codebases into an Automatic Local Knowledge Graph and lineage for AI Agents with Zero API Key, Zero External Server."
Google Surf stores search and extraction results in a project-scoped local knowledge graph.
As you search, papers, code, web sources, session intent, plans, experiments, and decisions accumulate in a personal PKM. New research searches stored knowledge and fresh web results together, reducing repeated work while continuing to discover new information.
Projects remain isolated by default. Only verifiable links such as matching DOIs, repository URLs, or explicit aliases are added, so knowledge from one project can be reused in another without merging the original records.
Retrieval runs exact search, BM25, vector search, code and graph search, and live web independently, then combines them with RRF and one shared reranker.
Live web + Papers + Codebases + Project memory
↓
Exact + BM25 + Vector + Code graph + Graph PPR
↓
RRF + Shared reranker
↓
Results with evidence and provenanceSeven tools are available by default: search / search_parallel / extract / scholar_search / project_memory_search / project_memory / health.
Research mode and automatic capture are enabled by default. Set SURF_RESEARCH=false to use search and extraction without opening the database or graph sidecar; the project memory tools are not registered in that mode.
With research mode disabled, live search and search_parallel results still use a lightweight in-memory reranker. It fuses the provider order with query BM25 rank through RRF without loading the vector model or opening local storage.
Browser search needs no API key. SearchApi can be configured as an optional primary provider or fallback.
Core features
Web, paper, and codebase search: Google web search is the default, while Scholar is used for paper-specific metadata. SearchApi can act as an optional primary provider or fallback.
Web and academic document extraction: HTML and PDF extraction returns available titles, authors, DOIs, publication metadata, and body text.
searchandsearch_parallelcan include abstracts or full bodies.Automatic project memory: Search results, extracted bodies, and code repositories are stored in the current project. Unread results remain metadata; extracted content becomes active RAG evidence.
Structured codebase search: Tree-sitter links files, symbols, imports, and calls from local projects and relevant GitHub repositories. Exact, BM25, vector, and graph search retrieve the code.
Graph hybrid retrieval: Fresh web results, papers, stored content, codebases, and the project graph are searched independently. Exact, BM25, vector, and PPR candidates are combined through RRF and one shared reranker.
Ontology and data lineage: Web sources, papers, code, plans, experiments, and decisions become typed entities and relations. Evidence paths run from source through documents, chunks, symbols, evidence, and assertions.
Cross-project knowledge reuse: Projects remain isolated but selected projects can be searched together. Only verifiable links connect matching entities.
Durable research history: Session intent, plans, experiments, failures, and decisions supplied by the MCP host are stored as revisions and linked to their supporting evidence.
Local graph analysis and export: PageRank, PPR, connected components, and Louvain communities run without a separate Neo4j server. Results export to HTML, Graphviz, D3 JSON, and Neo4j import formats.
Related MCP server: G-Search MCP
Search
API-key-free system Chrome search
Dedicated logged-out profile that never reads or copies the user's Chrome profile
Multi-strategy SERP parsing with geometric verification
Sponsored block and knowledge panel removal
CAPTCHA detection and environment-specific recovery
Parser self-healing and context fallback
Numbers
Search speed
result | |
search | 4.0-5.1s/query |
scholar_search | 3.8-5.6s/query |
Live web search reranker check
32 held-out queries | nDCG@5 | MRR | Precision@5 |
Provider order | 0.8949 | 0.8203 | 0.6375 |
BM25 + RRF | 0.8971 | 0.8203 | 0.6500 |
web search responses with BM25+RRF reduce large retrieval payloads by up to 75%. Measured across three uncached queries per provider on a workstation with a 1Gb/s connection. Network and Google response time vary.
Tech Stack
Runtime: Node.js, TypeScript, Model Context Protocol SDK
Web search: System Chrome + CDP, Playwright compatibility fallback, SearchApi fallback
Web extraction: Mozilla Readability, Turndown
PDF extraction: LiteParse/PDFium, optional OCR,
pdf-libmetadata parsingCode collection: Local project roots and gated GitHub sparse download
Code parsing: Tree-sitter for files, symbols, imports, and call relations
Code search: Exact lookup, BM25, Multilingual E5 vector search, and graph PPR
Local database: Embedded SurrealDB on RocksDB
Hybrid retrieval: Live web, papers, project memory, and codebase results combined through RRF
Ranking: Reciprocal Rank Fusion and shared vector reranking
Graph analysis: Graphology, PageRank, PPR, connected components, and Louvain communities
Knowledge model: Versioned ontology, data lineage, cross-project schema and entity linking
Recovery: CAPTCHA recovery, Playwright pool fallback, and deterministic parser self-healing
Install
Requires Node 20.18.1+. Browser mode also requires Google Chrome or Chromium.
npx google-surf-mcp # actual MCP - register in client configFirst tool call auto-bootstraps the warm profile (you may see Chrome open briefly).
Or local clone:
git clone https://github.com/HarimxChoi/google-surf-mcp
cd google-surf-mcp
npm installIf auto-bootstrap fails (rare), run it manually:
npm run bootstrapOverride paths if needed:
CHROME_PATH=/path/to/chrome SURF_TZ=America/New_York npm run bootstrapOptional Codex output protection
Google Surf can install an opt-in Codex hook that ranks oversized Bash output before it reaches the model. The host still executes the command; the hook only applies stateless source-order, exact, and BM25 ranking with RRF. It opens neither the Google Surf database nor a browser and does not store command output.
npx -y google-surf-mcp@latest hooks install --host codexRestart Codex, then open /hooks to review and trust the definitions. Shell output larger than 6,000 characters is reranked and capped per result, without a cumulative per-turn command or output limit. Two identical or near-duplicate searches and explicit foreground polling loops are blocked. Existing write_stdin polls for an already-running unified command remain controlled by the Codex runtime, not this hook.
npx -y google-surf-mcp@latest hooks status --host codex
npx -y google-surf-mcp@latest hooks update --host codex
npx -y google-surf-mcp@latest hooks uninstall --host codexUse with Claude Code
Paste this into your ~/.claude.json:
{
"mcpServers": {
"google-surf": {
"command": "npx",
"args": ["-y", "google-surf-mcp"]
}
}
}Restart Claude Code. All seven tools, including project_memory_search and project_memory, are available by default.
For other MCP clients, use the same JSON shape in their config file.
Search providers
Browser search remains the default. SearchApi can be selected as the primary provider or used only when browser search fails.
value | behavior |
| Default. Uses system Chrome with a dedicated logged-out profile, keeps native search windows hidden, and does not require |
| Uses SearchApi as the primary provider and does not initialize Chrome for that tool. |
| Tries the current browser tier once, then uses SearchApi on browser errors, CAPTCHA/rate limits, profile failure, or parser degradation. It does not wait for human CAPTCHA recovery. Successful and normal empty browser responses are not repeated. |
SURF_SEARCH_PROVIDER controls search and search_parallel. SURF_SCHOLAR_PROVIDER controls scholar_search. SearchApi modes require your own SearchApi account, key, and available credits.
SURF_BROWSER_ENGINE=auto selects native Chrome on a local desktop and the Playwright compatibility path in cloud or remote-debug mode. Native mode uses a normal hidden Chrome window, not headless Chrome. Set native or playwright to pin the engine.
{
"mcpServers": {
"google-surf": {
"command": "npx",
"args": ["-y", "google-surf-mcp"],
"env": {
"SEARCH_API": "your-searchapi-key",
"SURF_SEARCH_PROVIDER": "fallback",
"SURF_SCHOLAR_PROVIDER": "searchapi"
}
}
}
}Local clone variant:
{
"mcpServers": {
"google-surf": {
"command": "node",
"args": ["/abs/path/to/google-surf-mcp/build/index.js"]
}
}
}Tools
search(query, limit?, extract_mode?, extract_limit?, response_content?, max_chars?)- primary single-query tool for live discovery and reading. When new sources must be found and read, setextract_modein this call instead of downloading PDFs, cloning repositories, or callingextractseparately. Useextractonly when the exact public URL is already known and no discovery is needed. Withproject_id, stored project knowledge is fused with live results, but the call never becomes local-only.limitis 1-20. Extraction defaults tonone;extract_limitis 1-10 with default 5.response_contentdefaults tosummaryto bound one-call output.scholar_search(query, limit?)- Google Scholar metadata search, max 10 papers. Supports browser, SearchApi primary, and fallback modes.search_parallel(queries[], limit?, extract_mode?, extract_limit?, response_content?, max_chars?)- primary multi-query tool for broad live discovery and reading through a continuous four-tab queue. Setextract_modein the same call when public web pages, PDFs, papers, or GitHub repositories must be read. Use local PDF tools only for local files or visual layout work, and clone repositories only for editing, building, testing, or full Git history.limitis 1-20 per query. The call-wideextract_limitdefaults to 12 and allows up to 20 for abstract; full defaults to and allows 10.response_contentdefaults tosummaryto bound one-call output.Integrated search extraction reports
requested,applied,skipped,truncated, andtotal_chars.remaining_urlscan be passed toextractwithout repeating the search.extract(url, max_chars?, mode?, response_content?)- secondary extraction tool for an exact public URL when no new discovery is required. If sources still need to be found, usesearchorsearch_parallelwithextract_modeinstead.mode="full"(default): reads up to 1000000 characters for research capture. Research mode stores deterministic 4000-character chunks;response_content="full"returns up to 50000 characters andsummaryreturns a 1500-character evidence excerpt.mode="abstract": ~1500-char survey (PDF page 1 or HTML meta description). Document metadata is included and stored with the survey when research mode is enabled.mode="metadata": metadata without body text. Returns available title, authors, publication, dates, DOI, description, keywords, canonical URL, and PDF properties including page count.GitHub repository URLs read the README in metadata mode. Abstract and full use the same download gate and differ only in indexed source depth.
Response: content fields plus available document metadata. Failures return
{ error }, never throw.
project_memory_search(query, query_variants?, project_id?, include_project_ids?, all_projects?, limit?)- searches stored local knowledge only. Up to 19 optional variants run inside one broker request with batched query embeddings, RRF fusion, evidence-seeded graph expansion, and one final rerank againstquery. The response contains only bounded query-focused summaries from the final ranking; stored bodies stay in the database. Exact identifiers and quoted phrases are added deterministically. Use this instead of repeated terminal calls. It never opens a browser or calls Google or SearchApi.project_memory(action, ...)- manages durable project knowledge whenSURF_RESEARCH=true.action="search": compatibility alias forproject_memory_search.action="show": always returns a bounded summary with counts and active record IDs.detail_level="full"is accepted for compatibility but never dumps every record body. Useproject_memory_searchfor relevant bodies ortarget_idfor one assertion or entity.action="record": stores the submitted body and returns only its ID, revision, and status.action="export": writes a standalone interactive HTML explorer, Graphviz DOT, D3 node-link JSON, or a Neo4j import bundle under<research-root>/exports.
health()- server status, including the local research runtime.
Need | Tool |
Search only previously stored research and project memory |
|
Find new information on the web |
|
Compare new web results with stored project knowledge |
|
Run several new web queries |
|
Replayable research collection
google-surf-collect runs a versioned JSON specification through one persistent MCP session. A specification can mix live search jobs with local-only project_memory_search jobs. Live jobs can extract bodies in the same call, while local jobs reuse indexed project knowledge without opening Google.
npx google-surf-collect examples/research-collection.example.jsonFrom a source checkout:
npm run build
npm run research:collect -- examples/research-collection.example.jsonA project workflow can also record durable sessions and plans, rebuild approved code roots, search the resulting local knowledge, and export its graph:
npm run research:collect -- examples/project-memory-workflow.example.jsonproject_memory collection jobs allow record, rebuild, and export. Destructive forget operations are not accepted by the collection schema. Project-level project_id is inherited by every job unless an all-project export is requested.
The output is append-only JSONL. Its manifest records the normalized specification hash, package version, Git commit, Node runtime, platform, project setup, and server health. Every search, record, rebuild, and export result records the stable job id, exact tool arguments, attempt, timestamps, elapsed time, response, and error state. Successful jobs are skipped on resume; failed jobs are retried. A changed specification requires a new output file. Set project_name with project_id when the runner should create a missing project; existing projects are reused.
Set retrieval_mode to live when prior project RAG state must not affect live result ranking. Results are still captured under project_id. Use hybrid when the collection intentionally ranks new web evidence together with stored project knowledge. API keys and environment variable values are never written to the collection log.
This makes the collection procedure and returned snapshot replayable and auditable. Live web results can still change with time, locale, network route, and upstream ranking.
Graph hybrid RAG with ontology and lineage
flowchart TB
subgraph SOURCES["1. Search and research"]
direction LR
LIVE["Live web<br/>Google browser • SearchApi fallback"]
PAPER["Pages and papers<br/>extract • Scholar metadata"]
PROJECT_INPUT["Code and project records<br/>local roots • GitHub • host-provided session/plan"]
end
INGEST["2. Deterministic capture<br/>normalize • deduplicate • content hash<br/>repository source gate • Tree-sitter"]
subgraph KNOWLEDGE_BASE["3. SurrealDB knowledge base"]
direction LR
CONTENT["Content and code index<br/>exact • BM25 • HNSW vector<br/>documents • chunks • symbols"]
PROV["Data lineage and provenance<br/>source → evidence → assertion<br/>valid time • recorded time • correction"]
ONTOLOGY["Versioned ontology<br/>core/project term revisions<br/>entity type • relation • alias • merge/split"]
MEMORY["Project memory<br/>session intent • plan revisions<br/>experiments • decisions"]
end
subgraph INTELLIGENCE["4. Graph intelligence"]
direction LR
SCHEMA["Cross-project schema linking<br/>type and relation alignment<br/>stable identifier → identity bridge"]
SIDECAR["Typed graph sidecar<br/>PageRank • Louvain • query-time PPR"]
end
FUSION["5. Hybrid retrieval<br/>live • exact • BM25 • vector • graph<br/>deterministic RRF • shared reranker • fresh-web floor"]
RESULTS["Results + provenance<br/>short storage receipt"]
LIVE --> INGEST
PAPER --> INGEST
PROJECT_INPUT --> INGEST
INGEST --> CONTENT
INGEST --> PROV
INGEST --> ONTOLOGY
INGEST --> MEMORY
ONTOLOGY --> SCHEMA
CONTENT --> SIDECAR
PROV --> SIDECAR
MEMORY --> SIDECAR
SCHEMA --> SIDECAR
LIVE --> FUSION
CONTENT --> FUSION
SIDECAR --> FUSION
FUSION --> RESULTS
RESULTS -. "automatic search/extract capture" .-> INGEST
classDef inputStyle fill:#eef6ff,stroke:#2563eb,color:#172554
classDef processStyle fill:#fff7ed,stroke:#ea580c,color:#431407
classDef storageStyle fill:#ecfdf5,stroke:#059669,color:#052e16
classDef intelligenceStyle fill:#f5f3ff,stroke:#7c3aed,color:#2e1065
classDef outputStyle fill:#f8fafc,stroke:#475569,color:#0f172a
class LIVE,PAPER,PROJECT_INPUT inputStyle
class INGEST processStyle
class CONTENT,PROV,ONTOLOGY,MEMORY storageStyle
class SCHEMA,SIDECAR intelligenceStyle
class FUSION,RESULTS outputStyleOne local knowledge base
One SurrealDB instance persistently stores web results, papers, code, sessions, plans, experiments, decisions, ontology, and provenance. A single local research broker owns the embedded RocksDB connection. Multiple MCP sessions connect to it through authenticated local IPC, run reads concurrently, and order writes without opening the database themselves. Graph projections and analytics such as PageRank and communities are derived data that can be rebuilt from source hashes and ontology versions.
Ontology and cross-project links
The versioned ontology preserves entity type and relation changes as revisions. Schema linking aligns project-specific types and relations with the shared schema. Entity linking connects the same paper, repository, or entity only when verifiable identifiers such as a DOI, repository URL, or explicit alias match. Ambiguous candidates are not linked automatically.
Data and research lineage
Source lineage:
source → document → chunk → evidence → assertionCode lineage:
repository → directory → file → symbol → import/callResearch lineage:
session → intent → plan revision → experiment → decision
This preserves the evidence behind claims and decisions while retaining corrected or superseded history.
Graph retrieval
Graphology builds a typed graph projection from SurrealDB and computes PageRank, connected components, and Louvain communities. At retrieval time, related nodes seed PPR-based multi-hop search. Live web, exact, BM25, vector, and graph candidates are combined through deterministic RRF and one shared reranker.
Local multi-query retrieval batches query embeddings, fuses lexical and vector candidates first, expands the graph once, hydrates selected chunks once, and reranks once. Graph-only all-project searches use a lightweight memory-node index and verified identity aliases to select at most four graph scopes instead of constructing every project graph at query time.
Project isolation and knowledge reuse
project_id selects where new results are stored. include_project_ids expands the read scope without changing the write target. Original records remain isolated by project, while verified schema and entity links allow papers, code, and experiment results to be reused across selected projects.
Interactive graph and export
Use project_memory(action="export", export_format="html", export_view="graph"). The returned standalone HTML opens locally without a server and contains three coordinated views. Use project_id for one project, include_project_ids for a selected combined graph, or all_projects=true for every project.
PKM groups the integrated project graph by community and sizes nodes by PageRank.
Lineage separates source and code lineage from session, intent, plan, experiment, and decision lineage while keeping both flows aligned by stage.
Ontology shows core types and relations, aligned shared schema, and typed instances. A verified identity layer appears only when stable identifiers or explicit aliases prove a cross-project match.
Search, type filters, one to three hop local focus, pan, zoom, and the provenance inspector work inside the file. Large graphs use a deterministic semantic projection that balances node type, PageRank, degree, and community coverage. The viewer reports source and displayed counts, replaces internal IDs with local aliases, disambiguates repeated labels, and does not embed source IDs, local paths, node bodies, plan text, or evidence quotes.
The project menu switches between every project embedded in the export and an integrated All projects view. Use all_projects=true when exporting to include every named project in the local database, or use project_id and include_project_ids for a bounded set. Clicking empty canvas space clears local node focus. PNG exports the current canvas, while JSON exports the current tab, project, type filters, and local focus using only the anonymized viewer payload. The standalone file has a nonce-bound script CSP, makes no network connections, and permits source links only for stripped public HTTP or HTTPS URLs.
Neo4j export
Use project_memory(action="export", export_format="neo4j", export_view="graph"). The returned directory contains nodes.csv, relationships.csv, constraints.cypher, load.cypher, manifest.json, and README.txt. PageRank, community, ontology, lineage, project IDs, source IDs, and evidence IDs are preserved. Node bodies, plan text, and evidence quotes are not exported.
For a new or empty local database, run the Neo4j offline importer from the export directory:
neo4j-admin database import full --nodes=nodes.csv --relationships=relationships.csv neo4jFor an existing local database, copy both CSV files to the Neo4j import directory, then run:
cypher-shell -f constraints.cypher
cypher-shell -f load.cypherThe offline importer creates typed node labels and relationship types. The online loader uses SurfNode and SURF_RELATION, retaining the original kinds and relationship types as properties. neo4j-admin database import full is intended for a new or empty database; use LOAD CSV for an existing database. See the official Neo4j import and LOAD CSV documentation. Bolt is a connection protocol, not an export file format, so this command does not connect to or modify a Neo4j server.
Storage scope and security
Research mode is enabled by default. search, search_parallel, scholar_search, and extract results are captured automatically. Session intent, plans, experiments, and decisions are stored only when the MCP host sends them through project_memory; versioning, ontology mapping, and lineage linking then run automatically. Retrieval mode is server configuration, not a per-call argument.
Image retrieval, image embeddings, and visual reranking are not part of research memory. OCR is used only to recover searchable text from scanned PDF pages.
Credential and private-key files are excluded from body indexing. HTML exports omit source IDs, local paths, node bodies, plan text, and evidence quotes. The viewer initiates no network requests and opens stripped public HTTP or HTTPS source links only after user action.
Project and assertion deletion require a count preview and confirmation token. They create reversible tombstones and preserve evidence and correction history. Fact correction takes only target_id, replacement, and reason; the prior assertion remains as bitemporal history. Plan revisions are append-only. Experiments are bound to the active revision and must be finished explicitly as success, failed, or inconclusive. Logs are not used to infer an outcome. Receipts list stored categories only:
Project: Graph memory | Session: temporal graph research | Stored: paper 1 (Graphiti), repo 1 (getzep), search summaries 3 | Status: readySet SURF_RESEARCH=false to keep the database and sidecar closed and omit project_memory_search and project_memory. Obsidian and Notion sync are not included and will remain project-level opt-in when added.
Env vars
var | default | notes |
| unset | SearchApi API key. Required only when either provider setting is |
| unset | Alias for |
|
| Provider for |
|
| Provider for |
|
| Browser engine: |
| auto-detected | absolute path to Chrome binary |
|
| where the warm profile lives |
|
| enables local project memory, capture, indexing, |
|
| research search route: |
|
| embedded SurrealDB data directory |
|
| local 384-dimensional model used by HNSW vector retrieval and final reranking. The default model revision is pinned; |
|
| disables the ONNX CPU memory arena and memory pattern; set |
|
| ONNX intra-op thread count, clamped to 1-16 |
|
| automatically sparse-index at most one small, relevant GitHub repository per search call |
|
| maximum searchable source-text size for automatic GitHub indexing; assets are excluded |
|
| maximum searchable source file count for automatic GitHub indexing |
|
| how long the shared research broker remains available after the last client disconnects |
|
| maximum concurrent broker reads; identical in-flight reads share one operation |
|
| timeout per embedded SurrealDB query, clamped to 1-600 seconds; a timed-out retrieval lane is reported as partial while other lanes still return |
| unset | optional GitHub token that raises API limits for repository inspection |
| auto, max 4 | Tree-sitter worker count for initial code structure indexing |
|
| browser locale |
| system tz | e.g. |
|
| Controls Playwright extraction, compatibility, and recovery paths. Native search keeps a normal system Chrome window hidden and shows it only for CAPTCHA recovery. |
|
| set |
|
| lifetime of the background human-recovery window. MCP calls return immediately and do not wait for this timeout. |
|
| idle ms before closing the sequential ctx and pool. |
|
| set |
|
| full extraction limit (200-50000); abstract defaults to 1500 and per-call |
|
| OCR scanned/image PDFs via Tesseract (slower; off by default) |
|
| headless/serverless mode: TLS bypass + |
|
| pin a single stealth mode (chosen by |
|
| initial stealth tier; only consulted when |
|
|
|
|
| internal cap on Google-facing requests per minute |
|
| search cache TTL (24h); |
|
| LRU cap per cache namespace |
|
| cache directory |
|
|
|
|
|
|
|
| set |
|
| directory for jsonl telemetry files. UTC-dated one file per day ( |
|
| per-strategy outcome tracking + persisted reordering. Healing must win by 3 outcomes before reorder kicks in, so single-call flapping is impossible. Set |
|
| persistence path for healing state. Atomic tmp+rename writes; debounced 5s. |
|
| opt-in for LLM-assisted selector repair in the workflow-only |
|
| LLM repair provider: |
| provider default | model for LLM-assisted repair. Defaults to |
| unset | Anthropic key used only when LLM repair is enabled with the Anthropic provider. |
| unset | OrcaRouter key used only when LLM repair is enabled with the OrcaRouter provider. |
| unset | Alias for |
OrcaRouter
SURF_LLM_HEAL=true
SURF_LLM_PROVIDER=orcarouter
ORCAROUTER_API_KEY=...
SURF_LLM_MODEL=orcarouter/autoTroubleshooting
Native search keeps the current session open and shows Chrome when a CAPTCHA appears. Solve it in that window and retry; the next call verifies the page, restores the hidden window guard, and continues with the same session. SearchApi fallback remains available through
SURF_SEARCH_PROVIDER=fallbackandSURF_SCHOLAR_PROVIDER=fallback.Playwright CAPTCHA recovery has 4 modes (picked automatically from env):
default (local desktop): OS notification fires, headed Chrome opens, and the call returns; solve it and retry
SURF_HEADLESS=false: headed Chrome opens without a notification; solve it and retrySURF_REMOTE_DEBUG=true: DevTools port + instructions printed, attachchrome://inspectlocally to solveSURF_CLOUD_MODE=true: fail-fast withCAPTCHA_REQUIREDerror
Headed Chrome opens to a plain search box instead of CAPTCHA: just type any query in the box and press Enter. Subsequent calls work.
"Chrome not found": install Chrome or set
CHROME_PATH.Stale selectors: runtime per-strategy reorder (
SURF_SELF_HEALING, deterministic) plus a manually dispatched repair workflow (SURF_LLM_HEALoptional, human review required, never auto-merged).Playwright searches feel slower than expected: check
health().pool.fallback.truemeans the worker pool is using a single context. Native search uses one authenticated local browser broker across MCP sessions. The broker keeps one hidden Chrome process with up to four reusable tabs forsearch,search_parallel, andscholar_search. Query starts are staggered. A CAPTCHA shows and preserves that session for user recovery, then the window guard is restored on the next call. A browser crash starts a new session on the next call.SSRF:
extractblockslocalhost, private IPs, AWS metadata by default. SetSURF_ALLOW_PRIVATE=trueto allow them.Cache cleanup:
npm run cache:clearremoves search/extract and downloaded vector-model caches. It does not remove the research DB.The local research DB is not application-encrypted. Use OS account permissions and disk encryption such as BitLocker or FileVault when the machine or backups need at-rest protection.
Changelog
See CHANGELOG.md.
License
MIT
Available Tools
7 toolsextractKnown URL ExtractA
SECONDARY KNOWN-URL CONTENT EXTRACTION TOOL. Use only when the exact public URL is already known and no new web discovery is required. If sources still need to be found, use search or search_parallel with extract_mode instead. For semantic reading of a public PDF or GitHub repository, use this tool before local download or parsing. Local PDF tools are for local files, forms, OCR recovery, or visual layout inspection; general repository tools are for editing, building, testing, or full Git history. Fetch one public URL and return clean content. With research enabled, one local broker lets multiple MCP sessions query the same knowledge base concurrently and orders writes safely. For GitHub repository URLs, metadata reads the README; abstract and full use the same bounded download gate and differ only in indexed source depth. HTML via Mozilla Readability; academic PDFs (arxiv/biorxiv/Nature/OpenReview/NeurIPS/JMLR/PMLR/Springer/PubMed-via-PMC) auto-detected via Content-Type, %PDF magic, citation_pdf_url meta, and per-domain URL rules. Tiered depth: mode="metadata" returns document metadata without body text, mode="abstract" returns about 1500 chars for relevance checks, and mode="full" reads the full bounded source. response_content controls whether the response contains a 1500-character summary or up to 50000 characters. PDF and landing-page metadata are merged when available. With research enabled, abstract and full PDF reads are stored as searchable evidence with bibliographic metadata and provenance. Best-effort: failures return an errorInfo instead of throwing.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Public http(s) URL. Loopback/private IPs blocked unless SURF_ALLOW_PRIVATE=true. | |
| mode | No | Extraction depth. `full` = whole article body (default; uses Playwright if needed). `abstract` = cheap survey: PDF page 1 OR HTML meta description (~1500 chars); use to triage relevance before paying for full text. `metadata` = document metadata without body text: title, authors, publication details, dates, DOI, keywords, canonical URL, and PDF properties when available. Academic PDFs (arxiv/biorxiv/Nature/OpenReview/NeurIPS/JMLR/PMLR/Springer/PubMed-via-PMC) are auto-detected; abstract mode skips Playwright for them. | full |
| max_chars | No | Maximum returned characters when response_content=full. Defaults to 1500 for abstract and 50000 for full. | |
| project_id | No | Project memory id. | |
| session_id | No | Stable host task id. Reuses the same project session after restart. | |
| memory_handle | No | Reuse the handle returned by a prior project-aware call. | |
| session_intent | No | Current durable task intent. A changed value creates an immutable revision. | |
| response_content | No | Controls only the returned body. full returns up to max_chars; summary returns a 1500-character evidence excerpt. Research storage keeps the full captured text in deterministic chunks. | full |
Output Schema
| Name | Required | Description |
|---|---|---|
| doi | No | |
| url | No | |
| meta | No | |
| year | No | |
| error | No | |
| title | No | |
| is_pdf | No | |
| length | No | |
| memory | No | |
| authors | No | |
| content | No | |
| creator | No | |
| excerpt | No | |
| subject | No | |
| keywords | No | |
| language | No | |
| producer | No | |
| truncated | No | |
| created_at | No | |
| elapsed_ms | No | |
| page_count | No | |
| description | No | |
| modified_at | No | |
| publication | No | |
| published_at | No | |
| canonical_url | No | |
| memory_handle | No | |
| source_length | No | |
| extraction_quality | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations are minimal (readOnlyHint=false, openWorldHint=true, idempotentHint=false), so the description carries the burden. Description discloses several behavioral traits: best-effort failures return errorInfo instead of throwing; PDF auto-detection; bounded download gates; research-enabled concurrency; tiered depth behavior; response_content limits. However, openWorldHint=true suggests the tool may act beyond the given URL, but the description emphasizes 'fetch one public URL' and 'exact URL already known' — a mild tension but not a direct contradiction since openWorldHint may refer to research-storage side effects. The description adds substantial behavior beyond annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is information-dense but somewhat sprawling; it covers many behaviors in a single paragraph without clear topical organization. It is front-loaded with the key usage constraint ('Use only when the exact public URL is already known'), which is good, but later sentences about research-enabled brokers and local file tools add noise. Every sentence has some value, but readability suffers from length and lack of structure.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 8 parameters, an output schema, and moderate behavioral complexity, the description is fairly complete. It explains modes, response_content, best-effort errors, PDF auto-detection, and research evidence storage. It omits some specifics like exactly how project_id/memory_handle relate, but the schema covers those. The output schema exists, so return values need not be described. This is a thorough, if slightly over-stuffed, description.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3 even without additional param info. The description adds some context, such as 'abstract returns about 1500 chars' and 'PDF and landing-page metadata are merged when available,' but most parameter meaning is already in the schema. It doesn't significantly enrich beyond the schema, so 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a clear purpose: extracting clean content from a single known public URL, with tiered modes (metadata/abstract/full) and explicit limitations (known URL only, no discovery). It is distinguishable from siblings like search, search_parallel, and project_memory_search, though the title 'Known URL Extract' is redundant with the name. The description could be more concise but is specific about the resource and operation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use/when-not-to-use guidance: 'Use only when the exact public URL is already known and no new web discovery is required.' Names alternatives (search, search_parallel) for the discovery case, and differentiates from local PDF tools and repository tools. This is model guidance with clear exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
healthMCP Health CheckARead-onlyIdempotent
MCP server status: cascade mode + transitions, rate-limiter usage, cache size, config. Call this if searches start failing or returning empty -- check cascade.totalCaptchas and rateLimiter.queueSize, and reduce search volume if they are high.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| pool | No | |
| cache | No | |
| error | No | |
| config | No | |
| cascade | No | |
| version | No | |
| research | No | |
| telemetry | No | |
| rateLimiter | No | |
| selfHealing | No | |
| nativeBrowser | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and idempotentHint=true, so the agent knows this is a safe diagnostic call. The description adds useful context about what status information is available and how to interpret it, which goes beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two compact sentences: the first states what the tool returns, the second gives the trigger condition and recommended action. Every sentence earns its place with no filler or redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, read-only status endpoint with an output schema and strong annotations, this description is fully adequate. It tells the agent when to call, what to inspect, and what action to take, so no critical context is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool has zero parameters, so there is no parameter semantics burden. The description still adds value by pointing the agent to specific health fields to inspect, which is the closest analog to parameter guidance for this tool.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly identifies the tool as an MCP server status/health check, listing specific exposed areas: cascade mode, rate-limiter usage, cache size, and config. This distinguishes it from the search-oriented sibling tools, even without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to call the tool ('if searches start failing or returning empty') and what to do with the results ('check cascade.totalCaptchas and rateLimiter.queueSize, and reduce search volume if they are high'). This leaves no ambiguity about the intended use case.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
project_memoryProject MemoryA
PROJECT MEMORY MANAGEMENT. With research enabled, one local broker lets multiple MCP sessions query the same knowledge base concurrently and orders writes safely. Use create to create a project; show to inspect known projects or durable records; record to store session intent, immutable plan revisions, experiments, decisions, versioned ontology terms, or corrections; rebuild to index approved local roots; export to write graph or lineage views; and forget for reversible deletion. show always returns a bounded summary; detail_level=full is accepted for compatibility but never dumps every durable record body. Use project_memory_search for relevant bodies or target_id for one assertion or entity. record returns only a compact receipt and never echoes submitted bodies. action=search remains a compatibility alias, but project_memory_search should be used for local knowledge retrieval. Ontology revisions use supersedes_term_id. Corrections preserve bitemporal data lineage and support assertion replacement plus entity merge or split. rebuild indexes approved local roots and code structure into a reproducible snapshot; export writes an interactive HTML viewer, Graphviz DOT, D3 JSON, or a Neo4j import bundle. HTML always includes PKM, Lineage, and Ontology tabs, an embedded-project selector, empty-canvas focus reset, and visible PNG or JSON download. forget previews impact before reversible project or assertion deletion. Search, search_parallel, scholar_search, and extract automatically capture sources and provenance; project_memory manages their durable structure.
| Name | Required | Description | Default |
|---|---|---|---|
| body | No | Plan body for record_type=plan. | |
| name | No | Project name for create, or entity name lookup for show. | |
| limit | No | Maximum local RAG results for action=search. | |
| query | No | Required for action=search. Searches stored local knowledge only and does not start live web discovery. | |
| roots | No | Approved local roots to index. Omit to rebuild derived state from stored sources. | |
| title | No | Plan title, experiment name, or decision title. | |
| action | Yes | create: project_id and name required. show: inspect a known project, assertion, entity, or durable record. search: compatibility alias for project_memory_search. record: project_id and record_type required. rebuild: project_id required; roots optional. export: project_id, include_project_ids, or all_projects=true required. forget: project_id and forget_mode required; apply also requires confirm_token. | |
| intent | No | Durable intent for record_type=session. | |
| reason | No | Required reason for any correction. | |
| status | No | Use success, failed, or inconclusive to finish an experiment. Omit or use running when starting it. | |
| aliases | No | Ontology term aliases, or aliases moved during entity_split. | |
| metrics | No | Terminal experiment metrics. | |
| summary | No | Experiment result or decision summary. | |
| version | No | Ontology revision number. Defaults to 1 or the superseded term version plus one. | |
| git_root | No | Optional Git root recorded with a rebuild snapshot. | |
| valid_to | No | Corrected assertion valid-time end. | |
| artifacts | No | Experiment artifact paths or identifiers. | |
| target_id | No | Assertion or entity id for show or correction. | |
| hypothesis | No | Hypothesis when starting an experiment. | |
| project_id | No | Stable project id. For search, this is the write-isolated primary scope. Omit to list projects or use all_projects. | |
| source_ids | No | Entity ids merged into target_id by entity_merge. | |
| valid_from | No | Corrected assertion valid-time start. | |
| export_view | No | Initial HTML tab or non-HTML export scope. graph is PKM; ontology shows types, shared schema, verified identity links, and typed instances; lineage shows aligned data and research lineage. | graph |
| forget_mode | No | Preview first. Apply requires its confirm_token. Restore reverses deletion. | |
| record_type | No | Required for record. session stores intent; plan creates an immutable revision; experiment starts or finishes a run; decision links a conclusion; ontology creates a versioned type or relation; correction replaces an assertion or merges/splits entities. | |
| replacement | No | New assertion value, or new entity name for entity_split. | |
| all_projects | No | Search or export every active named project. Excludes Inbox and cannot be combined with project ids. | |
| detail_level | No | For show with project_id and no target_id, both values return a bounded summary. full is retained only for compatibility and never returns every durable record body. | summary |
| evidence_ids | No | Evidence retained on a corrected assertion. | |
| change_reason | No | Reason for a new plan revision. | |
| confirm_token | No | Token returned by the matching forget preview. | |
| experiment_id | No | Experiment to finish or associate with a decision. | |
| export_format | No | Visualization file format. html writes one offline explorer with PKM, Lineage, Ontology, project selection, and current-view PNG or anonymized JSON download; d3 writes node-link JSON; dot writes Graphviz DOT; neo4j writes an import-ready CSV and Cypher bundle. | d3 |
| memory_handle | No | Existing project session handle for a session intent revision. | |
| ontology_kind | No | Ontology term kind for record_type=ontology. | |
| query_variants | No | Optional local retrieval variants for action=search. They run inside one broker request and are fused before one rerank. | |
| correction_kind | No | Correction operation. Defaults to assertion. | |
| plan_revision_id | No | Plan revision associated with an experiment or decision. | |
| supersedes_term_id | No | Prior ontology term replaced by this revision. | |
| include_project_ids | No | Additional read-only projects for one local search or integrated visualization export. | |
| based_on_experiment_id | No | Experiment that motivated a plan revision. |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | |
| error | No | |
| index | No | |
| plans | No | |
| query | No | |
| entity | No | |
| forget | No | |
| memory | No | |
| record | No | |
| project | No | |
| results | No | |
| session | No | |
| entities | No | |
| projects | No | |
| assertion | No | |
| decisions | No | |
| elapsed_ms | No | |
| job_counts | No | |
| plan_count | No | |
| experiments | No | |
| entity_count | No | |
| memory_handle | No | |
| session_count | No | |
| visualization | No | |
| decision_count | No | |
| document_count | No | |
| assertion_count | No | |
| correction_count | No | |
| experiment_count | No | |
| search_event_count | No | |
| source_entry_count | No | |
| active_source_snapshot | No | |
| entity_operation_count | No | |
| citation_observation_count | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description goes well beyond the annotations by disclosing bounded show results, detail_level=full compatibility behavior, compact record receipts that never echo bodies, reversible deletion with preview, bitemporal lineage for corrections, and provenance capture from sibling search tools. This substantially enriches the agent's understanding of side effects and response boundaries.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense and every sentence contributes, but it is presented as one long paragraph with all-caps opening, which makes parsing harder than needed. A structured bullet list per action would improve scannability without adding length. Still, there is little waste.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with 41 parameters and 7 actions, the description covers action selection, output shapes, compatibility aliases, correction modes, export formats, and the relationship to sibling search tools. Combined with the highly detailed schema and output schema, nothing critical is missing for an agent to invoke it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the description does not need to re-explain each parameter. It adds valuable cross-parameter context such as supersedes_term_id for ontology revisions, entity_merge/entity_split semantics, include_project_ids as read-only projects, and action-specific requirements like export needing project_id, include_project_ids, or all_projects. This exceeds the baseline of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'PROJECT MEMORY MANAGEMENT' and enumerates concrete verbs and resources: create, show, record, rebuild, export, and forget. It distinguishes itself from sibling project_memory_search by explicitly telling the agent to use project_memory_search for local knowledge retrieval instead of action=search.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit per-action instructions: 'Use create to create a project; show to inspect known projects or durable records; record to store...; rebuild to index approved local roots; export to write graph or lineage views; and forget for reversible deletion.' It also clarifies when to prefer project_memory_search over the search alias.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
project_memory_searchLocal Project Memory SearchARead-onlyIdempotent
LOCAL PROJECT KNOWLEDGE SEARCH ONLY. With research enabled, one local broker lets multiple MCP sessions query the same knowledge base concurrently and orders writes safely. Always use this tool when the user asks to find, recall, inspect, or search information already stored in project memory, indexed local roots, papers, codebases, plans, experiments, or decisions. Use query_variants for multiple retrieval angles in one call instead of opening terminals or calling this tool repeatedly. Query embeddings are batched, candidates are fused with RRF, graph expansion starts from retrieved evidence, and the primary query is reranked once. Only bounded query-focused summaries from that final ranking are returned; stored bodies never bypass this response gate. Uses exact, BM25, vector, and graph retrieval. It never opens Google, a browser, or SearchApi. Use search or search_parallel only when new external information is required.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum local RAG results. | |
| query | Yes | Natural-language query over indexed local project knowledge. | |
| project_id | No | Primary project to search. Required unless all_projects=true. | |
| all_projects | No | Search every active named project. Excludes Inbox and cannot be combined with project ids. | |
| query_variants | No | Optional retrieval variants executed inside this one broker request. Exact identifiers and quoted phrases are added deterministically, candidates are fused with RRF, and the primary query is reranked once. Do not make repeated terminal or tool calls for query variants. | |
| include_project_ids | No | Additional read-only projects searched with the primary project. |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | |
| error | No | |
| index | No | |
| plans | No | |
| query | No | |
| entity | No | |
| forget | No | |
| memory | No | |
| record | No | |
| project | No | |
| results | No | |
| session | No | |
| entities | No | |
| projects | No | |
| assertion | No | |
| decisions | No | |
| elapsed_ms | No | |
| job_counts | No | |
| plan_count | No | |
| experiments | No | |
| entity_count | No | |
| memory_handle | No | |
| session_count | No | |
| visualization | No | |
| decision_count | No | |
| document_count | No | |
| assertion_count | No | |
| correction_count | No | |
| experiment_count | No | |
| search_event_count | No | |
| source_entry_count | No | |
| active_source_snapshot | No | |
| entity_operation_count | No | |
| citation_observation_count | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, openWorldHint=false, and idempotentHint=true, and the description adds substantial context beyond them: concurrent broker behavior, write ordering, RRF fusion, reranking, and the response gate that returns only bounded query-focused summaries while stored bodies never bypass it. This rich behavioral detail helps an agent understand side effects and output boundaries.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but every sentence earns its place: scope, concurrency, when-to-use, variant guidance, retrieval pipeline, response gate, and external-search exclusion. It is front-loaded with the most important constraint ('LOCAL PROJECT KNOWLEDGE SEARCH ONLY') and avoids filler while still being comprehensive.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's complexity, six parameters, and existing output schema, the description covers everything an agent needs: scope, usage conditions, alternatives, internal query pipeline, response restrictions, and anti-external behavior. The output schema handles return-value structure, so the description's omission of that detail is appropriate.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description adds value on top by explaining query_variants as a way to get multiple retrieval angles in one call and by noting that the primary query is reranked once, giving behavioral meaning beyond the schema's type and bounds. It does not need to restate parameter mechanics because the schema already documents them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'LOCAL PROJECT KNOWLEDGE SEARCH ONLY' and states a specific verb and resource: search over project memory, indexed local roots, papers, codebases, plans, experiments, and decisions. It also names retrieval modes (exact, BM25, vector, graph) and explicitly excludes external sources, clearly distinguishing it from sibling search tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit always-use guidance: 'Always use this tool when the user asks to find, recall, inspect, or search information already stored in project memory.' It also tells the agent when not to use it: 'Use search or search_parallel only when new external information is required,' and directs query_variants usage instead of repeated calls.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scholar_searchGoogle Scholar SearchA
Use only for paper metadata such as authors, venue, year, versions, and citation counts. With research enabled, one local broker lets multiple MCP sessions query the same knowledge base concurrently and orders writes safely. Do not use it to discover or read paper content; use search with extract_mode instead. Returns title, authors, publication, year, snippet, citation count, related/version links, and an available full-text link. With research enabled, metadata and citation observations retain provider provenance and research_context exposes related prior searches. Google Scholar uses browser search, SearchApi primary, or configured fallback. Results are cached with the same TTL as search. Max 10 papers per call.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum papers. Integer 1-10, default 10. | |
| query | Yes | Google Scholar query. Supports quotes and author: operators. | |
| project_id | No | Project memory id. | |
| session_id | No | Stable host task id. Reuses the same project session after restart. | |
| memory_handle | No | Reuse the handle returned by a prior project-aware call. | |
| session_intent | No | Current durable task intent. A changed value creates an immutable revision. |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | |
| error | No | |
| query | No | |
| memory | No | |
| results | No | |
| elapsed_ms | No | |
| memory_handle | No | |
| research_context | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already convey readOnlyHint=false, openWorldHint=true, and idempotentHint=false. The description adds useful behavioral context: provider/fallback behavior, caching with the same TTL as search, a 10-paper cap, and provenance in research mode. However, the write-side behavior is only vaguely referenced through 'orders writes safely', so the side effects are not fully transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded and many sentences carry value, but the description is somewhat sprawling. The 'one local broker lets multiple MCP sessions query the same knowledge base concurrently and orders writes safely' clause feels tangential, and 'with research enabled' is repeated, making the description less crisp than it could be.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The tool has an output schema and full schema-level parameter descriptions, so return-value documentation is not required. Still, the research-mode behavior is under-specified: the description mentions research_context and provenance but does not explain how to enable research mode or which optional parameters activate it. For a 6-parameter tool with memory/session semantics, this is a notable gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description does not meaningfully enrich parameter semantics: it describes what the tool returns but does not explain how project_id, session_id, memory_handle, or session_intent should be used. It adds no parameter-level guidance beyond what the schema already states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description is specific: it states the tool is strictly for paper metadata (authors, venue, year, versions, citation counts) and explicitly contrasts this with discovering or reading paper content. This clearly distinguishes scholar_search from the sibling search tool, so an agent can understand its role without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit when-to-use rule ('Use only for paper metadata') and an explicit when-not-to-use rule ('Do not use it to discover or read paper content; use search with extract_mode instead'). This is a clear alternative-routing instruction that requires no inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
searchWeb Search and ExtractA
PRIMARY SINGLE-QUERY LIVE DISCOVERY AND CONTENT INGESTION TOOL. ALWAYS PERFORMS LIVE WEB SEARCH. With research enabled, one local broker lets multiple MCP sessions query the same knowledge base concurrently and orders writes safely. Use this tool only when new external information from Google, public websites, papers, or repositories is required. When the task requires both finding and reading public web pages, PDFs, papers, or GitHub repositories, set extract_mode=abstract or full in this call. Do not download public PDFs, clone repositories, or invoke local parsers first. Select extract_mode=full, not abstract, when the user asks to read originals, full text, document bodies, or code, or to compare source contents. Use extract separately only when the exact public URL is already known and no new discovery is required. Use general repository tools only for editing, building, testing, or full Git history. Providing project_id also fuses stored project evidence with live results, but never makes this a local-only search. For stored project knowledge without live web discovery, use project_memory_search. limit accepts integers from 1 to 20. extract_limit accepts integers from 1 to 10, defaults to 5, and limits unique extracted URLs. Final results use one bounded response budget after ranking; full captured bodies remain local. Use extract on one selected URL when longer response text is explicitly required. The response includes applied, skipped, truncated, total_chars, and a bounded remaining_urls list. GitHub none mode reads the README; abstract and full can sparse-index eligible repositories with Tree-sitter. With research enabled and project_id set, live web, exact, BM25, vector, code, and graph lanes are fused by RRF and one reranker. With research disabled, provider order and query BM25 rank are fused by a lightweight in-memory RRF reranker without opening local storage or loading the vector model. research_context returns up to three prior searches for deeper or adjacent follow-up work. Captured search, source, session, and project provenance form data lineage; extracted bodies become evidence while unread hits remain metadata. include_project_ids adds read-only cross-project retrieval through versioned ontology and verified schema/entity links without merging records. Browser search is the no-key default and SearchApi is the configured primary or fallback provider. Results are cached for 24h unless the TTL is disabled.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum results. Integer 1-20, default 10. | |
| query | Yes | Google search query. Use site: filters and quotes for exact match. | |
| max_chars | No | Maximum returned characters per extracted result in response_content=full. Defaults to 1500 for abstract and 50000 for full. | |
| project_id | No | Project memory id. | |
| session_id | No | Stable host task id. Reuses the same project session after restart. | |
| extract_mode | No | Content depth in this search call. Use none only when titles and snippets are enough, abstract for relevance evidence, and full when the user asks to read originals, full text, document bodies, or code, or to compare source contents. Use abstract or full here instead of following search with separate extraction, PDF download, or repository clone. For GitHub results, none reads the README; abstract and full use the same repository eligibility gate but index different source amounts. | none |
| extract_limit | No | Maximum unique result URLs to extract. Integer 1-10, default 5. | |
| memory_handle | No | Reuse the handle returned by a prior project-aware call. | |
| session_intent | No | Current durable task intent. A changed value creates an immutable revision. | |
| response_content | No | Controls only the returned body. summary is the default and returns a 1500-character evidence excerpt; full returns up to max_chars. Research storage keeps the full captured text in deterministic chunks. | summary |
| include_project_ids | No | Additional read-only projects joined through ontology-aligned schema and identity links. New records stay in project_id. |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | |
| error | No | |
| query | No | |
| memory | No | |
| results | No | |
| elapsed_ms | No | |
| memory_handle | No | |
| research_context | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already signal open-world, non-read-only, non-idempotent behavior, but the description adds substantial extra context: live search is always performed, results are cached for 24h, extract modes handle GitHub repositories differently, response budgets are bounded, and research-enabled behavior fuses multiple retrieval lanes. This is far beyond what annotations alone provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with the core purpose and has valuable content, but it is extremely long and dense, mixing essential invocation guidance with internal architecture details like RRF fusion, brokers, and provenance. While complex behavior justifies some length, tighter organization and less repetition of schema-covered details would improve it.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Despite its density, the description covers invocation criteria, extract-mode selection, parameter ranges, caching, result response fields, output schema availability, and alternatives. For a complex tool with 11 parameters and multiple modes, nothing critical for correct invocation appears missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds useful semantic context for extract_mode, extract_limit, limit, and response_content, including GitHub-specific behavior and the relationship between extraction and final response budget. It does not merely restate the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'PRIMARY SINGLE-QUERY LIVE DISCOVERY AND CONTENT INGESTION TOOL. ALWAYS PERFORMS LIVE WEB SEARCH,' which is a specific verb, resource, and behavioral commitment. It also clearly distinguishes this tool from siblings by naming alternatives like extract, project_memory_search, and repository tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit conditions: 'Use this tool only when new external information from Google, public websites, papers, or repositories is required.' It also states when to use extract separately, when to use project_memory_search, and when to use general repository tools, making the decision boundary explicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_parallelParallel Web Search and ExtractA
PRIMARY MULTI-QUERY LIVE DISCOVERY AND CONTENT INGESTION TOOL. ALWAYS PERFORMS MULTIPLE LIVE WEB SEARCHES. With research enabled, one local broker lets multiple MCP sessions query the same knowledge base concurrently and orders writes safely. Use this tool only when 2-12 new external queries are required. Providing project_id adds stored evidence but never makes the searches local-only. When the task requires broad discovery plus reading public web pages, PDFs, papers, or GitHub repositories, set extract_mode=abstract or full in this call. Do not download public PDFs, clone repositories, or invoke local parsers first. Select extract_mode=full, not abstract, when the user asks to read originals, full text, document bodies, or code, or to compare source contents. Use extract separately only when the exact public URL is already known and no new discovery is required. Use general repository tools only for editing, building, testing, or full Git history. For stored project knowledge without live web discovery, use project_memory_search. Each query limit accepts integers from 1 to 20. Call-wide extract_limit accepts 1-20 with default 12 for abstract, and 1-10 with default 10 for full. Final results use one call-wide bounded response budget after ranking; at most 36 ranked rows are returned across query groups and full captured bodies remain local. Use extract on one selected URL when longer response text is explicitly required. The response includes applied, skipped, truncated, total_chars, and a bounded remaining_urls list. GitHub none mode reads the README; abstract and full can sparse-index eligible repositories with Tree-sitter. With research enabled and project_id set, each query fuses live, exact, BM25, vector, code, and graph lanes through RRF and one reranker. With research disabled, each query independently fuses provider order and query BM25 rank through a lightweight in-memory RRF reranker without opening local storage or loading the vector model. research_context returns prior project searches; capture retains data lineage and include_project_ids uses versioned ontology and verified cross-project schema/entity links. Extracted bodies become evidence while unread hits remain metadata. Native Chrome uses one authenticated local broker across MCP sessions and keeps one hidden process with up to four reusable tabs. Query starts are staggered and each tab continuously consumes the remaining queue. SearchApi fallback replaces failed queries individually.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum results per query. Integer 1-20, default 10. | |
| queries | Yes | 2-12 independent live queries. | |
| max_chars | No | Maximum returned characters per extracted result in response_content=full. Defaults to 1500 for abstract and 50000 for full. | |
| project_id | No | Project memory id. | |
| session_id | No | Stable host task id. Reuses the same project session after restart. | |
| extract_mode | No | Content depth in this search call. Use none only when titles and snippets are enough, abstract for relevance evidence, and full when the user asks to read originals, full text, document bodies, or code, or to compare source contents. Use abstract or full here instead of following search with separate extraction, PDF download, or repository clone. For GitHub results, none reads the README; abstract and full use the same repository eligibility gate but index different source amounts. | none |
| extract_limit | No | Call-wide maximum unique result URLs to extract. Integer 1-20, default 12 for abstract; full defaults to and allows at most 10. | |
| memory_handle | No | Reuse the handle returned by a prior project-aware call. | |
| session_intent | No | Current durable task intent. A changed value creates an immutable revision. | |
| response_content | No | Controls only returned bodies. summary is the default and returns 1500-character evidence excerpts; full returns up to max_chars. Research storage keeps the full captured text in deterministic chunks. | summary |
| include_project_ids | No | Additional read-only projects joined through ontology-aligned schema and identity links. New records stay in project_id. |
Output Schema
| Name | Required | Description |
|---|---|---|
| meta | No | |
| error | No | |
| memory | No | |
| results | No | |
| elapsed_ms | No | |
| memory_handle | No | |
| research_context | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only convey readOnly=false, openWorldHint=true, and idempotentHint=false; the description adds substantial behavioral detail beyond those: writes are ordered, full captured bodies remain local, the response includes applied/skipped/truncated/total_chars and a bounded remaining_urls list, results are capped at 36 ranked rows, GitHub indexing behavior differs by mode, and SearchApi fallback replaces failed queries. This far exceeds what the annotations provide.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well front-loaded and information-dense, but it is very long and contains redundancy, such as repeated extract guidance, and implementation details like 'Native Chrome uses one authenticated local broker...' and tree-sitter sparse-indexing that could be trimmed. Not every sentence earns its place despite the overall usefulness.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 11-parameter tool with an output schema, the description is remarkably complete: it covers use cases, parameter semantics, response contents, result budgets, extraction limits, research modes, repository handling, and fallback behavior. An agent has enough context to invoke the tool correctly in most scenarios without additional inference.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, and the description still adds meaning: it explains that query limit accepts 1-20, extract_limit defaults differ by mode, extract_mode maps to user intent ('full, not abstract, when the user asks to read originals'), max_chars defaults to 1500/50000 behavior, and project_id never makes searches local-only. This is strong value beyond the structured schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'MULTI-QUERY LIVE DISCOVERY AND CONTENT INGESTION TOOL. ALWAYS PERFORMS MULTIPLE LIVE WEB SEARCHES.' It clearly differentiates from siblings by naming when to use extract, project_memory_search, and repository tools instead, so an agent can distinguish it without ambiguity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when/when-not guidance: 'Use this tool only when 2-12 new external queries are required,' and it names alternatives: 'Use extract separately only when the exact public URL is already known,' 'For stored project knowledge without live web discovery, use project_memory_search,' and 'Use general repository tools only for editing, building, testing, or full Git history.' This is model usage guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
3 tool updates
v1.0.6- Changed
project_memory4 fields changed- added
Input schema / properties / detail_levelAdded value: +{ + "default": "summary", + "description": "For show with project_id and no target_id, both values return a bounded summary. full is retained only for compatibility and never returns every durable record body.", + "enum": [ + "summary", + "full" + ], + "type": "string" +} - added
Output schema / properties / decision_countAdded value: +{ + "type": "number" +} - added
Output schema / properties / experiment_countAdded value: +{ + "type": "number" +} - added
Output schema / properties / plan_countAdded value: +{ + "type": "number" +}
- Changed
project_memory_search3 fields changed- added
Output schema / properties / decision_countAdded value: +{ + "type": "number" +} - added
Output schema / properties / experiment_countAdded value: +{ + "type": "number" +} - added
Output schema / properties / plan_countAdded value: +{ + "type": "number" +}
- Changed
search2 fields changed- changed
Input schema / properties / response_content / defaultPrevious value: -"full"New value: +"summary" - changed
Input schema / properties / response_content / descriptionPrevious value: -"Controls only the returned body. full returns up to max_chars; summary returns a 1500-character evidence excerpt. Research storage keeps the full captured text in deterministic chunks."New value: +"Controls only the returned body. summary is the default and returns a 1500-character evidence excerpt; full returns up to max_chars. Research storage keeps the full captured text in deterministic chunks."
5 tool updates
v1.0.4- Changed
health1 field changed- added
Output schema / properties / nativeBrowserAdded value: +{ + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" +}
- Changed
project_memory1 field changed- added
Input schema / properties / query_variantsAdded value: +{ + "description": "Optional local retrieval variants for action=search. They run inside one broker request and are fused before one rerank.", + "items": { + "maxLength": 400, + "minLength": 1, + "type": "string" + }, + "maxItems": 19, + "type": "array" +}
- Changed
project_memory_search1 field changed- added
Input schema / properties / query_variantsAdded value: +{ + "description": "Optional retrieval variants executed inside this one broker request. Exact identifiers and quoted phrases are added deterministically, candidates are fused with RRF, and the primary query is reranked once. Do not make repeated terminal or tool calls for query variants.", + "items": { + "maxLength": 400, + "minLength": 1, + "type": "string" + }, + "maxItems": 19, + "type": "array" +}
- Changed
search1 field changed- changed
Input schema / properties / extract_mode / descriptionPrevious value: -"Content depth in this search call. Use abstract or full during research instead of following search with separate extract calls. For GitHub results, none reads the README; abstract and full use the same repository eligibility gate but index different source amounts."New value: +"Content depth in this search call. Use none only when titles and snippets are enough, abstract for relevance evidence, and full when the user asks to read originals, full text, document bodies, or code, or to compare source contents. Use abstract or full here instead of following search with separate extraction, PDF download, or repository clone. For GitHub results, none reads the README; abstract and full use the same repository eligibility gate but index different source amounts."
- Changed
search_parallel1 field changed- changed
Input schema / properties / extract_mode / descriptionPrevious value: -"Content depth in this search call. Use abstract or full during research instead of following search with separate extract calls. For GitHub results, none reads the README; abstract and full use the same repository eligibility gate but index different source amounts."New value: +"Content depth in this search call. Use none only when titles and snippets are enough, abstract for relevance evidence, and full when the user asks to read originals, full text, document bodies, or code, or to compare source contents. Use abstract or full here instead of following search with separate extraction, PDF download, or repository clone. For GitHub results, none reads the README; abstract and full use the same repository eligibility gate but index different source amounts."
6 tool updates
v1.0.2- Changed
extract4 fields changed- changed
Input schema / properties / max_chars / descriptionPrevious value: -"Truncate body to this many chars. Defaults to 1500 for abstract and 50000 for full."New value: +"Maximum returned characters when response_content=full. Defaults to 1500 for abstract and 50000 for full." - added
Input schema / properties / response_contentAdded value: +{ + "default": "full", + "description": "Controls only the returned body. full returns up to max_chars; summary returns a 1500-character evidence excerpt. Research storage keeps the full captured text in deterministic chunks.", + "enum": [ + "summary", + "full" + ], + "type": "string" +} - added
Output schema / properties / source_lengthAdded value: +{ + "type": "number" +} - added
Output schema / properties / truncatedAdded value: +{ + "type": "boolean" +}
- Changed
project_memory2 fields changed- added
Output schema / properties / results / items / properties / source_lengthAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / truncatedAdded value: +{ + "type": "boolean" +}
- Changed
project_memory_search2 fields changed- added
Output schema / properties / results / items / properties / source_lengthAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / truncatedAdded value: +{ + "type": "boolean" +}
- Changed
scholar_search1 field changed- changed
Input schema / properties / limit / descriptionPrevious value: -"Max papers (default 10)."New value: +"Maximum papers. Integer 1-10, default 10."
- Changed
search6 fields changed- changed
Input schema / properties / extract_limit / descriptionPrevious value: -"Maximum unique result URLs to extract. For parallel search this is one call-wide limit, not a per-query limit."New value: +"Maximum unique result URLs to extract. Integer 1-10, default 5." - changed
Input schema / properties / limit / descriptionPrevious value: -"Max results (default 10)."New value: +"Maximum results. Integer 1-20, default 10." - changed
Input schema / properties / max_chars / descriptionPrevious value: -"Maximum characters per extracted result. Defaults to 1500 for abstract and 50000 for full."New value: +"Maximum returned characters per extracted result in response_content=full. Defaults to 1500 for abstract and 50000 for full." - added
Input schema / properties / response_contentAdded value: +{ + "default": "full", + "description": "Controls only the returned body. full returns up to max_chars; summary returns a 1500-character evidence excerpt. Research storage keeps the full captured text in deterministic chunks.", + "enum": [ + "summary", + "full" + ], + "type": "string" +} - added
Output schema / properties / results / items / properties / source_lengthAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / truncatedAdded value: +{ + "type": "boolean" +}
- Changed
search_parallel8 fields changed- removed
Input schema / properties / extract_limit / defaultRemoved value: -5 - changed
Input schema / properties / extract_limit / descriptionPrevious value: -"Maximum unique result URLs to extract. For parallel search this is one call-wide limit, not a per-query limit."New value: +"Call-wide maximum unique result URLs to extract. Integer 1-20, default 12 for abstract; full defaults to and allows at most 10." - changed
Input schema / properties / extract_limit / maximumPrevious value: -10New value: +20 - changed
Input schema / properties / limit / descriptionPrevious value: -"Max results per query."New value: +"Maximum results per query. Integer 1-20, default 10." - changed
Input schema / properties / max_chars / descriptionPrevious value: -"Maximum characters per extracted result. Defaults to 1500 for abstract and 50000 for full."New value: +"Maximum returned characters per extracted result in response_content=full. Defaults to 1500 for abstract and 50000 for full." - added
Input schema / properties / response_contentAdded value: +{ + "default": "summary", + "description": "Controls only returned bodies. summary is the default and returns 1500-character evidence excerpts; full returns up to max_chars. Research storage keeps the full captured text in deterministic chunks.", + "enum": [ + "summary", + "full" + ], + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / source_lengthAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / results / items / properties / truncatedAdded value: +{ + "type": "boolean" +}
8 tool updates
v1.0.0- Changed
extract23 fields changed- removed
Input schema / properties / max_chars / defaultRemoved value: -8000 - changed
Input schema / properties / max_chars / descriptionPrevious value: -"Truncate body to this many chars (default 8000, set via SURF_EXTRACT_MAX_CHARS)."New value: +"Truncate body to this many chars. Defaults to 1500 for abstract and 50000 for full." - added
Input schema / properties / memory_handleAdded value: +{ + "description": "Reuse the handle returned by a prior project-aware call.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" +} - changed
Input schema / properties / mode / descriptionPrevious value: -"Extraction depth. `full` = whole article body (default; uses Playwright if needed). `abstract` = cheap survey: PDF page 1 OR HTML meta description (~1500 chars); use to triage relevance before paying for full text. `metadata` = page count only (PDF). Academic PDFs (arxiv/biorxiv/Nature/OpenReview/NeurIPS/JMLR/PMLR/Springer/PubMed-via-PMC) are auto-detected; abstract mode skips Playwright for them."New value: +"Extraction depth. `full` = whole article body (default; uses Playwright if needed). `abstract` = cheap survey: PDF page 1 OR HTML meta description (~1500 chars); use to triage relevance before paying for full text. `metadata` = document metadata without body text: title, authors, publication details, dates, DOI, keywords, canonical URL, and PDF properties when available. Academic PDFs (arxiv/biorxiv/Nature/OpenReview/NeurIPS/JMLR/PMLR/Springer/PubMed-via-PMC) are auto-detected; abstract mode skips Playwright for them." - added
Input schema / properties / project_idAdded value: +{ + "description": "Project memory id.", + "maxLength": 64, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / session_idAdded value: +{ + "description": "Stable host task id. Reuses the same project session after restart.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / session_intentAdded value: +{ + "description": "Current durable task intent. A changed value creates an immutable revision.", + "maxLength": 2000, + "minLength": 1, + "type": "string" +} - added
Output schema / properties / authorsAdded value: +{ + "type": "string" +} - added
Output schema / properties / canonical_urlAdded value: +{ + "type": "string" +} - added
Output schema / properties / created_atAdded value: +{ + "type": "string" +} - added
Output schema / properties / creatorAdded value: +{ + "type": "string" +} - added
Output schema / properties / descriptionAdded value: +{ + "type": "string" +} - added
Output schema / properties / doiAdded value: +{ + "type": "string" +} - added
Output schema / properties / keywordsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / languageAdded value: +{ + "type": "string" +} - added
Output schema / properties / memoryAdded value: +{ + "type": "string" +} - added
Output schema / properties / memory_handleAdded value: +{ + "type": "string" +} - added
Output schema / properties / modified_atAdded value: +{ + "type": "string" +} - added
Output schema / properties / producerAdded value: +{ + "type": "string" +} - added
Output schema / properties / publicationAdded value: +{ + "type": "string" +} - added
Output schema / properties / published_atAdded value: +{ + "type": "string" +} - added
Output schema / properties / subjectAdded value: +{ + "type": "string" +} - added
Output schema / properties / yearAdded value: +{ + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +}
- Changed
health1 field changed- added
Output schema / properties / researchAdded value: +{ + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" +}
- Added
project_memory - Added
project_memory_search - Changed
scholar_search7 fields changed- added
Input schema / properties / memory_handleAdded value: +{ + "description": "Reuse the handle returned by a prior project-aware call.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" +} - added
Input schema / properties / project_idAdded value: +{ + "description": "Project memory id.", + "maxLength": 64, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / session_idAdded value: +{ + "description": "Stable host task id. Reuses the same project session after restart.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / session_intentAdded value: +{ + "description": "Current durable task intent. A changed value creates an immutable revision.", + "maxLength": 2000, + "minLength": 1, + "type": "string" +} - added
Output schema / properties / memoryAdded value: +{ + "type": "string" +} - added
Output schema / properties / memory_handleAdded value: +{ + "type": "string" +} - added
Output schema / properties / research_contextAdded value: +{ + "additionalProperties": false, + "properties": { + "prior_searches": { + "items": { + "additionalProperties": false, + "properties": { + "query": { + "type": "string" + }, + "relation": { + "enum": [ + "same", + "related", + "recent" + ], + "type": "string" + }, + "results": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "searched_at": { + "type": "string" + }, + "surface": { + "type": "string" + } + }, + "required": [ + "query", + "relation", + "searched_at", + "results" + ], + "type": "object" + }, + "maxItems": 3, + "type": "array" + } + }, + "required": [ + "prior_searches" + ], + "type": "object" +}
- Changed
search37 fields changed- added
Input schema / properties / extract_limitAdded value: +{ + "default": 5, + "description": "Maximum unique result URLs to extract. For parallel search this is one call-wide limit, not a per-query limit.", + "maximum": 10, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / extract_modeAdded value: +{ + "default": "none", + "description": "Content depth in this search call. Use abstract or full during research instead of following search with separate extract calls. For GitHub results, none reads the README; abstract and full use the same repository eligibility gate but index different source amounts.", + "enum": [ + "none", + "abstract", + "full" + ], + "type": "string" +} - added
Input schema / properties / include_project_idsAdded value: +{ + "description": "Additional read-only projects joined through ontology-aligned schema and identity links. New records stay in project_id.", + "items": { + "maxLength": 64, + "minLength": 1, + "type": "string" + }, + "maxItems": 8, + "type": "array" +} - added
Input schema / properties / max_charsAdded value: +{ + "description": "Maximum characters per extracted result. Defaults to 1500 for abstract and 50000 for full.", + "maximum": 50000, + "minimum": 200, + "type": "integer" +} - added
Input schema / properties / memory_handleAdded value: +{ + "description": "Reuse the handle returned by a prior project-aware call.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" +} - added
Input schema / properties / project_idAdded value: +{ + "description": "Project memory id.", + "maxLength": 64, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / session_idAdded value: +{ + "description": "Stable host task id. Reuses the same project session after restart.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / session_intentAdded value: +{ + "description": "Current durable task intent. A changed value creates an immutable revision.", + "maxLength": 2000, + "minLength": 1, + "type": "string" +} - added
Output schema / properties / memoryAdded value: +{ + "type": "string" +} - added
Output schema / properties / memory_handleAdded value: +{ + "type": "string" +} - added
Output schema / properties / research_contextAdded value: +{ + "additionalProperties": false, + "properties": { + "prior_searches": { + "items": { + "additionalProperties": false, + "properties": { + "query": { + "type": "string" + }, + "relation": { + "enum": [ + "same", + "related", + "recent" + ], + "type": "string" + }, + "results": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "searched_at": { + "type": "string" + }, + "surface": { + "type": "string" + } + }, + "required": [ + "query", + "relation", + "searched_at", + "results" + ], + "type": "object" + }, + "maxItems": 3, + "type": "array" + } + }, + "required": [ + "prior_searches" + ], + "type": "object" +} - added
Output schema / properties / results / items / properties / authorsAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / canonical_urlAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / contentAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / created_atAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / creatorAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / document_idAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / doiAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / excerptAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / extract_errorAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / extraction_qualityAdded value: +{ + "enum": [ + "full_text", + "abstract", + "meta_abstract", + "metadata_only" + ], + "type": "string" +} - added
Output schema / properties / results / items / properties / fresh_webAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / results / items / properties / is_pdfAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / results / items / properties / keywordsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / results / items / properties / languageAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / lengthAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / modified_atAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / page_countAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / producerAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / project_idsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / results / items / properties / publicationAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / published_atAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / retrieval_familiesAdded value: +{ + "items": { + "enum": [ + "exact", + "bm25", + "vector", + "graph" + ], + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / results / items / properties / scoreAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / source_familyAdded value: +{ + "enum": [ + "live", + "document", + "code", + "graph" + ], + "type": "string" +} - added
Output schema / properties / results / items / properties / subjectAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / yearAdded value: +{ + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +}
- Removed
search_extract - Changed
search_parallel43 fields changed- added
Input schema / properties / extract_limitAdded value: +{ + "default": 5, + "description": "Maximum unique result URLs to extract. For parallel search this is one call-wide limit, not a per-query limit.", + "maximum": 10, + "minimum": 1, + "type": "integer" +} - added
Input schema / properties / extract_modeAdded value: +{ + "default": "none", + "description": "Content depth in this search call. Use abstract or full during research instead of following search with separate extract calls. For GitHub results, none reads the README; abstract and full use the same repository eligibility gate but index different source amounts.", + "enum": [ + "none", + "abstract", + "full" + ], + "type": "string" +} - added
Input schema / properties / include_project_idsAdded value: +{ + "description": "Additional read-only projects joined through ontology-aligned schema and identity links. New records stay in project_id.", + "items": { + "maxLength": 64, + "minLength": 1, + "type": "string" + }, + "maxItems": 8, + "type": "array" +} - added
Input schema / properties / max_charsAdded value: +{ + "description": "Maximum characters per extracted result. Defaults to 1500 for abstract and 50000 for full.", + "maximum": 50000, + "minimum": 200, + "type": "integer" +} - added
Input schema / properties / memory_handleAdded value: +{ + "description": "Reuse the handle returned by a prior project-aware call.", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$", + "type": "string" +} - added
Input schema / properties / project_idAdded value: +{ + "description": "Project memory id.", + "maxLength": 64, + "minLength": 1, + "type": "string" +} - changed
Input schema / properties / queries / descriptionPrevious value: -"2-10 queries to run concurrently."New value: +"2-12 independent live queries." - added
Input schema / properties / queries / items / maxLengthAdded value: +400 - added
Input schema / properties / queries / items / minLengthAdded value: +1 - changed
Input schema / properties / queries / maxItemsPrevious value: -10New value: +12 - changed
Input schema / properties / queries / minItemsPrevious value: -1New value: +2 - added
Input schema / properties / session_idAdded value: +{ + "description": "Stable host task id. Reuses the same project session after restart.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - added
Input schema / properties / session_intentAdded value: +{ + "description": "Current durable task intent. A changed value creates an immutable revision.", + "maxLength": 2000, + "minLength": 1, + "type": "string" +} - added
Output schema / properties / memoryAdded value: +{ + "type": "string" +} - added
Output schema / properties / memory_handleAdded value: +{ + "type": "string" +} - added
Output schema / properties / research_contextAdded value: +{ + "additionalProperties": false, + "properties": { + "prior_searches": { + "items": { + "additionalProperties": false, + "properties": { + "query": { + "type": "string" + }, + "relation": { + "enum": [ + "same", + "related", + "recent" + ], + "type": "string" + }, + "results": { + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" + }, + "searched_at": { + "type": "string" + }, + "surface": { + "type": "string" + } + }, + "required": [ + "query", + "relation", + "searched_at", + "results" + ], + "type": "object" + }, + "maxItems": 3, + "type": "array" + } + }, + "required": [ + "prior_searches" + ], + "type": "object" +} - changed
Output schema / properties / results / items / properties / provider / enumPrevious value: -[ - "browser", - "searchapi" -]New value: +[ + "browser", + "searchapi", + "local" +] - added
Output schema / properties / results / items / properties / results / items / properties / authorsAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / canonical_urlAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / contentAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / created_atAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / creatorAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / document_idAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / doiAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / excerptAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / extract_errorAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / extraction_qualityAdded value: +{ + "enum": [ + "full_text", + "abstract", + "meta_abstract", + "metadata_only" + ], + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / fresh_webAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / results / items / properties / results / items / properties / is_pdfAdded value: +{ + "type": "boolean" +} - added
Output schema / properties / results / items / properties / results / items / properties / keywordsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / results / items / properties / results / items / properties / languageAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / lengthAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / results / items / properties / modified_atAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / page_countAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / results / items / properties / producerAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / project_idsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / results / items / properties / results / items / properties / publicationAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / published_atAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / retrieval_familiesAdded value: +{ + "items": { + "enum": [ + "exact", + "bm25", + "vector", + "graph" + ], + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / results / items / properties / results / items / properties / scoreAdded value: +{ + "type": "number" +} - added
Output schema / properties / results / items / properties / results / items / properties / source_familyAdded value: +{ + "enum": [ + "live", + "document", + "code", + "graph" + ], + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / subjectAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / results / items / properties / yearAdded value: +{ + "maximum": 9007199254740991, + "minimum": -9007199254740991, + "type": "integer" +}
3 tool updates
v0.8.0- Added
scholar_search - Changed
search_extract1 field changed- changed
Input schema / properties / max_chars / descriptionPrevious value: -"Truncate each result body. Default depends on mode: ~1500 for abstract, 8000 for full."New value: +"Truncate each result body. Default depends on mode: ~1500 for abstract, 8000 for full (SURF_EXTRACT_MAX_CHARS, capped at 20000 here)."
- Changed
search_parallel3 fields changed- added
Output schema / properties / results / items / properties / degraded_reasonsAdded value: +{ + "items": { + "type": "string" + }, + "type": "array" +} - added
Output schema / properties / results / items / properties / fallback_reasonAdded value: +{ + "type": "string" +} - added
Output schema / properties / results / items / properties / providerAdded value: +{ + "enum": [ + "browser", + "searchapi" + ], + "type": "string" +}
1 tool update
v0.6.8- Changed
extract1 field changed- changed
Input schema / properties / max_chars / descriptionPrevious value: -"Truncate body to this many chars (default 8000)."New value: +"Truncate body to this many chars (default 8000, set via SURF_EXTRACT_MAX_CHARS)."
5 tool updates
v0.6.5- Added
extract - Added
health - Added
search - Added
search_extract - Added
search_parallel
5 tool updates
v0.6.1- Removed
extract - Removed
health - Removed
search - Removed
search_extract - Removed
search_parallel
1 tool update
v0.5.6- Changed
health3 fields changed- added
Output schema / properties / poolAdded value: +{ + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - added
Output schema / properties / selfHealingAdded value: +{ + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" +} - added
Output schema / properties / telemetryAdded value: +{ + "additionalProperties": {}, + "propertyNames": { + "type": "string" + }, + "type": "object" +}
5 tool updates
v0.5.2- Added
extract - Added
health - Added
search - Added
search_extract - Added
search_parallel
5 tool updates
v0.5.1- Removed
extract - Removed
health - Removed
search - Removed
search_extract - Removed
search_parallel
5 tool updates
v0.4.8- Added
extract - Added
health - Added
search - Added
search_extract - Added
search_parallel
5 tool updates
v0.4.7- Removed
extract - Removed
health - Removed
search - Removed
search_extract - Removed
search_parallel
TDQS
Each tool has a clearly delineated purpose: search for single live queries, search_parallel for multi-query discovery, scholar_search for paper metadata, extract for known URLs, project_memory_search for local retrieval, project_memory for local memory management, and health for server status. Even the overlapping search/extract pair is carefully separated by whether discovery is needed.
Most tools follow a descriptive snake_case pattern (search, search_parallel, scholar_search, project_memory_search). Minor deviations like 'extract' and 'health' are verbs/nouns rather than resource_action pairs, but the intent remains predictable and readable.
Seven tools is well-scoped for a server that combines live web search, parallel discovery, paper metadata, content extraction, local project memory, and health monitoring. Each tool earns its place without redundancy or bloat.
The surface covers the full discovery-to-extraction-to-memory lifecycle: live search, parallel queries, scholarly metadata, content extraction, local memory search, memory management, and health checks. Minor gaps exist, such as no explicit tool for managing cache or search providers, but these are edge concerns rather than core workflow blockers.
Maintenance
Related MCP Connectors
MCP server for Google search results via SERP API
One MCP for the Web. Easily search, crawl, navigate, and extract websites without getting blocked.…
Serper MCP — wraps the Serper Google Search API (serper.dev)
SerpApi MCP — wraps SerpApi (serpapi.com) search engines
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceProvides Google search capabilities, web content extraction, and screenshot functionality with advanced bot detection avoidance through the MCP protocol.276-
- AlicenseBqualityFmaintenanceA powerful MCP server that enables parallel Google searching with multiple keywords simultaneously, providing structured results while handling CAPTCHAs and simulating user browsing patterns.198272MIT
- MIT

Brave Search MCP Serverofficial
AlicenseAqualityAmaintenanceAn MCP implementation that integrates the Brave Search API, providing comprehensive search capabilities including web, local business, image, video, news searches, and AI-powered summarization.815,8531,409MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/HarimxChoi/google-surf-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server