Skip to main content
Glama

docs-search-mcp

A small Model Context Protocol server in Python that gives an AI client (Claude Desktop, Claude Code, or any MCP client) five tools: search and read a folder of local notes, fetch a public web page as markdown, and convert times between timezones.

Based in part on the fetch and time reference servers from modelcontextprotocol/servers. See SOURCES.md.

Why I built it

To learn how MCP works end to end: tool schemas, annotations, transports, error handling, and testing a server through a real MCP client - and to practise the security thinking tools need (path traversal, SSRF).

Related MCP server: vault-mcp-bridge

Tools

Tool

What it does

list_notes

Lists .md/.txt files under the notes folder

search_notes(query, limit)

BM25 keyword search over note sections; returns path, heading, score, snippet

read_note(path, max_chars)

Reads one note; refuses paths outside the folder, symlinks, other file types

fetch_url(url, max_length, start_index, raw)

Fetches a page → markdown, paginated; honours robots.txt; refuses private addresses

convert_time(source_timezone, time, target_timezone, date?)

HH:MM conversion between IANA zones, DST-aware for a given date

All tools are marked read-only via MCP annotations (fetch_url additionally openWorldHint).

Architecture

MCP client (Claude Desktop / Code / your script)
        | JSON-RPC over stdio (or streamable HTTP)
        v
  server.py  (FastMCP: tool registration, schemas from type hints, annotations, error mapping)
     |-- notes.py     BM25 index over sections, safe path resolution
     |-- web.py       SSRF guard -> robots.txt -> size-capped fetch -> HTML to markdown
     '-- timeconv.py  zoneinfo conversion, DST edge cases

Tech stack

Python 3.10+, official mcp SDK (1.x, FastMCP), httpx, markdownify, zoneinfo/tzdata, pytest + pytest-asyncio.

How to run

python -m venv .venv && source .venv/bin/activate       # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
python -m pytest                                         # 39 tests, no network needed
python examples/client_demo.py                           # starts the server and calls tools
docs-search-mcp --docs-dir /path/to/your/notes           # run it yourself (stdio)

Use with Claude Desktop: add examples/claude_desktop_config.json to your MCP config (use the absolute path to docs-search-mcp inside your venv if it is not on PATH), then restart the app.

Example

examples/client_demo_output.txt is real output from running client_demo.py, e.g. searching "stdio logs stdout" returns the Transports section of mcp-notes.md, and converting 10:00 Warsaw → Dubai on 2026-07-15 returns 12:00 (UTC+2 → UTC+4).

What I changed / added vs. upstream

See SOURCES.md: a new local-notes toolset, SSRF-guarded fetch, DST-aware time conversion, FastMCP-based server, tests.

Key Technical Concepts

  • MCP: a protocol where a client (inside an AI app) discovers a server's tools (also resources, prompts) and the model decides when to call them. Messages are JSON-RPC 2.0.

  • Transports: stdio (client launches the server; stdout is the protocol channel, so logs go to stderr) vs. streamable HTTP (server on a port).

  • Tool schema: name + description + JSON Schema for inputs. FastMCP derives it from Python type hints and the docstring; the description is what the model reads to decide when to use a tool.

  • Tool annotations: hints like readOnlyHint and openWorldHint that let clients decide about confirmations. They are hints, not enforcement.

  • Tool errors vs. protocol errors: bad input returns isError: true with a message the model can react to; the server keeps running.

  • Path traversal: resolve the final path (following symlinks) and verify it stays under the root.

  • SSRF: a fetch tool lets the model (or a prompt injection) make your machine request URLs. Resolve the host, block non-public IPs, re-check on every redirect.

  • BM25: ranking by term frequency × inverse document frequency with length normalisation.

  • DST edge cases: some local times don't exist (spring forward) and some occur twice (fall back).

Limitations

  • Keyword search only (no embeddings); the index is built at startup, so new notes need a restart.

  • SSRF guard resolves DNS before connecting; a DNS-rebinding attacker could still race it. Don't expose this server to untrusted networks.

  • HTML cleanup is simple; JavaScript-rendered pages return little. No authentication on the HTTP transport.

  • Tested with the official Python MCP client and a local HTTP test server; I have not tested it inside Claude Desktop.

Future improvements

Hybrid (embedding + BM25) search reusing ideas from my rag-eval-lab, MCP resources for notes, file watching/reindex, auth for HTTP transport, readability-style article extraction.

