# Skills, plugins, and MCP

> Listing and configuring skills, plugin marketplace install flows, `mcpServerStatus/list`, OAuth login, tool calls, reload after config edits, and `skills/changed` notifications.

- Repository: openai/codex
- GitHub: https://github.com/openai/codex
- Human docs: https://grok-wiki.com/public/docs/openai-codex-c82680b15ec1
- Complete Markdown: https://grok-wiki.com/public/docs/openai-codex-c82680b15ec1/llms-full.txt

## Source Files

- `README.md`
- `src/skills_watcher.rs`
- `src/mcp_refresh.rs`
- `src/request_processors/mcp_processor.rs`
- `src/request_processors/marketplace_processor.rs`
- `src/request_processors/plugins.rs`
- `tests/suite/v2/mcp_server_status.rs`

---

---
title: "Skills, plugins, and MCP"
description: "Listing and configuring skills, plugin marketplace install flows, `mcpServerStatus/list`, OAuth login, tool calls, reload after config edits, and `skills/changed` notifications."
---

`codex app-server` exposes v2 JSON-RPC methods for discovering skills and plugins, mutating user config, and operating MCP servers configured in `config.toml`. Processors in `MessageProcessor` route catalog calls (`skills/*`, `hooks/list`), marketplace/plugin calls, and MCP calls; `SkillsWatcher` watches skill roots and emits `skills/changed`; `mcp_refresh` queues `RefreshMcpServers` on loaded threads after MCP-related config or plugin changes.

## Architecture

```mermaid
flowchart TB
  subgraph client [Client]
    RPC[JSON-RPC client]
  end

  subgraph app_server [codex app-server]
    MP[MessageProcessor]
    CP[CatalogProcessor]
    PP[PluginRequestProcessor]
    MKP[MarketplaceRequestProcessor]
    MCP[McpRequestProcessor]
    SW[SkillsWatcher]
    MR[mcp_refresh]
  end

  subgraph core [ThreadManager / core]
    SM[SkillsManager]
    PM[PluginsManager]
    MM[McpManager]
    CT[CodexThread]
  end

  RPC --> MP
  MP --> CP
  MP --> PP
  MP --> MKP
  MP --> MCP
  CP --> SM
  CP --> SW
  PP --> PM
  PP --> MR
  MCP --> MM
  MCP --> MR
  MR --> CT
  SW --> SM
  SW -->|skills/changed| RPC
  MCP -->|deferred JSON-RPC result| RPC
```

| Component | Responsibility |
| --- | --- |
| `CatalogProcessor` | `skills/list`, `skills/extraRoots/set`, `skills/config/write` |
| `PluginRequestProcessor` | `plugin/*`, plugin-driven cache clears, best-effort MCP refresh |
| `MarketplaceRequestProcessor` | `marketplace/add`, `marketplace/remove`, `marketplace/upgrade` |
| `McpRequestProcessor` | `mcpServerStatus/list`, `mcpServer/oauth/login`, `mcpServer/tool/call`, `mcpServer/resource/read`, `config/mcpServer/reload` |
| `SkillsWatcher` | Registers watch roots per thread/environment; throttled file events → cache clear + `skills/changed` |
| `mcp_refresh` | Reloads config, builds `McpServerRefreshConfig`, submits `Op::RefreshMcpServers` per loaded thread |

<Note>
Several `plugin/*` methods are marked **under development** in `README.md` — avoid production clients until the API stabilizes.
</Note>

## Skills

### List and enable

`skills/list` returns skills per `cwd`, defaulting to the server session cwd when `cwds` is empty. Results merge standalone skill roots, bundled skills (when enabled), and plugin skill roots when the `Plugins` feature and workspace policy allow Codex plugins.

<ParamField body="cwds" type="PathBuf[]">
Working directories to resolve config layers and discover skills. Empty → session cwd.
</ParamField>

<ParamField body="forceReload" type="boolean" default="false">
When `true`, bypasses the per-cwd skills cache and rescans disk.
</ParamField>

Per-entry failures (invalid cwd, config load errors) appear in `errors[]` without failing the whole request.

`skills/config/write` persists enable/disable to user `config.toml` via `path` **or** `name` (exactly one required). It clears plugin and skills caches; it does not emit `skills/changed`.

### Runtime extra roots

`skills/extraRoots/set` replaces process-wide standalone skill roots (not persisted). It registers paths with `SkillsWatcher`, updates `SkillsManager`, and immediately sends `skills/changed`.

Layout: each root contains skill directories; each directory has `SKILL.md`. Missing directories are accepted and contribute no skills until they exist.

### File watching and `skills/changed`

On `thread/start` / subscribe, `SkillsWatcher.register_thread_config` watches skill roots derived from cwd, plugin effective skill roots, and environment filesystem (skips remote environments). Runtime `extraRoots` are watched separately.

On filesystem events (throttled to ~10s in production):

1. `SkillsManager.clear_cache()`
2. Server notification `skills/changed` with empty params `{}`

Treat `skills/changed` as an **invalidation signal** — re-call `skills/list` with your current `cwds` / `forceReload` when the UI needs fresh data.

