What Is the Model Context Protocol?
The Model Context Protocol (MCP) is an open standard created by Anthropic that defines a clean interface between LLMs and the external tools or data sources they need to act on. Instead of every AI application reinventing its own tool-calling glue code, MCP gives you a uniform way to expose capabilities — and any MCP-compatible client (Claude Desktop, custom agents, IDE plugins) can consume them without changes.
Think of MCP like a USB standard: you build one server that speaks the protocol, and any compliant client can plug in.
GitHub: github.com/tusharrayamajhi/devtoolkit-mcp
Why I Built devtoolkit-mcp
I was building CoderSathi, a full-stack AI coding assistant powered by a LangGraph agent. The agent needed to do real developer work: read files, write code, run shell commands, check Git history, make HTTP requests.
Off-the-shelf MCP servers existed but none had exactly the toolkit I needed — specifically the combination of file operations, Git inspection, code analysis, HTTP utilities, and process management all in one server with proper Zod validation and isolated workspace paths.
So I built devtoolkit-mcp from scratch in TypeScript using the official @modelcontextprotocol/sdk.
Architecture Overview
CoderSathi Agent (Python/LangGraph)
│
│ MCP stdio transport
▼
devtoolkit-mcp (Node.js process)
│
├── File Tools
├── Git Tools
├── Code Analysis Tools
├── HTTP Tools
└── System Tools
The server runs as a subprocess of the Python backend. The agent spawns it via stdio_client from the MCP Python SDK, which handles the process lifecycle and JSON-RPC message passing transparently. Every tool call is validated by a Zod schema before execution.
Tech Stack
| Layer | Technology |
|---|---|
| Runtime | Node.js 18+ |
| Language | TypeScript |
| MCP SDK | @modelcontextprotocol/sdk v1.29+ |
| Validation | Zod |
| Transport | stdio (spawned as subprocess) |
The Tools
File Operations
read_file(file_path: string): string
write_file(file_path: string, content: string): void
list_directory(dir_path: string): FileEntry[]
search_in_files(dir: string, pattern: string, extension?: string): Match[]
get_file_info(file_path: string): FileInfo
delete_file(file_path: string): void
These are the most-used tools. Every path is resolved relative to the workspace directory assigned to the conversation — the agent cannot accidentally escape its sandbox.
search_in_files is especially useful: it lets the agent grep across a whole codebase for a symbol, pattern, or error string, then read only the relevant files. This dramatically reduces context usage versus reading everything blindly.
Git Tools
git_status(repo_path?: string): string
git_log(repo_path?: string, max_commits?: number): Commit[]
git_diff(repo_path?: string, base?: string): string
git_blame(file_path: string): BlameLine[]
git_branches(repo_path?: string): Branch[]
git_show_commit(commit_hash: string): string
Read-only Git tools give the agent full visibility into project history without any risk of destructive operations. The agent can answer questions like "what changed in the last three commits?" or "who wrote this function?" without the user needing to run any commands manually.
Code Analysis Tools
analyze_complexity(file_path: string): ComplexityReport
find_todos(dir_path?: string): TodoItem[]
count_lines(target: string): LineCount
detect_language(file_path: string): Language
find_duplicates(dir_path: string): DuplicateGroup[]
get_imports(file_path: string): Import[]
analyze_complexity computes cyclomatic complexity per function — useful for finding which functions are hardest to refactor or test. find_duplicates detects near-identical code blocks across files, helping the agent suggest consolidation.
HTTP Tools
http_request(method: string, url: string, headers?: object, body?: string): Response
fetch_json(url: string): object
check_url_status(urls: string[]): StatusResult[]
download_file(url: string, dest_path: string): void
These let the agent interact with external APIs, download dependencies, check endpoint health, and validate responses — all from within the coding workflow.
System Tools
run_command(command: string, cwd?: string, timeout?: number): CommandResult
run_background(command: string, cwd?: string): BackgroundResult
check_command_status(pid: number): ProcessStatus
get_system_info(): SystemInfo
get_env(keys?: string[]): EnvVars
list_processes(): Process[]
get_disk_usage(path?: string): DiskUsage
get_network_info(): NetworkInfo
run_command is the most powerful tool — it lets the agent install packages, run tests, build projects, start dev servers, and more. The optional timeout prevents the agent from blocking indefinitely on a hanging process.
run_background + check_command_status form a polling pair: spawn a long-running command (like npm run dev), get a PID back, then poll until completion. This avoids blocking the entire WebSocket while waiting.
Implementation Pattern
Every tool follows the same structure:
import { z } from "zod";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({ name: "devtoolkit", version: "1.0.0" });
server.tool(
"read_file",
"Read the full contents of a file",
{
file_path: z.string().describe("Absolute or workspace-relative path"),
},
async ({ file_path }) => {
const resolved = resolvePath(file_path); // sandbox enforcement
const content = await fs.readFile(resolved, "utf-8");
return { content: [{ type: "text", text: content }] };
}
);
The resolvePath helper normalises every path against the workspace root and throws if the result escapes it — preventing path traversal attacks. This matters because the agent is LLM-driven and a prompt injection in user-provided filenames could otherwise cause the server to read files outside the workspace.
Connecting to the Python Agent
On the Python side, the MCP session manager spawns the server process:
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
params = StdioServerParameters(
command="node",
args=["/path/to/devtoolkit-mcp/dist/index.js"],
)
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
result = await session.call_tool("read_file", {"file_path": "src/index.ts"})
LangGraph then wraps each MCP tool in a BaseTool subclass and binds them to the LLM. The LLM sees a flat list of 25+ tools with typed schemas — it picks the right one, fills in arguments, and LangGraph dispatches the call through this same session.
Permission Gating
One non-MCP feature I added on the agent side: every tool call goes through a permission callback before execution. The WebSocket handler asks the frontend "do you allow this?", waits for user confirmation, then executes (or aborts) the call. This means the user always sees what the agent is about to do — crucial for destructive operations like delete_file or run_command.
Lessons Learned
1. Zod schemas are non-negotiable. Early versions used loose TypeScript types and the LLM occasionally sent null where it shouldn't. Zod .parse() caught these before they hit the filesystem.
2. Workspace isolation prevents a whole class of bugs. Without it, the agent would occasionally write to the wrong path when given ambiguous instructions. The sandbox makes the agent's "world" explicit.
3. Read-only Git tools are surprisingly useful. I almost skipped them, but they turn out to be the tools the agent reaches for most often when understanding an unfamiliar codebase.
4. stdio transport is simpler than HTTP for subprocess tools. No port management, no auth headers, no CORS — the process just reads and writes JSON-RPC on stdin/stdout. MCP's SDK abstracts all of this.
What's Next
- Add
create_directory,move_file, andcopy_file - Integrate language server protocol (LSP) for type-aware completions
- Publish to npm so other projects can use it without cloning
Source code: github.com/tusharrayamajhi/devtoolkit-mcp
If you find this useful or have tool ideas, open an issue or PR — contributions are welcome.