Skip to main content
Glama

mcp-project-helper

A minimal MCP (Model Context Protocol) server that gives an AI coding assistant — in particular, Claude Code — a small, safe set of tools for working with one specific project directory: searching its files, reading a file, searching local documentation, and running a pre-approved check (tests).

The project is implemented in stages (Stage 0 → Stage 3, prompt history is in PROMPTS.md); the current stage is Stage 3: finalization. All four tools are implemented and covered by tests (Stage 1), the server is connected to Claude Code and manually verified with real requests through the Claude Code CLI (Stage 2–3, evidence is in evidence/).

About the project

Instead of giving the assistant direct shell access or unrestricted access to the file system, the project provides a narrow, easily auditable surface of four tools:

  • search_project_files — text search, restricted to the project root.

  • read_project_file — reading a single file, restricted to the project root.

  • get_docs — search of local documentation in docs/ of this repository.

  • run_project_check — running a check from a whitelist (currently tests), but never an arbitrary shell command.

This is a learning project (homework) — its goal is not to cover all possible use cases, but to show an end-to-end, honestly documented example of an MCP server: from the skeleton and security primitives (Stage 0), through a real implementation of tools (Stage 1), to integration with an IDE agent and reproducible evidence of real calls (Stage 2–3).

Related MCP server: GPT Commander

What MCP is and how agent connection works

MCP (Model Context Protocol) is an open protocol based on JSON-RPC that describes how an AI assistant (client/host, e.g. Claude Code) discovers and calls external tools provided by a separate process (MCP server), without the assistant having direct access to the shell, network, or host file system.

This project uses the stdio transport — the simplest and most common way for local tools:

  1. The host (Claude Code) reads its MCP configuration (.mcp.json) and launches the server as an ordinary local subprocess, with the specified command/arguments and environment variables.

  2. The host and server exchange JSON-RPC messages via the stdin/stdout of this subprocess (hence the requirement that stdout is reserved exclusively for the protocol — see the "Logging and debugging" section).

  3. The host calls initialize() — the server responds with its name/version (mcp-project-helper 0.1.0) and capabilities.

  4. The host calls list_tools() — the server returns the list of registered tools with their name, description, and JSON Schema of input parameters (inputSchema), generated by the MCP SDK from the function signature.

  5. When the user (or the model itself) decides to call one of the tools, the host sends call_tool(name, arguments); the server executes the corresponding Python function and returns a structured result (see the "Tool outputs contract" section below) or a tool-level error.

  6. No network port is opened: the server lifecycle is fully tied to the subprocess launched by the host — if the host closes the connection, the subprocess terminates.

There are no calls to any LLM/AI API (OpenAI, Anthropic, etc.) here: this server only provides tools that the client (Claude Code) calls; "search" in get_docs is a simple deterministic substring match over Markdown sections, without embeddings/vector DB. No API key is needed to run the server.

What counts as a tool in this server

A tool is an ordinary Python function decorated with @mcp.tool(), accepting JSON-serializable arguments and returning dict[str, Any]. The MCP SDK automatically:

  • generates inputSchema (JSON Schema) from the signature and type annotations of the function arguments — this schema does not have to be described by hand anywhere separately;

  • turns the return type annotation -> dict[str, Any] into structured tool output (outputSchema/structuredContent), see the "Tool outputs contract" section below;

  • turns an unhandled Python exception inside the tool function into a structured result with a tool-level error (CallToolResult.is_error = True), without crashing the MCP session itself.