<RequestExample>

```json
{ "method": "skills/list", "id": 25, "params": {
    "cwds": ["/Users/me/project"],
    "forceReload": true
} }
```

</RequestExample>

<RequestExample>

```json
{ "method": "skills/changed", "params": {} }
```

</RequestExample>

### Invoking skills in turns

Include `$<skill-name>` in text and add a `skill` input item with `name` and `path` from `skills/list` so the backend injects full instructions. Omitting the `skill` item forces the model to resolve the name and adds latency.

## Plugins and marketplaces

Plugin discovery is **provider-neutral**: marketplaces are local Git checkouts, repo-scoped manifests, or remote catalogs keyed off `chatgpt_base_url` and ChatGPT auth — not a single model vendor.

### Marketplace install flow

<Steps>
<Step title="Add marketplace">

`marketplace/add` accepts HTTP(S) Git URL, SSH Git URL, or GitHub `owner/repo` shorthand. It persists into user marketplace config under `codex_home` and returns `marketplaceName`, `installedRoot`, and `alreadyAdded`.

</Step>
<Step title="Upgrade or remove">

`marketplace/upgrade` upgrades configured Git marketplaces (all, or one `marketplaceName`). Response includes `selectedMarketplaces`, `upgradedRoots`, and per-marketplace `errors`.

`marketplace/remove` drops a marketplace from config and deletes its installed root when present.

</Step>
<Step title="Discover plugins">

`plugin/list` — catalog view with `marketplaceKinds` filter (`local`, `vertical`, `workspace-directory`, `shared-with-me`). Default kinds: local only, plus remote global catalog when `RemotePlugin` feature is on.

`plugin/installed` — installed rows plus optional `installSuggestionPluginNames` for mention UIs.

`plugin/read` — detail for one plugin via **exactly one** of `marketplacePath` or `remoteMarketplaceName`, plus `pluginName`. Includes bundled `skills`, `hooks`, `apps`, and MCP server names.

`plugin/skill/read` — remote skill markdown preview without installing (`remoteMarketplaceName`, `remotePluginId`, `skillName`).

</Step>
<Step title="Install or uninstall">

`plugin/install` — local: `marketplacePath` + `pluginName`. Remote: `remoteMarketplaceName` + `pluginName` (remote plugin id). Returns `authPolicy` and `appsNeedingAuth`.

After install, bundled MCP servers may trigger silent OAuth (`mcpServer/oauthLogin/completed`). Plugin caches clear and **best-effort** MCP refresh runs on loaded threads.

`plugin/uninstall` — local `pluginId` (`<plugin>@<marketplace>`) or remote backend id.

</Step>
</Steps>

<Warning>
`plugin/list`, `plugin/installed`, `plugin/read`, `plugin/install`, and `plugin/uninstall` are documented as under development in `README.md`.
</Warning>

### Plugin mentions in turns

Use `@<plugin>` in text plus a `mention` item:

```json
{ "type": "mention", "name": "Sample Plugin", "path": "plugin://sample@test" }
```

Paths come from `plugin/installed` or `plugin/list`.

### Effective plugin changes → MCP

`PluginRequestProcessor::on_effective_plugins_changed` clears plugin and skills caches. If any threads are loaded, it calls `queue_best_effort_refresh` (continues on per-thread failures). Same pattern runs after account-driven remote plugin cache refresh.

## MCP servers

MCP servers are declared under `[mcp_servers.<name>]` in layered `config.toml` (user, project `.codex/config.toml`, plugins). App-server does not start long-lived MCP connections for status listing; it probes inventory and delegates live tool calls to the target `CodexThread`.

### `mcpServerStatus/list`

Async RPC: handler returns immediately; the JSON-RPC **result** arrives on the same connection id after background work.

<ParamField body="threadId" type="string">
When set, config is resolved for that thread (includes project-local MCP entries). When omitted, uses latest global config only — project MCP servers are **not** visible without `threadId`.
</ParamField>

<ParamField body="detail" type="full | toolsAndAuthOnly" default="full">
`full` — tools, auth, server info, resources, resource templates. `toolsAndAuthOnly` — skips slow resource/template inventory (returns within ~500ms in tests).
</ParamField>

<ParamField body="cursor" type="string">
Opaque pagination index (stringified usize). Invalid cursor → invalid request.
</ParamField>

<ParamField body="limit" type="number">
Page size; defaults to all servers when omitted.
</ParamField>

<ResponseField name="data" type="McpServerStatus[]">
Each entry: `name`, `serverInfo`, `tools` (map by tool name), `resources`, `resourceTemplates`, `authStatus` (`unsupported`, `notLoggedIn`, `bearerToken`, `oauth`).
</ResponseField>

<ResponseField name="nextCursor" type="string | null">
Present when more servers remain after this page.
</ResponseField>

### OAuth login

`mcpServer/oauth/login` requires a configured server name. OAuth is supported only for **streamable HTTP** transports.

<ParamField body="name" type="string" required>
MCP server key from `config.toml`.
</ParamField>

<ParamField body="scopes" type="string[]">
Optional explicit scopes; server config scopes and discovery may apply when omitted.
</ParamField>

