Skip to main content
Glama
osJedi

artifacts_colector

by osJedi
README.md
# artifacts_colector

An example MCP server for sharing versioned **skills** and **artifacts** with a
team: by default it serves the latest version, but any older one can be pinned
by tag (e.g. `stable`) or by an exact version number.

Implemented in Python (MCP Python SDK v2). The server talks to its client
(Claude Code) over stdio and is distributed as a Docker image.

## Layout

```
content/
  skills/<name>/manifest.json          # description + tags (alias -> version)
  skills/<name>/versions/<semver>/...  # files for one skill version
  artifacts/<name>/manifest.json
  artifacts/<name>/versions/<semver>/...
server/
  models.py    # Pydantic response models (LibraryItemSummary, ResolvedItem)
  library.py   # scans content/, resolves versions/tags, reads files safely
  app.py       # MCP server: registers tools and serves them over stdio
Dockerfile
requirements.txt
```

Each skill/artifact is a folder with a `manifest.json` (description + optional
tags) and a `versions/` subfolder, where every version is its own folder named
after its semver (`1.0.0`, `1.1.0`, ...). The "latest" version is computed
dynamically as the highest semver among the existing folders, so there is
nothing to update by hand when a new version is added.

## MCP tools

| Tool             | Description                                                        |
| ---------------- | ------------------------------------------------------------------ |
| `list_skills`     | List every skill: name, description, `latest`, available versions/tags |
| `get_skill`       | Fetch a skill's content by `name` (+ optional `version`: tag or semver) |
| `list_artifacts`  | List every artifact: name, description, `latest`, versions/tags        |
| `get_artifact`    | Fetch an artifact's content by `name` (+ optional `version`: tag or semver) |

If `version` is omitted, the latest version is returned. If a tag (e.g.
`stable`) is given, it resolves to whatever version that tag points to in
`manifest.json`. If an exact semver is given, that exact version is returned.

## Local run (venv)

Requires Python 3.10+.

```bash
python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt
python -m server.app
```

The process prints nothing and never returns — it's waiting on stdin for an
MCP host to speak first. That is expected.

Local sanity check without Claude Code, via MCP Inspector (requires `npx`):

```bash
npx @modelcontextprotocol/inspector --cli python -m server.app --method tools/list
```

## Running in Docker

Build the image:

```bash
docker build -t artifacts-collector-mcp .
```

Run it manually:

```bash
docker run -i --rm artifacts-collector-mcp
```

(same as above: it prints nothing and blocks, waiting for JSON-RPC requests on
stdin; to actually exercise the tools, point MCP Inspector at the same
`docker run -i --rm ...` command).

## Connecting from Claude Code

### Option 1 — Docker (recommended for team distribution)

```bash
docker build -t artifacts-collector-mcp .
claude mcp add artifacts-collector -- docker run -i --rm artifacts-collector-mcp
```

Or via a `.mcp.json` file at the root of the project where Claude Code runs:

```json
{
  "mcpServers": {
    "artifacts-collector": {
      "command": "docker",
      "args": ["run", "-i", "--rm", "artifacts-collector-mcp"]
    }
  }
}
```

The image can be published to a team registry (e.g.
`ghcr.io/<org>/artifacts-collector-mcp`) and shared via that same `.mcp.json`,
so teammates only need `docker pull` instead of a local build.

### Option 2 — local venv (for developing the server itself)

```bash
claude mcp add artifacts-collector -- /absolute/path/to/artifacts_colector/.venv/bin/python -m server.app
```

## Adding a new skill/artifact version

1. Copy the latest version into a new `versions/<new-semver>/` folder and make your changes.
2. Optionally pin a stable version with a tag in `manifest.json` (`"tags": { "stable": "1.0.0" }`).
3. Nothing else needs registering — `latest` is recomputed automatically.
4. If distributing via Docker, rebuild the image (`docker build ...`).

## Security

A `name` and `version`/tag coming from an MCP client are never substituted
directly into a filesystem path: they're only accepted if they match a real
folder name found by scanning `content/`. This rules out path traversal via
values like `../../etc`. The container also runs as a non-root user
(`USER mcp` in the Dockerfile).