SkillWire is a remote MCP skill catalog. It combines ten curated first-party skills with verified or curated public GitHub imports, loads exact immutable instructions, reads declared textual resources progressively, and remembers optional repository-scoped usage in PostgreSQL. Skill content is returned as inert MCP response data; it is never installed, executed, or written into a client repository.
MCP client -> Hono Streamable HTTP -> unified catalog -> first-party releases
| -> imported PostgreSQL bundles
+-> repository memory
Admin scheduler/CLI -> fixed-origin GitHub REST -> validation -> PostgreSQL
The service is one stateless TypeScript modular monolith. Catalog releases, provenance, resource hashes, and the append-only advisory chain are version-controlled. PostgreSQL is the sole authority for imported immutable bundles, source state, accounts, API-key hashes, repository usage, outcomes, and privacy-safe erasure audit events. There is no repository-memory cache, Redis, queue, repository clone, content execution, or client-side skill installation.
Requirements are Node.js 24, pnpm 11.21.0, and Docker with Compose.
corepack enable
corepack prepare pnpm@11.21.0 --activate
pnpm install --frozen-lockfile
pnpm format:check
pnpm lint
pnpm typecheck
pnpm build
pnpm test:unit
pnpm test:contract
pnpm test:evaluationPostgreSQL-backed tests use TEST_DATABASE_URL. CI and compose.test.yaml
create disposable databases automatically.
The signed self-hosted distribution installs one user-owned Compose service and
optionally integrates the ordinary codex and claude executables in their
normal user profiles. It creates no wrapper command, alternate client home,
shell-startup edit, repository configuration, or local catalog-skill copy.
Verify the four sibling assets first using
the bootstrap guide, then run:
skillwire setup --clients codex,claude --preview-only --output json
skillwire setup --clients codex,claude --confirm-preview EXACT_SHA256
skillwire status
skillwire doctor
skillwire repair --component COMPONENT --preview-only
skillwire backup --preview-only
skillwire upgrade --release /absolute/verified/release --preview-only
skillwire clients uninstall codex --preview-only
skillwire uninstall --preview-only
skillwire purge --preview-onlyEvery mutation requires the exact current preview hash. Default uninstall stops
owned services and removes only proven owned client deltas while retaining the
PostgreSQL volume, backups, releases, service secrets, and recovery state.
purge is a distinct destructive operation with an installation-bound asset
inventory and separate confirmation. See operations and
release evidence.
The ten first-party skills are verified from the signed release and need no
GitHub token or GitHub access. --source mattpocock/skills and
--source obra/superpowers are explicit post-readiness choices. For a first
registration, pipe one separate read-only GitHub token on stdin from a protected
credential command; never place it in argv or an environment value. Imported
content remains inert, provenance-bound, classified, and quarantined until the
existing source pipeline makes it eligible.
Copy the non-secret configuration and create local secret files:
cp .env.example .env
install -d -m 700 .secrets
openssl rand -hex 32 > .secrets/postgres-password
openssl rand -hex 32 > .secrets/api-key-pepper
: > .secrets/github-token
postgres_password="$(tr -d '\n' < .secrets/postgres-password)"
printf 'postgresql://skillwire:%s@postgres:5432/skillwire\n' "$postgres_password" \
> .secrets/database-url
chmod 400 .secrets/postgres-password
chmod 444 .secrets/database-url .secrets/api-key-pepper .secrets/github-token
unset postgres_password
docker compose up --build --wait
curl --fail http://127.0.0.1:3000/health/live
curl --fail http://127.0.0.1:3000/health/readyCompose starts PostgreSQL, waits for it to become healthy, runs migrations to completion, and only then starts SkillWire. The application runs as UID/GID 10001 with a read-only root filesystem, dropped capabilities, and bounded temporary storage.
Bootstrap an account and API key using the out-of-band CLI:
account_json="$(docker compose exec -T skillwire \
node dist/src/authentication/admin-cli.js account:create)"
account_id="$(node -e \
'process.stdout.write(JSON.parse(process.argv[1]).accountId)' "$account_json")"
key_id="$(node -e \
'process.stdout.write(require("node:crypto").randomUUID())')"
private_directory="$(mktemp -d)"
mkfifo --mode=0600 "$private_directory/token"
docker compose run --rm --no-TTY --no-deps \
--user "$(id -u):$(id -g)" \
--entrypoint node \
--volume "$private_directory:/run/skillwire-private:rw" \
skillwire dist/src/authentication/admin-cli.js key:create \
--account-id "$account_id" --key-id "$key_id" \
--token-output /run/skillwire-private/token \
> "$private_directory/metadata.json" &
admin_pid=$!
timeout 30s sh -c 'cat "$1" > "$2"' _ \
"$private_directory/token" .secrets/api-key
wait "$admin_pid"
chmod 0600 .secrets/api-key
rm -f "$private_directory/token" "$private_directory/metadata.json"
rmdir "$private_directory"
unset account_json account_id key_id admin_pid private_directoryThe key token crosses only the owner-only FIFO and is stored in
.secrets/api-key with mode 0600; stdout contains non-secret metadata. The
database URL and pepper are read-only because Compose bind-mounts them into the
UID 10001 runtime; the enclosing .secrets directory remains mode 0700. See
API-key operations for rotation and revocation.
Configure a Streamable HTTP MCP server at http://127.0.0.1:3000/mcp and source
the bearer token from the client's secret mechanism. A representative
configuration is:
{
"mcpServers": {
"skillwire": {
"type": "streamable-http",
"url": "http://127.0.0.1:3000/mcp",
"headers": {
"Authorization": "Bearer ${SKILLWIRE_API_KEY}"
}
}
}
}Environment interpolation depends on the MCP client; do not commit a literal token.
SkillWire publishes one versioned, client-agnostic activation policy through the
standard MCP server instruction field. The same text is available through legacy
initialize and current server/discover. It advises an MCP-capable agent to
search once for a specialized task only when no applicable local or already
loaded skill exists. Greetings, trivial or unrelated work, repeated attempts,
and tasks already covered locally are non-triggers.
The instructed workflow is preview → one exact load → only the next useful
declared resource. Agent-initiated searches use automatic; user-requested is
reserved for explicit user intent. An empty result or any SkillWire failure ends
the attempt without retry, reformulation, polling, context escalation, another
candidate, or revision substitution, so normal agent work can continue.
These instructions and tool annotations are advisory metadata. An MCP server cannot force an arbitrary harness to read them or invoke its tools. Clients that ignore the guidance retain the same six authenticated operations and degrade without blocking ordinary work. SkillWire returns inert untrusted content, never installs it, never writes the client tree, and cannot inspect local skill inventory. Local precedence and per-task call bounds are therefore measured harness behaviors; server enforcement remains authentication, tenant isolation, eligibility, positive relevance, exact verified loading, provenance, advisory, integrity, resource, rate-limit, and memory scope validation.
Codex users may optionally install the experimental
skillwire-autonomous-activation@skillwire plugin from a configured SkillWire
marketplace. The plugin contains only bounded activation guidance, one
credential-free SkillWire MCP dependency declaration, version metadata, and
uninstall metadata. It contains no remote skill content, executable code, API
key, bearer token, account data, or repository hash. Install, upgrade, verify,
and remove it only through the Codex plugin manager; SkillWire application code
never writes Codex-managed directories or a client repository.
In the pinned 15-case release-candidate pilot, the plugin cohort produced exact
search_skills -> load_skill traces in all seven completed automatic cases;
the eighth selected automatic case timed out and remains incomplete. The
observer also recorded unnecessary resource reads, so the evidence validator
keeps claimEligibility.eligible=false. The adapter is therefore experimental,
and SkillWire makes no definitive autonomous-activation claim. Explicit use of
the six MCP operations remains available without the plugin.
Every input object is strict. Repository hashes are opaque client-generated SHA-256 values encoded as exactly 64 lowercase hexadecimal characters.
{
"name": "search_skills",
"arguments": { "task": "Review TypeScript type safety", "limit": 3 }
}Returns previews only—never instruction or resource bodies.
Imported user-only skills require explicit intent:
{
"name": "search_skills",
"arguments": {
"task": "ask matt",
"invocationContext": "user-requested",
"limit": 3
}
}Missing invocationContext means automatic; an exact-name query does not
change that default. Imported previews include source owner/repository, pinned
commit, original path, SPDX license/attribution, classification, and invocation
mode, but no instruction, resource, or license body.
{
"name": "load_skill",
"arguments": {
"skillId": "typescript-code-review",
"revision": "1.0.0",
"repositoryHash": "<64-lowercase-hex>"
}
}Returns exact instructions, immutable provenance, trust fields, bundle hash, and the complete resource manifest. Supplying a repository hash records usage; omitting it writes no memory.
{
"name": "read_skill_resource",
"arguments": {
"skillId": "typescript-code-review",
"revision": "1.0.0",
"path": "references/review-checklist.md"
}
}Returns exactly one declared, hash-verified textual resource.
{
"name": "list_repo_memory",
"arguments": { "repositoryHash": "<64-lowercase-hex>" }
}Returns the authenticated account's bounded usage list directly from PostgreSQL.
{
"name": "record_skill_outcome",
"arguments": {
"repositoryHash": "<64-lowercase-hex>",
"skillId": "typescript-code-review",
"revision": "1.0.0",
"outcome": "useful"
}
}Replaces the current outcome with useful, neutral, or unsuccessful. Record
useful only after the exact SkillWire load is attributable and completed-task
evidence or explicit user feedback exists.
{
"name": "forget_repo_memory",
"arguments": { "repositoryHash": "<64-lowercase-hex>" }
}Transactionally deletes the live repository-memory scope and returns the same
{"forgotten":true} shape whether data previously existed.
repository_hash="$(printf 'example/repository' | sha256sum | cut -d' ' -f1)"
pnpm smoke:mcp --endpoint http://127.0.0.1:3000/mcp \
--api-key-file .secrets/api-key \
--task 'Review TypeScript type safety and narrowing' \
--repository-hash "$repository_hash" \
--verify-memory
unset repository_hashThe script performs search → load → resource in three calls, then optionally verifies the recorded repository memory. It prints identities and hashes, not skill bodies or secrets, and writes nothing to the client tree.
- API keys are high-entropy bearer tokens; PostgreSQL stores only keyed digests.
- API-key and repository-memory state are authoritative in PostgreSQL. A cheap global token bucket may reject excess syntactically valid bearer attempts before authentication; accepted attempts are checked directly in PostgreSQL.
- Published trust is immutable; current availability/revocation is derived from a verified, release-anchored advisory chain.
- Runtime schemas accept no caller URL, repository source, executable extension, or client path.
- Agent requests never discover, synchronize, clone, or directly read GitHub; exact imported revisions are served from verified PostgreSQL bundles.
- Resource paths, text encoding, media types, sizes, and hashes are validated before response.
- Structured security logs redact credentials, repository hashes, queries, content, and paths.
forget_repo_memorycovers authoritative live PostgreSQL rows. Backup, WAL, snapshot, and media retention remain operator responsibilities.
See source administration, privacy boundaries, catalog publication, and operations. The deterministic and manual activation evaluation boundaries are documented in autonomous activation evaluation.
SkillWire is a retrieval service, not a package installer. MCP responses are transient data for the caller. The service has no client-path argument, filesystem transport, package-manager hook, or tool that can materialize catalog content in a repository or user directory. This preserves client state and keeps every loaded revision tied to its server-verified provenance and hashes.