All four registrations are located next to each other in server.py:37-58; each thin wrapper facing MCP (whose docstring becomes the tool description visible to the model) delegates the call to the real implementation in tools/*.py, separating the protocol-level signature from the logic.

Stack

  • Python 3.14 (requires-python = ">=3.10" in pyproject.toml — this is the actual lower bound of the MCP SDK used, not a claim that only 3.14 works).

  • Official MCP Python SDK (package mcp, installed version 2.0.0) — provides the server framework (mcp.server.MCPServer), tool registration (@mcp.tool()) and the stdio transport (mcp.run(transport="stdio")).

  • pytest — the only dev dependency, for the test suite.

  • No LLM/AI API integration and no network transport (HTTP/SSE is not configured) — see the previous section.

Architecture

src/mcp_project_helper/
  server.py        точка входа: создаёт MCPServer, регистрирует tools, запускает stdio
  config.py        корень проекта / корень docs / настройки логирования / whitelist проверок / лимиты
  security.py      resolve_within_root() — единый шлюз ограничения путей
  logging_setup.py логирование в stderr (+ опционально файл), не затрагивая stdout
  tools/
    search_project_files.py   поиск текста в пределах корня проекта
    read_project_file.py      чтение одного файла в пределах корня проекта
    get_docs.py                поиск по секциям markdown в docs/
    run_project_check.py       запуск подпроцесса из белого списка

Every tool that works with files goes through security.resolve_within_root(root, relative_path) before opening any path. config.py determines the project root from the environment variable MCP_PROJECT_HELPER_ROOT (default ./demo_project), so the server can be pointed at any project without changing code.

Implemented MCP tools

search_project_files(query, path=".", max_results=50)

Recursively searches text files under path (relative to the project root; by default — the entire root) for an exact substring match of query. Skips directories from config.IGNORED_DIR_NAMES (.git, .venv, __pycache__, node_modules, ...) and any *.egg-info directories. Files are checked for binary content (NUL byte or invalid UTF-8 in the first 4 KB) and silently skipped rather than causing an error. Never follows symbolic links to directories or files outside the root — every candidate path is additionally checked via resolve_within_root in addition to the standard os.walk behavior, which does not follow symbolic links to directories.

max_results is capped at config.SEARCH_RESULTS_CAP (200); matched lines longer than config.SEARCH_MAX_LINE_CHARS (300) are truncated; files larger than config.SEARCH_MAX_FILE_BYTES (2 MB) are skipped rather than scanned.

Implementation: tools/search_project_files.py:41-130.

read_project_file(path)

Reads a single text file at path path (relative to the project root). Rejects directories, nonexistent files, and binary content (NUL byte or invalid UTF-8). Content is limited by config.READ_MAX_FILE_BYTES (200 KB) — larger files are returned truncated rather than rejected.

Implementation: tools/read_project_file.py:25-69.

get_docs(query=None, max_results=10)

Searches docs/*.md (recursively), split into sections by Markdown headings. When query is provided, returns sections whose heading or body contains the search substring (case-insensitive), each with the source file and heading indicated. Without query, returns a list of one section per file — an inventory of what documentation exists. Limited only by config.get_docs_root() — never by the project root.

max_results is capped at config.DOCS_RESULTS_CAP (50); snippets are limited by config.DOCS_MAX_SNIPPET_CHARS (800 characters).

Implementation: tools/get_docs.py:58-114.

run_project_check(check_name)

Runs a check from the whitelist. check_name is looked up in config.ALLOWED_CHECKS before anything is run — an unknown name immediately raises an error, and the subprocess is never started. The whitelisted argv is executed via subprocess.run(argv, shell=False, cwd=<project root>, timeout=...): without shell, with a fixed working directory, and nothing from the calling side is added to the command line.

Implementation: tools/run_project_check.py:32-90.

Whitelist

ALLOWED_CHECKS = {
    "tests": [sys.executable, "-m", "pytest", "-q"],
}

Defined in config.py:55-57. sys.executable (rather than just the string "pytest") is used so that the check always runs with the same interpreter/environment as the server itself, regardless of what comes first in PATH. There is deliberately no lint entry: this repository has no ruff dependency or configuration, so wiring up a "lint" check would be either a sham or a deception. It can be added later (config.ALLOWED_CHECKS["lint"] = [sys.executable, "-m", "ruff", "check", "."]), when ruff becomes a real project dependency with real configuration — the whitelist mechanism already supports this without any other code changes.

A timeout (config.CHECK_TIMEOUT_SECONDS, default 60 s) and an output volume limit (config.CHECK_MAX_OUTPUT_CHARS, default 20,000 characters per stream) are applied to every check run.

Tool outputs contract

Each tool returns an ordinary Python dict from a function annotated -> dict[str, Any]; the MCP SDK automatically recognizes this as structured tool output (populates CallToolResult.structured_content and emits outputSchema) — results are never manually serialized into a JSON string anywhere here. Error situations (invalid input, path escape, unknown check, file not found, binary content, etc.) raise a Python exception rather than returning a dict; the SDK automatically turns this into a tool-level error result (CallToolResult.is_error = True). The only exception is a check timeout: this is a legitimate result of a successfully started check, not an input error, so it is returned as a structured dict {"status": "error", ...} rather than raising an exception.

search_project_files

{
  "status": "success",
  "query": "apply_discount",
  "path": ".",
  "matches": [
    {"file": "demo_app/services.py", "line": 12, "text": "def apply_discount(order: Order, percent: float) -> float:"}
  ],
  "count": 4,
  "truncated": false
}

read_project_file

{
  "status": "success",
  "file": "demo_app/models.py",
  "content": "...",
  "size": 397,
  "truncated": false
}

get_docs

{
  "status": "success",
  "query": "whitelist",
  "results": [
    {"file": "architecture.md", "heading": "Whitelist", "snippet": "..."}
  ],
  "count": 1,
  "truncated": false
}

run_project_check

{
  "status": "success",
  "check_name": "tests",
  "exit_code": 0,
  "stdout": "...",
  "stderr": "",
  "truncated": false
}

On timeout: {"status": "error", "check_name": ..., "error": "check timed out after 60s", "exit_code": null, "stdout": "...", "stderr": "...", "truncated": ...}.

The field names and status/count/truncated conventions above are considered a stable contract for the future, not an implementation detail.

Security limitations

  • Path restriction: security.resolve_within_root (security.py:19-50) rejects absolute paths, .. traversal (at any depth), NUL bytes, and symbolic links leading outside the configured root. It is used in read_project_file and search_project_files relative to the project root, and again in search_project_files for each candidate file during traversal. Covered by unit tests in tests/test_security.py and confirmed manually with a real negative test through Claude Code (see "Verification results" below, test 6).

  • No escaping outside the root via symbolic links during traversal: search_project_files and get_docs never follow symbolic links to directories (the default os.walk behavior) and completely skip symbolic links to files.

  • No arbitrary shell commands: run_project_check validates the requested check name against config.ALLOWED_CHECKS (config.py:55-57) before anything is run; unknown names are rejected immediately, and the check itself is executed via subprocess.run(argv, shell=False, ...) with a fixed cwd and without any arguments added by the caller.

  • Bounded output everywhere: each tool limits the amount of returned data — max_results + hard limits for search and docs, a byte limit for file reads, a character limit + timeout for check output — so no call can return an unbounded amount of data or run indefinitely.

  • stdout stays clean: all logging goes through logging_setup.py to stderr (and optionally to a log file); nothing in the server writes to stdout, which is reserved for the JSON-RPC framing of the MCP protocol.

  • No secrets in logs: the server does not accept any API keys or credentials at all. Each real tool call logs the tool name, its safe input parameters (query strings, paths, check names, result count/size — but never file contents), and the final status=success/status=error.

Logging and debugging

Each real tool call logs a single line through the shared mcp_project_helper logger (stderr, plus an optional file via MCP_PROJECT_HELPER_LOG_FILE), for example (real lines from evidence/tool-calls.log):

INFO mcp_project_helper: tool=search_project_files query='apply_discount' path='.' max_results=50 matches=4 truncated=False status=success
INFO mcp_project_helper: tool=read_project_file path='demo_app/models.py' size=397 truncated=False status=success
INFO mcp_project_helper: tool=run_project_check check_name='tests' exit_code=0 status=success
INFO mcp_project_helper: tool=read_project_file path='../../../../etc/passwd' status=error

File contents are never logged — only call metadata (paths, query strings, sizes, counts, exit codes). Logging setup — logging_setup.py:20-42.

For debugging:

  • The logging level is controlled via MCP_PROJECT_HELPER_LOG_LEVEL (DEBUG, INFO, WARNING, ERROR, CRITICAL; default INFO).

  • The log file is set via MCP_PROJECT_HELPER_LOG_FILE; by default (without this variable) it writes to stderr only. When run from Claude Code (.mcp.json), it points to evidence/tool-calls.log.

  • Never use print() in server code — stdout is reserved for the JSON-RPC protocol; any extra output to stdout breaks the stdio transport.

  • To see which calls actually occurred in the current Claude Code session, open the file pointed to by MCP_PROJECT_HELPER_LOG_FILE (evidence/tool-calls.log), or run the server manually (python -m mcp_project_helper.server) and watch stderr.

Installation

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

The only runtime dependency is the mcp package; pytest is a development/test-only dependency (both are pinned in pyproject.toml).

Environment setup

Configuration is set via environment variables — copy .env.example to .env and change the values as needed:

Variable

Purpose

Default value

MCP_PROJECT_HELPER_ROOT

The only directory that the file tools have access to (search_project_files, read_project_file, get_docs — the latter only to its docs/; get_docs works from the repository root, not MCP_PROJECT_HELPER_ROOT).

./demo_project

MCP_PROJECT_HELPER_LOG_FILE

Path to the log file (see "Logging and debugging"). Logs always also go to stderr.

not set (stderr only)

MCP_PROJECT_HELPER_LOG_LEVEL

One of DEBUG/INFO/WARNING/ERROR/CRITICAL.

INFO

The server does not need secrets (API keys, tokens) — .env.example contains only safe example paths and a logging level, and .env is ignored by git (see "Project structure" below).

Running the MCP server

Running the server directly (it will wait for a client on stdin — that is normal for MCP servers with stdio transport; exit with Ctrl+C):

python -m mcp_project_helper.server

Running the test suite:

pytest -q

Running the demo project's own tests directly (what run_project_check("tests") runs by default, since MCP_PROJECT_HELPER_ROOT points to demo_project by default):

cd demo_project && pytest -q

Claude Code integration

This repository contains a project-scoped .mcp.json file in the repository root — a configuration specifically for Claude Code (as opposed to .vscode/mcp.json, a separate configuration for the native MCP host VS Code; see the detailed comparison below).

Claude Code detects .mcp.json when the project folder is opened, starts the server as a child process, and communicates with it via JSON-RPC over stdio — the same transport used in all automated tests of this project, just launched by Claude Code itself rather than by a test harness.

Confirmed end-to-end scenario: Claude Code CLI → MCP server → custom tools. All 6 verification requests (see "Verification results" below) were actually executed through the Claude Code CLI with this server connected via .mcp.json — not just configured, but actually invoked, with real screenshots and entries in the server-side log.

Configuration for Claude Code

.mcp.json:

{
  "mcpServers": {
    "mcp-project-helper": {
      "command": "${CLAUDE_PROJECT_DIR:-.}/.venv/bin/python",
      "args": ["-m", "mcp_project_helper.server"],
      "env": {
        "MCP_PROJECT_HELPER_ROOT": "${CLAUDE_PROJECT_DIR:-.}/demo_project",
        "MCP_PROJECT_HELPER_LOG_FILE": "${CLAUDE_PROJECT_DIR:-.}/evidence/tool-calls.log"
      }
    }
  }
}

${CLAUDE_PROJECT_DIR} is expanded by Claude Code itself into the absolute path of the directory into which the repository was cloned, so the file contains no machine-specific paths and requires no edits after git clone. The form with the fallback value ${CLAUDE_PROJECT_DIR:-.}, rather than the bare ${CLAUDE_PROJECT_DIR}, is deliberately used: without :-. the variable was not expanded, and Claude Code tried to literally run ${CLAUDE_PROJECT_DIR}/.venv/bin/python as the path to an executable (this error was actually observed with the first version of the Stage 2 configuration, see REPORT.md). MCP_PROJECT_HELPER_ROOT is set explicitly to ${CLAUDE_PROJECT_DIR:-.}/demo_project, so that the project root passed to the server is unambiguous regardless of its own default value in config.py.

Platform note: .venv/bin/python is the Unix (macOS/Linux) venv layout used throughout this project. On Windows, the equivalent path is .venv\Scripts\python.exe; to support that platform as well, .mcp.json would require a second, Windows-specific entry (or a wrapper script) — this was not done, since the project was developed and tested only on macOS.

Configuration for VS Code

This repository also contains .vscode/mcp.json — a separate workspace configuration for the built-in VS Code MCP host (used by the GitHub Copilot Chat agent mode):

{
  "servers": {
    "mcp-project-helper": {
      "type": "stdio",
      "command": "${workspaceFolder}/.venv/bin/python",
      "args": ["-m", "mcp_project_helper.server"],
      "env": {
        "MCP_PROJECT_HELPER_ROOT": "${workspaceFolder}/demo_project",
        "MCP_PROJECT_HELPER_LOG_FILE": "${workspaceFolder}/evidence/tool-calls.log"
      }
    }
  }
}

The same stdio server mcp-project-helper, with MCP_PROJECT_HELPER_ROOT set to ${workspaceFolder}/demo_project and MCP_PROJECT_HELPER_LOG_FILE set to ${workspaceFolder}/evidence/tool-calls.log.

Why two files instead of one: .mcp.json and .vscode/mcp.json follow different, incompatible schemas, and their path substitution variables are not interchangeable between hosts:

  • .mcp.json (Claude Code configuration) uses the top-level mcpServers key and expands ${CLAUDE_PROJECT_DIR:-.} to the repository root.

  • .vscode/mcp.json (VS Code native MCP host configuration) uses the top-level servers key, an explicit "type": "stdio" field, and instead expands ${workspaceFolder} to the path of the opened folder. The VS Code MCP host does not understand ${CLAUDE_PROJECT_DIR} — when trying to open .mcp.json directly from VS Code, the variable is passed literally, and the server cannot start (spawn ${CLAUDE_PROJECT_DIR}/.venv/bin/python ENOENT) — this is a real observed error, which is why the separate .vscode/mcp.json was created. Storing each host's configuration in its own file, with its own variable, avoids this error and allows both tools to be used with the same clone without one config compromising the syntax of the other.

.vscode/mcp.json is the only exception to the general rule of ignoring .vscode/* in .gitignore; the rest of the local VS Code state (settings.local.json etc.) is not tracked.

VS Code verification status: .vscode/mcp.json is syntactically and semantically correct (the same server, the same command/environment variables as the working Claude Code configuration) and has been validated as JSON. Additionally, a real screenshot evidence/vscode_mcp_server_connected.png confirms that the built-in VS Code native MCP host actually starts the server with this configuration: Starting server mcp-project-helperConnection state: RunningDiscovered 4 tools, with a confirming line from the mcp_project_helper process's own stderr log in the same output. This is not the same as confirming custom tool calls through the VS Code interface — no user scenario (search_project_files etc.) was executed through that interface, and none is claimed as verified. The only IDE integration confirmed all the way down to actual tool calls by a user (screenshots + server-side logs for all 6 scenarios) is Claude Code CLI, see "Verification results" below. Separate from both: integration via Claude Code Desktop / the Claude Code extension inside VS Code was not tested in this session at all — not to be confused with either the VS Code native MCP host (this section) or the Claude Code CLI.

How to enable MCP

Briefly (details in the subsections above):

Claude Code:

  1. Create a venv and install the dependencies (the "Installation" section).

  2. Open the repository root in Claude Code (claude from the repository root).

  3. Claude Code detects .mcp.json and once prompts you to confirm workspace trust for the mcp-project-helper server — confirm it.

  4. Run /mcp (or claude mcp list in the terminal) and make sure mcp-project-helper is connected with 4 tools.

VS Code (native MCP host, Copilot Chat agent mode):

  1. Create a venv the same way as for Claude Code — .vscode/mcp.json expects the same .venv/bin/python.

  2. Open the repository root as a folder in VS Code.

  3. VS Code detects .vscode/mcp.json and offers to start the server — start it/confirm trust.

  4. Check the status via MCP: List Servers.

Both options assume a Unix-style venv structure (.venv/bin/python); on Windows — .venv\Scripts\python.exe (not configured, see above).

Verification queries

Six scenarios actually executed via the Claude Code CLI to confirm the integration (full table with results — in evidence/README.md):

  1. Find via MCP all places where the apply_discount function is used in demo_project → expected search_project_files.

  2. Read via MCP the file demo_app/models.py and briefly explain which models are defined there → expected read_project_file.

  3. Using the MCP project documentation, tell what security restrictions the MCP server has → expected get_docs.

  4. Check via the MCP tool whether the demo_project tests pass → expected run_project_check.

  5. Using only MCP tools, find the apply_discount implementation in demo_project, then read the file where it is defined and explain its parameters/return/discount calculation → expected a chain of two tools: search_project_files, then read_project_file.

  6. (negative / security test) Try to read the file ../../../../etc/passwd via MCP → expected refusal of read_project_file with a structured error (the path goes beyond the allowed root).

Verification results

All 6 out of 6 queries completed successfully (in test 5 — both expected tools, in the correct order; in test 6, the expected refusal counts as success). Each line is confirmed both by an actual screenshot and by an independent line in evidence/tool-calls.log. Full table — evidence/README.md; detailed breakdown with links to code and logs — REPORT.md.

Tool

Result

1

search_project_files

Success, 4 matches

2

read_project_file

Success, size=397

3

get_docs

Success, "Security" section found

4

run_project_check

Success, exit_code=0, 2/2 tests passed

5

search_project_filesread_project_file

Success, chain of two tools

6

read_project_file

Successful refusal (path traversal blocked)

Automated checks (they do not replace, but complement the manual IDE testing above):

  • pytest -q from the repository root — 44 passed.

  • pytest -q inside demo_project/2 passed.

  • Programmatic stdio handshake (initialize() + list_tools()) — the server reports mcp-project-helper 0.1.0 and exactly 4 tools: get_docs, read_project_file, run_project_check, search_project_files.

Project structure

mcp-project-helper/
  .mcp.json                конфигурация MCP для Claude Code (project-scoped)
  .vscode/mcp.json          конфигурация MCP для native MCP host VS Code
  .env.example              безопасные примеры переменных окружения (без секретов)
  pyproject.toml            зависимости, entry point, конфигурация pytest
  README.md                 этот файл
  REPORT.md                 итоговый отчёт по всем стадиям, со ссылками файл:строки
  PROMPTS.md                история фактически использованных промптов (Этапы 0-3)
  docs/
    architecture.md          документация, которую обслуживает get_docs
  src/mcp_project_helper/
    server.py                 точка входа: MCPServer, регистрация tools, stdio
    config.py                  корень проекта/docs, лимиты, whitelist проверок
    security.py                resolve_within_root() — ограничение путей
    logging_setup.py           логирование в stderr (+ опционально файл)
    tools/
      search_project_files.py
      read_project_file.py
      get_docs.py
      run_project_check.py
  tests/                     unit- и интеграционные тесты mcp_project_helper (44 теста)
  demo_project/              демонстрационный проект — цель для файловых tools
    demo_app/
      models.py                Product, Order
      services.py               apply_discount, OrderBuilder
      tests/test_services.py    2 теста, запускаемые run_project_check("tests")
  evidence/                  реальные доказательства ручного тестирования через Claude Code и VS Code
    README.md                  реестр всех 6 тестов с результатами + доп. evidence по VS Code
    tool-calls.log              реальный server-side лог всех 6 тестов (закоммичен)
    tool-calls.log.example      формат строки лога (шаблон)
    test1_search_project_files.png … test6_path_traversal.png   скриншоты 6 тестов Claude Code CLI (закоммичены)
    vscode_mcp_server_connected.png   доп. скриншот: native MCP host VS Code подключился, 4 tools (закоммичен)

Available Tools

4 tools
get_docsC

Search local documentation from the repo's docs/ directory.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryNo
max_resultsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.6/5.0
Behavior2/5

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

No annotations are provided, so the description carries the full burden. 'Search' implies read-only behavior, but the description does not state read-only safety, permissions, rate limits, or other constraints, and only discloses the local docs/ scope.

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?

A single front-loaded sentence with no filler. It is appropriately brief, though it omits useful structure for parameters and usage context.

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

Completeness2/5

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

For a two-parameter search tool with no annotations and 0% schema descriptions, the description leaves core parameter behavior and usage context unspecified. The output schema reduces return-value burden, but invocation guidance remains largely missing.

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

Parameters1/5

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

Schema description coverage is 0%, and the description never mentions either parameter. It does not explain that query is optional or what null means, nor what max_results controls.

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 ('Search') and resource ('local documentation') with scope ('repo's docs/ directory'). It distinguishes itself by directory scope, but does not explicitly differentiate from sibling tools such as search_project_files or read_project_file.

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

Usage Guidelines2/5

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

No when-to-use guidance, exclusions, or alternatives are provided. The description implies documentation search, but gives no routing advice for choosing this over search_project_files or read_project_file.

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

read_project_fileB

Read one text file inside the allowed project root.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

B3.2/5.0
Behavior3/5

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

No annotations are provided, so the description carries the full behavioral burden. It does disclose two useful traits: only 'text' files are handled and paths are confined to the 'allowed project root', implying rejection of out-of-root or binary inputs. It omits error behavior, encoding, and size limits.

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?

One efficient sentence with the resource and the key constraint front-loaded; nothing is wasted. It is arguably under-specified for a tool with no annotations, but that is a completeness issue rather than verbosity.

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?

The tool is simple (one required param, output schema present so return values need not be explained), and the description covers the basic contract. However, with no annotations it leaves error handling, path resolution rules, and file-type restrictions implicit — the minimum viable level.

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?

The single 'path' parameter has no schema description (0% coverage), so the description must compensate. It implies the path is relative to the allowed project root and must point to a text file, but adds no syntax, format, or relative-vs-absolute guidance.

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 (Read) and resource (one text file) plus a scoping constraint (inside the allowed project root), so the agent knows it is a single-file read rather than a search. It does not name the sibling search_project_files or get_docs, so differentiation is implied by 'one text file' rather than explicit.

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

Usage Guidelines2/5

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

There is no when-to-use or when-not-to-use guidance and no mention of the alternatives (search_project_files, get_docs). The 'allowed project root' phrasing hints at a boundary, but the agent must infer when this tool is preferable to its siblings.

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

run_project_checkC

Run a whitelisted project check (e.g. 'tests').

ParametersJSON Schema
NameRequiredDescriptionDefault
check_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.9/5.0
Behavior2/5

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

With no annotations, the description carries the full behavioral burden, and it says nothing about side effects, permissions, runtime, or failure behavior for what is an execution-style tool. The word 'whitelisted' hints at a restricted input set but never explains enforcement or consequences.

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?

A single front-loaded sentence with no filler, which is structurally efficient. The brevity is under-specification rather than waste, so it doesn't reach a 5.

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

Completeness2/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 needn't be described, but for an execution tool with no annotations the description should still cover side effects and the allowed input values. Neither is present, leaving the definition materially incomplete.

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 coverage is 0% and there are no enums, so the description must compensate; it does add a sample value ('tests') and implies a fixed allowed set via 'whitelisted'. But the actual list of valid check names — the single most useful piece of information — is absent.

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 (Run) and resource (project check), which clearly distinguishes it from the read-oriented siblings (get_docs, search_project_files, read_project_file). However, 'whitelisted project check' remains somewhat abstract — the only concrete anchor is the parenthetical example 'tests'.

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

Usage Guidelines2/5

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

No guidance on when to invoke this versus the sibling read tools, no prerequisites, and no mention of the whitelist constraint's practical implications (e.g. what happens if the name isn't whitelisted). The agent must infer usage entirely.

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

search_project_filesC

Search for text inside files under the allowed project root.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathNo.
queryYes
max_resultsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

C2.7/5.0
Behavior2/5

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

With no annotations, the description carries the full burden, and it discloses only that searching is confined to "the allowed project root." It says nothing about case sensitivity, regex vs literal matching, traversal depth, or result cap behavior, and does not hint that it is a non-destructive read.

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?

A single tight sentence with the scope constraint front-loaded and no filler. It is efficiently structured, though its brevity reflects under-specification elsewhere rather than optimal density.

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

Completeness2/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 described, but for a 3-parameter tool with zero schema coverage and no annotations, the description omits parameter meaning and usage context. An agent would call this only by guessing how query, path, and max_results interact.

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 path, query, and max_results, and it does not. Only broad implications appear ("text" for query, "project root" for path), leaving the default of "." and the meaning/limits of max_results unexplained.

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 ("Search for text") and resource ("files under the allowed project root"), which is enough to distinguish it from get_docs, run_project_check, and read_project_file. It is clear but never explicitly contrasts itself with the siblings, so it falls short of a 5.

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

Usage Guidelines2/5

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

No when-to-use guidance is given, and no alternatives are named. An agent must infer on its own that this is for locating text across many files rather than reading a single file via read_project_file.

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. 4 tool updatesv0.1.0
    • First observedget_docs
    • First observedread_project_file
    • First observedrun_project_check
    • First observedsearch_project_files

TDQS

B3.2/5.0

Scored across 4 tools

Disambiguation4/5

get_docs and search_project_files both search text, but get_docs is scoped to docs/ while search_project_files covers the entire project root. run_project_check and read_project_file are clearly distinct, so only minor overlap exists.

Naming Consistency5/5

All four tools follow a consistent snake_case verb_noun pattern: get_docs, run_project_check, search_project_files, read_project_file. The convention is predictable and easy to parse.

Tool Count4/5

Four tools is a reasonable, well-scoped set for a project helper, though it sits at the lower end of the typical 3–15 range. Each tool has a clear purpose, but the set feels slightly thin for broader project assistance.

Completeness3/5

The surface is read-only and lacks file listing, write/edit operations, and general command execution. Core read/search/check operations are present, but notable gaps remain for a server named 'project helper'.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Agent-safe code retrieval MCP server that indexes repositories and provides semantic search, file navigation, call graph analysis, and bounded file reading tools for coding agents.
    2,731,155
    3
    AGPL 3.0
  • A
    license
    Not graded
    quality
    A
    maintenance
    A lightweight local coding MCP server that exposes a single project directory to ChatGPT via Streamable HTTP, enabling file operations, command execution, search, and web fetching without authentication.
    331
    20
    MIT