Project Structure and Organization for MCP Servers
Organize MCP server projects for maintainability with recommended file layouts, naming conventions, and configuration management patterns.
title: "Project Structure and Organization for MCP Servers" description: "Organize MCP server projects for maintainability with recommended file layouts, naming conventions, and configuration management patterns." order: 8 keywords:
- MCP project structure
- MCP file organization
- MCP naming conventions
- MCP server layout
- MCP code organization date: "2026-04-01"
Learn how to organize MCP server projects for long-term maintainability. Covers recommended file layouts for both mcp-framework and the official SDK, naming conventions, module organization, configuration management, and patterns for growing projects.
Why Structure Matters
A well-organized project is easier to navigate, test, and extend. When a new team member opens your MCP server repo, they should immediately understand where things live and how they connect.
A design paradigm where the framework assumes sensible defaults based on file and directory naming conventions. mcp-framework uses this approach: place a tool class in src/tools/ and it is automatically discovered. No manual registration needed.
Recommended Structure: mcp-framework
mcp-framework auto-discovers tools, resources, and prompts from specific directories. Follow the convention for zero-configuration setup.
Directory Roles
| Directory | Purpose | Auto-discovered? |
|-----------|---------|-----------------|
| src/tools/ | MCP tool classes | Yes (mcp-framework) |
| src/resources/ | MCP resource classes | Yes (mcp-framework) |
| src/prompts/ | MCP prompt classes | Yes (mcp-framework) |
| src/services/ | Business logic and external API clients | No |
| src/utils/ | Shared utilities (logger, cache, validation) | No |
| src/types/ | TypeScript type definitions | No |
| tests/ | All test files, mirroring src structure | No |
Recommended Structure: Official SDK
With the official SDK, there is no auto-discovery. Organize by feature domain and register everything explicitly in a central setup file.
The key difference is the registration pattern:
// src/tools/index.ts -- explicit registration
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerSearchTool } from "./search.js";
import { registerCreateTool } from "./create.js";
import { registerDeleteTool } from "./delete.js";
export function registerAllTools(server: McpServer) {
registerSearchTool(server);
registerCreateTool(server);
registerDeleteTool(server);
}
// src/tools/search.ts
export function registerSearchTool(server: McpServer) {
server.tool("search", "Search items", { query: z.string() },
async ({ query }) => {
// ...
}
);
}
// src/server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { registerAllTools } from "./tools/index.js";
import { registerAllResources } from "./resources/index.js";
export function createServer(): McpServer {
const server = new McpServer({ name: "my-server", version: "1.0.0" });
registerAllTools(server);
registerAllResources(server);
return server;
}
Naming Conventions
Consistent naming reduces cognitive load. Pick a convention and apply it everywhere -- file names, class names, tool names, and variable names should all follow the same pattern.
| Element | Convention | Example |
|---------|-----------|---------|
| Tool files (mcp-framework) | PascalCase | SearchTool.ts |
| Tool files (SDK) | kebab-case | search.ts |
| Tool names | snake_case | search_items |
| Service classes | PascalCase | DatabaseService |
| Utility files | camelCase or kebab-case | logger.ts |
| Test files | Match source + .test | SearchTool.test.ts |
| Environment variables | UPPER_SNAKE_CASE | DATABASE_URL |
Configuration Management
Create a single configuration module that reads and validates environment variables. All other modules import configuration from this one source.
// src/config.ts
import { z } from "zod";
const envSchema = z.object({
PORT: z.string().transform(Number).default("3001"),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
CACHE_TTL_MS: z.string().transform(Number).default("60000"),
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
});
export type Config = z.infer<typeof envSchema>;
let config: Config;
export function getConfig(): Config {
if (!config) {
const result = envSchema.safeParse(process.env);
if (!result.success) {
console.error("Invalid configuration:", result.error.format());
process.exit(1);
}
config = result.data;
}
return config;
}
Environment File
Create .env.example (committed to git):
# Server
PORT=3001
NODE_ENV=development
LOG_LEVEL=info
# Database
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
# External APIs
API_KEY=your-api-key-here
# Cache
CACHE_TTL_MS=60000
Add .env to your .gitignore. Commit .env.example with placeholder values so new developers know what variables are needed.
Scaling the Project
When to Split Into Multiple Servers
Keep related tools in one server. Split into multiple servers when: tools have different deployment requirements, different auth needs, unrelated domains, or when one server grows beyond 15-20 tools.
Signs you should split:
- Tools require different environment variables (one needs DB, another needs API key)
- Some tools need heavy dependencies (headless browser, ML models)
- The server handles two unrelated domains (GitHub AND Slack)
- Different teams own different tools
Monorepo for Multiple Servers
my-mcp-servers/
packages/
github-server/
src/
package.json
database-server/
src/
package.json
shared/
src/
logger.ts
cache.ts
package.json
package.json # Workspace root
tsconfig.base.json # Shared TS config
The Essential Files Checklist
| File | Purpose | Required? |
|------|---------|-----------|
| src/index.ts | Entry point, starts the server | Yes |
| package.json | Dependencies and scripts | Yes |
| tsconfig.json | TypeScript configuration | Yes |
| .env.example | Document required env vars | Yes |
| .gitignore | Exclude node_modules, dist, .env | Yes |
| src/config.ts | Centralized configuration | Recommended |
| src/utils/logger.ts | Structured logging | Recommended |
| tests/ | Test files | Recommended |
| Dockerfile | Container build | For deployment |
| docker-compose.yml | Local dev with dependencies | Optional |