Skip to main content
Glama

google-surf-mcp

English | Korean

npm version npm downloads ci MCP Toplist google-surf-mcp MCP server

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 provenance

Seven 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. search and search_parallel can 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

  • 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-lib metadata parsing

  • Code 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 config

First 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 install

If auto-bootstrap fails (rare), run it manually:

npm run bootstrap

Override paths if needed:

CHROME_PATH=/path/to/chrome SURF_TZ=America/New_York npm run bootstrap

Optional 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 codex

Restart 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 codex

Use 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

browser

Default. Uses system Chrome with a dedicated logged-out profile, keeps native search windows hidden, and does not require SEARCH_API. Multiple MCP sessions share one local browser broker.

searchapi

Uses SearchApi as the primary provider and does not initialize Chrome for that tool.

fallback

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, set extract_mode in this call instead of downloading PDFs, cloning repositories, or calling extract separately. Use extract only when the exact public URL is already known and no discovery is needed. With project_id, stored project knowledge is fused with live results, but the call never becomes local-only. limit is 1-20. Extraction defaults to none; extract_limit is 1-10 with default 5. response_content defaults to summary to 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. Set extract_mode in 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. limit is 1-20 per query. The call-wide extract_limit defaults to 12 and allows up to 20 for abstract; full defaults to and allows 10. response_content defaults to summary to bound one-call output.

  • Integrated search extraction reports requested, applied, skipped, truncated, and total_chars. remaining_urls can be passed to extract without 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, use search or search_parallel with extract_mode instead.

    • 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 and summary returns 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 against query. 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 when SURF_RESEARCH=true.

    • action="search": compatibility alias for project_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. Use project_memory_search for relevant bodies or target_id for 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

project_memory_search

Find new information on the web

search

Compare new web results with stored project knowledge

search with project_id

Run several new web queries

search_parallel

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.json

From a source checkout:

npm run build
npm run research:collect -- examples/research-collection.example.json

A 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.json

project_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 outputStyle

One 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.

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 → assertion

  • Code lineage: repository → directory → file → symbol → import/call

  • Research 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 neo4j

For an existing local database, copy both CSV files to the Neo4j import directory, then run:

cypher-shell -f constraints.cypher
cypher-shell -f load.cypher

The 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: ready

Set 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

SEARCH_API

unset

SearchApi API key. Required only when either provider setting is searchapi or fallback. Sent as a bearer token and never placed in the request URL.

SEARCHAPI_API_KEY

unset

Alias for SEARCH_API.

SURF_SEARCH_PROVIDER

browser

Provider for search and search_parallel: browser, searchapi, or fallback.

SURF_SCHOLAR_PROVIDER

browser

Provider for scholar_search: browser, searchapi, or fallback.

SURF_BROWSER_ENGINE

auto

Browser engine: auto, native, or playwright. Native Chrome performs the request before read-only CDP attachment.

CHROME_PATH

auto-detected

absolute path to Chrome binary

SURF_PROFILE_ROOT

~/.google-surf-mcp

where the warm profile lives

SURF_RESEARCH

true

enables local project memory, capture, indexing, project_memory_search, and project_memory; set false for search and extraction only

SURF_RETRIEVAL_MODE

hybrid

research search route: live or hybrid; used only when SURF_RESEARCH=true

SURF_RESEARCH_ROOT

<profile>/research

embedded SurrealDB data directory

SURF_RESEARCH_VECTOR_MODEL

Xenova/multilingual-e5-small

local 384-dimensional model used by HNSW vector retrieval and final reranking. The default model revision is pinned; off disables the vector lane.

SURF_RESEARCH_VECTOR_LOW_MEMORY

true

disables the ONNX CPU memory arena and memory pattern; set false to trade higher peak memory for faster initial indexing

SURF_RESEARCH_VECTOR_THREADS

4

ONNX intra-op thread count, clamped to 1-16

SURF_RESEARCH_REPO_AUTO

true

automatically sparse-index at most one small, relevant GitHub repository per search call

SURF_RESEARCH_REPO_AUTO_MAX_MB

20

maximum searchable source-text size for automatic GitHub indexing; assets are excluded

SURF_RESEARCH_REPO_AUTO_MAX_FILES

