Compare commits

...
Author SHA1 Message Date
Cursor Agentand陈大猫 80c28d8e31 fix(ai): show MCP tool names on External MCP approval alerts
Approval notifications used the internal RPC id. Resolve the catalog
tool name first so the alert matches the approval card, and keep the
raw id when no tool name exists.

Co-authored-by: 陈大猫 <binaricat@users.noreply.github.com>
2026-10-20 04:12:45 +00:00
3 changed files with 54 additions and 2 deletions

No files matched your search

@@ -44,6 +44,34 @@ test('unfocused approval alerts name the tool and host and defer focus to the ma
}]);
});
test('approval alerts show the MCP tool name for an internal rpc id', () => {
const calls: Array<{ body: string }> = [];
const shown = presentExternalMcpApprovalNotification({
toolName: 'netcatty/exec',
hostLabel: '127.0.0.1',
dedupeKey: 'mcp_approval_exec',
mode: 'always',
t,
notify: (payload) => calls.push(payload),
});
assert.equal(shown, true);
assert.equal(calls.length, 1);
assert.match(calls[0].body, /terminal_execute/);
assert.equal(calls[0].body, 'terminal_execute on 127.0.0.1 is waiting for approval');
});
test('approval alerts keep an id that has no MCP tool name', () => {
const calls: Array<{ body: string }> = [];
presentExternalMcpApprovalNotification({
toolName: 'netcatty/not-a-tool',
dedupeKey: 'mcp_approval_unknown',
mode: 'unfocused',
t,
notify: (payload) => calls.push(payload),
});
assert.equal(calls[0].body, 'netcatty/not-a-tool is waiting for approval');
});
test('always approval alerts notify even while a window may be focused', () => {
const calls: Array<{ title: string; body: string; whenUnfocused: boolean; dedupeKey: string }> = [];
presentExternalMcpApprovalNotification({
+24 -1
View File
@@ -1,10 +1,33 @@
import type { ApprovalRequest } from '../../infrastructure/ai/shared/approvalGate';
import cattyToolSpecs from '../../infrastructure/ai/harness/generated/cattyToolSpecs.json';
import { netcattyBridge } from '../../infrastructure/services/netcattyBridge';
import {
readExternalMcpApprovalAlertMode,
type ExternalMcpApprovalAlertMode,
} from './useExternalMcpToggleState';
type CatalogToolSpec = {
toolName?: string;
rpcMethod?: string | null;
};
const mcpToolNameByRpcMethod = new Map<string, string>();
for (const spec of cattyToolSpecs as CatalogToolSpec[]) {
if (!spec.rpcMethod || !spec.toolName || mcpToolNameByRpcMethod.has(spec.rpcMethod)) continue;
mcpToolNameByRpcMethod.set(spec.rpcMethod, spec.toolName);
}
/**
* Name shown on the External MCP approval card.
* Catalog MCP tool names win over internal RPC ids such as `netcatty/exec`.
* An id with no catalog tool name is shown as-is.
*/
export function externalMcpApprovalToolLabel(toolName: string | undefined): string {
const raw = toolName?.trim() || '';
if (!raw) return '';
return mcpToolNameByRpcMethod.get(raw) || raw;
}
export type ExternalMcpApprovalNotification = {
title: string;
body: string;
@@ -31,7 +54,7 @@ export function presentExternalMcpApprovalNotification(options: {
notify: (payload: ExternalMcpApprovalNotification) => void;
}): boolean {
if (options.mode === 'off') return false;
const tool = options.toolName || '';
const tool = externalMcpApprovalToolLabel(options.toolName);
const host = options.hostLabel?.trim() || '';
const body = host
? options.t('ai.externalMcp.approvalAlert.notificationBodyOnHost', { tool, host })
+2 -1
View File
@@ -22,6 +22,7 @@ import {
import { useI18n } from '../../application/i18n/I18nProvider';
import { AI_STATE_CHANGED_EVENT } from '../../application/state/aiStateEvents';
import {
externalMcpApprovalToolLabel,
notifyExternalMcpApproval,
publishExternalMcpApprovalAlertMode,
} from '../../application/state/externalMcpApprovalAlert';
@@ -153,7 +154,7 @@ export const ExternalMcpApprovalsHost: React.FC = () => {
{entries.map(([id, req]) => (
<div key={id} className="space-y-1.5">
<ToolCall
name={req.toolName}
name={externalMcpApprovalToolLabel(req.toolName)}
args={req.args}
approvalTarget={req.target}
isLoading={false}