RustySkills is a Rust CLI and MCP server for managing agent skills. It stores
skills in a local SQLite database, returns SKILL.md content during activation,
and recreates support files in a per-session load root when a skill is used.
The same tool service backs the CLI, stdio MCP server, and Streamable HTTP MCP server:
listsearches the local SQLite database.searchqueriesskills.shwithout writing local state.getingests one skill from a package spec, GitHub repository, generic HTTPS git repository, or local path.activatereturns the primarySKILL.mdand prepares support files.removedeletes an exact local skill record and can optionally purge loaded support files.
Build and run the local stdio MCP server:
cargo build --release
./target/release/rustyskills serveInstall the binary from the working tree:
cargo install --path .
rustyskills --helpRun the shared Rust checks:
cargo fmt --check
cargo test -- --test-threads=1
cargo clippy --all-targets -- -D warningsFor a local HTTP smoke test, run the Streamable HTTP server on loopback:
rustyskills serve-http \
--bind 127.0.0.1:8000 \
--path /mcp \
--allowed-host 127.0.0.1:8000Then verify the health endpoint:
curl -fsS http://127.0.0.1:8000/healthzUse the CLI for direct local administration:
rustyskills list "docx"
rustyskills search "docx"
rustyskills get owner/repo@skill
rustyskills get --github-url https://github.com/owner/repo --skill-name skill
rustyskills get --git-url https://git.example.org/owner/repo --skill-name skill
rustyskills get --local-path ./path/to/repo-or-skill
rustyskills activate skill
rustyskills remove skill --dry-run
rustyskills remove skill --purge-loadedget accepts exactly one source mode:
| Source | CLI form | Notes |
|---|---|---|
| Package spec | owner/repo@skill |
Clones https://github.com/owner/repo.git and derives the selected skill from the spec. |
| GitHub URL | --github-url https://github.com/owner/repo |
Use --skill-name when the repository has more than one SKILL.md. |
| Generic git URL | --git-url https://git.example.org/owner/repo |
HTTPS only, without credentials, query, or fragment. |
| Local path | --local-path ./path |
The path can be a single skill directory or a repository containing skills. |
Use stdio mode when an MCP client launches RustySkills as a local process. It
opens no network port and must have writable, persistent paths for both the
SQLite database and activated support files (RUSTYSKILLS_DB and
RUSTYSKILLS_LOAD_ROOT).
cargo build --release
RUSTYSKILLS_DB="$PWD/data/skills.db" \
RUSTYSKILLS_LOAD_ROOT="$PWD/data/agents" \
./target/release/rustyskills serveRegister that exact release binary with Codex:
codex mcp add rustyskills \
--env "RUSTYSKILLS_DB=$PWD/data/skills.db" \
--env "RUSTYSKILLS_LOAD_ROOT=$PWD/data/agents" \
-- "$(pwd)/target/release/rustyskills" serve
codex mcp list --jsonRun the container in stdio mode with stdin kept open (-i); the named volumes
retain the required database and load-root state between client launches:
docker run --rm -i \
-v rustyskills-db-volume:/data/rustyskills \
-v rustyskills-agents-volume:/data/agents \
rustyskills:local serveThe command uses the container defaults of /data/rustyskills/skills.db and
/data/agents. Mount equivalent writable paths and set the two environment
variables explicitly when you use different locations.
Use HTTP mode for long-running processes, containers, and reverse proxy deployments:
rustyskills serve-httpDefault HTTP behavior:
| Setting | Default |
|---|---|
| Bind address | 0.0.0.0:8000 |
| MCP endpoint | /mcp |
| Health endpoint | /healthz |
| Session mode | stateful |
| Response framing | Streamable HTTP/SSE framing unless JSON response mode is enabled |
Useful flags:
rustyskills serve-http \
--bind 127.0.0.1:8000 \
--path /mcp \
--allowed-host 127.0.0.1:8000 \
--allowed-origin https://mcp.example.orgHTTP options can also come from environment variables. Use explicit allowed
hosts in deployed HTTP mode; --allow-any-host and
RUSTYSKILLS_HTTP_ALLOW_ANY_HOST=true are intended only for trusted proxy
setups that already enforce the host policy.
RustySkills discovers SKILL.md files in the source root, skills/,
.agents/skills/, and .codex/skills/. If a source contains multiple skills,
the caller must provide an exact skill_name unless the package spec already
selects it.
During ingestion, RustySkills:
- requires YAML frontmatter with non-empty
nameanddescription - validates skill names and support-file paths
- stores the primary
SKILL.mdseparately from support files - stores support-file content in SQLite with zstd compression
- excludes VCS and common build/cache directories such as
.git,target,node_modules,.cache, and__pycache__ - rewrites known hardcoded skill-root paths in text support files to
${RUSTYSKILLS_SKILL_DIR}so they can be rebound at activation time
Remote search and git-backed get operations can be disabled or
password-gated with configuration.
Activation does not execute support files. It returns the selected skill's
SKILL.md to the caller and prepares support files for the current session.
In local mode, activate extracts support files under the configured load root:
~/.agents/<skill_name>/
Local activation is replacement-based: stale files for that skill are removed,
the primary SKILL.md is not written into the support directory, and text files
containing ${RUSTYSKILLS_SKILL_DIR} are rebound to the absolute support path.
Activation uses a lock and journal under .rustyskills/activation/ in the load
root so interrupted replacements can be recovered on the next activation for
the same skill.
In remote mode, activate does not write support files on the RustySkills host.
For skills with support files, it returns the SKILL.md plus a short-lived
installer URL and command:
curl -fsSL 'https://mcp.example.org/rustyskills-install/<token>/skill-install.sh' | shRun the returned command on the MCP client and treat a non-zero exit status as an incomplete activation. The installer route is token-authenticated and is separate from the MCP route, which lets reverse proxies protect MCP traffic without also requiring the MCP bearer token for support-file downloads.
Legacy bundle output remains available for older automation:
rustyskills activate skill --bundle-output > /tmp/skill.bundle.json
rustyskills install-bundle --bundle /tmp/skill.bundle.jsonRustySkills loads configuration from:
- process environment variables
- an optional
.envfile beside therustyskillsbinary - built-in defaults
Process environment values override .env values. Docker -e flags and
Compose environment: entries therefore take precedence over a colocated
.env file.
Copy .env.example beside the installed binary when you want file-based native
configuration:
cp .env.example "$(dirname "$(command -v rustyskills)")/.env"| Variable | Default | Purpose |
|---|---|---|
RUSTYSKILLS_DB |
unset | Full SQLite database path. Overrides directory and filename settings. |
RUSTYSKILLS_DATABASE_DIRECTORY |
$HOME/.local/share/rustyskills/ |
Directory containing the SQLite database. |
RUSTYSKILLS_DATABASE_NAME |
skills.db |
Database filename inside the database directory. |
RUSTYSKILLS_LOAD_ROOT |
$HOME/.agents |
Root directory for activated support files. |
| Variable | Default | Purpose |
|---|---|---|
RUSTYSKILLS_DISABLE_DOWNLOADS |
false |
Disables search and hides it from MCP tool listings. |
RUSTYSKILLS_DISABLE_INSTALLS |
false |
Disables get and hides it from MCP tool listings. |
RUSTYSKILLS_DISABLE_REMOVAL |
false |
Disables remove and hides it from MCP tool listings. |
RUSTYSKILLS_DESTRUCTIVE_PASSWORD |
empty | Requires a per-call password for search, get, and remove. |
RUSTYSKILLS_REMOVAL_PASSWORD |
empty | Requires a dedicated password for remove; takes precedence over the destructive password for removal. |
CLI password example:
rustyskills get owner/repo@skill --password "$RUSTYSKILLS_DESTRUCTIVE_PASSWORD"
rustyskills remove skill --password "$RUSTYSKILLS_REMOVAL_PASSWORD"MCP callers pass the same values in the tool call password field.
| Variable | Default | Range | Purpose |
|---|---|---|---|
RUSTYSKILLS_LIST_COUNT_DEFAULT |
10 |
2..=30 |
Default result count for list when the caller omits limit. |
RUSTYSKILLS_SEARCH_COUNT_DEFAULT |
5 |
2..=30 |
Default result count for search when the caller omits limit. |
| Variable | Default | Purpose |
|---|---|---|
RUSTYSKILLS_HTTP_BIND |
0.0.0.0:8000 |
Listen address for serve-http. |
RUSTYSKILLS_HTTP_PATH |
/mcp |
MCP Streamable HTTP endpoint path. Must be non-root and begin with /. |
RUSTYSKILLS_HTTP_ALLOWED_HOSTS |
unset | Comma-separated allowed Host headers. |
RUSTYSKILLS_HTTP_ALLOW_ANY_HOST |
false |
Disables host header checks when true. |
RUSTYSKILLS_HTTP_ALLOWED_ORIGINS |
unset | Comma-separated allowed browser Origin headers. |
RUSTYSKILLS_HTTP_ALLOW_ANY_ORIGIN |
false |
Disables origin checks when true. |
RUSTYSKILLS_HTTP_STATEFUL |
true |
Uses stateful Streamable HTTP sessions when true. |
RUSTYSKILLS_HTTP_JSON_RESPONSE |
false |
Requests JSON responses where RMCP supports them. |
| Variable | Default | Purpose |
|---|---|---|
RUSTYSKILLS_REMOTE_MODE |
false |
Returns a client-side installer instead of extracting support files on the RustySkills host. |
RUSTYSKILLS_REMOTE_MODE_URL |
unset | Public external base URL used in installer commands. Required in remote mode for skills with support files. |
RUSTYSKILLS_REMOTE_INSTALL_PATH |
/rustyskills-install |
Public HTTP path for installer scripts and support archives. |
RUSTYSKILLS_REMOTE_INSTALL_TTL_SECS |
900 |
Installer token lifetime in seconds. |
RUSTYSKILLS_REMOTE_MODE_URL must be an HTTPS URL with no credentials, query,
or fragment. Loopback http://127.0.0.1:<port> is accepted for local tests.
The included Dockerfile builds the release binary with Cargo and copies it
into a Debian slim runtime image with ca-certificates, curl, and git.
The image starts rustyskills serve-http by default.
Build and run the image locally:
docker build -t rustyskills:local .
docker run --rm \
-p 127.0.0.1:18080:8000 \
-e RUSTYSKILLS_HTTP_ALLOWED_HOSTS=127.0.0.1:18080,localhost:18080 \
-v rustyskills-db-volume:/data/rustyskills \
-v rustyskills-agents-volume:/data/agents \
rustyskills:localContainer defaults:
| Variable | Value |
|---|---|
RUSTYSKILLS_DATABASE_DIRECTORY |
/data/rustyskills |
RUSTYSKILLS_DATABASE_NAME |
skills.db |
RUSTYSKILLS_LOAD_ROOT |
/data/agents |
RUSTYSKILLS_HTTP_BIND |
0.0.0.0:8000 |
RUSTYSKILLS_HTTP_PATH |
/mcp |
RUSTYSKILLS_REMOTE_INSTALL_PATH |
/rustyskills-install |
RUSTYSKILLS_REMOTE_INSTALL_TTL_SECS |
900 |
Check the container:
curl -fsS http://127.0.0.1:18080/healthzThe provided compose.yaml builds the local image, publishes it on loopback,
and persists the database and activation load root in named volumes:
docker compose up -d --build
docker compose ps
docker compose logs -f rustyskillsCompose defaults:
| Setting | Default |
|---|---|
| Host port | 18080 |
| Container port | 8000 |
| External MCP path | /rustyskills |
| Allowed hosts | mcp.phrk.org,localhost,127.0.0.1:18080 |
| Remote mode | false |
| Remote installer path | /rustyskills-install |
Override the common local settings at launch:
RUSTYSKILLS_HOST_PORT=18111 \
RUSTYSKILLS_HTTP_PATH=/mcp \
RUSTYSKILLS_HTTP_ALLOWED_HOSTS=127.0.0.1:18111,localhost:18111 \
docker compose up -d --buildFor reverse proxy deployments, bind the container to loopback, configure
RUSTYSKILLS_HTTP_ALLOWED_HOSTS for the proxy behavior, and proxy the installer
path separately from the MCP path. If MCP requests are bearer-authenticated at
the proxy, do not require that bearer token on the installer path; the generated
installer URL carries its own temporary token.
Common commands:
cargo check --all-features
cargo test --all-features -- --test-threads=1
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warningsFocused test suites:
cargo test --test cli
cargo test --test mcp
cargo test --test http -- --nocapture
cargo test --test storageThe storage and activation tests use SQLite and temporary directories. If tests hit intermittent filesystem or SQLite errors on a busy temporary directory, rerun with single-threaded tests:
cargo test -- --test-threads=1Docker remote-installer e2e coverage lives in scripts/docker-e2e.sh:
scripts/docker-e2e.shThe script builds a Docker image, runs HTTP mode in remote activation mode, ingests a fixture skill through MCP, fetches the generated installer script, and verifies support files are installed into a client-side load root.
The Forgejo workflow in .forgejo/workflows/release.yml runs on pushes to
main that touch source, tests, the README, .env.example, Cargo files, or the
workflow itself.
The workflow:
- installs Rust formatting and linting components in a Rust container
- clones the pushed source and fetches tags
- runs
cargo fmt --check,cargo check --all-features,cargo test --all-features -- --test-threads=1, andcargo clippy --all-targets --all-features -- -D warnings - increments the patch number in
VERSION - builds the release binary
- packages the binary with
VERSION,README.md, and.env.example - commits the new
VERSIONwith[skip ci], tags the release, creates a Forgejo release, and uploads the archive, checksum, and changelog
src/lib.rs exposes the runtime version from VERSION. Cargo.toml remains
the Cargo package manifest and currently marks the crate as unpublished.
Do not commit .env files, bearer tokens, API tokens, or deployment-only
credentials.