# Skill Router — connect your agent Skill Router is this organization's library of **skills**: written instructions for doing a particular kind of work the way the organization does it. It is an MCP server. Once connected, an agent can list, search and pull skills, report how they were used, and propose edits. - **MCP endpoint:** `https://skills.thirtytech.net/mcp` - **Transport:** Streamable HTTP - **Auth:** OAuth 2.1 sign-in (preferred, nothing to configure), or an API token **Installing it is two things, and it is not installed until both are done:** 1. **Connect the server** — *Install*, below. 2. **Wire up the stubs** — *Then make the skills trigger*, further down: something in the repository that makes every session sync one `SKILL.md` stub per skill — a plugin in Claude Code/OpenCode V2, an extension in Copilot CLI, a session-start hook in Codex. Without it the server is connected and no skill ever triggers. A hook does not wait for the sign-in: write it in the same go. ## Install **Install it for the project you are working in — not globally.** The first thing under each client below is its per-project form: a file in the repository, or a command run at its root. Use that one. The stubs and the hook further down live in the repository too, and a server registered for the whole machine turns up in every unrelated project the person opens. Each client's global (user-wide) form comes after it and is marked. Use it only if the person asked for the server on every project. "Install this" does not ask for that, and a global command being the shorter one is not a reason either. If you are not in a project, ask which one it is for. Where a step is a file that already exists, add the entry to it and keep everything else. Give the client the endpoint URL and nothing else. It discovers the sign-in by itself. Find the client you are running in below. ### VS Code .vscode/mcp.json in the repository. Safe to commit. ```json { "servers": { "skill-router": { "type": "http", "url": "https://skills.thirtytech.net/mcp" } } } ``` **Global — only if the person asked for it.** From a terminal, for your user profile: every workspace you open. ```bash code --add-mcp '{"name":"skill-router","type":"http","url":"https://skills.thirtytech.net/mcp"}' ``` ### Copilot CLI .mcp.json at the root of the repository. Safe to commit. Copilot CLI reads it once the folder is trusted. ```json { "mcpServers": { "skill-router": { "type": "http", "url": "https://skills.thirtytech.net/mcp" } } } ``` **Global — only if the person asked for it.** For every project on this machine. It is saved to ~/.copilot/mcp-config.json. ```bash copilot mcp add --transport http skill-router https://skills.thirtytech.net/mcp ``` ### Claude Code Run once, at the root of the repository. It writes .mcp.json there. Safe to commit. ```bash claude mcp add --transport http --scope project skill-router https://skills.thirtytech.net/mcp ``` **Global — only if the person asked for it.** For every project on this machine. ```bash claude mcp add --transport http --scope user skill-router https://skills.thirtytech.net/mcp ``` Then run /mcp inside Claude Code and choose skill-router to sign in. A server from .mcp.json is approved once before it is used. ### OpenCode V2 opencode.json at the root of the repository. Safe to commit. ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "servers": { "skill-router": { "type": "remote", "url": "https://skills.thirtytech.net/mcp" } } } } ``` Run /mcps inside OpenCode and choose skill-router to sign in. Only if it is wanted on every project: the same entry goes in ~/.config/opencode/opencode.json instead. ### Codex CLI .codex/config.toml in the repository. Safe to commit. Codex reads it once the project is trusted. ```toml [mcp_servers.skill-router] url = "https://skills.thirtytech.net/mcp" ``` Then sign in, from the repository. ```bash codex mcp login skill-router ``` **Global — only if the person asked for it.** For every project on this machine: it is saved to ~/.codex/config.toml. ```bash codex mcp add skill-router --url https://skills.thirtytech.net/mcp codex mcp login skill-router ``` ### Anything else Register a remote / Streamable HTTP MCP server at `https://skills.thirtytech.net/mcp`, in the client's per-project configuration if it has one. A client that implements MCP authorization needs no further configuration. ## What happens next — tell the person The first connection opens a browser on `https://skills.thirtytech.net`. **A person has to finish it**: they sign in with the GitHub account that belongs to the organization, check the name of the client that is asking, and press **Approve**. You cannot do this step for them. If you ran the install command, say that a browser window needs their approval rather than waiting silently. The connection then appears under Settings → API tokens on `https://skills.thirtytech.net`, where it can be revoked. ## Then make the skills trigger: stubs, synced at session start **This is the second half of the install.** Connecting the server is not enough on its own. An MCP tool is only called when you think to call it, whereas a native skill's description is in front of you on every turn. So a repository that uses the library keeps one **stub** `SKILL.md` per skill in `.claude/skills/` — which Claude Code, GitHub Copilot and OpenCode all read: the skill's name and description as the trigger, over a body that says to call `get_skill`. A stub holds none of the skill's content, so it never goes stale and every real use is still recorded. Codex does not read `.claude/skills/`. Its stubs go in `.agents/skills/`, and a repository people open with Codex and with another agent keeps both sets. They are kept in step through the MCP server you are already signed in to: by code that runs at session start in Claude Code and Codex, at the first prompt in Copilot CLI/OpenCode V2, by you on other Copilot surfaces. No API token for the CLI integrations. **1. Make it happen at the start of every session.** The agents differ, so set up the one in use — or each of them, in a repository people open with more than one. Everything here goes in the repository: every path is relative to its root, not to the home directory. Codex's setup is only files: on this page, or, for the scripts its hook runs, fetched from this host. Write them whether or not the server is connected yet: they are what gets the stubs written in every session from the next one on. If the server's tools are already there, `setup_session_sync` hands you the same files. Claude Code's, Copilot CLI's and OpenCode V2's come from the server, and so follow the sign-in. ### Claude Code Call `setup_session_sync` on the `skill-router` server and write the files it returns. They are a plugin, in `.claude/skills/skill-router-sync/`: code that runs at session start, calls `sync_skill_stubs` over the connection Claude Code is already signed in to, and writes the result. No model turn and no API token. - It comes from the server, so it follows the sign-in. If the server's tools are not there yet, say that this step is still to do, and that the person has to run `/mcp` and approve the sign-in first. - It takes effect in the next session, or in this one after `/reload-plugins`. `/skill-router-status` shows what the startup sync did. - Claude Code loads it only when started from the repository root, and only once that folder's trust dialog has been accepted. - Skills are reloaded live, so a stub written during a session is usable in that session. - Committing `.claude/skills/skill-router-sync/` gives the plugin to everybody who opens the repository, and it is code that runs at the start of their sessions. Say so when you propose committing it. ### GitHub Copilot CLI (VS Code/cloud fallback instructions) For the `copilot` CLI, call `setup_session_sync` with `agent: copilot`. Write the returned extension files in `.github/extensions/skill-router-sync/`. Start `copilot --experimental`; the bundled SDK runs the sync through the existing authenticated MCP connection, without a model turn, npm install, or separate router token. This follows MCP sign-in. `.github/copilot-instructions.md` — if it exists: Replace only the Skill Router section, or append if absent; preserve unrelated instructions. ```markdown ## Skill Router In Copilot CLI only: the project extension automatically syncs at the first prompt. Do not perform a model-driven sync. Use its router session_id on later MCP calls; do not invent or persist an ID. If it did not run or failed, say so once and continue without router tools. Check /extensions manage and /mcp. In VS Code or the cloud agent only (not Copilot CLI): Skill Router: before anything else in this session, call the sync_skill_stubs tool on the skill-router MCP server. Pass as have the stubs object from .claude/skills/.skill-router.json, or leave have out if that file does not exist. Follow the instructions in the answer, and keep the session_id it gives you: pass it as session_id on every later call to that server in this session. Then carry on with what the user asked. If the skill-router MCP server is not connected, say so once and carry on. ``` - Targets CLI 1.0.91 / prerelease 1.0.92-4, reviewed 2026-10-04. Extensions and native tool RPCs are experimental; check `copilot --version` and use `--experimental`. - The awaited first-prompt hook executes the sync, writes `.claude/skills`, and explicitly reloads skills before inference. First-prompt skill expansion ordering is not guaranteed by the experimental API. `/skill-router-status` reports the automatic result. - Remove the old Skill Router echo-only `.github/hooks/skill-router.json` entry; preserve unrelated hooks. Do not keep an instruction that asks the CLI model to sync. - MCP authentication and refresh stay with Copilot. Normal permissions apply; no blanket approval or OAuth file access. Router IDs stay in extension memory, not stub state. Failures continue without router tools or model-driven sync. - VS Code and the cloud agent do not run CLI extensions. Their instructions still ask the model to sync. The cloud agent needs an API token in its MCP configuration. - Committing `.github/extensions/skill-router-sync/` shares executable code; ask first. ### Codex CLI `.codex/hooks.json` — if it exists: Merge the `SessionStart` entry into the existing `hooks`; keep everything else. An entry there whose command is `echo 'Skill Router: …'` is the older form of this hook: replace it. ```json { "hooks": { "SessionStart": [ { "matcher": "startup", "hooks": [ { "type": "command", "command": "node .codex/scripts/sync-skills.mjs --codex-login --out .agents/skills", "timeout": 120, "statusMessage": "Syncing Skill Router stubs", "additionalContextLimit": 500 } ] } ] } } ``` `AGENTS.md` — if it exists: Append the section; keep everything else. A `## Skill Router` section for Codex that is already there is replaced by this one: an older one tells the model to sync, which the hook now does. ```markdown ## Skill Router For Codex only. Other agents are set up elsewhere and should skip this. The command hook in `.codex/hooks.json` runs `.codex/scripts/sync-skills.mjs --codex-login` when a session starts. It calls `sync_skill_stubs` through Codex's app-server using the existing Skill Router MCP login, then writes the returned changes to `.agents/skills` locally. Fetching and writing are automatic and require no model turn or separate router token. The helper uses an ephemeral thread with hooks disabled to prevent recursion. The hook adds this conversation's router `session_id` to the session context. Pass that exact value on every later call to the skill-router MCP server. Do not repeat the sync from the model, invent an ID, or save the ID to disk. If the hook did not run or failed, mention that once and continue with the user's task; do not substitute a model-driven sync. The hook requires Node and a Codex CLI supporting the experimental `mcpServer/tool/call` app-server API. Codex manages authentication and refresh; the scripts do not read or export OAuth secrets. Authentication failures display a warning with `codex mcp login skill-router` as the recovery command. Other failures return a nonzero exit status. Each run writes sanitized diagnostics under `.codex/logs/skill-router-sync/`. Changed command hooks must be reviewed and trusted through `/hooks` before Codex runs them. SessionStart follows Codex's session timing; this does not guarantee skill discovery before the first prompt is submitted. ``` Fetch each of these and write it at its path exactly as served, replacing one that is already there. They are code: do not reformat, shorten or correct them. ```bash curl -fsSL https://skills.thirtytech.net/codex-hook/sync-skills.mjs --create-dirs -o .codex/scripts/sync-skills.mjs curl -fsSL https://skills.thirtytech.net/codex-hook/sync-skills-mcp.mjs --create-dirs -o .codex/scripts/sync-skills-mcp.mjs curl -fsSL https://skills.thirtytech.net/codex-hook/codex-mcp-client.mjs --create-dirs -o .codex/scripts/codex-mcp-client.mjs curl -fsSL https://skills.thirtytech.net/codex-hook/sync-log.mjs --create-dirs -o .codex/scripts/sync-log.mjs ``` Any other way of fetching them will do. In Windows PowerShell, `curl.exe` takes the same arguments. - Codex discovers skills in `.agents/skills/` and reads nothing under `.claude/`. Its stubs and their state file go there, which is what `--out` in the hook's command is for. - The sync is code: at session start the hook runs the script, which calls `sync_skill_stubs` through Codex's own app-server, over the sign-in Codex already has, and writes what comes back. No model turn and no API token. It puts the session's `session_id` into the context. - It needs Node, and a Codex CLI whose app-server has the experimental `mcpServer/tool/call`. - Codex skips a hook until the person has reviewed it: each of them approves it once with `/hooks`, and again whenever it changes. Tell the person to run `/hooks`. Until they have, nothing syncs: `AGENTS.md` says what the hook does and where its `session_id` goes, and does not ask the model to sync in its place. - Codex reads `.codex/hooks.json`, like the `.codex/config.toml` beside it, only in a project it trusts. - Codex loads a new MCP server when it starts. In the session that installs it the tools are not there yet: write these files anyway, and the first sync happens in the next session, once the person has run `codex mcp login`. - A sign-in that has expired is a warning and not a failure: the hook says to run `codex mcp login skill-router`, and the session carries on. Every run leaves sanitized diagnostics in `.codex/logs/skill-router-sync/`. - Committing `.codex/hooks.json` and `.codex/scripts/` gives them to everybody who opens the repository, and they are code that runs at the start of their sessions. Say so when you propose committing them. `.codex/logs/` is not for committing: add it to `.gitignore`. ### OpenCode V2 Call `setup_session_sync` on the `skill-router` server with `agent`: `opencode` and write the returned files to `.opencode/plugins/skill-router-sync/`. Follow its dependency-install command. OpenCode auto-discovers that plugin package; do not also add it to the plugins array. It syncs through the authenticated MCP tool registry before the first prompt, with no model turn or separate API token. - Requires OpenCode V2; the plugin targets API 2.0.22. Check `opencode --version`. Do not install this plugin in V1. - This step follows the MCP sign-in. If tools are not available, tell the person to run `/mcps` and sign in, then ask for `setup_session_sync` again. - It syncs `.claude/skills` before first-prompt skill resolution, not when an empty session is created. Start a new session after installation; `/skill-router-status` reports the automatic result. Do not substitute a model-driven startup sync. - The returned router session ID stays in plugin memory and is attached to context and later router calls. Reloads/service restarts reset it; do not save it to a file. - Committing `.opencode/plugins/skill-router-sync/` shares executable startup code. Ask first, ignore its node_modules directory, and install dependencies per checkout. **2. Sync now, if the server's tools are there.** Call `sync_skill_stubs` on the `skill-router` server. Pass `have`: the `stubs` object from `.claude/skills/.skill-router.json` — leave it out the first time, when that file does not exist. In Codex, pass `directory`: `.agents/skills` as well, and take `have` from the state file there. The answer is only what changed, with instructions for writing it; follow them. Pass `project` or `tags` to narrow it, and `server` if this MCP server is registered under a name other than `skill-router`. In Copilot CLI or OpenCode V2, do not substitute this model-driven sync for the extension/plugin. Start a new session and let the first prompt run the automatic sync. If you want stubs during installation itself, `install_skill_stubs` is the separate one-time install tool. If the tools are not there — the sign-in is not finished, or the client only loads a new server when it restarts — step 1 still stands for the files you could write. Say that the first sync will happen at the start of the next session, and what the person has to do before then. In Claude Code and Copilot CLI/OpenCode V2 step 1 is itself still to do: say that too. The answer also carries a `session_id`. Pass it as `session_id` on every other call you make to the server in this conversation: it is what counts the conversation as one session and lets a `report_skill_usage` attach to its `get_skill`. Every sync also writes one stub that is not from the catalogue, `using-skill-router`, which keeps that rule in front of you. **3. Ask whether to commit.** A stub directory holds no secret and no skill content, so committing it — stubs, state file, and the hook files above or the Claude/OpenCode plugin — gives the whole team the triggers from their first session. The alternative is ignoring the directory and letting each person's first session fill it. Either works; it is the person's call. A skill with no description gets a stub that will rarely trigger. The fix is to write the description in Skill Router, not to edit the stub — edits to a stub are overwritten. **Where there is no agent** — CI, a setup script — the same stubs come from a standalone script. It runs outside any MCP client, so it does need an API token. A person creates one at `https://skills.thirtytech.net/settings/tokens`, or asks a connected agent for one: `create_api_token` mints it and says to save it, with this address, in `.env.local`. ```bash curl -fsSL https://skills.thirtytech.net/sync-skills.mjs -o sync-skills.mjs SKILL_ROUTER_URL=https://skills.thirtytech.net SKILL_ROUTER_TOKEN=sr_... node sync-skills.mjs --dry-run ``` It writes `.claude/skills/`. Where Codex is used too, name both directories: `--out .claude/skills --out .agents/skills`. ## If the client cannot sign in Scripts, CI, and clients without OAuth present an API token instead. A person creates one at `https://skills.thirtytech.net/settings/tokens` — it is shown once — and the client sends it as a header: ```bash claude mcp add --transport http skill-router https://skills.thirtytech.net/mcp \ --header "Authorization: Bearer $SKILL_ROUTER_TOKEN" ``` That is Claude Code's default scope: this project only, kept outside the repository. Do not add `--scope project` to it — the shell has already put the token in the header, and that would write it into `.mcp.json`. Never write the token into a file that gets committed. Read it from the environment (`SKILL_ROUTER_TOKEN`) or the client's own secret storage. ## Using it 1. `list_skills` — the catalogue, with slugs. 2. `search_skills` — when you know the problem and not the name. 3. `get_skill` — the whole document. Pass a `reason`: why you reached for it. 4. `report_skill_usage` — afterwards, whether it helped or fell short. 5. `propose_skill_update` — opens a review; a person approves it. Nothing you send changes a skill directly. 6. `sync_skill_stubs` — at the start of a session, as above. Records no usage. Three more set a repository up. They are for when a person asks, not for a session to reach for: - `install_skill_stubs` — every stub, now, in the directory your harness reads. - `setup_session_sync` — the session-start files for the agent you are. - `create_api_token` — a token for a script or CI, to be saved in `.env.local`. Two more curate the library, also when a person asks: - `create_skill` — a new skill, live at once and `personal` unless told otherwise. - `assign_skill_to_projects` — put a skill in projects, or take it out, by project slug. ## Discovery, for clients - Protected resource metadata: `https://skills.thirtytech.net/.well-known/oauth-protected-resource` - Authorization server metadata: `https://skills.thirtytech.net/.well-known/oauth-authorization-server` - These instructions as plain text: `https://skills.thirtytech.net/llms.txt` - The stub sync script: `https://skills.thirtytech.net/sync-skills.mjs`