Available Tools

5 tools
convert_timeA
Read-onlyIdempotent

Convert a 24-hour HH:MM time between IANA timezones (e.g. Europe/Warsaw -> Asia/Dubai). Optional date (YYYY-MM-DD) makes daylight-saving correct for that day; default is today.

ParametersJSON Schema
NameRequiredDescriptionDefault
dateNo
timeYes
source_timezoneYes
target_timezoneYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already establish readOnly/idempotent/non-destructive, and the description adds genuine behavioral context: DST correctness depends on the supplied date, and the result is day-sensitive. It does not describe invalid-timezone error behavior or the exact returned format, which keeps it short of a 5.

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 sentences, front-loaded with the core operation and followed by the one non-obvious qualifier (date/DST). No filler or repetition of the tool name.

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?

For a 4-parameter utility with no output schema and no schema-level descriptions, it supplies all input formats and behavior. The only omission is the shape of the returned value (e.g. converted HH:MM plus offset), which is implied but not stated.

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 description coverage is 0%, so the description must carry the load and does: it gives the time format (HH:MM 24-hour), the date format (YYYY-MM-DD) and its default/effect, and shows IANA timezone syntax via the Europe/Warsaw -> Asia/Dubai example, covering source and target zones.

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?

States a specific verb (Convert), resource (a 24-hour HH:MM time), and a scoped transformation between named IANA timezones, with a concrete example. Unambiguous and trivially distinguishable from the note/URL siblings.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explains when the optional date matters (DST-correct conversion for that day) and what the default is (today), which is the only real usage decision for this tool. No explicit exclusions, but there is no competing alternative tool to route away from.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

fetch_urlA
Read-onlyIdempotent

Fetch a public web page and return it as markdown (or raw text). Honors robots.txt and refuses private/internal addresses. Long pages: pass next_start_index as start_index.

ParametersJSON Schema
NameRequiredDescriptionDefault
rawNo
urlYes
max_lengthNo
start_indexNo

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, openWorld and non-destructive, so the safety profile is covered. The description adds genuinely useful behavior beyond that: robots.txt compliance, refusal of private/internal addresses, and a pagination convention for long pages. It stops short of mentioning rate limits, timeouts, or error modes.

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?

Three compact sentences, front-loaded with the core action and output format, then constraints, then the pagination hint. No filler, every clause earns its place.

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?

There is no output schema, and the description correctly compensates by stating the return format (markdown/raw text). Combined with the constraint disclosure and pagination guidance, an agent has everything needed to invoke this tool 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 description coverage is 0%, so the description must carry the load. It explains the raw flag ('or raw text') and the start_index pagination parameter explicitly, which is meaningful added value; only max_length goes unexplained, though its name and default are largely self-describing.

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?

States a specific verb (fetch) and resource (public web page) plus the return format (markdown or raw text). No sibling tool overlaps, so no differentiation is needed, and an agent can immediately tell what this does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear operating context and constraints (honors robots.txt, refuses private/internal addresses) and hints at the pagination workflow for long pages. It doesn't name explicit alternatives, but no sibling tool is a plausible substitute, so there is little to exclude.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_notesA
Read-onlyIdempotent

List the note files (.md/.txt) available to search, with sizes in bytes.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, so the safety profile is covered. The description adds the file-type filter and the byte-size output detail, but says nothing about scope (which directory/workspace is listed) or ordering.

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?

A single front-loaded sentence with no filler; the file-type constraint and output unit are packed in without waste.

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?

An output schema exists, so return values need not be explained, and a zero-parameter tool has little else to specify. The only gap is the unstated scope of what 'available' means (which directory or store is enumerated).

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 takes zero parameters, so there is no parameter semantics to document and the baseline is 4. Schema coverage is also 100%, leaving nothing for the description to compensate for.

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?

States a specific verb and resource ('List the note files') and adds useful scope detail: only .md/.txt files, with sizes in bytes. It does not explicitly contrast with sibling search_notes or read_note, but 'available to search' hints at the relationship between the listed corpus and search_notes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'available to search' implies this is a discovery step preceding search_notes, but the description never states when to use this versus search_notes or read_note, nor any exclusions. Usage must be inferred from the sibling names.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

read_noteA
Read-onlyIdempotent

