Problem
Skills that need compute (ephemeris, image processing, data pipelines, etc.) currently require Go code compiled into the host binary (backend/internal/mcp/platforms/). This means:
- Skill authors must fork the template to add compute
- Every skill is coupled to the host's Go version + dependency graph
- Skills can't be authored in Python, TypeScript, Rust, etc.
- Adding/removing a skill requires redeploying the whole backend
The current astrology demo (internal/astrology/ + platforms/astrology.go) proves the tool surface works but violates the modularity goal: the code lives inside the template, not in the skill repo.
Goal
Each skill repo can optionally ship its own MCP server. The host discovers it, connects to it, and the per-user agent gains the skill's tools — without touching the host binary or redeploying the backend.
This is the same architecture Claude Desktop (stdio MCP) and Anthropic Managed Agents (URL MCP) already use. We're just making the template aware that skills can bring their own server.
Design
Skill folder gains skill.yaml
name: astrology
description: Birth charts and transit forecasts.
mcp_server:
transport: http # "http" or "stdio"
url: http://astrology:9090/mcp/v1 # for http transport (docker-compose service name)
command: ["./bin/astrology-mcp"] # for stdio transport (local dev)
image: ghcr.io/teslashibe/astrology-skill:latest # optional: docker image to pull
SKILL.md + reference.md are still uploaded to Anthropic's Skills API (the instructions layer). skill.yaml drives the compute layer.
New table: skill_mcp_servers
CREATE TABLE skill_mcp_servers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
skill_id UUID NOT NULL REFERENCES skills(id) ON DELETE CASCADE,
transport TEXT NOT NULL DEFAULT 'http', -- 'http' | 'stdio'
url TEXT,
command TEXT[],
image TEXT,
healthy BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(skill_id)
);
Upload flow changes
Service.UploadDir / Service.UploadZip:
- Upload SKILL.md + reference files to Anthropic (existing flow, unchanged)
- If
skill.yaml exists and has mcp_server, parse it and upsert into skill_mcp_servers
- If transport is
http, smoke-test tools/list against the URL; set healthy = true on success
- Files listed in
skill.yaml that are part of the MCP server (Go binaries, Dockerfiles, etc.) are NOT uploaded to Anthropic — they stay local / containerized
Provisioner changes
internal/agent/provision.go createAgent:
- Query
skill_mcp_servers WHERE healthy = true
- For each, add a
BetaManagedAgentsURLMCPServerParams entry to MCPServers (alongside the existing engagement server)
- Add a corresponding
BetaManagedAgentsMCPToolsetParams entry to Tools
- Enforce Anthropic's 10-server cap; log warnings for overflow (same pattern as the 20-skill cap)
Local dev: docker-compose overlay
docker-compose.skills.yml:
services:
astrology:
build: ./skills/astrology/mcp
ports: ["9090:9090"]
Operator runs: docker compose -f docker-compose.yml -f docker-compose.skills.yml up
The skill's MCP server starts as a sidecar; the host API discovers it via skill_mcp_servers.url.
Astrology extraction
Move internal/astrology/ and platforms/astrology.go out of the template into teslashibe/astrology-skill:
teslashibe/astrology-skill/
├── SKILL.md
├── reference.md
├── skill.yaml
├── mcp/
│ ├── main.go # tiny MCP server using mcptool
│ ├── go.mod
│ └── Dockerfile
└── README.md
The template ships with zero domain-specific MCP tools. All 14 social platforms stay in-process (they share auth, rate limits, and credential storage — different pattern).
User Stories
- As a skill author, I want to ship my skill as a self-contained repo with its own MCP server (in any language), so that I can add compute capabilities to any fork of the template without modifying the host codebase.
- As a template forker, I want to
make skills-sync a directory containing skill folders with skill.yaml, so that the host automatically discovers and connects to each skill's MCP server alongside the built-in platform tools.
- As a backend operator, I want skill MCP servers to run as Docker sidecar containers, so that a crashing skill doesn't take down the host API, and I can scale/restart them independently.
- As a backend operator, I want a health check on each skill MCP server (smoke
tools/list), so that a misconfigured skill doesn't silently break agent provisioning.
Acceptance Criteria
skill.yaml parsing
- Given a skill folder with a valid
skill.yaml containing mcp_server.transport: http and mcp_server.url, when make skills-sync runs, then the skill is uploaded to Anthropic AND a row is inserted into skill_mcp_servers with the URL and healthy = true (assuming the server is reachable).
- Given a skill folder without
skill.yaml, when make skills-sync runs, then it uploads to Anthropic only (existing behavior, no skill_mcp_servers row).
Provisioner wiring
- Given 2 healthy skill MCP servers in the DB, when a new user creates their first session, then the Anthropic Agent has 3 MCPServers (
engagement + 2 skill servers) and 3 corresponding MCPToolset entries in Tools.
- Given 11 healthy skill MCP servers (over the 10-cap), when provisioning, then the oldest 10 are attached and a warning is logged naming the overflow.
Health check
- Given a
skill.yaml pointing at a URL that returns a valid tools/list response, when syncing, then healthy = true.
- Given a
skill.yaml pointing at an unreachable URL, when syncing, then healthy = false, a warning is logged, and the row is still persisted (so a restart can recheck).
Astrology extraction
- Given the
teslashibe/astrology-skill repo running as a Docker sidecar, when an agent calls astrology_birth_summary, then it routes through the skill's MCP server (not in-process code) and returns the correct chart JSON.
Negative paths
- Given a
skill.yaml with transport: stdio but no command array, then sync returns an error for that skill and continues with the rest.
- Given a skill MCP server crashes mid-session, then the agent receives a
tool_error for that tool; all other tools (including other skills and the built-in engagement server) continue working.
Out of scope (V2+)
- Automatic Docker image pulling + lifecycle management (operator deploys skill containers themselves for now)
- Skill marketplace / registry browser
- stdio transport for Managed Agents (Anthropic only supports URL MCP servers for Managed Agents today; stdio is for local Claude Code / Desktop)
- Per-team skill isolation
- Skill-to-skill communication
Implementation plan
| Step |
What |
Files |
| 1 |
Migration 00006_skill_mcp_servers.sql |
1 file |
| 2 |
Parse skill.yaml in internal/skills/skills.go |
extend existing |
| 3 |
Health-check helper (tools/list smoke test) |
internal/skills/health.go |
| 4 |
skill_mcp_servers CRUD in internal/skills/ |
extend existing |
| 5 |
Provisioner reads skill_mcp_servers + builds dynamic MCPServers list |
internal/agent/provision.go |
| 6 |
Extract astrology to teslashibe/astrology-skill with tiny MCP server |
new repo |
| 7 |
docker-compose.skills.yml overlay |
1 file |
| 8 |
Drop internal/astrology/ + platforms/astrology.go from template |
delete 2 files, update All() |
| 9 |
Update docs/SKILLS.md with the external MCP pattern |
extend existing |
Constraints
- Anthropic Managed Agents support URL MCP servers only (no stdio). Skills that need stdio transport are limited to Claude Code / Desktop.
- 10 MCP servers per agent (Anthropic cap). 1 is always
engagement; 9 available for skills.
- Skill MCP servers must be reachable from Anthropic's cloud (same constraint as the host's
engagement server — no localhost in production).
- For local dev, skill servers run on
host.docker.internal or as named Docker Compose services.
Problem
Skills that need compute (ephemeris, image processing, data pipelines, etc.) currently require Go code compiled into the host binary (
backend/internal/mcp/platforms/). This means:The current astrology demo (
internal/astrology/+platforms/astrology.go) proves the tool surface works but violates the modularity goal: the code lives inside the template, not in the skill repo.Goal
Each skill repo can optionally ship its own MCP server. The host discovers it, connects to it, and the per-user agent gains the skill's tools — without touching the host binary or redeploying the backend.
This is the same architecture Claude Desktop (stdio MCP) and Anthropic Managed Agents (URL MCP) already use. We're just making the template aware that skills can bring their own server.
Design
Skill folder gains
skill.yamlSKILL.md + reference.md are still uploaded to Anthropic's Skills API (the instructions layer).
skill.yamldrives the compute layer.New table:
skill_mcp_serversUpload flow changes
Service.UploadDir/Service.UploadZip:skill.yamlexists and hasmcp_server, parse it and upsert intoskill_mcp_servershttp, smoke-testtools/listagainst the URL; sethealthy = trueon successskill.yamlthat are part of the MCP server (Go binaries, Dockerfiles, etc.) are NOT uploaded to Anthropic — they stay local / containerizedProvisioner changes
internal/agent/provision.gocreateAgent:skill_mcp_servers WHERE healthy = trueBetaManagedAgentsURLMCPServerParamsentry toMCPServers(alongside the existingengagementserver)BetaManagedAgentsMCPToolsetParamsentry toToolsLocal dev: docker-compose overlay
docker-compose.skills.yml:Operator runs:
docker compose -f docker-compose.yml -f docker-compose.skills.yml upThe skill's MCP server starts as a sidecar; the host API discovers it via
skill_mcp_servers.url.Astrology extraction
Move
internal/astrology/andplatforms/astrology.goout of the template intoteslashibe/astrology-skill:The template ships with zero domain-specific MCP tools. All 14 social platforms stay in-process (they share auth, rate limits, and credential storage — different pattern).
User Stories
make skills-synca directory containing skill folders withskill.yaml, so that the host automatically discovers and connects to each skill's MCP server alongside the built-in platform tools.tools/list), so that a misconfigured skill doesn't silently break agent provisioning.Acceptance Criteria
skill.yaml parsing
skill.yamlcontainingmcp_server.transport: httpandmcp_server.url, whenmake skills-syncruns, then the skill is uploaded to Anthropic AND a row is inserted intoskill_mcp_serverswith the URL andhealthy = true(assuming the server is reachable).skill.yaml, whenmake skills-syncruns, then it uploads to Anthropic only (existing behavior, noskill_mcp_serversrow).Provisioner wiring
engagement+ 2 skill servers) and 3 corresponding MCPToolset entries in Tools.Health check
skill.yamlpointing at a URL that returns a validtools/listresponse, when syncing, thenhealthy = true.skill.yamlpointing at an unreachable URL, when syncing, thenhealthy = false, a warning is logged, and the row is still persisted (so a restart can recheck).Astrology extraction
teslashibe/astrology-skillrepo running as a Docker sidecar, when an agent callsastrology_birth_summary, then it routes through the skill's MCP server (not in-process code) and returns the correct chart JSON.Negative paths
skill.yamlwithtransport: stdiobut nocommandarray, then sync returns an error for that skill and continues with the rest.tool_errorfor that tool; all other tools (including other skills and the built-in engagement server) continue working.Out of scope (V2+)
Implementation plan
00006_skill_mcp_servers.sqlskill.yamlininternal/skills/skills.gotools/listsmoke test)internal/skills/health.goskill_mcp_serversCRUD ininternal/skills/skill_mcp_servers+ builds dynamicMCPServerslistinternal/agent/provision.goteslashibe/astrology-skillwith tiny MCP serverdocker-compose.skills.ymloverlayinternal/astrology/+platforms/astrology.gofrom templateAll()docs/SKILLS.mdwith the external MCP patternConstraints
engagement; 9 available for skills.engagementserver — no localhost in production).host.docker.internalor as named Docker Compose services.