1049 lines
41 KiB
Markdown
1049 lines
41 KiB
Markdown
|
|
# mcpctl — Comprehensive Project Summary
|
||
|
|
|
||
|
|
**kubectl for Model Context Protocol servers.**
|
||
|
|
|
||
|
|
mcpctl is a production-grade management system for MCP servers, providing a Kubernetes-inspired declarative interface for deploying, orchestrating, and observing MCP servers that connect to Claude and other LLM clients.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Table of Contents
|
||
|
|
|
||
|
|
1. [System Architecture](#1-system-architecture)
|
||
|
|
2. [Component Overview](#2-component-overview)
|
||
|
|
3. [Resource Model & Design Decisions](#3-resource-model--design-decisions)
|
||
|
|
4. [CLI Reference](#4-cli-reference)
|
||
|
|
5. [API Surface (mcpd)](#5-api-surface-mcpd)
|
||
|
|
6. [Database Schema](#6-database-schema)
|
||
|
|
7. [Local Proxy (mcplocal)](#7-local-proxy-mcplocal)
|
||
|
|
8. [ProxyModel Plugin System](#8-proxymodel-plugin-system)
|
||
|
|
9. [Gated Sessions](#9-gated-sessions)
|
||
|
|
10. [Content Pipeline & Stages](#10-content-pipeline--stages)
|
||
|
|
11. [LLM Provider Integration](#11-llm-provider-integration)
|
||
|
|
12. [Caching](#12-caching)
|
||
|
|
13. [Authentication & RBAC](#13-authentication--rbac)
|
||
|
|
14. [Audit Infrastructure & Trust Model](#14-audit-infrastructure--trust-model)
|
||
|
|
15. [Container Orchestration](#15-container-orchestration)
|
||
|
|
16. [Deployment & Distribution](#16-deployment--distribution)
|
||
|
|
17. [Testing Strategy](#17-testing-strategy)
|
||
|
|
18. [Technology Stack](#18-technology-stack)
|
||
|
|
19. [Project Structure](#19-project-structure)
|
||
|
|
20. [Deferred & Future Work](#20-deferred--future-work)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 1. System Architecture
|
||
|
|
|
||
|
|
### Three-Tier Design
|
||
|
|
|
||
|
|
```
|
||
|
|
Claude Code / LLM Client
|
||
|
|
| (STDIO — MCP JSON-RPC protocol)
|
||
|
|
v
|
||
|
|
mcplocal (Local Daemon — developer machine)
|
||
|
|
| (HTTP REST)
|
||
|
|
v
|
||
|
|
mcpd (Remote Daemon — server/NAS, e.g. 10.0.0.194)
|
||
|
|
| (Docker/Podman API)
|
||
|
|
v
|
||
|
|
MCP Server Containers (isolated network)
|
||
|
|
```
|
||
|
|
|
||
|
|
```
|
||
|
|
┌────────────┐
|
||
|
|
┌─────────────────┐ HTTP ┌──────────────┐ │ PostgreSQL │
|
||
|
|
│ mcpctl CLI │──────────────>│ mcpd │──>│ │
|
||
|
|
│ (Commander.js) │ │ (Fastify 5) │ └────────────┘
|
||
|
|
└─────────────────┘ └──────┬───────┘
|
||
|
|
│ Docker/Podman API
|
||
|
|
v
|
||
|
|
┌──────────────┐
|
||
|
|
│ Containers │
|
||
|
|
│ (MCP servers)│
|
||
|
|
└──────────────┘
|
||
|
|
|
||
|
|
┌─────────────────┐ STDIO ┌──────────────┐ STDIO/HTTP ┌────────────┐
|
||
|
|
│ Claude / LLM │────────────>│ mcplocal │───────────────>│ MCP Servers│
|
||
|
|
│ │ │ (McpRouter) │ │ │
|
||
|
|
└─────────────────┘ └──────────────┘ └────────────┘
|
||
|
|
```
|
||
|
|
|
||
|
|
### Key Principle
|
||
|
|
|
||
|
|
- **mcpd owns the database** (PostgreSQL) — the only component that talks to the DB
|
||
|
|
- **mcplocal is stateless** — config-only, no database, acts as intelligent proxy
|
||
|
|
- **mcpctl stores only credentials** — `~/.mcpctl/config.json` and `~/.mcpctl/credentials.json`
|
||
|
|
- **All MCP servers run inside the mcpd container** on the NAS via podman (container-in-container)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 2. Component Overview
|
||
|
|
|
||
|
|
### mcpctl (CLI)
|
||
|
|
- kubectl-like interface for managing the entire system
|
||
|
|
- Talks to mcplocal (local daemon) via HTTP REST, or directly to mcpd with `--direct`
|
||
|
|
- Distributed as RPM/DEB package via Gitea registry
|
||
|
|
- Built with Commander.js, Ink/React for TUI, Inquirer for prompts
|
||
|
|
|
||
|
|
### mcplocal (Local Daemon)
|
||
|
|
- Runs on developer machine as a systemd user service
|
||
|
|
- Exposes MCP protocol via STDIO to Claude
|
||
|
|
- Exposes HTTP REST API for mcpctl management commands
|
||
|
|
- Core responsibilities:
|
||
|
|
- Tool namespacing and routing (`server/tool` format)
|
||
|
|
- Gated sessions and prompt delivery
|
||
|
|
- Content pipeline (transformation stages)
|
||
|
|
- LLM integration for intelligent prompt selection
|
||
|
|
- Pipeline result caching
|
||
|
|
- Audit event collection
|
||
|
|
|
||
|
|
### mcpd (Remote Daemon)
|
||
|
|
- Server-side daemon on NAS/cloud (Fastify 5)
|
||
|
|
- Manages MCP server containers (Docker/Podman via dockerode)
|
||
|
|
- PostgreSQL for state, audit logs, access control
|
||
|
|
- Owns credentials (never exposed to mcplocal)
|
||
|
|
- REST API for all management operations
|
||
|
|
- MCP proxy endpoint for direct tool invocation
|
||
|
|
- Health probe runner for container monitoring
|
||
|
|
- Git-based backup system
|
||
|
|
|
||
|
|
### @mcpctl/db (Database Layer)
|
||
|
|
- Prisma ORM with PostgreSQL
|
||
|
|
- 22 models, 11 migrations
|
||
|
|
- Template seeding from YAML files at startup
|
||
|
|
|
||
|
|
### @mcpctl/shared (Shared Utilities)
|
||
|
|
- Constants, types, validation schemas (Zod)
|
||
|
|
- Secret encryption/decryption utilities
|
||
|
|
- Zero external dependencies beyond Zod
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 3. Resource Model & Design Decisions
|
||
|
|
|
||
|
|
### ADR-001: Kubernetes-Style Resource Model
|
||
|
|
|
||
|
|
| mcpctl Resource | K8s Analogy | Behavior |
|
||
|
|
|----------------|-------------|----------|
|
||
|
|
| **Server** | Deployment | Self-contained, complete definition. Contains image, command, transport, env refs, replicas. No external template dependencies at runtime. |
|
||
|
|
| **Instance** | Pod | Immutable, ephemeral, auto-managed by reconciliation loop. No `create instance` or `edit instance`. Delete triggers re-creation. |
|
||
|
|
| **Secret** | Secret | Holds sensitive key-value pairs. Servers reference via `env[].valueFrom.secretRef`. |
|
||
|
|
| **Project** | Namespace | Groups servers, configures ProxyModel and LLM provider. Generates `.mcp.json`. |
|
||
|
|
| **Prompt** | ConfigMap (sort of) | Instruction text delivered to Claude. Global or project-scoped. Priority-ranked. |
|
||
|
|
| **Template** | — | Blueprints for server creation. Used at create-time only, not runtime. |
|
||
|
|
| **RbacDefinition** | ClusterRoleBinding | Named policies with subjects and roleBindings. |
|
||
|
|
|
||
|
|
### ADR-002: Profiles Replaced with Secrets
|
||
|
|
|
||
|
|
The original `McpProfile` resource tried to be secrets, configmaps, and project-server links simultaneously. Environment variables declared in profiles were never actually passed to running containers.
|
||
|
|
|
||
|
|
**Decision:** Replace with dedicated `Secret` resource following Kubernetes conventions:
|
||
|
|
```yaml
|
||
|
|
servers:
|
||
|
|
- name: ha-mcp
|
||
|
|
env:
|
||
|
|
- name: HOMEASSISTANT_TOKEN
|
||
|
|
valueFrom:
|
||
|
|
secretRef:
|
||
|
|
name: ha-credentials
|
||
|
|
key: HOMEASSISTANT_TOKEN
|
||
|
|
```
|
||
|
|
|
||
|
|
### ADR-003: Self-Contained Servers with Source Tracking
|
||
|
|
|
||
|
|
Servers store complete definitions (no runtime template dependencies). Optional `source` metadata enables registry-based upgrades via 3-way diff (old snapshot vs current server vs new template).
|
||
|
|
|
||
|
|
**Rationale:** Matches kubectl mental model. `get server X -o yaml > new.yaml && edit && apply` works naturally. Duplication is minimal (~10 lines YAML).
|
||
|
|
|
||
|
|
### ADR-004: ConfigMaps Deferred
|
||
|
|
|
||
|
|
Only Secrets implemented. ConfigMap separation can be added later if needed. Keeps the model simple.
|
||
|
|
|
||
|
|
### ADR-005: Apply-Compatible YAML Round-Trip
|
||
|
|
|
||
|
|
`mcpctl get server ha-mcp -o yaml > s.yaml && mcpctl apply -f s.yaml` must work:
|
||
|
|
- `get -o yaml/json` strips internal fields (id, createdAt, updatedAt, version, ownerId)
|
||
|
|
- Output wrapped in resource key: `{ servers: [...] }`
|
||
|
|
- `describe -o yaml/json` keeps full raw output (for debugging)
|
||
|
|
|
||
|
|
### ADR-006: CLI Design Principles
|
||
|
|
|
||
|
|
1. Everything possible via `apply -f` MUST also be possible via `create` CLI flags
|
||
|
|
2. Support `-o yaml` and `-o json` like kubectl
|
||
|
|
3. `describe` shows visually clean sectioned output with tables
|
||
|
|
4. Name resolution works everywhere (not just IDs)
|
||
|
|
5. Instances are immutable (like pods) — no create/edit
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 4. CLI Reference
|
||
|
|
|
||
|
|
### Global Options
|
||
|
|
```
|
||
|
|
--daemon-url <url> mcplocal daemon URL
|
||
|
|
--direct bypass mcplocal, connect directly to mcpd
|
||
|
|
-p, --project <name> Target project
|
||
|
|
-o, --output <format> table | json | yaml
|
||
|
|
-v, --version Show version
|
||
|
|
```
|
||
|
|
|
||
|
|
### Resource Operations
|
||
|
|
|
||
|
|
| Command | Description |
|
||
|
|
|---------|-------------|
|
||
|
|
| `mcpctl get <resource> [name]` | List resources or fetch by name/ID. Supports glob patterns (`graf*`). |
|
||
|
|
| `mcpctl describe <resource> <name>` | Detailed view with sections and tables. |
|
||
|
|
| `mcpctl create <resource> <name> [opts]` | Create resource. Mirrors `apply -f` capabilities. |
|
||
|
|
| `mcpctl edit <resource> <name>` | Open in `$EDITOR` as YAML, apply on save. |
|
||
|
|
| `mcpctl patch <resource> <name> key=val...` | Patch individual fields without editor. |
|
||
|
|
| `mcpctl delete <resource> <name>` | Delete resource. |
|
||
|
|
| `mcpctl apply -f <file>` | Declarative YAML/JSON application (like `kubectl apply`). Supports `--dry-run`. |
|
||
|
|
|
||
|
|
### Supported Resources
|
||
|
|
|
||
|
|
servers, projects, instances, secrets, templates, users, groups, rbac, prompts, promptrequests, serverattachments (virtual), proxymodels (virtual, from mcplocal), all (project export)
|
||
|
|
|
||
|
|
### Resource Aliases
|
||
|
|
```
|
||
|
|
server/srv → servers project/proj → projects
|
||
|
|
instance/inst → instances secret/sec → secrets
|
||
|
|
template/tpl → templates prompt → prompts
|
||
|
|
user → users group → groups
|
||
|
|
rbac/rbac-definition → rbac promptrequest/pr → promptrequests
|
||
|
|
serverattachment/sa → serverattachments proxymodel/pm → proxymodels
|
||
|
|
```
|
||
|
|
|
||
|
|
### Lifecycle & Diagnostics
|
||
|
|
|
||
|
|
| Command | Description |
|
||
|
|
|---------|-------------|
|
||
|
|
| `mcpctl status` | Show connectivity, auth status, LLM provider health, available models. |
|
||
|
|
| `mcpctl login` | Authenticate with mcpd (first login bootstraps initial user). |
|
||
|
|
| `mcpctl logout` | Clear stored credentials. |
|
||
|
|
| `mcpctl logs <name> [-t N] [-i index]` | Stream container logs. Resolves server name → running instance. |
|
||
|
|
| `mcpctl cache stats` | Show pipeline cache statistics per namespace. |
|
||
|
|
| `mcpctl cache clear [ns] [--older-than N]` | Clear pipeline cache. |
|
||
|
|
| `mcpctl backup` | Show git backup status, public SSH key. |
|
||
|
|
| `mcpctl backup log [-n N]` | Show backup commit history. |
|
||
|
|
| `mcpctl backup restore list/diff/to` | Restore to specific backup commit. |
|
||
|
|
|
||
|
|
### Console & Inspection
|
||
|
|
|
||
|
|
| Command | Description |
|
||
|
|
|---------|-------------|
|
||
|
|
| `mcpctl console [project]` | Interactive TUI — request/response timeline, tool inspection. |
|
||
|
|
| `mcpctl console --stdin-mcp` | MCP server mode over stdin/stdout (for Claude integration). |
|
||
|
|
| `mcpctl console --audit` | Browse audit events from mcpd interactively. |
|
||
|
|
|
||
|
|
### Configuration
|
||
|
|
|
||
|
|
| Command | Description |
|
||
|
|
|---------|-------------|
|
||
|
|
| `mcpctl config view` | Show current configuration. |
|
||
|
|
| `mcpctl config set <key> <value>` | Set config value (mcplocalUrl, mcpdUrl, registries, outputFormat, etc.). |
|
||
|
|
| `mcpctl config path` | Show config file path. |
|
||
|
|
| `mcpctl config setup` | Interactive configuration wizard. |
|
||
|
|
| `mcpctl config claude -p <project>` | Generate `.mcp.json` for Claude Code. |
|
||
|
|
|
||
|
|
### Create Subcommands
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mcpctl create server <name> [--package-name X] [--docker-image X] [--transport STDIO|SSE|STREAMABLE_HTTP]
|
||
|
|
[--runtime node|python] [--replicas N] [--env KEY=val] [--from-template name:version]
|
||
|
|
|
||
|
|
mcpctl create secret <name> [--data key=val ...] [--data-file path.json]
|
||
|
|
|
||
|
|
mcpctl create project <name> [-d desc] [--proxy-model default|gate|content-pipeline] [--server name ...]
|
||
|
|
|
||
|
|
mcpctl create user <email> [--password pass] [--name name]
|
||
|
|
|
||
|
|
mcpctl create group <name> [-d desc] [--member email ...]
|
||
|
|
|
||
|
|
mcpctl create rbac <name> [--subject kind:name] [--role-binding role:resource[:name]]
|
||
|
|
|
||
|
|
mcpctl create prompt <name> [--content text] [--project name] [--priority 1-10] [--link url]
|
||
|
|
```
|
||
|
|
|
||
|
|
### Apply File Format
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
secrets:
|
||
|
|
- name: my-secret
|
||
|
|
data:
|
||
|
|
KEY: value
|
||
|
|
|
||
|
|
servers:
|
||
|
|
- name: my-server
|
||
|
|
transport: STDIO
|
||
|
|
packageName: "@modelcontextprotocol/server-example"
|
||
|
|
env:
|
||
|
|
- name: API_KEY
|
||
|
|
valueFrom:
|
||
|
|
secretRef:
|
||
|
|
name: my-secret
|
||
|
|
key: KEY
|
||
|
|
|
||
|
|
projects:
|
||
|
|
- name: my-project
|
||
|
|
proxyModel: default
|
||
|
|
servers:
|
||
|
|
- my-server
|
||
|
|
|
||
|
|
serverattachments:
|
||
|
|
- server: my-server
|
||
|
|
project: my-project
|
||
|
|
|
||
|
|
prompts:
|
||
|
|
- name: my-prompt
|
||
|
|
project: my-project
|
||
|
|
content: "Instruction text..."
|
||
|
|
priority: 5
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 5. API Surface (mcpd)
|
||
|
|
|
||
|
|
All endpoints under `/api/v1/` require Bearer token auth except `/auth/*` and `/health*`.
|
||
|
|
|
||
|
|
### Authentication
|
||
|
|
| Endpoint | Method | Description |
|
||
|
|
|----------|--------|-------------|
|
||
|
|
| `/auth/bootstrap` | POST | First-user setup (creates admin + bootstrap RBAC) |
|
||
|
|
| `/auth/status` | GET | `{hasUsers: boolean}` (unauthenticated) |
|
||
|
|
| `/auth/login` | POST | Returns token + user info |
|
||
|
|
| `/auth/logout` | POST | Invalidate session |
|
||
|
|
| `/auth/me` | GET | Current user identity |
|
||
|
|
| `/auth/impersonate` | POST | Create session for another user (requires `run:impersonate`) |
|
||
|
|
|
||
|
|
### Servers
|
||
|
|
| Endpoint | Method | Description |
|
||
|
|
|----------|--------|-------------|
|
||
|
|
| `/servers` | GET | List all servers |
|
||
|
|
| `/servers/:id` | GET | Get server by CUID |
|
||
|
|
| `/servers` | POST | Create server (validates name uniqueness, image/package) |
|
||
|
|
| `/servers/:id` | PUT | Update server, re-reconciles replicas |
|
||
|
|
| `/servers/:id` | DELETE | Delete server + cascade-delete all instances |
|
||
|
|
|
||
|
|
### Instances
|
||
|
|
| Endpoint | Method | Description |
|
||
|
|
|----------|--------|-------------|
|
||
|
|
| `/instances` | GET | List (optional `?serverId=` filter) |
|
||
|
|
| `/instances/:id` | GET | Get instance |
|
||
|
|
| `/instances/:id` | DELETE | Delete instance, triggers reconciliation |
|
||
|
|
| `/instances/:id/inspect` | GET | Docker inspect output (state, port, IP) |
|
||
|
|
| `/instances/:id/logs` | GET | Container logs (`?tail=N`) |
|
||
|
|
|
||
|
|
### Projects
|
||
|
|
| Endpoint | Method | Description |
|
||
|
|
|----------|--------|-------------|
|
||
|
|
| `/projects` | GET | List (RBAC-filtered) |
|
||
|
|
| `/projects/:id` | GET/POST/PUT/DELETE | CRUD by CUID or name |
|
||
|
|
| `/projects/:id/mcp-config` | GET | Generate `.mcp.json` |
|
||
|
|
| `/projects/:id/instructions` | GET | Get prompt + attached servers for system message |
|
||
|
|
| `/projects/:id/servers` | GET/POST | List/attach servers |
|
||
|
|
| `/projects/:id/servers/:name` | DELETE | Detach server |
|
||
|
|
|
||
|
|
### Prompts & Prompt Requests
|
||
|
|
| Endpoint | Method | Description |
|
||
|
|
|----------|--------|-------------|
|
||
|
|
| `/prompts` | GET/POST | List/create approved prompts |
|
||
|
|
| `/prompts/:id` | PUT/DELETE | Update/delete (system prompts reset to default) |
|
||
|
|
| `/prompts/:id/regenerate-summary` | POST | Force re-generate summary/chapters |
|
||
|
|
| `/promptrequests` | GET/POST | List/create pending requests |
|
||
|
|
| `/promptrequests/:id/approve` | POST | Atomic delete request → create prompt |
|
||
|
|
| `/projects/:name/prompts/visible` | GET | Approved + session's pending |
|
||
|
|
| `/projects/:name/prompt-index` | GET | Compact index for gating |
|
||
|
|
|
||
|
|
### Secrets, Users, Groups, RBAC
|
||
|
|
Standard CRUD on `/secrets`, `/users`, `/groups`, `/rbac-definitions`.
|
||
|
|
|
||
|
|
### Health & Monitoring
|
||
|
|
| Endpoint | Method | Description |
|
||
|
|
|----------|--------|-------------|
|
||
|
|
| `/health/overview` | GET | System health, instance counts, error rate |
|
||
|
|
| `/health/instances/:id` | GET | Instance-specific health, uptime, latency |
|
||
|
|
| `/metrics` | GET | Request counts, error counts, last request time |
|
||
|
|
| `/healthz` | GET | Liveness probe |
|
||
|
|
|
||
|
|
### Backup, Restore, Audit
|
||
|
|
| Endpoint | Method | Description |
|
||
|
|
|----------|--------|-------------|
|
||
|
|
| `/backup` | POST | Create encrypted bundle (servers, secrets, projects, users, groups, rbac) |
|
||
|
|
| `/restore` | POST | Restore bundle (merge/skip/overwrite strategy) |
|
||
|
|
| `/audit/events` | POST/GET | Batch insert from mcplocal / query with filters |
|
||
|
|
| `/audit/sessions` | GET | Session aggregates (first/last seen, event counts) |
|
||
|
|
| `/git/backup/init` | POST | Initialize git backup with SSH credentials |
|
||
|
|
| `/git/backup/status` | GET | Backup sync status |
|
||
|
|
| `/git/backup/sync` | POST | Manual trigger sync |
|
||
|
|
|
||
|
|
### MCP Proxy
|
||
|
|
| Endpoint | Method | Description |
|
||
|
|
|----------|--------|-------------|
|
||
|
|
| `/mcp/proxy` | POST | Forward JSON-RPC to running MCP server instance. Dispatches by transport (STDIO via docker exec, SSE/HTTP via direct HTTP). Maintains persistent STDIO connections. |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 6. Database Schema
|
||
|
|
|
||
|
|
PostgreSQL via Prisma ORM. 22 models across 11 migrations.
|
||
|
|
|
||
|
|
### Core Models
|
||
|
|
|
||
|
|
**User & Auth:**
|
||
|
|
- `User` — email/password (bcrypt), role (USER/ADMIN), optional OAuth
|
||
|
|
- `Session` — Bearer token with 30-day TTL
|
||
|
|
- `Group` / `GroupMember` — user groups for RBAC
|
||
|
|
|
||
|
|
**MCP Infrastructure:**
|
||
|
|
- `McpServer` — transport (STDIO/SSE/STREAMABLE_HTTP), docker image, package name, runtime (node/python), env vars (JSON), health check config, replicas, external URL
|
||
|
|
- `McpTemplate` — reusable blueprints for server creation (mirrors McpServer fields)
|
||
|
|
- `McpInstance` — running containers, status (STARTING/RUNNING/STOPPING/STOPPED/ERROR), container ID, port, health status, events
|
||
|
|
|
||
|
|
**Organization:**
|
||
|
|
- `Project` — LLM config (provider, model), proxy model, gated flag, prompt instructions, server overrides
|
||
|
|
- `ProjectServer` — junction table linking projects to servers
|
||
|
|
- `Secret` — named secret bundles (data as encrypted JSON), versioned
|
||
|
|
|
||
|
|
**Content:**
|
||
|
|
- `Prompt` — approved system prompts (global or project-scoped), priority, summary/chapters, optional link target
|
||
|
|
- `PromptRequest` — pending prompt proposals from LLM sessions
|
||
|
|
|
||
|
|
**Audit & Backup:**
|
||
|
|
- `AuditLog` — user action trail (action, resource, resourceId, details)
|
||
|
|
- `AuditEvent` — pipeline/gate/tool trace events from mcplocal (sessionId, projectName, eventKind, correlationId, userName)
|
||
|
|
- `BackupPending` — queue for git-based backup sync
|
||
|
|
- `RbacDefinition` — named RBAC policies
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 7. Local Proxy (mcplocal)
|
||
|
|
|
||
|
|
### Request Flow
|
||
|
|
|
||
|
|
```
|
||
|
|
Claude (STDIO JSON-RPC)
|
||
|
|
↓
|
||
|
|
StdioProxyServer (reads from stdin)
|
||
|
|
↓
|
||
|
|
McpRouter.route(request)
|
||
|
|
├→ PluginSessionContext (per-session state)
|
||
|
|
├→ ProxyModelPlugin hooks (intercept/transform)
|
||
|
|
├→ Upstream lookup (tool name prefix → server)
|
||
|
|
└→ Response (with optional drill-down sections)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Router Responsibilities
|
||
|
|
|
||
|
|
- Manages upstream connections (STDIO child processes, HTTP)
|
||
|
|
- Maps tools/resources/prompts to servers via name prefix (`servername/toolname`)
|
||
|
|
- Maintains prompt index + system prompt cache (TTL-based)
|
||
|
|
- Dispatches plugin hooks via `getOrCreatePluginContext`
|
||
|
|
- Section storage for drill-down navigation
|
||
|
|
- Audit event collection and batching
|
||
|
|
- Link resolution (relative → absolute URLs)
|
||
|
|
|
||
|
|
### Upstream Transports
|
||
|
|
|
||
|
|
| Transport | Implementation |
|
||
|
|
|-----------|---------------|
|
||
|
|
| STDIO | Spawns child process, bidirectional pipe, JSON-RPC over newline-delimited JSON |
|
||
|
|
| SSE | HTTP GET for event stream, POST for messages |
|
||
|
|
| Streamable HTTP | HTTP POST with JSON-RPC payloads |
|
||
|
|
|
||
|
|
### HTTP Endpoints (mcplocal)
|
||
|
|
|
||
|
|
| Endpoint | Description |
|
||
|
|
|----------|-------------|
|
||
|
|
| `GET /proxymodels` | List all models (YAML pipelines + TS plugins) |
|
||
|
|
| `GET /proxymodels/:name` | Single model details |
|
||
|
|
| `GET /proxymodels/stages` | List available stages |
|
||
|
|
| `POST /proxymodels/reload` | Force reload stages from disk |
|
||
|
|
| `GET /cache/stats` | Per-namespace cache statistics |
|
||
|
|
| `DELETE /cache` | Clear all or by age |
|
||
|
|
| `DELETE /cache/:namespace` | Clear specific namespace |
|
||
|
|
| `POST /mcp` | JSON-RPC request forwarding |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 8. ProxyModel Plugin System
|
||
|
|
|
||
|
|
A **ProxyModel** is either a **Pipeline** (YAML) or a **Plugin** (TypeScript).
|
||
|
|
|
||
|
|
### Plugin Interface
|
||
|
|
|
||
|
|
| Hook | When it fires |
|
||
|
|
|------|--------------|
|
||
|
|
| `onSessionCreate` | New MCP session established |
|
||
|
|
| `onSessionDestroy` | Session ends |
|
||
|
|
| `onInitialize` | MCP initialize request — can inject instructions |
|
||
|
|
| `onToolsList` | tools/list — can filter/modify tool list |
|
||
|
|
| `onToolCallBefore` | Before forwarding a tool call — can intercept |
|
||
|
|
| `onToolCallAfter` | After receiving tool result — can transform |
|
||
|
|
| `onResourcesList` | resources/list — can filter resources |
|
||
|
|
| `onResourceRead` | resources/read — can intercept reads |
|
||
|
|
| `onPromptsList` | prompts/list — can filter prompts |
|
||
|
|
| `onPromptGet` | prompts/get — can intercept reads |
|
||
|
|
|
||
|
|
### Built-in Plugins
|
||
|
|
|
||
|
|
| Plugin | Extends | Gating | Content Pipeline | Use Case |
|
||
|
|
|--------|---------|:------:|:----------------:|----------|
|
||
|
|
| `gate` | — | Yes | No | Gating + prompt delivery only |
|
||
|
|
| `content-pipeline` | — | No | Yes | Content transformation only |
|
||
|
|
| `default` | gate + content-pipeline | Yes | Yes | Full pipeline (most common) |
|
||
|
|
|
||
|
|
**Inheritance:** Plugins can extend parents. Conflicting hooks from multiple parents cause load-time errors (except chainable lifecycle hooks which run sequentially).
|
||
|
|
|
||
|
|
### Pipeline Configuration (YAML)
|
||
|
|
|
||
|
|
```yaml
|
||
|
|
name: default
|
||
|
|
spec:
|
||
|
|
controller: gate
|
||
|
|
controllerConfig: { byteBudget: 8192 }
|
||
|
|
stages:
|
||
|
|
- type: passthrough
|
||
|
|
- type: paginate
|
||
|
|
config: { pageSize: 8000 }
|
||
|
|
appliesTo: [prompt, toolResult]
|
||
|
|
cacheable: true
|
||
|
|
```
|
||
|
|
|
||
|
|
### Per-Session Context
|
||
|
|
|
||
|
|
Each session gets a `PluginSessionContext` providing:
|
||
|
|
- Session state (`Map<string, unknown>`)
|
||
|
|
- LLM provider, cache provider, structured logger
|
||
|
|
- Virtual tool/server registration
|
||
|
|
- Upstream routing and tool discovery
|
||
|
|
- Content processing and notifications
|
||
|
|
- Audit event emission
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 9. Gated Sessions
|
||
|
|
|
||
|
|
### Problem
|
||
|
|
|
||
|
|
When Claude connects to an MCP server, it sees all tools immediately and starts using them. In a managed environment, you want to deliver relevant context (prompts/instructions) before granting tool access.
|
||
|
|
|
||
|
|
### Solution: Keyword-Driven Prompt Retrieval
|
||
|
|
|
||
|
|
1. **Initialize:** Instructions include prompt index + "call `begin_session` immediately"
|
||
|
|
2. **Gated `tools/list`:** Only `begin_session` visible
|
||
|
|
3. **Claude calls `begin_session`** with keywords describing the task
|
||
|
|
4. **Prompt matching:** Keywords matched against prompt summaries/chapters
|
||
|
|
5. **Ungating:** Matched prompts returned + `tools/list_changed` notification sent
|
||
|
|
6. **Full access:** All upstream tools now visible
|
||
|
|
|
||
|
|
### Prompt Scoring
|
||
|
|
|
||
|
|
**Formula:** `priority + (matchCount * priority)`
|
||
|
|
|
||
|
|
- Priority alone is baseline — ensures global prompts compete for inclusion
|
||
|
|
- Tag matches multiply priority — relevant prompts score higher
|
||
|
|
- Priority 10 = always included (bypasses budget)
|
||
|
|
- 8KB byte budget cap; overflow prompts listed as index-only
|
||
|
|
|
||
|
|
### Critical Design Lessons
|
||
|
|
|
||
|
|
**What works:**
|
||
|
|
- One gate tool (`begin_session`), zero ambiguity
|
||
|
|
- Instructions say "check its input schema" (not naming specific parameters)
|
||
|
|
- "immediately" and "required" prevent Claude from exploring first
|
||
|
|
- Tool names listed as preview in instructions (helps keyword generation)
|
||
|
|
- `tools/list_changed` notification mandatory after ungating
|
||
|
|
- Auto-ungate fallback if Claude bypasses gate
|
||
|
|
|
||
|
|
**What fails:**
|
||
|
|
- Naming parameters that don't match the schema
|
||
|
|
- Complex conditional instructions (Claude prefers simple paths)
|
||
|
|
- Multiple tools in gated state (Claude skips the gate)
|
||
|
|
- Gate instructions only in tool description (must be in initialize response)
|
||
|
|
- Burying the call-to-action after 200 lines of context
|
||
|
|
|
||
|
|
### Complete Flow
|
||
|
|
|
||
|
|
```
|
||
|
|
Client mcplocal upstream
|
||
|
|
│── initialize ────────>│
|
||
|
|
│<── instructions ──────│ (gate instructions + prompt index + tool preview)
|
||
|
|
│── tools/list ────────>│
|
||
|
|
│<── [begin_session] ───│ (ONLY begin_session visible)
|
||
|
|
│── tools/call ────────>│
|
||
|
|
│ begin_session │── match prompts ─────────>│
|
||
|
|
│ {tags:[...]} │<── prompt content ────────│
|
||
|
|
│<── matched prompts ───│ (full content + encouragement)
|
||
|
|
│<── notification ──────│ (tools/list_changed)
|
||
|
|
│── tools/list ────────>│
|
||
|
|
│<── [108+ tools] ──────│ (ALL tools now visible)
|
||
|
|
│ │
|
||
|
|
│ Claude proceeds with full tool access
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 10. Content Pipeline & Stages
|
||
|
|
|
||
|
|
### How It Works
|
||
|
|
|
||
|
|
Tool results pass through an ordered sequence of stages before reaching Claude:
|
||
|
|
|
||
|
|
1. Each stage receives previous stage's content
|
||
|
|
2. Returns `{content, sections?, metadata?}`
|
||
|
|
3. Sections enable drill-down navigation
|
||
|
|
4. Stage errors are caught — pipeline continues with previous content
|
||
|
|
|
||
|
|
### Built-in Stages
|
||
|
|
|
||
|
|
| Stage | Purpose |
|
||
|
|
|-------|---------|
|
||
|
|
| `passthrough` | Identity transform (testing/baseline) |
|
||
|
|
| `paginate` | Split large content into numbered pages (8KB default). LLM-generated page titles (cached). |
|
||
|
|
| `section-split` | Split by structure: JSON arrays/objects → elements/keys, YAML → keys, prose → `##` headers, code → function/class boundaries. Merges tiny sections, re-splits oversized ones. |
|
||
|
|
| `summarize-tree` | Hierarchical LLM-generated section summaries. Groups sections into trees. Cached. |
|
||
|
|
|
||
|
|
### Section Drill-Down
|
||
|
|
|
||
|
|
After pipeline produces sections:
|
||
|
|
1. Full content replaced with compact table of contents + `_resultId`
|
||
|
|
2. Sections stored in session-scoped store (5-minute TTL)
|
||
|
|
3. Client calls same tool with `_resultId` + `_section` to retrieve specific section
|
||
|
|
4. Supports hierarchical navigation (sections within sections)
|
||
|
|
|
||
|
|
### Custom Stages
|
||
|
|
|
||
|
|
Drop `.js` files in `~/.mcpctl/stages/`:
|
||
|
|
```javascript
|
||
|
|
export default async function myStage(input, context) {
|
||
|
|
// context.llm, context.cache, context.log available
|
||
|
|
return { content: transformedContent, sections: [...] };
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Hot-reload with 300ms file watch debounce. Built-in stages take precedence.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 11. LLM Provider Integration
|
||
|
|
|
||
|
|
### Supported Providers
|
||
|
|
|
||
|
|
| Provider | Type | Tier |
|
||
|
|
|----------|------|------|
|
||
|
|
| Gemini CLI | Local | Fast |
|
||
|
|
| Ollama | Local | Fast |
|
||
|
|
| DeepSeek | API | Fast/Heavy |
|
||
|
|
| OpenAI | API | Heavy |
|
||
|
|
| Anthropic | API | Heavy |
|
||
|
|
| vLLM | Local | Configurable |
|
||
|
|
| vLLM Managed | Auto-managed local | Configurable |
|
||
|
|
|
||
|
|
### Tier System
|
||
|
|
|
||
|
|
- **Fast tier:** Quick, cheap models for pipeline stages and keyword extraction
|
||
|
|
- **Heavy tier:** Full models for complex prompt selection and summarization
|
||
|
|
- **Legacy active:** Single default provider (fallback)
|
||
|
|
|
||
|
|
### LLM Adapter
|
||
|
|
|
||
|
|
Stages use a simple interface:
|
||
|
|
```typescript
|
||
|
|
interface LLMProvider {
|
||
|
|
complete(prompt: string, options?): Promise<string>;
|
||
|
|
available(): boolean;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Resolution order: named provider → fast tier → heavy tier → active provider.
|
||
|
|
|
||
|
|
### Multi-Provider Configuration
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"llm": {
|
||
|
|
"providers": [
|
||
|
|
{ "name": "fast-local", "type": "ollama", "model": "llama3", "tier": "fast" },
|
||
|
|
{ "name": "heavy-api", "type": "openai", "model": "gpt-4", "tier": "heavy" }
|
||
|
|
]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 12. Caching
|
||
|
|
|
||
|
|
### Architecture: L1 Memory + L2 Disk
|
||
|
|
|
||
|
|
- **L1 in-memory:** LRU map (default 500 entries) for fast lookups
|
||
|
|
- **L2 disk:** `~/.mcpctl/cache/<namespace>/<key>.dat`
|
||
|
|
- Namespace: `provider--model--proxymodel` (e.g., `openai--gpt-4o--content-pipeline`)
|
||
|
|
- Key: 16-char hex SHA256 prefix of content
|
||
|
|
- Value: raw content (no JSON wrapper)
|
||
|
|
|
||
|
|
### Configuration
|
||
|
|
|
||
|
|
| Option | Default | Examples |
|
||
|
|
|--------|---------|---------|
|
||
|
|
| `maxSize` | 256MB | `"1GB"`, `"10%"` (of partition), `536870912` (bytes) |
|
||
|
|
| `ttlMs` | 30 days | Any millisecond value |
|
||
|
|
| `maxMemoryEntries` | 500 | L1 LRU cap |
|
||
|
|
| `dir` | `~/.mcpctl/cache` | Custom path |
|
||
|
|
|
||
|
|
### Management
|
||
|
|
|
||
|
|
```bash
|
||
|
|
mcpctl cache stats # Per-namespace breakdown
|
||
|
|
mcpctl cache clear # Clear everything
|
||
|
|
mcpctl cache clear openai--gpt-4--default # Clear specific namespace
|
||
|
|
mcpctl cache clear --older-than 7 # Clear entries older than 7 days
|
||
|
|
```
|
||
|
|
|
||
|
|
### HTTP API
|
||
|
|
```
|
||
|
|
GET /cache/stats # Per-namespace stats
|
||
|
|
DELETE /cache # Clear all (or ?olderThan=N)
|
||
|
|
DELETE /cache/:namespace # Clear specific namespace
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 13. Authentication & RBAC
|
||
|
|
|
||
|
|
### Auth Flow
|
||
|
|
|
||
|
|
1. `mcpctl login` → prompts for email/password
|
||
|
|
2. First login (no users in system) → bootstrap: creates admin user + admin group + bootstrap RBAC
|
||
|
|
3. POST `/auth/login` → returns 30-day bearer token
|
||
|
|
4. Token stored in `~/.mcpctl/credentials.json`
|
||
|
|
5. CLI passes token to mcplocal config
|
||
|
|
6. mcplocal attaches `Authorization: Bearer <token>` to all mcpd requests
|
||
|
|
7. mcpd validates token against Session table
|
||
|
|
|
||
|
|
### RBAC Model
|
||
|
|
|
||
|
|
**Subjects:** User (by email), Group, ServiceAccount
|
||
|
|
|
||
|
|
**Roles & Capabilities:**
|
||
|
|
|
||
|
|
| Role | Grants |
|
||
|
|
|------|--------|
|
||
|
|
| `edit` | view, create, delete, edit, expose |
|
||
|
|
| `view` | view |
|
||
|
|
| `create` | create |
|
||
|
|
| `delete` | delete |
|
||
|
|
| `run` | run (for operations) |
|
||
|
|
| `expose` | expose, view |
|
||
|
|
|
||
|
|
**Resources:** `*`, servers, instances, secrets, projects, templates, users, groups, rbac, prompts, promptrequests
|
||
|
|
|
||
|
|
**Binding Types:**
|
||
|
|
- **Resource binding:** `{role: 'edit', resource: 'servers', name?: 'my-server'}`
|
||
|
|
- With `name`: user can only access that specific resource
|
||
|
|
- Without `name`: user can access all resources of that type
|
||
|
|
- **Operation binding:** `{role: 'run', action: 'impersonate'}`
|
||
|
|
- Grants permission for named operations (backup, restore, audit-purge, logs, impersonate)
|
||
|
|
|
||
|
|
### Resolution
|
||
|
|
|
||
|
|
1. CLI resolves name → CUID client-side before API calls
|
||
|
|
2. RBAC hook resolves CUID → name before checking bindings
|
||
|
|
3. List filtering: `getAllowedScope()` computes allowed names, `preSerialization` hook filters arrays
|
||
|
|
4. Wildcard scope: user has unscoped binding → sees all resources
|
||
|
|
5. Named scope: user has only name-scoped bindings → filtered to allowed names
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 14. Audit Infrastructure & Trust Model
|
||
|
|
|
||
|
|
### Event Kinds
|
||
|
|
|
||
|
|
| Event Kind | Description |
|
||
|
|
|------------|-------------|
|
||
|
|
| `pipeline_execution` | Full pipeline run summary (duration, stage count, sizes) |
|
||
|
|
| `stage_execution` | Individual stage detail (duration, input/output size, error) |
|
||
|
|
| `gate_decision` | Gate open/close with client intent and matched prompts |
|
||
|
|
| `prompt_delivery` | Which prompts were sent, match scores |
|
||
|
|
| `tool_call_trace` | Tool call with server + timing + result size |
|
||
|
|
| `rbac_decision` | Access control decisions |
|
||
|
|
| `session_bind` | Session initialization |
|
||
|
|
|
||
|
|
### Trust Model
|
||
|
|
|
||
|
|
| Source | Verified | Meaning |
|
||
|
|
|--------|----------|---------|
|
||
|
|
| `client` | false | Client LLM claims (begin_session intent, tags) |
|
||
|
|
| `mcplocal` | true | Server-side data (prompt matches, pipeline transforms) |
|
||
|
|
| `mcpd` | true | mcpd-originated events |
|
||
|
|
|
||
|
|
### AuditCollector
|
||
|
|
|
||
|
|
Fire-and-forget batching: 50 events max, 5-second flush interval. POSTs to mcpd. Non-blocking — audit failures don't affect tool calls.
|
||
|
|
|
||
|
|
### Correlation & Causality
|
||
|
|
|
||
|
|
- `correlationId` links related events (all events from one tool call)
|
||
|
|
- `parentEventId` enables causal chains (gate_decision → pipeline_execution)
|
||
|
|
- `userName` tracks which user triggered the event
|
||
|
|
- Designed for future graphiti knowledge graph ingestion
|
||
|
|
|
||
|
|
### Per-Server Targeting
|
||
|
|
|
||
|
|
Different servers in a project can have different proxymodel configs via `serverOverrides` on the project resource. Resolution: server override → project default → null.
|
||
|
|
|
||
|
|
### Future (Designed, Not Implemented)
|
||
|
|
|
||
|
|
- **Virtual MCP Audit Server:** mcpd-hosted virtual server providing `query_audit_log`, `get_session_timeline` tools. Claude can directly query audit data.
|
||
|
|
- **Graphiti Integration:** Causal graph with entity types (Session, Tool, Server, ProxyModel, Prompt, Stage) and edges (`triggered_by`, `transformed_by`, `verified_by`).
|
||
|
|
- **Lab Parameter Simulation:** Select any pipeline event, retrieve original input, re-run with different proxyModel/LLM/stages, side-by-side diff.
|
||
|
|
- **Audit Level Config:** Per-server `auditLevel: 'full' | 'hash-only' | 'disabled'`.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 15. Container Orchestration
|
||
|
|
|
||
|
|
### Orchestrator Interface
|
||
|
|
|
||
|
|
`McpOrchestrator` abstracts container management (Docker/Podman today, Kubernetes in the future):
|
||
|
|
- `pullImage`, `createContainer`, `stopContainer`, `removeContainer`
|
||
|
|
- `inspectContainer`, `getContainerLogs`, `execInContainer`, `ping`
|
||
|
|
|
||
|
|
### Container Management
|
||
|
|
|
||
|
|
- Labels: `mcpctl.managed=true` for filtering
|
||
|
|
- Network: `mcp-servers` (configurable via `MCPD_MCP_NETWORK`)
|
||
|
|
- Resource limits: 512MB RAM, 0.5 CPU (configurable)
|
||
|
|
- Internal container IP exposed via inspect
|
||
|
|
|
||
|
|
### Runtime Spawn Commands
|
||
|
|
|
||
|
|
| Runtime | Command |
|
||
|
|
|---------|---------|
|
||
|
|
| Node | `npx --prefer-offline -y <packageName>` |
|
||
|
|
| Python | `uvx <packageName>` |
|
||
|
|
| Custom | Explicit `command` field |
|
||
|
|
|
||
|
|
### Health Probes
|
||
|
|
|
||
|
|
Periodic MCP tool-call probes (like K8s livenessProbe):
|
||
|
|
- Default interval: 15 seconds
|
||
|
|
- Dispatch by transport: STDIO (docker exec), HTTP (JSON-RPC)
|
||
|
|
- Failure threshold: 3 consecutive failures → unhealthy
|
||
|
|
- Updates instance `healthStatus` and `lastHealthCheck`
|
||
|
|
|
||
|
|
### Reconciliation Loop
|
||
|
|
|
||
|
|
Maintains desired replica count:
|
||
|
|
- If running < desired → start new instances
|
||
|
|
- If running > desired → stop excess instances
|
||
|
|
- Detects crashed containers → marks ERROR → triggers re-creation
|
||
|
|
|
||
|
|
### Persistent STDIO Connections
|
||
|
|
|
||
|
|
For STDIO transport, mcpd maintains long-lived exec sessions (`PersistentStdioClient`) to avoid repeated `docker exec` overhead. Bidirectional streaming for interactive sessions.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 16. Deployment & Distribution
|
||
|
|
|
||
|
|
### Production Deployment
|
||
|
|
|
||
|
|
**mcpd runs on 10.0.0.194** (NAS, managed via Portainer), NOT on the dev machine.
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Full deploy (preferred after merging)
|
||
|
|
bash fulldeploy.sh
|
||
|
|
```
|
||
|
|
|
||
|
|
`fulldeploy.sh` runs three steps:
|
||
|
|
1. `scripts/build-mcpd.sh` — build + push Docker image to `mysources.co.uk/michal/mcpctl-mcpd`
|
||
|
|
2. `deploy.sh` — deploy stack to production via Portainer API at `http://10.0.0.194:9000`
|
||
|
|
3. `scripts/release.sh` — build RPM + publish to Gitea + install locally + smoke tests
|
||
|
|
|
||
|
|
### Docker Images
|
||
|
|
|
||
|
|
| Image | Purpose |
|
||
|
|
|-------|---------|
|
||
|
|
| `mcpctl-mcpd` | Multi-stage build: Node 20 Alpine, includes git/ssh, Prisma |
|
||
|
|
| `mcpctl-node-runner` | Node 20 slim, runs `npx -y` for npm packages |
|
||
|
|
| `mcpctl-python-runner` | Python 3.12 slim, uses `uv` for Python packages |
|
||
|
|
|
||
|
|
All pushed to `mysources.co.uk/michal/` registry.
|
||
|
|
|
||
|
|
### Stack Services (Production)
|
||
|
|
|
||
|
|
- `postgres` — PostgreSQL 16 (port 5432)
|
||
|
|
- `mcpd` — Daemon (port 3100)
|
||
|
|
- `node-runner`, `python-runner` — Base images
|
||
|
|
- Networks: `mcpctl` (management), `mcp-servers` (container communication)
|
||
|
|
|
||
|
|
### RPM/DEB Distribution
|
||
|
|
|
||
|
|
```bash
|
||
|
|
source .env && bash scripts/release.sh
|
||
|
|
```
|
||
|
|
|
||
|
|
Installs via nfpm:
|
||
|
|
- `/usr/bin/mcpctl` — CLI binary (bun compiled)
|
||
|
|
- `/usr/bin/mcpctl-local` — Local proxy binary (bun compiled)
|
||
|
|
- `/usr/share/fish/vendor_completions.d/mcpctl.fish` — Fish completions
|
||
|
|
- `/usr/share/bash-completion/completions/mcpctl` — Bash completions
|
||
|
|
- `/usr/lib/systemd/user/mcplocal.service` — Systemd user service
|
||
|
|
|
||
|
|
User install:
|
||
|
|
```bash
|
||
|
|
dnf config-manager --add-repo https://mysources.co.uk/api/packages/michal/rpm.repo
|
||
|
|
dnf install mcpctl
|
||
|
|
```
|
||
|
|
|
||
|
|
### Git & PR Workflow
|
||
|
|
|
||
|
|
- Gitea at `http://10.0.0.194:3012` (internal) / `https://mysources.co.uk/michal/mcpctl` (public)
|
||
|
|
- `pr.sh` in project root creates PRs via Gitea API
|
||
|
|
- `gh` CLI not installed — use `pr.sh` or direct API calls
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 17. Testing Strategy
|
||
|
|
|
||
|
|
### Test Tiers
|
||
|
|
|
||
|
|
| Tier | Tool | Scope | When |
|
||
|
|
|------|------|-------|------|
|
||
|
|
| Unit tests | Vitest | Package-level, mocked dependencies | `pnpm test:run` |
|
||
|
|
| DB tests | Vitest | Full Prisma + test PostgreSQL | `pnpm --filter db exec vitest run` (separate) |
|
||
|
|
| Smoke tests | Vitest | Live mcplocal + mcpd (not mocked) | `pnpm test:smoke` (post-deploy) |
|
||
|
|
|
||
|
|
### Convention
|
||
|
|
|
||
|
|
- Every new feature MUST include smoke tests
|
||
|
|
- Smoke tests live in `src/mcplocal/tests/smoke/`
|
||
|
|
- Use `SmokeMcpSession` from `tests/smoke/mcp-client.ts` for MCP protocol interactions
|
||
|
|
- Smoke tests run automatically in the build/deploy pipeline
|
||
|
|
|
||
|
|
### Critical Rules
|
||
|
|
|
||
|
|
- **NEVER pipe pnpm test output** to `tail`, `grep`, `head` — pnpm hangs when it detects non-TTY
|
||
|
|
- Always capture full output with `2>&1` and read directly
|
||
|
|
- DB tests excluded from workspace-root vitest (need test database)
|
||
|
|
- Tests integrated into pipeline: `build-rpm.sh` runs unit tests; `release.sh` runs smoke tests
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 18. Technology Stack
|
||
|
|
|
||
|
|
| Layer | Technology |
|
||
|
|
|-------|-----------|
|
||
|
|
| CLI Framework | Commander.js, Ink/React (TUI), Inquirer |
|
||
|
|
| API Server | Fastify 5, TypeScript strict mode |
|
||
|
|
| Database | PostgreSQL 16, Prisma ORM v6 |
|
||
|
|
| Container Runtime | Docker/Podman via dockerode |
|
||
|
|
| MCP Protocol | @modelcontextprotocol/sdk |
|
||
|
|
| Validation | Zod schemas everywhere |
|
||
|
|
| LLM Providers | OpenAI, Anthropic, Google Gemini, Ollama, DeepSeek, Groq, Mistral, OpenRouter, Azure |
|
||
|
|
| Testing | Vitest, coverage via v8 |
|
||
|
|
| Build | TypeScript project references, pnpm workspaces |
|
||
|
|
| Compilation | Bun (binary compilation for RPM) |
|
||
|
|
| Packaging | nfpm (RPM/DEB), Docker multi-stage |
|
||
|
|
| CI/CD | fulldeploy.sh → Portainer API + Gitea packages |
|
||
|
|
| Shell Completions | Fish + Bash (auto-generated via `scripts/generate-completions.ts`) |
|
||
|
|
|
||
|
|
### Design Patterns
|
||
|
|
|
||
|
|
1. **Monorepo** — pnpm workspaces with shared base TypeScript config
|
||
|
|
2. **Layered architecture** — Routes → Services → Repositories (Prisma)
|
||
|
|
3. **Interface-based repositories** — all data access through interfaces for testability
|
||
|
|
4. **Dependency injection** — services receive dependencies via constructor
|
||
|
|
5. **Zod validation** — all input validated at API boundary
|
||
|
|
6. **Plugin inheritance** — composable ProxyModel plugins with conflict detection
|
||
|
|
7. **Content-addressed caching** — SHA256 hash keys for deduplication
|
||
|
|
8. **TTL-based stores** — prompt index (60s), system prompts (5min), sections (5min)
|
||
|
|
9. **Fire-and-forget audit** — non-blocking event collection
|
||
|
|
10. **Declarative config** — kubectl-style YAML/JSON for all resource management
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 19. Project Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
mcpctl/
|
||
|
|
├── src/
|
||
|
|
│ ├── cli/ @mcpctl/cli CLI (Commander.js)
|
||
|
|
│ │ ├── src/commands/ 22 command handlers
|
||
|
|
│ │ ├── src/registry/ MCP server registry client
|
||
|
|
│ │ ├── src/formatters/ Output formatting (table/json/yaml)
|
||
|
|
│ │ └── src/auth/ Credential storage
|
||
|
|
│ ├── mcpd/ @mcpctl/mcpd Daemon (Fastify 5)
|
||
|
|
│ │ ├── src/routes/ 18 route handlers
|
||
|
|
│ │ ├── src/services/ 13 services
|
||
|
|
│ │ ├── src/repositories/ Data access layer
|
||
|
|
│ │ ├── src/middleware/ Auth, logging, error handling
|
||
|
|
│ │ └── src/validation/ Zod schemas, RBAC rules
|
||
|
|
│ ├── mcplocal/ @mcpctl/mcplocal Local proxy
|
||
|
|
│ │ ├── src/gate/ Session gating + tag matching
|
||
|
|
│ │ ├── src/proxymodel/ Plugin system + stages + cache
|
||
|
|
│ │ ├── src/providers/ 6 LLM providers
|
||
|
|
│ │ ├── src/upstream/ STDIO + HTTP upstream connections
|
||
|
|
│ │ ├── src/audit/ Event collection + batching
|
||
|
|
│ │ └── src/health/ Health monitoring
|
||
|
|
│ ├── db/ @mcpctl/db Database (Prisma)
|
||
|
|
│ │ ├── prisma/schema.prisma 22 models
|
||
|
|
│ │ └── prisma/migrations/ 11 migrations
|
||
|
|
│ └── shared/ @mcpctl/shared Constants, types, validation
|
||
|
|
├── deploy/ Dockerfiles + entrypoint
|
||
|
|
├── stack/ Production docker-compose + env
|
||
|
|
├── scripts/ Build, release, deploy scripts
|
||
|
|
├── completions/ Fish + Bash completions
|
||
|
|
├── templates/ MCP server YAML templates
|
||
|
|
├── docs/ Architecture + design docs
|
||
|
|
├── fulldeploy.sh Full build → deploy → release
|
||
|
|
├── deploy.sh Portainer stack deploy
|
||
|
|
├── pr.sh Gitea PR creation
|
||
|
|
├── nfpm.yaml RPM/DEB package metadata
|
||
|
|
├── vitest.config.ts Root test config
|
||
|
|
├── vitest.workspace.ts Workspace test config
|
||
|
|
├── tsconfig.base.json Base TypeScript config (strict)
|
||
|
|
└── pnpm-workspace.yaml Monorepo workspace definition
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 20. Deferred & Future Work
|
||
|
|
|
||
|
|
### Deferred Tasks
|
||
|
|
|
||
|
|
| ID | Description | Status |
|
||
|
|
|----|-------------|--------|
|
||
|
|
| 88 | Rename proxyMode: filtered → proxy | Deferred |
|
||
|
|
| 105-109 | Model Studio TUI | Deferred |
|
||
|
|
| 110 | RBAC for ProxyModels | Deferred |
|
||
|
|
| 113 | Model Studio docs | Deferred |
|
||
|
|
|
||
|
|
### Future Architecture
|
||
|
|
|
||
|
|
- **Virtual MCP Audit Server** — Claude-queryable audit tools
|
||
|
|
- **Graphiti Knowledge Graph** — causal graph from audit events
|
||
|
|
- **Lab Parameter Simulation** — re-run pipelines with different configs
|
||
|
|
- **Kubernetes Orchestrator** — beyond Docker/Podman
|
||
|
|
- **ConfigMaps** — non-sensitive config separate from Secrets
|
||
|
|
- **Multi-provider failover** — automatic LLM provider cascading
|
||
|
|
|
||
|
|
### Completed Major Features
|
||
|
|
|
||
|
|
- Project structure + monorepo setup
|
||
|
|
- MCP Registry Client (official, glama, smithery — 53 tests)
|
||
|
|
- Health Probe Runner (STDIO, SSE, Streamable HTTP — 12 tests)
|
||
|
|
- Container orchestration with reconciliation
|
||
|
|
- Full RBAC with name-scoped bindings
|
||
|
|
- Gated sessions with prompt scoring
|
||
|
|
- ProxyModel plugin system with inheritance
|
||
|
|
- Content pipeline with 4 built-in stages
|
||
|
|
- Pipeline cache (L1 memory + L2 disk)
|
||
|
|
- Audit infrastructure with trust model
|
||
|
|
- Git-based backup and restore
|
||
|
|
- Shell completions (Fish + Bash)
|
||
|
|
- RPM/DEB packaging and distribution
|
||
|
|
- Smoke test framework
|
||
|
|
- Console inspector for debugging
|