Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/developers/tools/mcp-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -836,7 +836,9 @@ qwen mcp add --transport sse secure-sse https://api.example.com/sse/ --header "A

### Listing Servers (`qwen mcp list`)

To view all MCP servers currently configured, use the `list` command. It displays each server's name, configuration details, and connection status.
To view all MCP servers currently configured, use the `list` command. It displays each server's name, configuration details, and status.

If a server is disabled by `mcp.allowed`/`mcp.excluded`, the CLI shows it as `disabled` and does not run a connectivity check for that server.

**Command:**

Expand All @@ -850,6 +852,7 @@ qwen mcp list
✓ stdio-server: command: python3 server.py (stdio) - Connected
✓ http-server: https://api.example.com/mcp (http) - Connected
✗ sse-server: https://api.example.com/sse (sse) - Disconnected
○ github-server: command: npx -y @modelcontextprotocol/server-github (stdio) - disabled
```

### Removing a Server (`qwen mcp remove`)
Expand Down
72 changes: 72 additions & 0 deletions packages/cli/src/commands/mcp/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,76 @@ describe('mcp list command', () => {
),
);
});

it('should show excluded servers as disabled without testing them', async () => {
mockedLoadSettings.mockReturnValue({
merged: {
mcp: {
excluded: ['disabled-server'],
},
mcpServers: {
'enabled-server': { command: '/enabled/server' },
'disabled-server': { command: '/disabled/server' },
},
},
});

mockClient.connect.mockResolvedValue(undefined);
mockClient.ping.mockResolvedValue(undefined);

await listMcpServers();

expect(mockWriteStdoutLine).toHaveBeenCalledWith(
expect.stringContaining(
'enabled-server: /enabled/server (stdio) - Connected',
),
);
expect(mockWriteStdoutLine).toHaveBeenCalledWith(
expect.stringContaining(
'disabled-server: /disabled/server (stdio) - disabled',
),
);
expect(mockedCreateTransport).toHaveBeenCalledTimes(1);
expect(mockedCreateTransport).toHaveBeenCalledWith(
'enabled-server',
expect.any(Object),
false,
);
});

it('should not test servers that are not in mcp.allowed', async () => {
mockedLoadSettings.mockReturnValue({
merged: {
mcp: {
allowed: ['enabled-server'],
},
mcpServers: {
'enabled-server': { command: '/enabled/server' },
'not-allowed-server': { command: '/not-allowed/server' },
},
},
});

mockClient.connect.mockResolvedValue(undefined);
mockClient.ping.mockResolvedValue(undefined);

await listMcpServers();

expect(mockWriteStdoutLine).toHaveBeenCalledWith(
expect.stringContaining(
'enabled-server: /enabled/server (stdio) - Connected',
),
);
expect(mockWriteStdoutLine).toHaveBeenCalledWith(
expect.stringContaining(
'not-allowed-server: /not-allowed/server (stdio) - disabled',
),
);
expect(mockedCreateTransport).toHaveBeenCalledTimes(1);
expect(mockedCreateTransport).toHaveBeenCalledWith(
'enabled-server',
expect.any(Object),
false,
);
});
});
75 changes: 54 additions & 21 deletions packages/cli/src/commands/mcp/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ const COLOR_YELLOW = '\u001b[33m';
const COLOR_RED = '\u001b[31m';
const RESET_COLOR = '\u001b[0m';

async function getMcpServersFromConfig(): Promise<
Record<string, MCPServerConfig>
> {
interface McpServersWithFilters {
mcpServers: Record<string, MCPServerConfig>;
allowedServerNames?: Set<string>;
excludedServerNames?: Set<string>;
}

async function getMcpServersFromConfig(): Promise<McpServersWithFilters> {
const settings = loadSettings();
const extensionManager = new ExtensionManager({
isWorkspaceTrusted: !!isWorkspaceTrusted(settings.merged),
Expand All @@ -48,7 +52,16 @@ async function getMcpServersFromConfig(): Promise<
);
}
}
return mcpServers;

return {
mcpServers,
allowedServerNames: settings.merged.mcp?.allowed
? new Set(settings.merged.mcp.allowed.filter(Boolean))
: undefined,
excludedServerNames: settings.merged.mcp?.excluded
? new Set(settings.merged.mcp.excluded.filter(Boolean))
: undefined,
};
}

async function testMCPConnection(
Expand Down Expand Up @@ -92,8 +105,19 @@ async function getServerStatus(
return await testMCPConnection(serverName, server);
}

function isServerEnabled(
serverName: string,
allowedServerNames?: Set<string>,
excludedServerNames?: Set<string>,
): boolean {
const isAllowed = !allowedServerNames || allowedServerNames.has(serverName);
const isExcluded = !!excludedServerNames && excludedServerNames.has(serverName);
return isAllowed && !isExcluded;
}

export async function listMcpServers(): Promise<void> {
const mcpServers = await getMcpServersFromConfig();
const { mcpServers, allowedServerNames, excludedServerNames } =
await getMcpServersFromConfig();
const serverNames = Object.keys(mcpServers);

if (serverNames.length === 0) {
Expand All @@ -105,25 +129,34 @@ export async function listMcpServers(): Promise<void> {

for (const serverName of serverNames) {
const server = mcpServers[serverName];

const status = await getServerStatus(serverName, server);
const enabled = isServerEnabled(
serverName,
allowedServerNames,
excludedServerNames,
);

let statusIndicator = '';
let statusText = '';
switch (status) {
case MCPServerStatus.CONNECTED:
statusIndicator = COLOR_GREEN + '✓' + RESET_COLOR;
statusText = 'Connected';
break;
case MCPServerStatus.CONNECTING:
statusIndicator = COLOR_YELLOW + '…' + RESET_COLOR;
statusText = 'Connecting';
break;
case MCPServerStatus.DISCONNECTED:
default:
statusIndicator = COLOR_RED + '✗' + RESET_COLOR;
statusText = 'Disconnected';
break;
if (!enabled) {
statusIndicator = COLOR_YELLOW + '○' + RESET_COLOR;
statusText = 'disabled';
} else {
const status = await getServerStatus(serverName, server);
switch (status) {
case MCPServerStatus.CONNECTED:
statusIndicator = COLOR_GREEN + '✓' + RESET_COLOR;
statusText = 'Connected';
break;
case MCPServerStatus.CONNECTING:
statusIndicator = COLOR_YELLOW + '…' + RESET_COLOR;
statusText = 'Connecting';
break;
case MCPServerStatus.DISCONNECTED:
default:
statusIndicator = COLOR_RED + '✗' + RESET_COLOR;
statusText = 'Disconnected';
break;
}
}

let serverInfo = `${serverName}: `;
Expand Down