2000

maximum searchable source file count for automatic GitHub indexing

SURF_RESEARCH_BROKER_IDLE_MS

60000

how long the shared research broker remains available after the last client disconnects

SURF_RESEARCH_READ_CONCURRENCY

4

maximum concurrent broker reads; identical in-flight reads share one operation

SURF_RESEARCH_QUERY_TIMEOUT_MS

120000

timeout per embedded SurrealDB query, clamped to 1-600 seconds; a timed-out retrieval lane is reported as partial while other lanes still return

GITHUB_TOKEN

unset

optional GitHub token that raises API limits for repository inspection

SURF_RESEARCH_CODE_WORKERS

auto, max 4

Tree-sitter worker count for initial code structure indexing

SURF_LOCALE

en-US

browser locale

SURF_TZ

system tz

e.g. America/New_York

SURF_HEADLESS

true

Controls Playwright extraction, compatibility, and recovery paths. Native search keeps a normal system Chrome window hidden and shows it only for CAPTCHA recovery.

SURF_REMOTE_DEBUG

false

set true on a headless server with remote DevTools. CAPTCHA path emits the DevTools port and throws instead of spawning a window; attach chrome://inspect from a local machine over SSH port-forward to solve.

SURF_CAPTCHA_TIMEOUT_MS

180000

lifetime of the background human-recovery window. MCP calls return immediately and do not wait for this timeout.

SURF_IDLE_CLOSE_MS

30000

idle ms before closing the sequential ctx and pool. 0 disables idle auto-close. Lower = faster cleanup, higher = warmer cache for spaced-out calls.

SURF_ALLOW_PRIVATE

false

set true to allow extract to fetch private/loopback addresses (localhost, 127.0.0.1, 10.x, 192.168.x, 169.254.x, etc). Default blocks them as an SSRF guard.

SURF_EXTRACT_MAX_CHARS

50000

full extraction limit (200-50000); abstract defaults to 1500 and per-call max_chars overrides both

SURF_EXTRACT_OCR

false

OCR scanned/image PDFs via Tesseract (slower; off by default)

SURF_CLOUD_MODE

false

headless/serverless mode: TLS bypass + --no-sandbox + --disable-dev-shm-usage + worker pool disabled + fail-fast on CAPTCHA

SURF_CASCADE_DISABLED

false

pin a single stealth mode (chosen by SURF_USE_STEALTH) instead of the 3-tier auto-cascade

SURF_USE_STEALTH

true

initial stealth tier; only consulted when SURF_CASCADE_DISABLED=true

SURF_HUMANLIKE_MODE

background

off / background (fire-and-forget after returning results) / inline (await before returning, slower)

SURF_RATE_LIMIT_PER_MIN

10

internal cap on Google-facing requests per minute

SURF_CACHE_TTL_SEARCH_MS

86400000

search cache TTL (24h); 0 disables caching

SURF_CACHE_MAX_ENTRIES

1000

LRU cap per cache namespace

SURF_CACHE_ROOT

<profile>/cache

cache directory

SURF_INSECURE_TLS

=SURF_CLOUD_MODE

--ignore-certificate-errors (auto-on in cloud mode)

SURF_NO_SANDBOX

=SURF_CLOUD_MODE

--no-sandbox (auto-on in cloud mode)

SURF_TELEMETRY

false

set true to enable jsonl event logging (search outcomes, cache hits/misses, tool errors, parser staleness) under {SURF_TELEMETRY_ROOT}. Designed as the input feed for the self-healing pipeline. Off by default.

SURF_TELEMETRY_ROOT

<profile>/telemetry

directory for jsonl telemetry files. UTC-dated one file per day (YYYY-MM-DD.jsonl).

SURF_SELF_HEALING

true

per-strategy outcome tracking + persisted reordering. Healing must win by 3 outcomes before reorder kicks in, so single-call flapping is impossible. Set false to pin the default strategy order.

SURF_SELF_HEALING_FILE

<profile>/.heal/strategy-order.json

persistence path for healing state. Atomic tmp+rename writes; debounced 5s.

SURF_LLM_HEAL

false

opt-in for LLM-assisted selector repair in the workflow-only repairWithLLM helper. Off by default, so no third-party LLM request fires.