<ParamField body="timeoutSecs" type="number">
Optional login timeout.
</ParamField>

Returns `{ "authorizationUrl": "..." }` immediately. Completion is asynchronous:

```json
{ "method": "mcpServer/oauthLogin/completed", "params": {
    "name": "my-server",
    "success": true,
    "error": null
} }
```

`plugin/install` may spawn silent OAuth for each bundled MCP server that supports login, using the same notification.

### Tool calls and resources

`mcpServer/tool/call` requires `threadId`, `server`, `tool`; optional `arguments` and `_meta`. The server injects `threadId` into `_meta` under key `threadId` for downstream MCP handlers. Result is deferred (spawned task → `send_result`).

`mcpServer/resource/read` accepts optional `threadId`. With `threadId`, reads via the thread; without, uses latest global MCP config and config cwd as stdio fallback.

During turns, MCP activity also surfaces as thread items (`mcpToolCall`) and notifications (`mcpServer/startupStatus/updated`, `mcpServer/elicitation/request`) — see approvals and notifications pages.

### Reload after config edits

| Method | Behavior |
| --- | --- |
| `config/mcpServer/reload` | **Strict**: `load_latest_config`, rebuild refresh config per loaded thread, queue `Op::RefreshMcpServers`. Fails the RPC if any thread cannot load refresh config. |
| `config/batchWrite` with `reloadUserConfig: true` | Hot-reloads thread runtime config via `refresh_runtime_config`; does **not** automatically queue MCP server refresh. |
| Plugin install/uninstall / effective plugin change | **Best-effort** MCP refresh per thread; warnings on individual thread failures. |

<Info>
After editing `[mcp_servers.*]` in `config.toml` while app-server stays up, call `config/mcpServer/reload`. Refresh applies on each thread's next active turn via `RefreshMcpServers`.
</Info>

<RequestExample>

```json
{ "method": "config/mcpServer/reload", "id": 40, "params": null }
{ "id": 40, "result": {} }
```

</RequestExample>

### Startup status

For loaded threads, `mcpServer/startupStatus/updated` reports `{ name, status, error }` where `status` is `starting`, `ready`, `failed`, or `cancelled`.

## RPC quick reference

| Method | Sync response | Notes |
| --- | --- | --- |
| `skills/list` | Yes | Per-cwd `data[]` with `skills`, `errors` |
| `skills/extraRoots/set` | Yes | Emits `skills/changed` |
| `skills/config/write` | Yes | `path` xor `name` |
| `marketplace/add` | Yes | Git remote → user config |
| `marketplace/remove` | Yes | |
| `marketplace/upgrade` | Yes | Partial errors in `errors[]` |
| `plugin/list` | Yes | Feature + workspace gated |
| `plugin/installed` | Yes | Narrower than `plugin/list` |
| `plugin/read` | Yes | Local or remote marketplace |
| `plugin/install` | Yes | May trigger OAuth notifications |
| `plugin/uninstall` | Yes | Local or remote id |
| `mcpServerStatus/list` | **Deferred** | Pagination; optional `threadId` |
| `mcpServer/oauth/login` | Yes (+ notify) | HTTP streamable only |
| `mcpServer/tool/call` | **Deferred** | Requires `threadId` |
| `mcpServer/resource/read` | **Deferred** | |
| `config/mcpServer/reload` | Yes | Strict MCP refresh |

Serialization groups (from protocol): skills/hooks/marketplace use `global("config")`; MCP registry methods use `global("mcp-registry")`; `mcpServer/tool/call` uses per-thread serialization.

## Client workflow (checklist)

```text
Skills UI
  initialize → skills/list(cwds)
  subscribe to skills/changed → skills/list(forceReload?) on notify
  skills/config/write or skills/extraRoots/set for prefs/roots

Plugin UI
  marketplace/add → plugin/list → plugin/read → plugin/install
  plugin/installed for mention paths → turn/start with mention item

MCP UI
  thread/start (for project MCP) → mcpServerStatus/list(threadId)
  mcpServer/oauth/login → open authorizationUrl → wait oauthLogin/completed
  edit config.toml → config/mcpServer/reload
  mcpServer/tool/call(threadId, ...) for direct tool invocation
```

<Tip>
For MCP inventory in a workspace with `.codex/config.toml`, always pass the active `threadId` to `mcpServerStatus/list`. Threadless calls only see home/global config.
</Tip>

## Related pages

<CardGroup>
<Card title="Config RPC reference" href="/config-rpc">
`config/read`, `config/batchWrite`, and snake_case config fields including `[mcp_servers]`.
</Card>
<Card title="RPC methods reference" href="/rpc-methods">
Full v2 method catalog with stable vs experimental markers.
</Card>
<Card title="Approvals and server requests" href="/approvals-and-server-requests">
MCP elicitations, tool approvals, and `serverRequest/resolved`.
</Card>
<Card title="Notifications and events" href="/notifications-and-events">
`mcpServer/startupStatus/updated`, `mcpToolCall` items, and turn streaming.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
MCP `failed` startup, OAuth errors, and strict reload failures.
</Card>
</CardGroup>
