Skip to main content
Glama
AutomateIP

skills-mcp-server

by AutomateIP
README.md
# skills-mcp-server

An MCP server, built with [FastMCP](https://gofastmcp.com), that serves a directory of
`SKILL.md`-based "skills" over the network — as both MCP **resources** and MCP **tools**.

## How it works

- **Resources** come from FastMCP's built-in
  `fastmcp.server.providers.skills.SkillsDirectoryProvider`. It scans a root directory, and
  treats every subdirectory containing a `SKILL.md` file as one skill, exposing:
  - `skill://{name}/SKILL.md` — the skill's main file
  - `skill://{name}/_manifest` — a synthetic JSON file listing (path/size/hash) for the skill
  - `skill://{name}/{relative/path}` — every other file in the skill folder (e.g. `references/*.md`)

  This server configures the provider with `supporting_files="resources"`, so every supporting
  file is individually listed by `list_resources()` up front (not hidden behind a lazy URI
  template) — good for upfront discoverability by clients that just call `list_resources()`
  once.

- **Tools** (`list_skills`, `get_skill`) are hand-written on top of the same skills directory,
  since the provider only exposes resources, not tools. They parse full YAML frontmatter
  (`interface:` blocks, nested lists, etc.) rather than relying on the provider's internal
  simplified line-based frontmatter parser, so nested metadata (e.g. `risk_level`,
  `confirmation_points`) comes through correctly.
  - `list_skills()` → name, description, argument hint, risk level, and path for every skill.
  - `get_skill(name)` → full parsed frontmatter, the markdown body, and a list of that skill's
    supporting reference files.

- **Transport**: streamable-HTTP (`mcp.run(transport="http", ...)`), since this is meant to run
  in a container and be reached by remote MCP clients rather than over stdio.

- **Skills directory is never baked into the image.** The Dockerfile only ships the server code;
  the actual skills live on the host and are mounted as a **read-only** volume at container
  runtime (`docker-compose.yml` mounts them to `/skills`). Swap in a different skills directory by
  changing the compose volume mount or the `SKILLS_DIR` env var — no rebuild needed.

## Configuration

All configuration is via environment variables (see [`.env.example`](.env.example)):

| Variable | Default | Meaning |
|---|---|---|
| `SKILLS_DIR` | `/skills` | Root directory to scan for skill folders |
| `MCP_HOST` | `0.0.0.0` | Host/interface the HTTP transport binds to |
| `MCP_PORT` | `8010` | Port the HTTP transport binds to |
| `SKILLS_RELOAD` | `true` | If `true`, re-scan the skills directory on every request. Default on since an external process (e.g. a cron job re-cloning a skills repo into the mounted volume) may mutate content behind the server's back. Set `false` for a static skills directory to skip re-scan overhead. |
| `SKILLS_SUPPORTING_FILES` | `resources` | `resources` (list every file upfront) or `template` (lazy URI template) |
| `LOG_LEVEL` | `INFO` | Standard Python logging level |

A `GET /health` route is also registered for container/orchestrator liveness checks.

## Local development quickstart

Requires Python 3.11+ and [`uv`](https://docs.astral.sh/uv/).

```bash
uv venv .venv
source .venv/bin/activate
uv pip install -e .

# Point at any skills directory you like — this repo bundles a minimal
# example under ./example-skills:
export SKILLS_DIR=./example-skills
skills-mcp-server
# -> Starting MCP server 'Skills MCP Server' with transport 'http' on http://0.0.0.0:8010/mcp
```

(`pip install -e .` in a plain venv works too, if you'd rather not use `uv`.)

### Quick smoke test with the FastMCP client

```python
import asyncio
from fastmcp import Client

async def main():
    async with Client("http://127.0.0.1:8010/mcp") as client:
        tools = await client.list_tools()
        print([t.name for t in tools])

        resources = await client.list_resources()
        print([str(r.uri) for r in resources])

        result = await client.call_tool("list_skills", {})
        print(result.data)

asyncio.run(main())
```

Or with `curl` against the health route:

```bash
curl http://127.0.0.1:8010/health
# {"status": "ok", "skills_dir": "./example-skills"}
```

## Docker quickstart

```bash
docker compose up --build
```

This builds the image (server code only — no skills baked in), mounts the bundled
`./example-skills` directory from the repo to `/skills` inside the container **read-only**, and
serves on `http://localhost:8010/mcp`. No configuration is required for this to work on a fresh
clone.

### Pointing at a different skills directory

Copy `docker-compose.override.yml.example` to `docker-compose.override.yml` (untracked — see
`.gitignore`) and edit the path:

```yaml
services:
  skills-mcp-server:
    volumes:
      - /path/to/your/skills:/skills:ro
```

Compose automatically merges `docker-compose.override.yml` over `docker-compose.yml`, so your
own skills directory is used without editing the tracked file.

Or, if running the container directly instead of via compose:

```bash
docker run --rm -p 8010:8010 \
  -v /path/to/your/skills:/skills:ro \
  -e SKILLS_DIR=/skills \
  skills-mcp-server:local
```

## Connecting an MCP client

Any MCP client that supports streamable-HTTP transport can connect directly to
`http://<host>:8010/mcp`. Example generic client config:

```json
{
  "mcpServers": {
    "skills": {
      "url": "http://localhost:8010/mcp",
      "transport": "http"
    }
  }
}
```

## Project layout

```
skills-mcp-server/
├── pyproject.toml
├── Dockerfile
├── docker-compose.yml
├── docker-compose.override.yml.example  # template for pointing at your own skills dir
├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── example-skills/            # bundled fixture so the server works out of the box
│   └── hello-world/
│       ├── SKILL.md
│       └── references/
│           └── greeting-styles.md
└── src/skills_mcp_server/
    ├── __init__.py
    ├── config.py      # env-driven Settings
    ├── discovery.py    # full YAML frontmatter parsing for list_skills/get_skill
    ├── tools.py        # list_skills / get_skill MCP tool definitions
    └── server.py       # FastMCP app wiring, provider, health route, entrypoint
```

## License

Apache License 2.0 — see [LICENSE](LICENSE).