SURF_LLM_PROVIDER

anthropic

LLM repair provider: anthropic or orcarouter.

SURF_LLM_MODEL

provider default

model for LLM-assisted repair. Defaults to claude-sonnet-4-6 for Anthropic and orcarouter/auto for OrcaRouter.

ANTHROPIC_API_KEY

unset

Anthropic key used only when LLM repair is enabled with the Anthropic provider.

ORCAROUTER_API_KEY

unset

OrcaRouter key used only when LLM repair is enabled with the OrcaRouter provider.

ORCA_KEY

unset

Alias for ORCAROUTER_API_KEY.

OrcaRouter

SURF_LLM_HEAL=true
SURF_LLM_PROVIDER=orcarouter
ORCAROUTER_API_KEY=...
SURF_LLM_MODEL=orcarouter/auto

Troubleshooting

  • 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=fallback and SURF_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 retry

    • SURF_REMOTE_DEBUG=true: DevTools port + instructions printed, attach chrome://inspect locally to solve

    • SURF_CLOUD_MODE=true: fail-fast with CAPTCHA_REQUIRED error

  • 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_HEAL optional, human review required, never auto-merged).

  • Playwright searches feel slower than expected: check health().pool.fallback. true means 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 for search, search_parallel, and scholar_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: extract blocks localhost, private IPs, AWS metadata by default. Set SURF_ALLOW_PRIVATE=true to allow them.

  • Cache cleanup: npm run cache:clear removes 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 tools
extractKnown 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.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesPublic http(s) URL. Loopback/private IPs blocked unless SURF_ALLOW_PRIVATE=true.
modeNoExtraction 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_charsNoMaximum returned characters when response_content=full. Defaults to 1500 for abstract and 50000 for full.
project_idNoProject memory id.
session_idNoStable host task id. Reuses the same project session after restart.
memory_handleNoReuse the handle returned by a prior project-aware call.
session_intentNoCurrent durable task intent. A changed value creates an immutable revision.
response_contentNoControls 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

ParametersJSON Schema
NameRequiredDescription
doiNo
urlNo
metaNo
yearNo
errorNo
titleNo
is_pdfNo
lengthNo
memoryNo
authorsNo
contentNo
creatorNo
excerptNo
subjectNo
keywordsNo
languageNo
producerNo
truncatedNo
created_atNo
elapsed_msNo
page_countNo
descriptionNo
modified_atNo
publicationNo
published_atNo
canonical_urlNo
memory_handleNo
source_lengthNo
extraction_qualityNo

TDQS

A4/5.0
Behavior4/5

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.

Conciseness3/5

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.

Completeness4/5

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.

Parameters3/5

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.

Purpose4/5

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.

Usage Guidelines5/5

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 CheckA
Read-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.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
poolNo
cacheNo
errorNo
configNo
cascadeNo
versionNo
researchNo
telemetryNo
rateLimiterNo
selfHealingNo
nativeBrowserNo

TDQS

A4.7/5.0
Behavior4/5

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.

