fix(coding-agent): scope disabled-only autocomplete to enable/disable; fix changelog section
- collectMcpServerNames takes an includeDisabled flag (default true); the /mcp completer now passes false for test/reconnect/reauth/unauth, whose handlers (#resolveServerForAuth, reconnectServer) can never resolve a disabled-only discovered name (dropped from mcpManager, absent from mcpServers). enable/disable keep offering it since #handleSetEnabled handles that case for both directions. - Moved the /mcp autocomplete CHANGELOG entry from the now-released ## [17.1.0] section (where an earlier upstream merge relocated it) back under ## [Unreleased].
This commit is contained in:
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added server-name autocomplete for `/mcp enable`, `disable`, `test`, `remove`, `reconnect`, `reauth`, and `unauth`, sourced from configured and runtime-discovered MCP servers ([#5654](https://github.com/can1357/oh-my-pi/issues/5654)).
|
||||
|
||||
## [17.1.0] - 2026-07-24
|
||||
|
||||
### Breaking Changes
|
||||
@@ -23,7 +27,6 @@
|
||||
- Added a toggle-list editor in `/settings` for managing array-of-enum settings like search and image provider orders.
|
||||
- Added `models.yml` Bedrock Converse prompt-cache capability overrides for bundled and opaque inference profiles.
|
||||
- Documented Vibe mode (`/vibe`) in `docs/vibe-mode.md` and the `/fresh` provider-stream reset in the session-operations doc, and linked both from the README's new "Session controls" section ([#6440](https://github.com/can1357/oh-my-pi/issues/6440)).
|
||||
- Added server-name autocomplete for `/mcp enable`, `disable`, `test`, `remove`, `reconnect`, `reauth`, and `unauth`, sourced from configured and runtime-discovered MCP servers ([#5654](https://github.com/can1357/oh-my-pi/issues/5654)).
|
||||
- Added `getServiceTiers()` and `setServiceTier()` extension APIs for reading and changing the live per-family service tier used by subsequent session requests ([#5860](https://github.com/can1357/oh-my-pi/issues/5860)).
|
||||
- Added opt-in `omp bench --cache` independent cold/warm prompt-cache pairs with stable-prefix controls, sequential-by-default execution, mechanism-specific provider proof, and privacy-safe JSON output; it rejects `openai-codex-responses`, whose WebSocket transport chains turns.
|
||||
- Added `tools.xdevDocs` prompt-doc modes (`inline`, `builtins`, `catalog`) and the `tools.xdevInlineDevices` glob allowlist controlling which mounted `xd://` device docs are inlined into the system prompt; dynamic-device catalog summaries and mid-session mount notices are capped to one line, and changing the mode in `/settings` refreshes the active prompt ([#6063](https://github.com/can1357/oh-my-pi/issues/6063)).
|
||||
|
||||
@@ -254,16 +254,21 @@ type MCPSearchParsed = {
|
||||
|
||||
/**
|
||||
* Collect the de-duplicated union of every MCP server name we know about:
|
||||
* user config, project config, disabled-server entries, and any
|
||||
* runtime-discovered servers not already present in either config
|
||||
* (`ctx.mcpManager.getAllServerNames()` covers connections, pending
|
||||
* connections, and discovered-but-not-yet-connected sources). Disabled
|
||||
* servers stay in this list — disabling a server only flips its config
|
||||
* `enabled` flag, it doesn't remove the config entry, so a disabled server
|
||||
* is still a valid `/mcp enable <name>` target. This also covers a
|
||||
* discovered (non-config) server that was disabled: `loadAllMCPConfigs`
|
||||
* filters it out of `getAllServerNames()`, but its name survives in
|
||||
* `userConfig.disabledServers`.
|
||||
* user config, project config, and any runtime-discovered servers not
|
||||
* already present in either config (`ctx.mcpManager.getAllServerNames()`
|
||||
* covers connections, pending connections, and discovered-but-not-yet-
|
||||
* connected sources).
|
||||
*
|
||||
* When `includeDisabled` is true (the default), disabled-server entries
|
||||
* are unioned in too — disabling a server only flips its config `enabled`
|
||||
* flag, it doesn't remove the config entry, so a disabled server is still
|
||||
* a valid `/mcp enable <name>` target. This also covers a discovered
|
||||
* (non-config) server that was disabled: `loadAllMCPConfigs` filters it
|
||||
* out of `getAllServerNames()`, but its name survives in
|
||||
* `userConfig.disabledServers`. Callers whose target operation needs a
|
||||
* live connection or config entry (`/mcp test`/`reconnect`/`reauth`/
|
||||
* `unauth`) — which a disabled-only name can never satisfy — must pass
|
||||
* `includeDisabled: false`.
|
||||
*
|
||||
* This is the single source of truth for "every known server name": both
|
||||
* `MCPCommandController#handleList()` and the `/mcp` slash-command argument
|
||||
@@ -276,6 +281,7 @@ type MCPSearchParsed = {
|
||||
export async function collectMcpServerNames(
|
||||
ctx: InteractiveModeContext,
|
||||
preloaded?: { userConfig: MCPConfigFile; projectConfig: MCPConfigFile },
|
||||
includeDisabled = true,
|
||||
): Promise<string[]> {
|
||||
let userConfig: MCPConfigFile;
|
||||
let projectConfig: MCPConfigFile;
|
||||
@@ -292,7 +298,7 @@ export async function collectMcpServerNames(
|
||||
const names = new Set<string>([
|
||||
...Object.keys(userConfig.mcpServers ?? {}),
|
||||
...Object.keys(projectConfig.mcpServers ?? {}),
|
||||
...(userConfig.disabledServers ?? []),
|
||||
...(includeDisabled ? (userConfig.disabledServers ?? []) : []),
|
||||
]);
|
||||
if (ctx.mcpManager) {
|
||||
for (const name of ctx.mcpManager.getAllServerNames()) {
|
||||
|
||||
@@ -2457,6 +2457,18 @@ const MCP_SERVER_NAME_SUBCOMMANDS: ReadonlySet<string> = new Set([
|
||||
"unauth",
|
||||
]);
|
||||
|
||||
/**
|
||||
* `/mcp` subcommands where a discovered server disabled via `/mcp disable`
|
||||
* (name only in `userConfig.disabledServers`, dropped from
|
||||
* `mcpManager.getAllServerNames()` by `loadAllMCPConfigs`) is still a valid
|
||||
* completion target: `enable` is the primary re-enable path, and offering
|
||||
* it for `disable` is a harmless no-op (`#handleSetEnabled` reports
|
||||
* "already disabled"). The rest (`test`/`reconnect`/`reauth`/`unauth`) need
|
||||
* a live connection or config entry that a disabled-only name never has —
|
||||
* `#resolveServerForAuth`/`reconnectServer` would report it as not found.
|
||||
*/
|
||||
const MCP_DISABLED_ONLY_ELIGIBLE_SUBCOMMANDS: ReadonlySet<string> = new Set(["enable", "disable"]);
|
||||
|
||||
/**
|
||||
* Build getArgumentCompletions for /mcp. Delegates to the generic
|
||||
* declarative subcommand completer while the subcommand name itself is
|
||||
@@ -2489,7 +2501,11 @@ function buildMcpArgumentCompletions(
|
||||
|
||||
let serverNames: string[];
|
||||
try {
|
||||
serverNames = await collectMcpServerNames(runtime.ctx);
|
||||
serverNames = await collectMcpServerNames(
|
||||
runtime.ctx,
|
||||
undefined,
|
||||
MCP_DISABLED_ONLY_ELIGIBLE_SUBCOMMANDS.has(lowerSubcommand),
|
||||
);
|
||||
} catch (error) {
|
||||
logger.warn("MCP server-name autocomplete failed to read config", { error });
|
||||
return null;
|
||||
|
||||
@@ -136,6 +136,30 @@ describe("MCP server-name autocomplete", () => {
|
||||
expect(filtered?.[0]?.value).toBe("enable my-server ");
|
||||
});
|
||||
|
||||
test("/mcp getArgumentCompletions offers a disabled-only discovered name for enable/disable but not test/reconnect/reauth/unauth", async () => {
|
||||
// "discovered-disabled" is a third-party server that was /mcp disable'd:
|
||||
// present only in userConfig.disabledServers, absent from mcpServers, and
|
||||
// no longer reported by the manager (loadAllMCPConfigs drops disabled
|
||||
// sources). #resolveServerForAuth/reconnectServer can't resolve it, so
|
||||
// test/reconnect/reauth/unauth must not suggest it.
|
||||
await Bun.write(
|
||||
getMCPConfigPath("user", projectDir),
|
||||
`${JSON.stringify({ mcpServers: {}, disabledServers: ["discovered-disabled"] }, null, 2)}\n`,
|
||||
);
|
||||
await writeConfig("project", projectDir, {});
|
||||
const { ctx } = createFakeCtx([]);
|
||||
const runtime: TuiSlashCommandRuntime = { ctx };
|
||||
const mcp = buildTuiBuiltinSlashCommands(runtime).find(c => c.name === "mcp");
|
||||
if (!mcp?.getArgumentCompletions) throw new Error("expected /mcp command with getArgumentCompletions");
|
||||
|
||||
expect((await mcp.getArgumentCompletions("enable "))?.map(item => item.label)).toEqual(["discovered-disabled"]);
|
||||
expect((await mcp.getArgumentCompletions("disable "))?.map(item => item.label)).toEqual(["discovered-disabled"]);
|
||||
expect(await mcp.getArgumentCompletions("test ")).toBeNull();
|
||||
expect(await mcp.getArgumentCompletions("reconnect ")).toBeNull();
|
||||
expect(await mcp.getArgumentCompletions("reauth ")).toBeNull();
|
||||
expect(await mcp.getArgumentCompletions("unauth ")).toBeNull();
|
||||
});
|
||||
|
||||
test("/mcp getArgumentCompletions returns null for subcommands that don't take a server name", async () => {
|
||||
await writeConfig("user", projectDir, { "my-server": { type: "stdio", command: "one" } });
|
||||
const { ctx } = createFakeCtx([]);
|
||||
|
||||
Reference in New Issue
Block a user