The Model Context Protocol (MCP) is a standard that lets AI coding tools like Claude Code or Cline interact directly with your codebase. Instead of copying snippets manually, you can build a custom MCP server that exposes your project's structure, file contents, and search capabilities. This tutorial walks you through creating a lightweight MCP server tailored to your codebase.
get-files to list the project tree and read-file to retrieve file contents. The AI agent can then call these tools to explore your codebase autonomously.
Prerequisites
- Node.js 18+ and npm installed
- Basic familiarity with JavaScript and terminal
- An MCP-compatible AI client (e.g., Claude Code, Cline, or the official
mcp-cli)
Step 1: Initialize the Project
Create a new directory and initialize a Node.js project:
mkdir my-codebase-mcp-server && cd my-codebase-mcp-server
npm init -y
Install the MCP SDK:
npm install @modelcontextprotocol/sdk
Step 2: Implement the Server
Create a file server.js. We'll use the SDK's Server class to define tools. The server will expose two tools: get-files (lists files in a directory) and read-file (returns the content of a specific file).
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { readdir, readFile, stat } from "fs/promises";
import { join, relative } from "path";
const PROJECT_ROOT = process.env.PROJECT_ROOT || process.cwd();
const server = new Server(
{ name: "codebase-server", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "get-files",
description: "List all files in a directory recursively, respecting .gitignore",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Relative path from project root (default: '.')" }
}
}
},
{
name: "read-file",
description: "Read the full content of a file",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "Relative path to the file" }
},
required: ["path"]
}
}
]
}));
// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "get-files") {
const dirPath = join(PROJECT_ROOT, args?.path || ".");
const files = await listFiles(dirPath);
return { content: [{ type: "text", text: files.join("\n") }] };
}
if (name === "read-file") {
const filePath = join(PROJECT_ROOT, args.path);
const content = await readFile(filePath, "utf-8");
return { content: [{ type: "text", text: content }] };
}
throw new Error(`Unknown tool: ${name}`);
});
async function listFiles(dir, baseDir = dir) {
const entries = await readdir(dir, { withFileTypes: true });
const results = [];
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.name === ".git" || entry.name === "node_modules") continue;
if (entry.isDirectory()) {
const subFiles = await listFiles(fullPath, baseDir);
results.push(...subFiles);
} else {
results.push(relative(baseDir, fullPath));
}
}
return results;
}
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Codebase MCP server running on stdio");
Step 3: Run and Connect
First, test the server manually. Set the PROJECT_ROOT environment variable to the absolute path of your codebase (or omit it to use the current directory). Then run:
PROJECT_ROOT=/path/to/your/project node server.js
Now configure your AI client to use this server. For example, in Claude Code's claude.json:
{
"mcpServers": {
"codebase": {
"command": "node",
"args": ["/absolute/path/to/server.js"],
"env": {
"PROJECT_ROOT": "/path/to/your/project"
}
}
}
}
Restart the client. You can now ask your AI to "explore the project structure" or "show me the main entry point" — it will call the tools you built!
Going Further
Extend your server with additional tools:
grep-code: Search for text patterns across filesgit-log: Get recent commits and diffsrun-tests: Execute a test suite and return results
join which resolves relative paths, but consider adding a check that the resolved path starts with PROJECT_ROOT.
With this foundation, you can build a rich set of tools that unlock unprecedented codebase awareness for your AI coding workflows.
Interesting approach, but I wonder about security when exposing the entire codebase to an AI agent. How do you handle access control?