Conciseness5/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
bodyNoPlan body for record_type=plan.
nameNoProject name for create, or entity name lookup for show.
limitNoMaximum local RAG results for action=search.
queryNoRequired for action=search. Searches stored local knowledge only and does not start live web discovery.
rootsNoApproved local roots to index. Omit to rebuild derived state from stored sources.
titleNoPlan title, experiment name, or decision title.
actionYescreate: 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.
intentNoDurable intent for record_type=session.
reasonNoRequired reason for any correction.
statusNoUse success, failed, or inconclusive to finish an experiment. Omit or use running when starting it.
aliasesNoOntology term aliases, or aliases moved during entity_split.
metricsNoTerminal experiment metrics.
summaryNoExperiment result or decision summary.
versionNoOntology revision number. Defaults to 1 or the superseded term version plus one.
git_rootNoOptional Git root recorded with a rebuild snapshot.
valid_toNoCorrected assertion valid-time end.
artifactsNoExperiment artifact paths or identifiers.
target_idNoAssertion or entity id for show or correction.
hypothesisNoHypothesis when starting an experiment.
project_idNoStable project id. For search, this is the write-isolated primary scope. Omit to list projects or use all_projects.
source_idsNoEntity ids merged into target_id by entity_merge.
valid_fromNoCorrected assertion valid-time start.
export_viewNoInitial 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_modeNoPreview first. Apply requires its confirm_token. Restore reverses deletion.
record_typeNoRequired 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.
replacementNoNew assertion value, or new entity name for entity_split.
all_projectsNoSearch or export every active named project. Excludes Inbox and cannot be combined with project ids.
detail_levelNoFor 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_idsNoEvidence retained on a corrected assertion.
change_reasonNoReason for a new plan revision.
confirm_tokenNoToken returned by the matching forget preview.
experiment_idNoExperiment to finish or associate with a decision.
export_formatNoVisualization 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_handleNoExisting project session handle for a session intent revision.
ontology_kindNoOntology term kind for record_type=ontology.
query_variantsNoOptional local retrieval variants for action=search. They run inside one broker request and are fused before one rerank.
correction_kindNoCorrection operation. Defaults to assertion.
plan_revision_idNoPlan revision associated with an experiment or decision.
supersedes_term_idNoPrior ontology term replaced by this revision.
include_project_idsNoAdditional read-only projects for one local search or integrated visualization export.
based_on_experiment_idNoExperiment that motivated a plan revision.

Output Schema

ParametersJSON Schema
NameRequiredDescription
metaNo
errorNo
indexNo
plansNo
queryNo
entityNo
forgetNo
memoryNo
recordNo
projectNo
resultsNo
sessionNo
entitiesNo
projectsNo
assertionNo
decisionsNo
elapsed_msNo
job_countsNo
plan_countNo
experimentsNo
entity_countNo
memory_handleNo
session_countNo
visualizationNo
decision_countNo
document_countNo
assertion_countNo
correction_countNo
experiment_countNo
search_event_countNo
source_entry_countNo
active_source_snapshotNo
entity_operation_countNo
citation_observation_countNo

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness4/5

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.

Completeness5/5

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.

Parameters4/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

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.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoMaximum results per query. Integer 1-20, default 10.
queriesYes2-12 independent live queries.
max_charsNoMaximum returned characters per extracted result in response_content=full. Defaults to 1500 for abstract and 50000 for full.
project_idNoProject memory id.
session_idNoStable host task id. Reuses the same project session after restart.
extract_modeNoContent 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_limitNoCall-wide maximum unique result URLs to extract. Integer 1-20, default 12 for abstract; full defaults to and allows at most 10.
memory_handleNoReuse the handle returned by a prior project-aware call.
session_intentNoCurrent durable task intent. A changed value creates an immutable revision.
response_contentNoControls 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_idsNoAdditional read-only projects joined through ontology-aligned schema and identity links. New records stay in project_id.

Output Schema

ParametersJSON Schema
NameRequiredDescription
metaNo
errorNo
memoryNo
resultsNo
elapsed_msNo
memory_handleNo
research_contextNo

TDQS

A4.8/5.0
Behavior5/5

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.

Conciseness3/5

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.

Completeness5/5

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.

Parameters5/5

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.

Purpose5/5

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.

