Local MCP Gateway is a monorepo application that serves as a local gateway for MCP (Model Context Protocol) servers. It allows users to:
- Aggregate multiple MCP servers (stdio, SSE, HTTP) into a single endpoint
- Create MCP servers as packages in
mcp-servers/folder with auto-discovery - Manage servers via a Web UI with React 19 frontend
- Inspect MCP traffic with built-in debug logging
- Secure MCP servers with API keys and OAuth 2.1 (PKCE, DCR)
- Profile-based tool management - group MCP servers into profiles for different use cases
NOTE: This application does not require user authentication. Users can immediately configure and use MCP servers without login.
This is a pnpm workspace + Turborepo monorepo built with:
- Backend: NestJS 11.x with TypeScript, Prisma ORM, SQLite
- Frontend: React 19 with Vite, TanStack Query, Zustand, Tailwind CSS
- Testing: Vitest (unit/integration), Playwright (E2E)
- Code Quality: Biome (linting/formatting), TypeScript strict mode
- Build: Turborepo for monorepo orchestration
- MCP Servers: Auto-discovered packages in
mcp-servers/folder
- Node.js: >=22.0.0 (check
.nvmrcfor exact version) - pnpm: >=9.0.0 (specified in
packageManagerfield) - Docker: Optional, for containerized deployment
# Install dependencies for all packages
pnpm install
# Initialize database with seed data
pnpm db:seed
# Start development
pnpm dev# Start both backend and frontend with hot-reload
pnpm dev
# Start only backend
pnpm dev:backend
# Start only frontend
pnpm dev:frontendPorts:
- Backend: http://localhost:3001
- Frontend: http://localhost:3000
# Build all packages and apps
pnpm build
# Build specific package/app
pnpm --filter backend build
pnpm --filter frontend buildBuild Output:
- Backend:
apps/backend/dist/(NestJS compiled) - Frontend:
apps/frontend/dist/(Vite production build)
# Type check all packages
pnpm typecheck
# Type check specific package
pnpm --filter backend typecheck
pnpm --filter frontend typecheck# Lint all packages
pnpm lint
# Format all files
pnpm format
# Check formatting without changes
pnpm format:checkLinting Rules (Biome):
- Single quotes for strings
- Semicolons required
- Trailing commas (ES5 style)
- 2-space indentation
- 100 character line width
- No unused variables
- Security rules (no dangerouslySetInnerHtml)
Note: useImportType is disabled for NestJS compatibility (dependency injection requires runtime imports).
graph TB
subgraph "Client Layer"
Claude[Claude Desktop]
WebUI[Web UI]
end
subgraph "Gateway Layer"
Backend[NestJS Backend<br/>Port 3001]
Frontend[React Frontend<br/>Port 3000]
end
subgraph "MCP Layer"
Profile1[Profile 1<br/>Tools A, B, C]
Profile2[Profile 2<br/>Tools D, E]
McpPackage[MCP Package<br/>mcp-servers/]
end
subgraph "External Services"
MCP1[External MCP Server 1]
MCP2[External MCP Server 2]
OAuth[OAuth Providers<br/>For MCP servers]
end
Claude -->|HTTP/SSE| Backend
WebUI --> Frontend
Frontend -->|API Calls| Backend
Backend --> Profile1
Backend --> Profile2
Backend --> McpPackage
Profile1 --> MCP1
Profile2 --> MCP2
Backend --> OAuth
Backend -->|SQLite/Prisma| DB[(Database)]
sequenceDiagram
participant Dev as Developer
participant Pkg as mcp-servers/
participant Backend as NestJS Backend
participant Discovery as McpDiscoveryService
participant Registry as McpRegistry
participant Seed as McpSeedService
participant DB as Prisma/SQLite
Dev->>Pkg: Create new MCP package
Dev->>Pkg: Add "mcpPackage": true
Dev->>Backend: pnpm install && pnpm dev
Backend->>Discovery: onModuleInit()
Discovery->>Pkg: Scan dependencies
Discovery->>Discovery: Filter mcpPackage=true
Discovery->>Registry: Register packages
Registry->>Seed: Run seeds
Seed->>DB: Create MCP server records
Seed->>DB: Link to default profile
Backend->>Backend: Ready to serve
sequenceDiagram
participant User
participant Frontend
participant Backend
participant Profile
participant MCP1
participant MCP2
User->>Frontend: Create Profile
Frontend->>Backend: POST /api/profiles
Backend->>Profile: Store profile config
User->>Frontend: Add MCP Server 1
Frontend->>Backend: POST /api/mcp-servers
Backend->>MCP1: Register server
User->>Frontend: Assign servers to profile
Frontend->>Backend: PUT /api/profiles/:id
Backend->>Profile: Link servers
User->>Claude: Use profile endpoint
Claude->>Backend: POST /api/mcp/my-profile
Backend->>Profile: Get tools
Profile->>MCP1: Aggregate tools
Profile->>MCP2: Aggregate tools
Backend->>Claude: Return combined tools
Context: You need to add a new REST endpoint to the backend.
Steps:
-
Create controller and service in appropriate module:
// apps/backend/src/modules/my-feature/my-feature.controller.ts import { Controller, Get, Post, Body, Param } from '@nestjs/common'; import { MyFeatureService } from './my-feature.service'; @Controller('my-feature') export class MyFeatureController { constructor(private readonly myFeatureService: MyFeatureService) {} @Get() findAll() { return this.myFeatureService.findAll(); } @Post() create(@Body() data: CreateDto) { return this.myFeatureService.create(data); } }
-
Create service:
// apps/backend/src/modules/my-feature/my-feature.service.ts import { Injectable } from '@nestjs/common'; import { PrismaService } from '../database/prisma.service'; @Injectable() export class MyFeatureService { constructor(private readonly prisma: PrismaService) {} async findAll() { return this.prisma.myModel.findMany(); } }
-
Create module:
// apps/backend/src/modules/my-feature/my-feature.module.ts import { Module } from '@nestjs/common'; import { MyFeatureController } from './my-feature.controller'; import { MyFeatureService } from './my-feature.service'; @Module({ controllers: [MyFeatureController], providers: [MyFeatureService], }) export class MyFeatureModule {}
-
Register module in
apps/backend/src/app.module.ts
Context: You need to add a new MCP server to the gateway.
Steps:
-
Create package structure:
mkdir -p mcp-servers/my-mcp/src
-
Create package.json with
mcpPackage: true:{ "name": "@dxheroes/mcp-my-mcp", "version": "1.0.0", "type": "module", "main": "./dist/index.js", "mcpPackage": true, "peerDependencies": { "@dxheroes/local-mcp-core": "workspace:*" } } -
Export McpPackage interface from
src/index.ts:import type { McpPackage } from '@dxheroes/local-mcp-core'; export const mcpPackage: McpPackage = { metadata: { id: 'my-mcp', name: 'My MCP', description: 'Description', version: '1.0.0', requiresApiKey: true, }, createServer: (apiKeyConfig) => new MyMcpServer(apiKeyConfig), seed: { defaultProfile: 'default', defaultOrder: 10, }, }; export default mcpPackage;
-
Run install - package is auto-discovered!
pnpm install pnpm dev:backend # Log: "Discovered X MCP packages" # Log: "Registered: My MCP (my-mcp)"
Context: You need to add a new database table or modify schema.
Steps:
-
Edit Prisma schema:
// packages/database/prisma/schema.prisma model MyModel { id String @id @default(uuid()) name String createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") @@map("my_models") }
-
Create migration:
cd packages/database pnpm prisma migrate dev --name add_my_model -
Generate client and Zod schemas:
pnpm prisma generate
-
Use in service:
// Use generated Prisma Client const items = await this.prisma.myModel.findMany(); // Use generated Zod schema for validation import { MyModelCreateInputSchema } from '@dxheroes/local-mcp-database'; const validated = MyModelCreateInputSchema.parse(input);
Context: You need to test a complete user flow.
Steps:
-
Create test file:
// apps/frontend/e2e/my-feature.spec.ts import { test, expect } from '@playwright/test'; test('user can complete flow', async ({ page }) => { await page.goto('/'); // ... test steps });
-
Run E2E tests:
pnpm test:e2e
- MCP (Model Context Protocol): Protocol for AI assistants to access external tools and data
- Profile: A named collection of MCP servers that can be accessed via a single endpoint (e.g.,
/api/mcp/my-profile) - MCP Server: An implementation of the MCP protocol that provides tools/resources/prompts
- MCP Package: A standalone npm package in
mcp-servers/withmcpPackage: true - Tool: A function that an AI can call (e.g.,
read_file,search_web) - Resource: Data that an AI can access (e.g., files, database records)
- builtin: MCP server package in
mcp-servers/folder (auto-discovered) - custom: Custom TypeScript module MCP server
- remote_http: External HTTP MCP server
- remote_sse: Server-Sent Events MCP server
- external: Spawned process MCP server
- No User Auth: Application does not require user login
- OAuth 2.1 for MCP: OAuth for MCP servers connecting to external services
- API Keys: For MCP servers requiring API authentication
- NestJS Module: Self-contained unit with controllers, services, providers
- Prisma Service: Database access layer with type-safe queries
- McpRegistry: In-memory registry of discovered MCP packages
- McpDiscoveryService: Scans dependencies for
mcpPackage: true - McpSeedService: Creates database records for discovered packages
- Strict mode: Enabled in all
tsconfig.jsonfiles - Module system: ES modules (
"type": "module"in package.json) - Import style: Regular imports for NestJS (DI requires runtime imports)
- Naming:
- Components: PascalCase (
MyComponent.tsx) - Files: kebab-case for non-components (
my-utils.ts) - Types/Interfaces: PascalCase (
UserProfile,McpServer) - NestJS: PascalCase for classes (
MyFeatureService,MyFeatureController)
- Components: PascalCase (
- Indentation: 2 spaces
- Line width: 100 characters
- Quotes: Single quotes for strings
- Semicolons: Always required
- Trailing commas: ES5 style (objects, arrays)
- Import organization: Auto-organized by Biome
apps/backend/src/
├── main.ts # NestJS bootstrap
├── app.module.ts # Root module
├── modules/
│ ├── database/ # Prisma service (global)
│ ├── mcp/ # MCP management, discovery, registry
│ ├── profiles/ # Profile CRUD
│ ├── oauth/ # OAuth for MCP servers
│ ├── proxy/ # MCP proxy endpoints
│ ├── health/ # Health checks
│ └── debug/ # Debug logs
├── common/
│ ├── filters/ # Exception filters
│ ├── interceptors/ # Logging, timeout
│ └── pipes/ # Validation
└── config/ # App configuration
apps/frontend/src/
├── components/ # React components
├── pages/ # Page components (routes)
├── lib/ # Shared utilities
└── utils/ # Helper functions
- Unit tests:
__tests__/unit/- Test individual functions/components in isolation - Integration tests:
__tests__/integration/- Test multiple components together - E2E tests:
apps/frontend/e2e/- Test complete user flows with Playwright
# Run all tests
pnpm test
# Run with watch mode
pnpm test:watch
# E2E tests
pnpm test:e2e
# Coverage report
pnpm test:coverage- Isolation: Each test should be independent
- Naming: Descriptive test names
- Arrange-Act-Assert: Clear test structure
- Mocking: Use MSW for API mocking in frontend tests
- Router Context: Wrap components using
useNavigatein<MemoryRouter>
local-mcp-gateway/
├── apps/ # Applications
│ ├── backend/ # NestJS backend
│ │ └── src/modules/ # NestJS modules
│ └── frontend/ # React 19 frontend
├── packages/ # Shared packages
│ ├── core/ # Core abstractions (types, interfaces)
│ │ └── src/types/
│ │ ├── mcp.ts # MCP types
│ │ └── mcp-package.ts # McpPackage interface
│ ├── database/ # Database layer (Prisma ORM)
│ │ ├── prisma/
│ │ │ ├── schema.prisma # Prisma schema
│ │ │ └── migrations/ # Prisma migrations
│ │ └── src/generated/
│ │ ├── prisma/ # Prisma Client
│ │ └── zod/ # Generated Zod schemas
│ ├── config/ # Shared configuration
│ └── ui/ # Shared UI components
├── mcp-servers/ # MCP server packages (auto-discovered)
│ └── gemini-deep-research/ # Example MCP package
├── docs/ # Documentation
├── package.json # Root package.json
├── pnpm-workspace.yaml # pnpm workspace config
├── turbo.json # Turborepo config
└── biome.json # Biome linting/formatting
@nestjs/common,@nestjs/core: NestJS framework@nestjs/config: Configuration management@nestjs/throttler: Rate limiting@modelcontextprotocol/sdk: MCP SDKhelmet: Security headerscompression: Response compressionzod: Schema validation
react,react-dom: React 19react-router: Routing@tanstack/react-query: Data fetchingzustand: State managementlucide-react: Iconszod: Schema validation
@prisma/client: Prisma Clientprisma: Prisma CLIzod-prisma-types: Zod schema generator
All packages use workspace protocol (workspace:*):
@dxheroes/local-mcp-core- Core types and abstractions@dxheroes/local-mcp-database- Prisma database layer@dxheroes/local-mcp-config- Shared configuration@dxheroes/local-mcp-ui- Shared UI components@dxheroes/mcp-*- MCP server packages in mcp-servers/
None - Application works out of the box without configuration.
Database:
DATABASE_URL: Prisma database URL (default:file:~/.local-mcp-gateway-data/local-mcp-gateway.db)
Server:
PORT: Backend port (default: 3001)NODE_ENV: Environment (development,production,test)
CORS:
CORS_ORIGINS: Comma-separated list of allowed origins
Frontend:
VITE_API_URL: Backend API URL (default:http://localhost:3001)
- All inputs validated using Zod schemas (auto-generated from Prisma)
- SQL injection prevention: Prisma ORM (parameterized queries)
- XSS prevention: React automatically escapes
- Helmet.js: Sets security headers (CSP, HSTS, etc.)
- CORS: Configurable allowed origins
- Rate limiting: NestJS throttler on API routes
- Validate all inputs: Use Zod schemas
- Use Prisma: Automatic SQL injection prevention
- Keep dependencies updated: Regular security audits
Common Issues:
- Port already in use: Change
PORTin.env - Database locked: Check if another process is using SQLite
- MCP not discovered: Verify
mcpPackage: truein package.json
Debug Mode:
LOG_LEVEL=debug pnpm dev:backendPrisma Studio (visual database browser):
cd packages/database
pnpm prisma studioReset Database:
pnpm db:resetDebug Logs:
- Available in Web UI:
/debug-logs - Shows all MCP requests/responses
- No User Auth: Application is immediately usable without login
- Auto-Discovery: MCP packages in
mcp-servers/are auto-discovered - Type Safety: Full TypeScript strict mode with Prisma-generated types
- NestJS Modules: Clean separation of concerns with dependency injection
- Prisma ORM: Type-safe database access with generated Zod schemas
Each directory has its own AGENTS.md with specific instructions:
- packages/AGENTS.md - Shared packages overview
- apps/AGENTS.md - Applications (backend, frontend)
- mcp-servers/AGENTS.md - MCP server packages
- docs/AGENTS.md - Documentation structure
package.json- Root package.json with workspace scriptspnpm-workspace.yaml- pnpm workspace configuration (includesmcp-servers/*)turbo.json- Turborepo pipeline configurationbiome.json- Biome linting and formatting (useImportType: offfor NestJS)docker-compose.yml- Docker Compose configuration.env.example- Environment variables templateREADME.md- Main project README
- No authentication required: Users can immediately use the application
- MCP packages are auto-discovered: Just add to
mcp-servers/withmcpPackage: true - Prisma for database: Type-safe queries with generated Zod schemas
- NestJS for backend: Modules, dependency injection, controllers, services
- All data stored in:
~/.local-mcp-gateway-data(home directory)