This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
MCP Dashboard is a GUI-based npm package for managing Claude Code and Claude Desktop's Model Context Protocol (MCP) settings. It provides a browser-based interface for configuring MCP servers without manually editing JSON files.
The tool supports multiple configuration scopes with automatic detection and prioritization:
- Project Scope (
.mcp.json): Searched upward from current directory - User Scope (
~/.claude.json): Claude Code user configuration - Claude Desktop (legacy): Platform-specific Claude Desktop config files
Priority: Project > User > Claude Desktop
This is a full-stack TypeScript application with a client-server architecture:
CLI (bin/cli.js)
↓ starts server + opens browser
Express Server (localhost:4000)
├─→ API Routes (/api/config, /api/presets)
├─→ Services Layer (configManager, validator, presetManager)
├─→ File System (reads/writes claude_desktop_config.json)
└─→ Static Files (serves bundled React app)
↓
React SPA (Vite build)
└─→ Components + Custom Hooks + API Client
Key Design Decisions:
- Service Layer Separation: Business logic isolated from HTTP handlers for testability and reusability
- Multiple TypeScript Configs: Server, client, and CLI have different compilation targets and module resolution needs
- Rate Limiting: Three-tier protection (API: 100/15min, Write: 20/15min, Static: 500/15min)
- Platform-Specific Paths: Auto-detects macOS/Windows/Linux config locations via
src/server/utils/paths.ts
- Backend: Node.js 22+ (ES Modules) + Express + TypeScript + Zod
- Frontend: React 19 + Vite 5 + TailwindCSS + React Hook Form
- CLI: Commander + Chalk + Open
- Testing: Jest + ts-jest
# Start both server and client with hot reload
npm run dev
# Server runs on :4000, Vite dev server on :62000
# In dev mode, API requests from :62000 are proxied to :4000
# Run separately if needed
npm run dev:server # Backend only (tsx watch)
npm run dev:client # Frontend only (Vite HMR)npm run build
# Runs 3 steps:
# 1. build:server → TypeScript compilation (src/server → dist/server)
# 2. build:client → Vite bundle (src/client → dist/client)
# 3. copy:presets → Copy JSON files (src/presets → dist/presets)npm test # Run all tests
npm test -- --watch # Watch mode
npm test -- --coverage # With coverage report
npm test -- --verbose # Detailed output
npm test -- configManager # Run specific test filenpm run lint # TypeScript type check (no emit)
npm run format # Prettier formattingnpm start # Start production server
npx mcp-dashboard # Run as CLI tool
mcp-dashboard -p 62000 # Custom port
mcp-dashboard --no-open # Don't auto-open browserCRITICAL: All imports MUST use .js extensions, even when importing .ts files:
// CORRECT
import { loadConfig } from "./services/configManager.js";
import type { MCPConfig } from "./types/index.js";
// WRONG - will cause module resolution errors
import { loadConfig } from "./services/configManager";This is because TypeScript compiles to ES modules and Node.js requires explicit extensions.
NEVER hardcode paths. Always use src/server/utils/paths.ts:
// CORRECT - Gets active config path (respects priority)
import { getConfigPath } from "./utils/paths.js";
const configPath = await getConfigPath();
// CORRECT - Gets all config locations with metadata
import { getConfigLocations } from "./utils/paths.js";
const locations = await getConfigLocations();
// CORRECT - Gets active config info (path + scope + displayName)
import { getActiveConfigLocation } from "./utils/paths.js";
const activeConfig = await getActiveConfigLocation();
// WRONG
const configPath = "~/.config/Claude/claude_desktop_config.json";Config file search order (priority: highest to lowest):
- Project:
.mcp.json(searched upward from cwd to home directory) - User:
~/.claude.json(all platforms) - Claude Desktop (legacy):
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%/Claude/claude_desktop_config.json - Linux:
~/.config/Claude/claude_desktop_config.json
- macOS:
Important: All path functions are now async because they check file existence.
- Input Validation: All user input validated with Zod schemas on both client and server
- Path Traversal Prevention:
isPathSafe()insrc/server/utils/fileSystem.ts - Localhost-Only Binding: Server MUST only bind to
127.0.0.1(never0.0.0.0) - Rate Limiting: Three separate limiters for different endpoint types
- Auto-Backup: Always backup before config writes
Always use async/await for file operations:
// CORRECT
const data = await fs.readFile(path, "utf-8");
// AVOID (blocks event loop)
const data = fs.readFileSync(path, "utf-8");Use functional updates when new state depends on previous state:
// CORRECT
setConfig((prev) => ({ ...prev, newField: value }));
// WRONG (may use stale data)
setConfig({ ...config, newField: value });| File | Purpose | When to Modify |
|---|---|---|
src/server/index.ts |
Express app setup, middleware, route registration | Adding middleware, new routes, server config |
src/server/services/configManager.ts |
Config CRUD operations, backup, validation | Changing load/save logic, backup strategy |
src/server/services/validator.ts |
Zod schemas, security checks | Adding validation rules, security checks |
src/server/utils/paths.ts |
Platform-specific path resolution | Supporting new platforms, config locations |
src/server/routes/config.ts |
Config API endpoints | Adding/modifying config endpoints |
src/server/routes/presets.ts |
Preset API endpoints | Adding/modifying preset endpoints |
| File | Purpose | When to Modify |
|---|---|---|
src/client/src/App.tsx |
Main component, global state, import/export | Adding top-level features, state changes |
src/client/src/hooks/useConfig.ts |
Config state management and API calls | Adding config operations |
src/client/src/hooks/usePresets.ts |
Preset state management and API calls | Adding preset operations |
src/client/src/components/ServerModal.tsx |
Add/Edit server form (React Hook Form + Zod) | Changing form fields, validation |
src/client/src/services/api.ts |
HTTP client for backend API | Adding new API calls |
| File | Purpose |
|---|---|
tsconfig.json |
Base TypeScript config |
tsconfig.server.json |
Server build (ES2020, Node modules) |
tsconfig.client.json |
Client build (ESNext, React JSX) |
tsconfig.node.json |
CLI utilities (Node environment) |
src/client/vite.config.ts |
Vite bundler config, proxy setup |
jest.config.js |
Jest test configuration (ES modules enabled) |
src/presets/mcpServers.json |
MCP server preset definitions |
User Action (React UI)
↓
Custom Hook (useConfig/usePresets)
↓
API Client (fetch to /api/*)
↓
Express Route Handler
↓
Service Layer (business logic)
↓
Validator (Zod schema validation)
↓
File System Utils (with backup)
↓
claude_desktop_config.json (+ timestamped backup)
The build is split into three independent steps:
-
Server:
tsc -p tsconfig.server.json- Compiles
src/server/**/*.ts→dist/server/**/*.js - ES modules output for Node.js runtime
- Compiles
-
Client:
cd src/client && vite build- Bundles React app →
dist/client/(optimized, minified) - Includes HTML, CSS, JS with content hashing
- Bundles React app →
-
Presets:
cp -r src/presets/* dist/presets/- Copies JSON preset files
Development Mode (npm run dev):
- Server: tsx watch (TypeScript execution + hot reload) on :4000
- Client: Vite dev server (:62000) with HMR
- API proxy: Vite proxies
/api/*tolocalhost:4000
Production Mode (npm start):
- Server: Compiled JS from
dist/server/ - Client: Pre-bundled files from
dist/client/ - Single server on port 4000 serves both API and static files
Tests are located in __tests__/ directories next to source files.
Focus areas:
- Service layer logic (not route handlers)
- Edge cases: empty configs, missing files, invalid input
- Error handling paths
- Security validation
Example structure:
describe("ConfigManager", () => {
describe("loadConfig", () => {
it("should load existing config");
it("should create default config if missing");
it("should handle file read errors");
});
});- Create route handler in
src/server/routes/ - Add business logic to appropriate service
- Register route in
src/server/index.ts - Add client-side API call to
src/client/src/services/api.ts - Add TypeScript types if needed
Edit src/presets/mcpServers.json:
{
"id": "my-server",
"name": "My MCP Server",
"description": "What it does",
"category": "Development",
"config": {
"command": "npx",
"args": ["-y", "@org/package"],
"env": { "API_KEY": "placeholder" }
}
}Update Zod schemas in src/server/services/validator.ts:
const serverSchema = z.object({
command: z.string().min(1).max(255),
args: z.array(z.string()).optional(),
env: z.record(z.string()).optional(),
disabled: z.boolean().optional(),
});- Check
npm installcompleted - Run
npm run lintto check TypeScript errors - Verify Node.js >= 22.0.0
- Run
npm test -- --verbosefor details - Check for ES module import issues (missing
.jsextensions) - Verify Jest config in
jest.config.js
mcp-dashboard -p 62000Ensure Claude Code is installed and has been run at least once to create the config file.
- Main branch:
main(stable, production-ready) - Development branch:
develop(current) - Feature branches:
claude/feature-name-<session-id>
Commit message style: Clear, descriptive, focus on "why" not "what"
Entry points:
bin/cli.js: Executable CLIdist/server/index.js: Main export
Files included in npm package:
bin/,dist/,README.md,LICENSE
Minimum Node version: 22.0.0 (specified in engines)
- Production: 4000 (configurable)
- Dev server: 4000
- Dev client (Vite): 62000
- Server entry:
src/server/index.ts - Client entry:
src/client/src/main.tsx - CLI entry:
bin/cli.js - Presets:
src/presets/mcpServers.json - Tests:
src/server/services/__tests__/
npm run dev # Development mode
npm run build # Production build
npm test # Run tests
npm run lint # Type check
npm start # Start server