mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
Load and use MCP server prompts as slash commands in the CLI (#4828)
Co-authored-by: harold <haroldmciver@google.com> Co-authored-by: N. Taylor Mullen <ntaylormullen@google.com>
This commit is contained in:
@@ -71,6 +71,7 @@ describe('mcpCommand', () => {
|
||||
getToolRegistry: ReturnType<typeof vi.fn>;
|
||||
getMcpServers: ReturnType<typeof vi.fn>;
|
||||
getBlockedMcpServers: ReturnType<typeof vi.fn>;
|
||||
getPromptRegistry: ReturnType<typeof vi.fn>;
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -92,6 +93,10 @@ describe('mcpCommand', () => {
|
||||
}),
|
||||
getMcpServers: vi.fn().mockReturnValue({}),
|
||||
getBlockedMcpServers: vi.fn().mockReturnValue([]),
|
||||
getPromptRegistry: vi.fn().mockResolvedValue({
|
||||
getAllPrompts: vi.fn().mockReturnValue([]),
|
||||
getPromptsByServer: vi.fn().mockReturnValue([]),
|
||||
}),
|
||||
};
|
||||
|
||||
mockContext = createMockCommandContext({
|
||||
@@ -223,7 +228,7 @@ describe('mcpCommand', () => {
|
||||
|
||||
// Server 2 - Connected
|
||||
expect(message).toContain(
|
||||
'🟢 \u001b[1mserver2\u001b[0m - Ready (1 tools)',
|
||||
'🟢 \u001b[1mserver2\u001b[0m - Ready (1 tool)',
|
||||
);
|
||||
expect(message).toContain('server2_tool1');
|
||||
|
||||
@@ -365,13 +370,13 @@ describe('mcpCommand', () => {
|
||||
if (isMessageAction(result)) {
|
||||
const message = result.content;
|
||||
expect(message).toContain(
|
||||
'🟢 \u001b[1mserver1\u001b[0m - Ready (1 tools)',
|
||||
'🟢 \u001b[1mserver1\u001b[0m - Ready (1 tool)',
|
||||
);
|
||||
expect(message).toContain('\u001b[36mserver1_tool1\u001b[0m');
|
||||
expect(message).toContain(
|
||||
'🔴 \u001b[1mserver2\u001b[0m - Disconnected (0 tools cached)',
|
||||
);
|
||||
expect(message).toContain('No tools available');
|
||||
expect(message).toContain('No tools or prompts available');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -421,10 +426,10 @@ describe('mcpCommand', () => {
|
||||
|
||||
// Check server statuses
|
||||
expect(message).toContain(
|
||||
'🟢 \u001b[1mserver1\u001b[0m - Ready (1 tools)',
|
||||
'🟢 \u001b[1mserver1\u001b[0m - Ready (1 tool)',
|
||||
);
|
||||
expect(message).toContain(
|
||||
'🔄 \u001b[1mserver2\u001b[0m - Starting... (first startup may take longer) (tools will appear when ready)',
|
||||
'🔄 \u001b[1mserver2\u001b[0m - Starting... (first startup may take longer) (tools and prompts will appear when ready)',
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -994,6 +999,9 @@ describe('mcpCommand', () => {
|
||||
getBlockedMcpServers: vi.fn().mockReturnValue([]),
|
||||
getToolRegistry: vi.fn().mockResolvedValue(mockToolRegistry),
|
||||
getGeminiClient: vi.fn().mockReturnValue(mockGeminiClient),
|
||||
getPromptRegistry: vi.fn().mockResolvedValue({
|
||||
getPromptsByServer: vi.fn().mockReturnValue([]),
|
||||
}),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
MessageActionReturn,
|
||||
} from './types.js';
|
||||
import {
|
||||
DiscoveredMCPPrompt,
|
||||
DiscoveredMCPTool,
|
||||
getMCPDiscoveryState,
|
||||
getMCPServerStatus,
|
||||
@@ -101,6 +102,8 @@ const getMcpStatus = async (
|
||||
(tool) =>
|
||||
tool instanceof DiscoveredMCPTool && tool.serverName === serverName,
|
||||
) as DiscoveredMCPTool[];
|
||||
const promptRegistry = await config.getPromptRegistry();
|
||||
const serverPrompts = promptRegistry.getPromptsByServer(serverName) || [];
|
||||
|
||||
const status = getMCPServerStatus(serverName);
|
||||
|
||||
@@ -160,9 +163,26 @@ const getMcpStatus = async (
|
||||
|
||||
// Add tool count with conditional messaging
|
||||
if (status === MCPServerStatus.CONNECTED) {
|
||||
message += ` (${serverTools.length} tools)`;
|
||||
const parts = [];
|
||||
if (serverTools.length > 0) {
|
||||
parts.push(
|
||||
`${serverTools.length} ${serverTools.length === 1 ? 'tool' : 'tools'}`,
|
||||
);
|
||||
}
|
||||
if (serverPrompts.length > 0) {
|
||||
parts.push(
|
||||
`${serverPrompts.length} ${
|
||||
serverPrompts.length === 1 ? 'prompt' : 'prompts'
|
||||
}`,
|
||||
);
|
||||
}
|
||||
if (parts.length > 0) {
|
||||
message += ` (${parts.join(', ')})`;
|
||||
} else {
|
||||
message += ` (0 tools)`;
|
||||
}
|
||||
} else if (status === MCPServerStatus.CONNECTING) {
|
||||
message += ` (tools will appear when ready)`;
|
||||
message += ` (tools and prompts will appear when ready)`;
|
||||
} else {
|
||||
message += ` (${serverTools.length} tools cached)`;
|
||||
}
|
||||
@@ -186,6 +206,7 @@ const getMcpStatus = async (
|
||||
message += RESET_COLOR;
|
||||
|
||||
if (serverTools.length > 0) {
|
||||
message += ` ${COLOR_CYAN}Tools:${RESET_COLOR}\n`;
|
||||
serverTools.forEach((tool) => {
|
||||
if (showDescriptions && tool.description) {
|
||||
// Format tool name in cyan using simple ANSI cyan color
|
||||
@@ -222,12 +243,41 @@ const getMcpStatus = async (
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
}
|
||||
if (serverPrompts.length > 0) {
|
||||
if (serverTools.length > 0) {
|
||||
message += '\n';
|
||||
}
|
||||
message += ` ${COLOR_CYAN}Prompts:${RESET_COLOR}\n`;
|
||||
serverPrompts.forEach((prompt: DiscoveredMCPPrompt) => {
|
||||
if (showDescriptions && prompt.description) {
|
||||
message += ` - ${COLOR_CYAN}${prompt.name}${RESET_COLOR}`;
|
||||
const descLines = prompt.description.trim().split('\n');
|
||||
if (descLines) {
|
||||
message += ':\n';
|
||||
for (const descLine of descLines) {
|
||||
message += ` ${COLOR_GREEN}${descLine}${RESET_COLOR}\n`;
|
||||
}
|
||||
} else {
|
||||
message += '\n';
|
||||
}
|
||||
} else {
|
||||
message += ` - ${COLOR_CYAN}${prompt.name}${RESET_COLOR}\n`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (serverTools.length === 0 && serverPrompts.length === 0) {
|
||||
message += ' No tools or prompts available\n';
|
||||
} else if (serverTools.length === 0) {
|
||||
message += ' No tools available';
|
||||
if (status === MCPServerStatus.DISCONNECTED && needsAuthHint) {
|
||||
message += ` ${COLOR_GREY}(type: "/mcp auth ${serverName}" to authenticate this server)${RESET_COLOR}`;
|
||||
}
|
||||
message += '\n';
|
||||
} else if (status === MCPServerStatus.DISCONNECTED && needsAuthHint) {
|
||||
// This case is for when serverTools.length > 0
|
||||
message += ` ${COLOR_GREY}(type: "/mcp auth ${serverName}" to authenticate this server)${RESET_COLOR}\n`;
|
||||
}
|
||||
message += '\n';
|
||||
}
|
||||
@@ -328,11 +378,10 @@ const authCommand: SlashCommand = {
|
||||
// Import dynamically to avoid circular dependencies
|
||||
const { MCPOAuthProvider } = await import('@google/gemini-cli-core');
|
||||
|
||||
// Create OAuth config for authentication (will be discovered automatically)
|
||||
const oauthConfig = server.oauth || {
|
||||
authorizationUrl: '', // Will be discovered automatically
|
||||
tokenUrl: '', // Will be discovered automatically
|
||||
};
|
||||
let oauthConfig = server.oauth;
|
||||
if (!oauthConfig) {
|
||||
oauthConfig = { enabled: false };
|
||||
}
|
||||
|
||||
// Pass the MCP server URL for OAuth discovery
|
||||
const mcpServerUrl = server.httpUrl || server.url;
|
||||
|
||||
@@ -128,6 +128,7 @@ export type SlashCommandActionReturn =
|
||||
export enum CommandKind {
|
||||
BUILT_IN = 'built-in',
|
||||
FILE = 'file',
|
||||
MCP_PROMPT = 'mcp-prompt',
|
||||
}
|
||||
|
||||
// The standardized contract for any command in the system.
|
||||
|
||||
Reference in New Issue
Block a user