Read one note by its relative path as returned by list_notes / search_notes.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYes
max_charsNo

TDQS

A3.9/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, idempotentHint=true and destructiveHint=false, so the safety profile is fully covered without description help. The description adds no behavioral context of its own — nothing about truncation via max_chars, missing-path errors, or what the note content looks like — so it neither helps nor contradicts.

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?

A single front-loaded sentence with no filler; the core action and its input contract are stated immediately.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

There is no output schema, so the description is the only place to explain the return value (note content, truncation behavior, format), and it does not. For a simple two-parameter read tool this is survivable but leaves an agent guessing about truncation and error cases.

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 0%, so the description must compensate, and it only partly does: it clarifies that 'path' is relative and produced by sibling tools, but says nothing about 'max_chars' or what happens when the note exceeds it. One of two parameters is meaningfully explained.

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?

States a specific verb (read) and a precisely scoped resource (one note), which immediately separates it from list_notes and search_notes that operate on collections. The addition of the relative-path provenance makes the operation unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description tells the agent where the required path comes from (list_notes / search_notes), which is the key usage dependency. It stops short of naming when not to use it (e.g., fetch_url for external content) or stating prerequisites, so it is clear context without exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_notesA
Read-onlyIdempotent

Keyword-search (BM25) the notes. Returns the best matching sections with a snippet. Use read_note afterwards to see a whole file.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNo
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and destructiveHint=false, so the safety profile is covered. The description adds meaningful behavioral context beyond annotations: BM25 keyword matching, snippet-level results rather than whole notes, and the need for read_note to get full files.

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?

Three tight sentences with zero waste: action, return shape, and follow-up. The main verb is front-loaded and every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema and annotations cover return values and safety, so the description needn't explain those. However, with 0% schema description coverage, the missing explanation of query syntax and limit semantics is a real gap for an agent trying to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for both parameters. It only hints that 'query' is a keyword search and says nothing about the 'limit' parameter (e.g., max results, default behavior), leaving the primary parameter semantics substantially undocumented.

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?

States a specific verb and resource with the search mechanism: 'Keyword-search (BM25) the notes.' It also clarifies the return granularity ('best matching sections with a snippet'), which distinguishes it from list_notes (browse all) and read_note (whole file). An agent can tell what this tool does without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the alternative read_note and the condition for using it ('afterwards to see a whole file'), giving clear follow-up guidance. However, it does not state when to prefer search_notes over list_notes or when not to use it, leaving some sibling selection to inference.

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.

  1. 5 tool updatesv0.1.0
    • First observedconvert_time
    • First observedfetch_url
    • First observedlist_notes
    • First observedread_note
    • First observedsearch_notes

TDQS

A3.9/5.0

Scored across 5 tools

Disambiguation3/5

The four notes/web tools are clearly distinct (list, search, read, fetch), but convert_time is a complete non-sequitur for a docs-search server and muddies the server's overall purpose. An agent may wonder why a timezone converter lives alongside note search.

Naming Consistency5/5

All five tools follow a consistent snake_case verb_noun pattern: fetch_url, convert_time, list_notes, search_notes, read_note. Naming is highly predictable and readable.

Tool Count4/5

Five tools is a well-scoped, lightweight set for a search server, and the notes trio (list/search/read) earns its place. Slightly penalized because convert_time does not belong to the domain.

Completeness4/5

For a read-only search server, list/search/read plus fetch_url covers the core retrieval lifecycle with no obvious dead ends. The only oddity is the unrelated convert_time tool rather than a true missing operation.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that gives AI agents read and write access to a folder of Markdown notes, supporting full-text search, reading, listing, and writing notes. It is deliberately small, with no dependencies beyond the MCP SDK, and includes proper path traversal protection.
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Read-only MCP bridge that exposes secure search and fetch tools over an Obsidian-compatible Markdown vault, enabling ChatGPT to query notes without write access.
    1
    Apache 2.0
  • F
    license
    Not graded
    quality
    C
    maintenance
    Exposes a folder of Markdown runbooks to an AI assistant via three tools (list_notes, search_notes, read_note) over MCP's streamable-http transport.
    -
  • F
    license
    A
    quality
    C
    maintenance
    Exposes a Notion-synced AI OS as read-only MCP tools, enabling MCP-compatible AI clients to search the vault, fetch page contents, list projects, and check project statuses without scraping public links.
    4
    -