Usage Guidelines5/5

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.

  1. 3 tool updatesv1.0.6
    • Changedproject_memory4 fields changed
      • addedInput schema / properties / detail_level
        Added 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"
        +}
      • addedOutput schema / properties / decision_count
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / experiment_count
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / plan_count
        Added value: +{
        +  "type": "number"
        +}
    • Changedproject_memory_search3 fields changed
      • addedOutput schema / properties / decision_count
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / experiment_count
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / plan_count
        Added value: +{
        +  "type": "number"
        +}
    • Changedsearch2 fields changed
      • changedInput schema / properties / response_content / default
        Previous value: -"full"New value: +"summary"
      • changedInput schema / properties / response_content / description
        Previous 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."
  2. 5 tool updatesv1.0.4
    • Changedhealth1 field changed
      • addedOutput schema / properties / nativeBrowser
        Added value: +{
        +  "additionalProperties": {},
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
    • Changedproject_memory1 field changed
      • addedInput schema / properties / query_variants
        Added 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"
        +}
    • Changedproject_memory_search1 field changed
      • addedInput schema / properties / query_variants
        Added 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"
        +}
    • Changedsearch1 field changed
      • changedInput schema / properties / extract_mode / description
        Previous 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."
    • Changedsearch_parallel1 field changed
      • changedInput schema / properties / extract_mode / description
        Previous 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."
  3. 6 tool updatesv1.0.2
    • Changedextract4 fields changed
      • changedInput schema / properties / max_chars / description
        Previous 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."
      • addedInput schema / properties / response_content
        Added 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"
        +}
      • addedOutput schema / properties / source_length
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / truncated
        Added value: +{
        +  "type": "boolean"
        +}
    • Changedproject_memory2 fields changed
      • addedOutput schema / properties / results / items / properties / source_length
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / truncated
        Added value: +{
        +  "type": "boolean"
        +}
    • Changedproject_memory_search2 fields changed
      • addedOutput schema / properties / results / items / properties / source_length
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / truncated
        Added value: +{
        +  "type": "boolean"
        +}
    • Changedscholar_search1 field changed
      • changedInput schema / properties / limit / description
        Previous value: -"Max papers (default 10)."New value: +"Maximum papers. Integer 1-10, default 10."
    • Changedsearch6 fields changed
      • changedInput schema / properties / extract_limit / description
        Previous 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."
      • changedInput schema / properties / limit / description
        Previous value: -"Max results (default 10)."New value: +"Maximum results. Integer 1-20, default 10."
      • changedInput schema / properties / max_chars / description
        Previous 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."
      • addedInput schema / properties / response_content
        Added 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"
        +}
      • addedOutput schema / properties / results / items / properties / source_length
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / truncated
        Added value: +{
        +  "type": "boolean"
        +}
    • Changedsearch_parallel8 fields changed
      • removedInput schema / properties / extract_limit / default
        Removed value: -5
      • changedInput schema / properties / extract_limit / description
        Previous 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."
      • changedInput schema / properties / extract_limit / maximum
        Previous value: -10New value: +20
      • changedInput schema / properties / limit / description
        Previous value: -"Max results per query."New value: +"Maximum results per query. Integer 1-20, default 10."
      • changedInput schema / properties / max_chars / description
        Previous 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."
      • addedInput schema / properties / response_content
        Added 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"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / source_length
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / truncated
        Added value: +{
        +  "type": "boolean"
        +}
  4. 8 tool updatesv1.0.0
    • Changedextract23 fields changed
      • removedInput schema / properties / max_chars / default
        Removed value: -8000
      • changedInput schema / properties / max_chars / description
        Previous 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."
      • addedInput schema / properties / memory_handle
        Added 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"
        +}
      • changedInput schema / properties / mode / description
        Previous 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."
      • addedInput schema / properties / project_id
        Added value: +{
        +  "description": "Project memory id.",
        +  "maxLength": 64,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedInput schema / properties / session_id
        Added value: +{
        +  "description": "Stable host task id. Reuses the same project session after restart.",
        +  "maxLength": 200,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedInput schema / properties / session_intent
        Added value: +{
        +  "description": "Current durable task intent. A changed value creates an immutable revision.",
        +  "maxLength": 2000,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedOutput schema / properties / authors
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / canonical_url
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / created_at
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / creator
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / description
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / doi
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / keywords
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / language
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / memory
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / memory_handle
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / modified_at
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / producer
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / publication
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / published_at
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / subject
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / year
        Added value: +{
        +  "maximum": 9007199254740991,
        +  "minimum": -9007199254740991,
        +  "type": "integer"
        +}
    • Changedhealth1 field changed
      • addedOutput schema / properties / research
        Added value: +{
        +  "additionalProperties": {},
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
    • Addedproject_memory
    • Addedproject_memory_search
    • Changedscholar_search7 fields changed
      • addedInput schema / properties / memory_handle
        Added 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"
        +}
      • addedInput schema / properties / project_id
        Added value: +{
        +  "description": "Project memory id.",
        +  "maxLength": 64,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedInput schema / properties / session_id
        Added value: +{
        +  "description": "Stable host task id. Reuses the same project session after restart.",
        +  "maxLength": 200,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedInput schema / properties / session_intent
        Added value: +{
        +  "description": "Current durable task intent. A changed value creates an immutable revision.",
        +  "maxLength": 2000,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedOutput schema / properties / memory
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / memory_handle
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / research_context
        Added 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"
        +}
    • Changedsearch37 fields changed
      • addedInput schema / properties / extract_limit
        Added 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"
        +}
      • addedInput schema / properties / extract_mode
        Added 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"
        +}
      • addedInput schema / properties / include_project_ids
        Added 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"
        +}
      • addedInput schema / properties / max_chars
        Added value: +{
        +  "description": "Maximum characters per extracted result. Defaults to 1500 for abstract and 50000 for full.",
        +  "maximum": 50000,
        +  "minimum": 200,
        +  "type": "integer"
        +}
      • addedInput schema / properties / memory_handle
        Added 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"
        +}
      • addedInput schema / properties / project_id
        Added value: +{
        +  "description": "Project memory id.",
        +  "maxLength": 64,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedInput schema / properties / session_id
        Added value: +{
        +  "description": "Stable host task id. Reuses the same project session after restart.",
        +  "maxLength": 200,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedInput schema / properties / session_intent
        Added value: +{
        +  "description": "Current durable task intent. A changed value creates an immutable revision.",
        +  "maxLength": 2000,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedOutput schema / properties / memory
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / memory_handle
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / research_context
        Added 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"
        +}
      • addedOutput schema / properties / results / items / properties / authors
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / canonical_url
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / content
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / created_at
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / creator
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / document_id
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / doi
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / excerpt
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / extract_error
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / extraction_quality
        Added value: +{
        +  "enum": [
        +    "full_text",
        +    "abstract",
        +    "meta_abstract",
        +    "metadata_only"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / fresh_web
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / results / items / properties / is_pdf
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / results / items / properties / keywords
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / results / items / properties / language
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / length
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / modified_at
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / page_count
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / producer
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / project_ids
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / results / items / properties / publication
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / published_at
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / retrieval_families
        Added value: +{
        +  "items": {
        +    "enum": [
        +      "exact",
        +      "bm25",
        +      "vector",
        +      "graph"
        +    ],
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / results / items / properties / score
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / source_family
        Added value: +{
        +  "enum": [
        +    "live",
        +    "document",
        +    "code",
        +    "graph"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / subject
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / year
        Added value: +{
        +  "maximum": 9007199254740991,
        +  "minimum": -9007199254740991,
        +  "type": "integer"
        +}
    • Removedsearch_extract
    • Changedsearch_parallel43 fields changed
      • addedInput schema / properties / extract_limit
        Added 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"
        +}
      • addedInput schema / properties / extract_mode
        Added 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"
        +}
      • addedInput schema / properties / include_project_ids
        Added 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"
        +}
      • addedInput schema / properties / max_chars
        Added value: +{
        +  "description": "Maximum characters per extracted result. Defaults to 1500 for abstract and 50000 for full.",
        +  "maximum": 50000,
        +  "minimum": 200,
        +  "type": "integer"
        +}
      • addedInput schema / properties / memory_handle
        Added 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"
        +}
      • addedInput schema / properties / project_id
        Added value: +{
        +  "description": "Project memory id.",
        +  "maxLength": 64,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • changedInput schema / properties / queries / description
        Previous value: -"2-10 queries to run concurrently."New value: +"2-12 independent live queries."
      • addedInput schema / properties / queries / items / maxLength
        Added value: +400
      • addedInput schema / properties / queries / items / minLength
        Added value: +1
      • changedInput schema / properties / queries / maxItems
        Previous value: -10New value: +12
      • changedInput schema / properties / queries / minItems
        Previous value: -1New value: +2
      • addedInput schema / properties / session_id
        Added value: +{
        +  "description": "Stable host task id. Reuses the same project session after restart.",
        +  "maxLength": 200,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedInput schema / properties / session_intent
        Added value: +{
        +  "description": "Current durable task intent. A changed value creates an immutable revision.",
        +  "maxLength": 2000,
        +  "minLength": 1,
        +  "type": "string"
        +}
      • addedOutput schema / properties / memory
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / memory_handle
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / research_context
        Added 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"
        +}
      • changedOutput schema / properties / results / items / properties / provider / enum
        Previous value: -[
        -  "browser",
        -  "searchapi"
        -]New value: +[
        +  "browser",
        +  "searchapi",
        +  "local"
        +]
      • addedOutput schema / properties / results / items / properties / results / items / properties / authors
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / canonical_url
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / content
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / created_at
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / creator
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / document_id
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / doi
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / excerpt
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / extract_error
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / extraction_quality
        Added value: +{
        +  "enum": [
        +    "full_text",
        +    "abstract",
        +    "meta_abstract",
        +    "metadata_only"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / fresh_web
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / is_pdf
        Added value: +{
        +  "type": "boolean"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / keywords
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / language
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / length
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / modified_at
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / page_count
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / producer
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / project_ids
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / publication
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / published_at
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / retrieval_families
        Added value: +{
        +  "items": {
        +    "enum": [
        +      "exact",
        +      "bm25",
        +      "vector",
        +      "graph"
        +    ],
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / score
        Added value: +{
        +  "type": "number"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / source_family
        Added value: +{
        +  "enum": [
        +    "live",
        +    "document",
        +    "code",
        +    "graph"
        +  ],
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / subject
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / results / items / properties / year
        Added value: +{
        +  "maximum": 9007199254740991,
        +  "minimum": -9007199254740991,
        +  "type": "integer"
        +}
  5. 3 tool updatesv0.8.0
    • Addedscholar_search
    • Changedsearch_extract1 field changed
      • changedInput schema / properties / max_chars / description
        Previous 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)."
    • Changedsearch_parallel3 fields changed
      • addedOutput schema / properties / results / items / properties / degraded_reasons
        Added value: +{
        +  "items": {
        +    "type": "string"
        +  },
        +  "type": "array"
        +}
      • addedOutput schema / properties / results / items / properties / fallback_reason
        Added value: +{
        +  "type": "string"
        +}
      • addedOutput schema / properties / results / items / properties / provider
        Added value: +{
        +  "enum": [
        +    "browser",
        +    "searchapi"
        +  ],
        +  "type": "string"
        +}
  6. 1 tool updatev0.6.8
    • Changedextract1 field changed
      • changedInput schema / properties / max_chars / description
        Previous 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)."
  7. 5 tool updatesv0.6.5
    • Addedextract
    • Addedhealth
    • Addedsearch
    • Addedsearch_extract
    • Addedsearch_parallel
  8. 5 tool updatesv0.6.1
    • Removedextract
    • Removedhealth
    • Removedsearch
    • Removedsearch_extract
    • Removedsearch_parallel
  9. 1 tool updatev0.5.6
    • Changedhealth3 fields changed
      • addedOutput schema / properties / pool
        Added value: +{
        +  "additionalProperties": {},
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • addedOutput schema / properties / selfHealing
        Added value: +{
        +  "additionalProperties": {},
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
      • addedOutput schema / properties / telemetry
        Added value: +{
        +  "additionalProperties": {},
        +  "propertyNames": {
        +    "type": "string"
        +  },
        +  "type": "object"
        +}
  10. 5 tool updatesv0.5.2
    • Addedextract
    • Addedhealth
    • Addedsearch
    • Addedsearch_extract
    • Addedsearch_parallel
  11. 5 tool updatesv0.5.1
    • Removedextract
    • Removedhealth
    • Removedsearch
    • Removedsearch_extract
    • Removedsearch_parallel
  12. 5 tool updatesv0.4.8
    • Addedextract
    • Addedhealth
    • Addedsearch
    • Addedsearch_extract
    • Addedsearch_parallel
  13. 5 tool updatesv0.4.7
    • Removedextract
    • Removedhealth
    • Removedsearch
    • Removedsearch_extract
    • Removedsearch_parallel

TDQS

A4.4/5.0
Disambiguation5/5

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.

Naming Consistency4/5

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.

Tool Count5/5

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.

Completeness4/5

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

ActivityActive
ResponsivenessSlow

Related MCP Connectors

Related MCP Servers

Latest Blog Posts

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