Two bugs fixed:
- GET /api/v1/servers/:cuid now resolves CUID→name before RBAC check,
so name-scoped bindings match correctly
- List endpoints now filter responses via preSerialization hook using
getAllowedScope(), so name-scoped users only see their resources
Also adds fulldeploy.sh orchestrator script.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix resourceName assignment in mapUrlToPermission for strictness
- Use RbacRoleBinding type in restore-service instead of loose cast
- Remove stale ProjectMemberInput export from validation index
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace admin role with granular roles: view, create, delete, edit, run
- Two binding types: resource bindings (role+resource+optional name) and
operation bindings (role:run + action like backup, logs, impersonate)
- Name-scoped resource bindings for per-instance access control
- Remove role from project members (all permissions via RBAC)
- Add users, groups, RBAC CRUD endpoints and CLI commands
- describe user/group shows all RBAC access (direct + inherited)
- create rbac supports --subject, --binding, --operation flags
- Backup/restore handles users, groups, RBAC definitions
- mcplocal project-based MCP endpoint discovery
- Full test coverage for all new functionality
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SSE-transport MCP servers (like ha-mcp) use a different protocol flow:
GET /sse to establish event stream, read endpoint event, then POST
JSON-RPC messages to /messages?session_id=... URL. Previously was
POSTing to root which returned 404.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
mcpd and MCP containers share the mcp-servers Docker network. HTTP probes
must use the container's internal IP + containerPort instead of localhost
+ host-mapped port. Also extracts container IP from Docker inspect.
Updated home-assistant template to use ghcr.io/homeassistant-ai/ha-mcp
Docker image (SSE transport) instead of broken npm package.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implements Kubernetes-style liveness probes that call MCP tools defined
in server healthCheck configs. For STDIO servers, uses docker exec to
spawn a disposable MCP client that sends initialize + tool call. For
HTTP/SSE servers, sends JSON-RPC directly.
- HealthProbeRunner service with configurable interval/threshold/timeout
- execInContainer added to orchestrator interface + Docker implementation
- Instance findById now includes server relation (fixes describe showing IDs)
- Events appended to instance (last 50), healthStatus tracked as
healthy/degraded/unhealthy
- 12 unit tests covering probing, thresholds, intervals, cleanup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
STDIO MCP servers read from stdin and exit on EOF. Docker containers close
stdin by default, causing all STDIO servers to crash immediately. Added
OpenStdin: true to container creation.
Describe instance now resolves server names (like logs command), preferring
RUNNING instances. Added 7 new describe tests covering server name resolution,
healthcheck display, events section, and template detail.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add syncStatus() to InstanceService: detects crashed/stopped containers,
marks them ERROR with last log line as context
- Reconcile now syncs container status first (detect dead before counting)
- Add 30s periodic sync loop in main.ts
- Switch node-runner from alpine to slim (Debian) for npm compatibility
(fixes home-assistant-mcp-server binary not found on Alpine)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- `mcpctl logs <server-name>` resolves to first RUNNING instance
- `mcpctl logs <server-name> -i <N>` selects specific replica
- Shows "instance N/M" hint when server has multiple replicas
- Added 5 proper tests: server name resolution, RUNNING preference,
replica selection, out-of-range error, no instances error
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Instance list now shows server NAME instead of cryptic server ID
- Include server relation in findAll query (Prisma include)
- Logs command accepts server name, server ID, or instance ID
(resolves server name → first RUNNING instance)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
STDIO servers with packageName (e.g. @leval/mcp-grafana) need a Node.js
container that runs `npx -y <package>`. Previously, packageName was used
as a Docker image reference causing "invalid reference format" errors.
- Add Dockerfile.node-runner: minimal node:20-alpine with npx entrypoint
- Update instance.service.ts: detect npm-based servers and use node-runner
image with npx command instead of treating packageName as image name
- Fix NanoCPUs: only set when explicitly provided (kernel CFS not available
on all hosts)
- Add mcp-servers network with explicit name for container isolation
- Configure MCPD_NODE_RUNNER_IMAGE and MCPD_MCP_NETWORK env vars
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add global error handler: clean messages instead of stack traces
- Add --force flag to create server/secret/project: updates on 409 conflict
- Strip null values and template-only fields from --from-template payload
- Add tests: 409 handling, --force update, null-stripping from templates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add healthCheck spec to templates and servers (tool, arguments, interval, timeout, failureThreshold)
- Add healthStatus, lastHealthCheck, events fields to instances
- Create grafana, home-assistant, node-red templates with healthcheck probes
- Add healthcheck probes to existing templates (github, slack, postgres, jira)
- Show HEALTH column in `get instances` and Events section in `describe instance`
- Display healthCheck details in `describe server` and `describe template`
- Schema + storage + display only; actual probe runner is future work
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce a Helm-chart-like template system for MCP servers. Templates are
YAML files in templates/ that get seeded into the DB on startup. Users can
browse them with `mcpctl get templates`, inspect with `mcpctl describe
template`, and instantiate with `mcpctl create server --from-template=`.
Also adds Portainer deployment scripts, mcplocal systemd service,
Streamable HTTP MCP endpoint, and RPM packaging for mcpctl-local.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the confused Profile abstraction with a dedicated Secret resource
following Kubernetes conventions. Servers now have env entries with inline
values or secretRef references. Env vars are resolved and passed to
containers at startup (fixes existing gap).
- Add Secret CRUD (model, repo, service, routes, CLI commands)
- Server env: {name, value} or {name, valueFrom: {secretRef: {name, key}}}
- Add env-resolver utility shared by instance startup and config generation
- Remove all profile-related code (models, services, routes, CLI, tests)
- Update backup/restore for secrets instead of profiles
- describe secret masks values by default, --show-values to reveal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove global -o/--output from parent program and enable
enablePositionalOptions() so -o yaml/json is parsed by subcommands.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fetchResource and fetchSingleResource now use resolveNameOrId so
`mcpctl get server ha-mcp` works by name, not just by ID.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- `create server/profile/project` with all CLI flags (kubectl parity)
- `edit server/profile/project` opens $EDITOR for in-flight editing
- `get -o yaml/json` now outputs apply-compatible format (strips internal fields, wraps in resource key)
- `describe` shows visually clean sectioned output with aligned columns
- Extract shared utilities (resolveResource, resolveNameOrId, stripInternalFields)
- Instances are immutable (no create/edit, like pods)
- Full test coverage for create, edit, and updated describe/get
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Server = Deployment (defines what to run + desired replicas)
Instance = Pod (ephemeral, auto-created by reconciliation)
Backend:
- Add replicas field to McpServer schema
- Add reconcile() to InstanceService (scales instances to match replicas)
- Remove manual start/stop/restart - instances are auto-managed
- Cascade: deleting server stops all containers then cascades DB
- Server create/update auto-triggers reconciliation
CLI:
- Add top-level delete command (servers, instances, profiles, projects)
- Add top-level logs command
- Remove instance compound command (use get/delete/logs instead)
- Clean up project command (list/show/delete → top-level get/describe/delete)
- Enhance describe for instances with container inspect info
- Add replicas to apply command's ServerSpec
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Support non-containerized MCP servers via externalUrl field and add
streamable-http session management for HA MCP proof of concept.
- Add externalUrl, command, containerPort fields to McpServer schema
- Skip Docker orchestration for external servers (virtual instances)
- Implement streamable-http proxy with Mcp-Session-Id session management
- Parse SSE-framed responses from streamable-http endpoints
- Add command passthrough to Docker container creation
- Create HA MCP example manifest (examples/ha-mcp.yaml)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>