docs: add architecture and getting-started docs, CLI e2e tests
Architecture doc covers all packages, APIs, and design principles. Getting-started guide covers installation, quick start, and config. E2e tests verify all CLI commands are properly registered. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
149
docs/architecture.md
Normal file
149
docs/architecture.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# mcpctl Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
mcpctl is a kubectl-like management tool for MCP (Model Context Protocol) servers. It consists of a CLI, a daemon server, a database layer, a local proxy, and shared utilities.
|
||||
|
||||
## Package Structure
|
||||
|
||||
```
|
||||
src/
|
||||
├── cli/ @mcpctl/cli - Command-line interface
|
||||
├── mcpd/ @mcpctl/mcpd - Daemon server (REST API)
|
||||
├── db/ @mcpctl/db - Database layer (Prisma + PostgreSQL)
|
||||
├── local-proxy/ @mcpctl/local-proxy - MCP protocol proxy
|
||||
└── shared/ @mcpctl/shared - Shared constants and utilities
|
||||
```
|
||||
|
||||
## Component Diagram
|
||||
|
||||
```
|
||||
┌─────────────────┐ HTTP ┌──────────────┐ Prisma ┌────────────┐
|
||||
│ mcpctl CLI │ ──────────────│ mcpd │ ──────────────│ PostgreSQL │
|
||||
│ (Commander.js) │ │ (Fastify 5) │ │ │
|
||||
└─────────────────┘ └──────┬───────┘ └────────────┘
|
||||
│
|
||||
│ Docker/Podman API
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ Containers │
|
||||
│ (MCP servers)│
|
||||
└──────────────┘
|
||||
|
||||
┌─────────────────┐ STDIO ┌──────────────┐ STDIO/HTTP ┌────────────┐
|
||||
│ Claude / LLM │ ────────────│ local-proxy │ ──────────────│ MCP Servers│
|
||||
│ │ │ (McpRouter) │ │ │
|
||||
└─────────────────┘ └──────────────┘ └────────────┘
|
||||
```
|
||||
|
||||
## CLI (`@mcpctl/cli`)
|
||||
|
||||
The CLI is built with Commander.js and communicates with mcpd via HTTP REST.
|
||||
|
||||
### Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `mcpctl get <resource>` | List resources (servers, profiles, projects, instances) |
|
||||
| `mcpctl describe <resource> <id>` | Show detailed resource info |
|
||||
| `mcpctl apply <file>` | Apply declarative YAML/JSON configuration |
|
||||
| `mcpctl setup [name]` | Interactive server setup wizard |
|
||||
| `mcpctl instance list/start/stop/restart/remove/logs/inspect` | Manage instances |
|
||||
| `mcpctl claude generate/show/add/remove` | Manage .mcp.json files |
|
||||
| `mcpctl project list/create/delete/show/profiles/set-profiles` | Manage projects |
|
||||
| `mcpctl config get/set/path` | Manage CLI configuration |
|
||||
| `mcpctl status` | Check daemon connectivity |
|
||||
|
||||
### Configuration
|
||||
|
||||
CLI config is stored at `~/.config/mcpctl/config.json` with:
|
||||
- `daemonUrl`: mcpd server URL (default: `http://localhost:4444`)
|
||||
|
||||
## Daemon (`@mcpctl/mcpd`)
|
||||
|
||||
Fastify 5-based REST API server that manages MCP server lifecycle.
|
||||
|
||||
### Layers
|
||||
|
||||
1. **Routes** - HTTP handlers, parameter extraction
|
||||
2. **Services** - Business logic, validation (Zod schemas), error handling
|
||||
3. **Repositories** - Data access via Prisma (interface-based for testability)
|
||||
|
||||
### API Endpoints
|
||||
|
||||
| Endpoint | Methods | Description |
|
||||
|----------|---------|-------------|
|
||||
| `/api/v1/servers` | GET, POST | MCP server definitions |
|
||||
| `/api/v1/servers/:id` | GET, PUT, DELETE | Single server operations |
|
||||
| `/api/v1/profiles` | GET, POST | Server configuration profiles |
|
||||
| `/api/v1/profiles/:id` | GET, PUT, DELETE | Single profile operations |
|
||||
| `/api/v1/projects` | GET, POST | Project management |
|
||||
| `/api/v1/projects/:id` | GET, PUT, DELETE | Single project operations |
|
||||
| `/api/v1/projects/:id/profiles` | GET, PUT | Project profile assignments |
|
||||
| `/api/v1/projects/:id/mcp-config` | GET | Generate .mcp.json |
|
||||
| `/api/v1/instances` | GET, POST | Instance lifecycle |
|
||||
| `/api/v1/instances/:id` | GET, DELETE | Instance operations |
|
||||
| `/api/v1/instances/:id/stop` | POST | Stop instance |
|
||||
| `/api/v1/instances/:id/restart` | POST | Restart instance |
|
||||
| `/api/v1/instances/:id/inspect` | GET | Container inspection |
|
||||
| `/api/v1/instances/:id/logs` | GET | Container logs |
|
||||
| `/api/v1/audit-logs` | GET | Query audit logs |
|
||||
| `/api/v1/audit-logs/:id` | GET | Single audit log |
|
||||
| `/api/v1/audit-logs/purge` | POST | Purge expired logs |
|
||||
| `/health` | GET | Health check (detailed) |
|
||||
| `/healthz` | GET | Liveness probe |
|
||||
|
||||
### Container Orchestration
|
||||
|
||||
The `McpOrchestrator` interface abstracts container management:
|
||||
- `DockerContainerManager` - Docker/Podman implementation via dockerode
|
||||
- Future: `KubernetesOrchestrator` for k8s deployments
|
||||
|
||||
## Local Proxy (`@mcpctl/local-proxy`)
|
||||
|
||||
Aggregates multiple MCP servers behind a single STDIO endpoint.
|
||||
|
||||
### Features
|
||||
|
||||
- **Tool namespacing**: `servername/toolname` routing
|
||||
- **Resource forwarding**: `resources/list` and `resources/read`
|
||||
- **Prompt forwarding**: `prompts/list` and `prompts/get`
|
||||
- **Notification pass-through**: Upstream notifications forwarded to client
|
||||
- **Health monitoring**: Periodic health checks with state tracking
|
||||
- **Transport support**: STDIO (child process) and HTTP (SSE/Streamable HTTP)
|
||||
|
||||
### Usage
|
||||
|
||||
```bash
|
||||
# Via config file
|
||||
mcpctl-proxy --config proxy.json
|
||||
|
||||
# Via CLI flags
|
||||
mcpctl-proxy --upstream "slack:npx -y @anthropic/slack-mcp" \
|
||||
--upstream "github:npx -y @anthropic/github-mcp"
|
||||
```
|
||||
|
||||
## Database (`@mcpctl/db`)
|
||||
|
||||
Prisma ORM with PostgreSQL. Key models:
|
||||
|
||||
- **User** / **Session** - Authentication
|
||||
- **McpServer** - Server definitions (name, transport, package, docker image)
|
||||
- **McpProfile** - Per-server configurations (env overrides, permissions)
|
||||
- **Project** - Grouping of profiles for a workspace
|
||||
- **McpInstance** - Running container instances with lifecycle state
|
||||
- **AuditLog** - Immutable operation audit trail
|
||||
|
||||
## Shared (`@mcpctl/shared`)
|
||||
|
||||
Constants and utilities shared across packages:
|
||||
- `APP_NAME`, `APP_VERSION`
|
||||
- Common type definitions
|
||||
|
||||
## Design Principles
|
||||
|
||||
1. **Interface-based repositories** - All data access through interfaces for testability
|
||||
2. **Dependency injection** - Services receive dependencies via constructor
|
||||
3. **Zod validation** - All user input validated with Zod schemas
|
||||
4. **Namespaced errors** - Custom error classes with HTTP status codes
|
||||
5. **TypeScript strict mode** - `exactOptionalPropertyTypes`, `noUncheckedIndexedAccess`
|
||||
155
docs/getting-started.md
Normal file
155
docs/getting-started.md
Normal file
@@ -0,0 +1,155 @@
|
||||
# Getting Started with mcpctl
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Node.js >= 20.0.0
|
||||
- pnpm >= 9.0.0
|
||||
- PostgreSQL (for mcpd)
|
||||
- Docker or Podman (for container management)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone <repo-url>
|
||||
cd mcpctl
|
||||
|
||||
# Install dependencies
|
||||
pnpm install
|
||||
|
||||
# Generate Prisma client
|
||||
pnpm --filter @mcpctl/db exec prisma generate
|
||||
|
||||
# Build all packages
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Start the Database
|
||||
|
||||
```bash
|
||||
# Start PostgreSQL via Docker Compose
|
||||
pnpm db:up
|
||||
|
||||
# Run database migrations
|
||||
pnpm --filter @mcpctl/db exec prisma db push
|
||||
```
|
||||
|
||||
### 2. Start the Daemon
|
||||
|
||||
```bash
|
||||
cd src/mcpd
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
The daemon starts on `http://localhost:4444` by default.
|
||||
|
||||
### 3. Use the CLI
|
||||
|
||||
```bash
|
||||
# Check daemon status
|
||||
mcpctl status
|
||||
|
||||
# Register an MCP server
|
||||
mcpctl apply config.yaml
|
||||
|
||||
# Or use the interactive wizard
|
||||
mcpctl setup my-server
|
||||
|
||||
# List registered servers
|
||||
mcpctl get servers
|
||||
|
||||
# Start an instance
|
||||
mcpctl instance start <server-id>
|
||||
|
||||
# Check instance status
|
||||
mcpctl instance list
|
||||
|
||||
# View instance logs
|
||||
mcpctl instance logs <instance-id>
|
||||
```
|
||||
|
||||
### 4. Generate .mcp.json for Claude
|
||||
|
||||
```bash
|
||||
# Create a project
|
||||
mcpctl project create my-workspace
|
||||
|
||||
# Assign profiles to project
|
||||
mcpctl project set-profiles <project-id> <profile-id-1> <profile-id-2>
|
||||
|
||||
# Generate .mcp.json
|
||||
mcpctl claude generate <project-id>
|
||||
|
||||
# Or manually add servers
|
||||
mcpctl claude add my-server -c npx -a -y @my/mcp-server
|
||||
```
|
||||
|
||||
## Example Configuration
|
||||
|
||||
Create a `config.yaml` file:
|
||||
|
||||
```yaml
|
||||
servers:
|
||||
- name: slack
|
||||
description: Slack MCP server
|
||||
transport: STDIO
|
||||
packageName: "@anthropic/slack-mcp"
|
||||
envTemplate:
|
||||
- name: SLACK_TOKEN
|
||||
description: Slack bot token
|
||||
isSecret: true
|
||||
|
||||
- name: github
|
||||
description: GitHub MCP server
|
||||
transport: STDIO
|
||||
packageName: "@anthropic/github-mcp"
|
||||
|
||||
profiles:
|
||||
- name: default
|
||||
server: slack
|
||||
envOverrides:
|
||||
SLACK_TOKEN: "xoxb-your-token"
|
||||
|
||||
projects:
|
||||
- name: dev-workspace
|
||||
description: Development workspace
|
||||
```
|
||||
|
||||
Apply it:
|
||||
|
||||
```bash
|
||||
mcpctl apply config.yaml
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
pnpm test:run
|
||||
|
||||
# Run tests for a specific package
|
||||
pnpm --filter @mcpctl/cli test:run
|
||||
pnpm --filter @mcpctl/mcpd test:run
|
||||
pnpm --filter @mcpctl/local-proxy test:run
|
||||
|
||||
# Run tests with coverage
|
||||
pnpm test:coverage
|
||||
|
||||
# Typecheck
|
||||
pnpm typecheck
|
||||
|
||||
# Lint
|
||||
pnpm lint
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
# Watch mode for tests
|
||||
pnpm test
|
||||
|
||||
# Build in watch mode
|
||||
cd src/cli && pnpm dev
|
||||
```
|
||||
77
src/cli/tests/e2e/cli-commands.test.ts
Normal file
77
src/cli/tests/e2e/cli-commands.test.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createProgram } from '../../src/index.js';
|
||||
|
||||
/**
|
||||
* End-to-end tests that verify CLI command registration and help output
|
||||
* without requiring a running daemon.
|
||||
*/
|
||||
describe('CLI command registration (e2e)', () => {
|
||||
it('program has all expected commands', () => {
|
||||
const program = createProgram();
|
||||
const commandNames = program.commands.map((c) => c.name());
|
||||
|
||||
expect(commandNames).toContain('config');
|
||||
expect(commandNames).toContain('status');
|
||||
expect(commandNames).toContain('get');
|
||||
expect(commandNames).toContain('describe');
|
||||
expect(commandNames).toContain('instance');
|
||||
expect(commandNames).toContain('apply');
|
||||
expect(commandNames).toContain('setup');
|
||||
expect(commandNames).toContain('claude');
|
||||
expect(commandNames).toContain('project');
|
||||
});
|
||||
|
||||
it('instance command has lifecycle subcommands', () => {
|
||||
const program = createProgram();
|
||||
const instance = program.commands.find((c) => c.name() === 'instance');
|
||||
expect(instance).toBeDefined();
|
||||
|
||||
const subcommands = instance!.commands.map((c) => c.name());
|
||||
expect(subcommands).toContain('list');
|
||||
expect(subcommands).toContain('start');
|
||||
expect(subcommands).toContain('stop');
|
||||
expect(subcommands).toContain('restart');
|
||||
expect(subcommands).toContain('remove');
|
||||
expect(subcommands).toContain('logs');
|
||||
expect(subcommands).toContain('inspect');
|
||||
});
|
||||
|
||||
it('claude command has config management subcommands', () => {
|
||||
const program = createProgram();
|
||||
const claude = program.commands.find((c) => c.name() === 'claude');
|
||||
expect(claude).toBeDefined();
|
||||
|
||||
const subcommands = claude!.commands.map((c) => c.name());
|
||||
expect(subcommands).toContain('generate');
|
||||
expect(subcommands).toContain('show');
|
||||
expect(subcommands).toContain('add');
|
||||
expect(subcommands).toContain('remove');
|
||||
});
|
||||
|
||||
it('project command has CRUD subcommands', () => {
|
||||
const program = createProgram();
|
||||
const project = program.commands.find((c) => c.name() === 'project');
|
||||
expect(project).toBeDefined();
|
||||
|
||||
const subcommands = project!.commands.map((c) => c.name());
|
||||
expect(subcommands).toContain('list');
|
||||
expect(subcommands).toContain('create');
|
||||
expect(subcommands).toContain('delete');
|
||||
expect(subcommands).toContain('show');
|
||||
expect(subcommands).toContain('profiles');
|
||||
expect(subcommands).toContain('set-profiles');
|
||||
});
|
||||
|
||||
it('displays version', () => {
|
||||
const program = createProgram();
|
||||
expect(program.version()).toBeDefined();
|
||||
expect(program.version()).toMatch(/^\d+\.\d+\.\d+$/);
|
||||
});
|
||||
|
||||
it('displays help without error', () => {
|
||||
const program = createProgram();
|
||||
const helpText = program.helpInformation();
|
||||
expect(helpText).toContain('mcpctl');
|
||||
expect(helpText).toContain('Manage MCP servers');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user