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.

What you'll build: A Node.js MCP server with two tools: 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");
Note: The server runs over stdio, which is the standard transport for MCP tools. This means your AI client will spawn the server as a subprocess.

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!

Success! Your AI coding agent can now navigate your codebase on its own. This is especially useful for large projects where the agent needs to understand file relationships or find specific implementations.

Going Further

Extend your server with additional tools:

  • grep-code: Search for text patterns across files
  • git-log: Get recent commits and diffs
  • run-tests: Execute a test suite and return results
Security tip: Always validate paths to prevent directory traversal. The example above uses 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.