feat: answer /mcp in chat with the MCP server status and a reconnect (#1218)

## Summary

In an ACP client, `/mcp` showed the CLI text "… Use `/mcp` in the
terminal for details." The user of an ACP client has no terminal. In SDK
mode, Claude Code also refuses `/mcp reconnect|enable|disable` with
"Reconnect, enable, and disable aren't available in this session." and
changes nothing.

- `/mcp` and `/mcp reconnect|enable|disable [<server>|all]` still run as
a normal Claude Code turn. The adapter replaces the text of the local
command, through the same hook that `/usage` uses. Prompt order, cancel,
and the provider update stay with the turn.
- For an action, the adapter runs it in that turn through the SDK
control API (`reconnectMcpServer`, `toggleMcpServer`). It shows one
result line for each server, then the status list.
- The status list comes from `query.mcpServerStatus()`. It has a summary
line and groups by status: failed, needs authentication, connecting,
connected, and disabled on one line. A failed server shows a short
cleaned error. A list is used and not a table, because a table breaks
words in narrow columns.
- Useful CLI sentences stay, for example "There's no MCP server named
…". Sentences that point to the terminal are removed.
- After an explicit reconnect, a server from the ACP session that needs
authentication starts the existing MCP OAuth flow, when the client
supports URL elicitation.
- `/mcp` is advertised with the input hint `[reconnect|enable|disable
[<server>|all]]` and is hidden on history replay.

## Other commits in this branch

This branch also carries commits of other work: skill command metadata
for AIR, a faster `session/load` replay, and the resumed permission
mode. They can move to separate PRs if preferred.

## Tests

- `src/tests/mcp-command.test.ts` (61 tests). The fake CLI answers with
the real SDK-mode texts and message shapes (synthetic assistant +
result) and does not change any status.
- A live run through the adapter with a broken and a working stdio
server: `/mcp`, `/mcp disable good`, `/mcp enable good`, `/mcp reconnect
broken`, `/mcp reconnect nosuch`.
- `compare.ts` allows one new difference against the origin/main
baseline: the `mcp` command in `available_commands_update`. The baseline
files are unchanged.
- `npm run check` and `npm run test:run` pass.
This commit is contained in:
nikita-ashihmin
2026-10-02 17:37:59 +04:00
committed by GitHub
parent ddd5f54187
commit 190a00f68f
58 changed files with 2454 additions and 216 deletions

View File

@@ -164,25 +164,27 @@ Every payload goes into `_meta.jetbrains.air`, next to `version: 1`.
The adapter merges a payload into an existing `_meta` and keeps the other namespaces.
The adapter sends these keys only to an AIR client. "AIR" in the Gate column means an AIR client without a further capability.
| Key | Message and field path | Shape | Gate |
| ------------------------------ | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------- |
| `capabilities` | `initialize` response `_meta.jetbrains.air` | string array | AIR |
| `goal` | `initialize` response `_meta.jetbrains.air` | `{version: 1, controlMethod, actions}` | AIR |
| `goal` | `session_info_update._meta.jetbrains.air` | goal snapshot or `null` | AIR |
| `diffPatch` | tool call and permission request `content[]` of type `diff`, `_meta.jetbrains.air` | `{version: 1, format: "git_patch", text}` | `diffPatch` |
| `permission` | `session/request_permission` request `_meta.jetbrains.air` | `{version: 1, title, description?, defaultToNo?}` | AIR |
| `recommendedValue` | `model` and effort config options, `_meta.jetbrains.air` | option value string | `recommendedValue` |
| `asyncTasks` | Bash `tool_call_update._meta.jetbrains.air` | `{backgrounded: true}` | `asyncTasks` |
| `agentFileChangeReportRequest` | `session/prompt` request `_meta.jetbrains.air` (client to agent) | `{version: 1, requestId}` | `agentFileChangeReport` |
| `agentFileChangeReport` | `session_info_update._meta.jetbrains.air` | report object | `agentFileChangeReport` |
| `sessionFailure` | `session_info_update._meta.jetbrains.air` or `PromptResponse._meta.jetbrains.air` | failure record | `sessionFailure` |
| `commandTitle` | Bash and PowerShell tool call `_meta.jetbrains.air` | the `description` input string | AIR |
| `subagent` | Agent and Task tool call `_meta.jetbrains.air` | `true` | AIR |
| `skill` | Skill tool call `_meta.jetbrains.air` | `{name, path?}` | AIR |
| `contextCompaction` | compaction tool call or `compaction_update`, `_meta.jetbrains.air` | `{version: 1, trigger?, preTokens?, postTokens?, durationMs?, error?}` | AIR |
| `customAnswer` | elicitation schema property `_meta.jetbrains.air` | `{questionId, isCustomAnswer: true}` | AIR, when the client supports form elicitation |
| `kind` | session mode `_meta.jetbrains.air` and mode config option value `_meta.jetbrains.air` | `standard`, `plan`, `auto_review`, or `full_access` | AIR |
| `fork` | `session/fork` request `_meta.jetbrains.air` (client to agent) | `{version: 1, messageId, messageFingerprint?, messageOccurrence?}` | none |
| Key | Message and field path | Shape | Gate |
| ------------------------------ | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| `capabilities` | `initialize` response `_meta.jetbrains.air` | string array | AIR |
| `goal` | `initialize` response `_meta.jetbrains.air` | `{version: 1, controlMethod, actions}` | AIR |
| `goal` | `session_info_update._meta.jetbrains.air` | goal snapshot or `null` | AIR |
| `diffPatch` | tool call and permission request `content[]` of type `diff`, `_meta.jetbrains.air` | `{version: 1, format: "git_patch", text}` | `diffPatch` |
| `permission` | `session/request_permission` request `_meta.jetbrains.air` | `{version: 1, title, description?, defaultToNo?}` | AIR |
| `recommendedValue` | `model` and effort config options, `_meta.jetbrains.air` | option value string | `recommendedValue` |
| `asyncTasks` | Bash `tool_call_update._meta.jetbrains.air` | `{backgrounded: true}` | `asyncTasks` |
| `agentFileChangeReportRequest` | `session/prompt` request `_meta.jetbrains.air` (client to agent) | `{version: 1, requestId}` | `agentFileChangeReport` |
| `agentFileChangeReport` | `session_info_update._meta.jetbrains.air` | report object | `agentFileChangeReport` |
| `sessionFailure` | `session_info_update._meta.jetbrains.air` or `PromptResponse._meta.jetbrains.air` | failure record | `sessionFailure` |
| `commandTitle` | Bash and PowerShell tool call `_meta.jetbrains.air` | the `description` input string | AIR |
| `subagent` | Agent and Task tool call `_meta.jetbrains.air` | `true` | AIR |
| `skill` | Skill tool call `_meta.jetbrains.air` | `{name, path?}` | AIR |
| `contextCompaction` | compaction tool call or `compaction_update`, `_meta.jetbrains.air` | `{version: 1, trigger?, preTokens?, postTokens?, durationMs?, error?}` | AIR |
| `customAnswer` | elicitation schema property `_meta.jetbrains.air` | `{questionId, isCustomAnswer: true}` | AIR, when the client supports form elicitation |
| `kind` | session mode `_meta.jetbrains.air` and mode config option value `_meta.jetbrains.air` | `standard`, `plan`, `auto_review`, or `full_access` | AIR |
| `kind` | available command `_meta.jetbrains.air` of a skill | `skill` | AIR, when the skill's `SKILL.md` is found; not for a built-in command or an MCP prompt |
| `skillPath` | available command `_meta.jetbrains.air` of a skill | absolute path of the skill's `SKILL.md` | AIR, together with `kind: "skill"` |
| `fork` | `session/fork` request `_meta.jetbrains.air` (client to agent) | `{version: 1, messageId, messageFingerprint?, messageOccurrence?}` | none |
## JetBrains shared keys