DevPilot MCP
Provides read-only access to GitHub issues, including fetching issue details, comments, labels, linked PRs, and analyzing issue content for error messages, stack frames, and suggested search queries.
Enables running the project's Jest test suite and summarizing failures, with parsing of Jest JSON output to identify test names, files, messages, and likely source files.
Enables running the project's pytest test suite and summarizing failures, with parsing of pytest JUnit XML output to group and report test failures.
Enables running the project's Vitest test suite and summarizing failures, with parsing of Vitest JSON output to identify test names, files, messages, and likely source files.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@DevPilot MCPtriage issue #12 in Ashbruh22/devpilot_mcp and propose a fix"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
DevPilot MCP
Give your coding agent the issue, the code, and the failing tests in one loop.
DevPilot is a Model Context Protocol server (TypeScript, MCP SDK v2) that gives
AI coding agents like Claude Code structured, scoped access to GitHub issues, a codebase, its docs, and its test
suite. It runs locally over stdio (npx) or remotely over Streamable HTTP.
Live server: devpilot-mcp.onrender.com (the landing page, with
/mcpas the MCP endpoint). See Deployment.
Why
Most of the time an agent spends on a bug goes to finding context: reading the issue, guessing which files matter, grepping, running the whole test suite, and scrolling through raw output. DevPilot collapses that into six structured, size-capped tool calls:
get_issue → analyze_issue → search_codebase → run_tests → summarize_test_failures → fixThe server makes no LLM calls. Extraction is deterministic, which keeps it cheap, fast, and testable, and the client's model already does the reasoning. See Benchmark for how the time savings are measured.
Related MCP server: GitHub MCP Server
Tools
Tool | Input | Output |
|
| title, state, labels, author, dates, body, comments |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Plus the triage_issue prompt (owner, repo, issue_number), which walks the agent through the whole loop and ends with a proposed fix.
Every tool validates its input with zod (every field is described for the model), declares an outputSchema, returns
structured content plus a one-line summary, truncates with an explicit …[truncated N chars] marker, and reports
errors as actionable tool errors (Issue #99 not found in owner/repo) rather than stack traces.
owner/repo are optional. Locally they default to the workspace's origin remote, and remotely to the only
allowlisted repo. In remote mode with several allowlisted repos, the workspace tools also take repo: "owner/repo".
Quick start
Local (stdio)
Run it inside the repository you want the agent to work on:
claude mcp add devpilot -- npx -y @ashbruh22/devpilot-mcp
# optional, for private repos and higher rate limits:
claude mcp add devpilot -e GITHUB_TOKEN=github_pat_xxx -- npx -y @ashbruh22/devpilot-mcpThen ask Claude Code to "triage issue #12", or run the prompt directly: /mcp__devpilot__triage_issue <owner> <repo> 12.
Any other MCP client works too:
{ "mcpServers": { "devpilot": { "command": "npx", "args": ["-y", "@ashbruh22/devpilot-mcp"] } } }Remote (Streamable HTTP)
claude mcp add --transport http devpilot https://devpilot-mcp.onrender.com/mcp
# if the server sets DEVPILOT_API_KEY:
claude mcp add --transport http devpilot https://devpilot-mcp.onrender.com/mcp --header "Authorization: Bearer <key>"The hosted server works on the demo project bundled in this repo (demo/devpilot-demo), which
has two real bugs. Try: "Use devpilot to run the tests, summarize the failures, and propose a fix for the largest
group." Or triage one of the demo issues end to
end: /mcp__devpilot__triage_issue Ashbruh22 Devpilot_MCP 1.
Architecture
flowchart LR
C["MCP client<br/>(Claude Code, Inspector)"] <--> S["stdio<br/>src/stdio.ts"]
C <--> H["Streamable HTTP<br/>src/http.ts<br/>auth · rate limit · CORS"]
S <--> T["Tool layer<br/>createServer(deps)"]
H <--> T
T --> G["GitHub API<br/>(read-only, 60 s cache)"]
T --> R["ripgrep<br/>(path-guarded)"]
T --> X["Test runner<br/>(no shell, timeout, tree kill)"]Two transports, one core.
createServer(deps)builds anMcpServerwith every tool registered; it knows nothing about transports.stdio.tsserves it withserveStdio, andhttp.tsserves it statelessly withcreateMcpHandler+toNodeHandleron Express, building one server per request. Long-lived state (GitHub cache, run store, workspaces) lives indepsand is shared.Token budget by design. Outputs are structured, every string is capped, search results shrink to fit
MAX_OUTPUT_CHARS, test output keeps only the tail, and failures are grouped by normalized error message (numbers, strings, and paths become placeholders), so 40 failures with one root cause read as one group.likely_source_filesare the non-test project files in each failure's stack. When the stack only reaches the test file (typical for assertion failures), DevPilot falls back to naming:src/pricing.test.ts→src/pricing.ts.
src/
├─ server.ts createServer(deps) + createDeps(config)
├─ stdio.ts local entry (bin)
├─ http.ts remote entry: Express, Streamable HTTP, landing page, /healthz
├─ config.ts env parsing with zod, fail-fast
├─ tools/ one file per tool
├─ prompts/ triage_issue
├─ lib/ github, workspace, pathGuard, exec, runStore, truncate, search, docs, failures, …
│ └─ parsers/ vitest/jest JSON, pytest JUnit XML, raw output, stack traces (JS + Python)
└─ public/index.html landing pageSecurity model
Running tests on a public server is remote code execution by design, so remote mode is locked down:
Allowlist only. Tools accept only repos in
ALLOWED_REPOS. At boot, each is shallow-cloned (git clone --depth 1 --branch <ref>) into/tmp/workspaces/<owner>__<repo>, and its dependencies are installed once withnpm ci --ignore-scripts.Fixed commands. Test commands come from
TEST_COMMANDS, never from tool input.filtermust match^[\w\-./ ]{1,100}$, can't start with-(so it can't inject flags), and can't contain... Commands run without a shell, and configured commands may not contain shell operators.Resource limits. Each run has a
TEST_TIMEOUT_MStimeout that kills the whole process group, output is capped, and each repo allows at most one concurrent test run ("busy, retry in N s")./mcpis rate-limited per IP (60/min by default) and request bodies are capped at 1 MB.Least privilege. The container runs as the non-root
nodeuser withtinias PID 1. The GitHub token should be a fine-grained, read-only PAT for public repos.GITHUB_TOKEN,DEVPILOT_API_KEY, and anything that looks like a secret (*TOKEN*,*SECRET*,*PASSWORD*,*API_KEY*, …) are stripped from child-process environments.Path guard. Every client-supplied path is resolved with
path.resolve, must stay under the workspace root, and is re-checked withrealpathso symlinks can't escape. Absolute paths,.., and NUL bytes are rejected. Search results go through the same check.No arbitrary network fetches. No tool fetches a URL from its input. GitHub is the only outbound API, and clone URLs are built from the allowlist.
The HTTP server also uses the SDK's Host-header validation (DNS-rebinding protection) and Origin validation on
localhost binds, an optional constant-time bearer-token check on /mcp (DEVPILOT_API_KEY), and CORS that exposes
only the MCP headers. Without a key, the server runs as a public read-only demo limited to the allowlist.
Configuration
All configuration comes from environment variables, validated at startup. See .env.example.
Var | Mode | Default | Purpose |
| both |
|
|
| both | – | Fine-grained PAT. Read-only, public repos only for the hosted deployment |
| local |
| Repo to operate on |
| local | auto-detect | Override the detected test command |
| remote | – (required) |
|
| remote |
| JSON |
| remote | – | Bearer token for |
| remote |
| Listen address ( |
| remote | Render hostname | Host-header allowlist for public binds |
| remote |
| Per-IP limit on |
| both |
| Test run timeout |
| both |
| Cap on any tool's text response |
| remote |
| Where to clone, and whether to install deps |
Test runner auto-detection (local mode) prefers machine-readable output:
vitest → vitest run --reporter=json, jest → jest --json, pytest → pytest --junitxml=… -q, otherwise
npm test with best-effort text parsing. Binaries are resolved from node_modules/.bin (walking up for monorepos)
or run with npx --no, so nothing is ever downloaded.
Development
npm install
npm run typecheck && npm run lint && npm test # unit + integration tests
npm run build # tsc → dist/, keeps the shebang, chmod +x
npm run dev:stdio # run from source over stdio
npm run dev:http # run from source over HTTP on :3000
npm run inspector # MCP Inspector against dist/stdio.js
npx @modelcontextprotocol/inspector # then connect to http://localhost:3000/mcpThe tests cover:
Unit: path guard (traversal, absolute paths, symlink escape), parsers (vitest/jest JSON, pytest JUnit XML, raw output), stack traces (Node, browser, Python), issue analysis, truncation, exec (tree kill on timeout, secret stripping, no shell), config validation.
Integration: a real MCP client (
@modelcontextprotocol/client) over in-memory and Streamable HTTP transports. It calls every tool againsttest/fixtures/sample-repo(real vitest runs with real failures) with GitHub mocked bynock. HTTP tests cover/healthz, 401 without a token, 429 rate limiting, CORS, Host/Origin validation, and body limits. Remote-mode tests clone from a local bare git repo and check allowlist enforcement and fixed commands.
Deployment
Docker → Render. The repo includes a multi-stage Dockerfile (non-root, tini, git) and a
render.yaml blueprint:
In Render, go to New → Blueprint and pick this repo. Render reads
render.yaml, builds the Docker image, and creates a free web service.When prompted, set
GITHUB_TOKEN: a fine-grained PAT with read-only access to public repositories. It's needed forget_issue/analyze_issue, since unauthenticated calls share a 60/hour limit. LeaveDEVPILOT_API_KEYempty for a public demo, or set it to require a bearer token.On boot, the server shallow-clones this repo, uses
demo/devpilot-demoas its workspace (ALLOWED_REPOS=Ashbruh22/Devpilot_MCP:demo/devpilot-demo), and installs that project's dependencies./healthzreports"ready"after a few seconds. Render's hostname is added to the Host allowlist automatically.Connect with
claude mcp add --transport http devpilot https://devpilot-mcp.onrender.com/mcp.Free instances sleep when idle: the first request can take 30–60 s. To keep it warm, set the repository variable
DEVPILOT_URLto the service URL, which enables thekeepaliveworkflow (a 10-minute ping). Check Render's current free-tier terms first.
npm. Tag a release (git tag v1.0.0 && git push --tags) and the release
workflow publishes with provenance (it needs an NPM_TOKEN secret). Or run npm publish --access public by hand, then
check it with npx -y @ashbruh22/devpilot-mcp in a clean folder.
Benchmark
bench/ has a reproducible method: 10 tasks on the demo repo ("find the root cause of issue #N and the file
to change"), each timed once without DevPilot (the agent reads files and runs commands itself) and once with the
triage_issue flow. It records time-to-context and tool calls or steps. Results go in
bench/results.csv, and npm run bench:summary prints the medians.
Status: the harness and tasks are in place. The results table is empty until the runs are done, so no time-saved number is claimed yet. Quote only the measured median.
Available Tools
6 toolsanalyze_issueAnalyze GitHub issueARead-onlyIdempotent
Deterministically extract triage signals from an issue (no LLM): a type guess (bug/feature/question/docs), error messages, parsed stack frames (JS and Python), mentioned file paths, fenced code blocks, and suggested queries to pass to search_codebase.
Inputs: issue_number (required), repo, owner. Returns: repo, owner, title, type_guess, +7 more
get_docsGet project docsARead-onlyIdempotent
Read project documentation (README*, CONTRIBUTING*, root .md, docs/**/.md). With "path": return that file, or just the section whose heading matches "topic". With only "topic": return the 3 best-matching sections ranked by keyword hits. With neither: return the README intro and the list of docs.
Inputs: path, repo, topic, max_chars. Returns: sections, workspace, available_docs
get_issueGet GitHub issueARead-onlyIdempotent
Fetch a GitHub issue with its discussion: title, state, labels, author, dates, body, recent comments, and linked pull requests. Read-only. Start here when triaging an issue.
Inputs: issue_number (required), repo, owner, max_comments. Returns: url, body, repo, owner, +11 more
run_testsRun testsA
Run the project's test suite (auto-detects vitest, jest, pytest, or npm test; in remote mode only the server's fixed, configured command runs). Returns pass/fail counts and a run_id; pass the run_id to summarize_test_failures for grouped failures and the source files to fix.
Inputs: repo, filter. Returns: failed, passed, run_id, runner, +8 more
search_codebaseSearch codebaseARead-onlyIdempotent
Search the workspace with ripgrep (respects .gitignore; skips node_modules, dist, lockfiles and binaries). Returns matching lines with surrounding context and paths relative to the workspace root.
Inputs: query (required), repo, is_regex, path_glob, +2 more. Returns: matches, truncated, workspace, total_matches, +1 more
summarize_test_failuresSummarize test failuresARead-onlyIdempotent
Summarize the failures of a previous run_tests call: each failing test with its message, top project stack frames, and likely_source_files (non-test files from the stack — where the fix probably goes), plus groups of failures sharing the same normalized error, largest first.
Inputs: run_id (required). Returns: groups, run_id, failures, truncated, +2 more
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
TDQS
Scored across 6 tools
Each tool targets a distinct purpose: get_issue fetches raw issue data, analyze_issue extracts deterministic signals, get_docs reads documentation, search_codebase searches code, run_tests executes tests, and summarize_test_failures analyzes test output. The descriptions make the boundaries clear, and while get_issue and analyze_issue both relate to issues, one is read-only fetching and the other is analysis, so misselection is unlikely.
All tool names use snake_case with a consistent verb_noun pattern (get_issue, analyze_issue, get_docs, search_codebase, run_tests, summarize_test_failures). There are no naming outliers or mixed conventions, making the set predictable and easy to scan.
Six tools is well-scoped for a developer triage and test-diagnosis assistant. Each tool earns its place by covering a distinct step in the workflow, with no redundant or filler tools.
The surface covers issue fetching and analysis, docs reading, code search, and test running plus failure summarization, forming a coherent triage loop. However, there is no way to list or search issues, which is a minor gap for a triage-focused server where an agent may need to discover which issue to work on.
Maintenance
Related MCP Connectors
An MCP server that gives your AI access to the source code and docs of all public github repos
Nifty's MCP server — exposes tasks, projects, messages, and files as tools for AI agents.
Official Duck Framework MCP server for AI-assisted development and project scaffolding.
Related MCP Servers
- AlicenseBqualityCmaintenanceMCP server that exposes GitHub operations as tools for AI agents, enabling code search, issue management, and PR review.12MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP server that enables AI agents to directly manage GitHub repositories, including PRs, issues, and code search, using natural language.MIT
- AlicenseNot gradedqualityDmaintenanceAn MCP server that provides tools for interacting with the GitHub API, enabling AI assistants to query repositories, pull requests, issues, commits, users, and more.456 npmISC
- AlicenseNot gradedqualityDmaintenanceA local MCP server that gives AI agents access to developer tooling — GitHub (read-only), documentation search, and web research — via stdio transport.MIT