Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5f8884b
feat(settings): add settings.env field for environment variable confi…
tanzhenxin Feb 8, 2026
6a867ed
refactor(cli): change auth hint to model hint in header
tanzhenxin Feb 8, 2026
49b1a39
refactor(core): simplify generation config and support extra_body/cus…
tanzhenxin Feb 8, 2026
21e7114
fix(mcp): update OAuth client names and improve MCP commands
tanzhenxin Feb 8, 2026
a3b9541
feat(mcp): auto-detect transport type from URL in mcp add command
tanzhenxin Feb 8, 2026
e17b800
fix(mcp): prefix MCP tool names with server name to avoid collisions
tanzhenxin Feb 8, 2026
7c53995
test: update tool-registry test for new MCP tool naming convention
tanzhenxin Feb 8, 2026
0d026a5
fix(core): properly handle MCP multi-part tool results in OpenAI conv…
tanzhenxin Feb 8, 2026
842ff42
refactor(cli): improve findEnvFile logic and remove flaky test
tanzhenxin Feb 8, 2026
07cd73c
fix: failed to initialize/send message in Electron using SDK
Mingholy Feb 8, 2026
a5d2ca9
Merge pull request #1751 from QwenLM/feat/settings-env-field
tanzhenxin Feb 9, 2026
cc55d78
Merge pull request #1752 from QwenLM/fix/mcp-oauth-branding-updates
tanzhenxin Feb 9, 2026
e48c538
Merge pull request #1755 from QwenLM/fix/mcp-multipart-tool-results
tanzhenxin Feb 9, 2026
c8e8581
Merge pull request #1758 from QwenLM/mingholy/fix/fork-mode-ipc
Mingholy Feb 9, 2026
c71cc9c
fix(integration-tests): correct MCP tool name in simple-mcp-server test
tanzhenxin Feb 9, 2026
9bccd49
Merge pull request #1763 from QwenLM/fix/simple-mcp-server-test
pomelo-nwu Feb 9, 2026
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
2 changes: 1 addition & 1 deletion integration-tests/mcp_server_cyclic_schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* schema object which has stricter typing and recursion restrictions.
* If this test fails, it's likely because either the GenAI SDK or Gemini API
* has become more restrictive about the type of tool parameter schemas that
* are accepted. If this occurs: Gemini CLI previously attempted to detect
* are accepted. If this occurs: Qwen Code previously attempted to detect
* such tools and proactively remove them from the set of tools provided in
* the Gemini API call (as FunctionDeclaration objects). It may be appropriate
* to resurrect that behavior but note that it's difficult to keep the
Expand Down
4 changes: 3 additions & 1 deletion integration-tests/simple-mcp-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ describe('simple-mcp-server', () => {
// Just run the command - MCP server config is in settings.json
const output = await rig.run('add 5 and 10, use tool if you can.');

const foundToolCall = await rig.waitForToolCall('add');
const foundToolCall = await rig.waitForToolCall(
'mcp__addition-server__add',
);

expect(foundToolCall, 'Expected to find an add tool call').toBeTruthy();

Expand Down
140 changes: 74 additions & 66 deletions packages/cli/src/commands/mcp/add.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,50 @@ describe('mcp add command', () => {
});
});

it('should add a stdio server to project settings', async () => {
it('should add a stdio server to user settings by default', async () => {
await parser.parseAsync(
'add my-server /path/to/server arg1 arg2 -e FOO=bar',
);

expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
'mcpServers',
{
'my-server': {
command: '/path/to/server',
args: ['arg1', 'arg2'],
env: { FOO: 'bar' },
},
expect(mockSetValue).toHaveBeenCalledWith(SettingScope.User, 'mcpServers', {
'my-server': {
command: '/path/to/server',
args: ['arg1', 'arg2'],
env: { FOO: 'bar' },
},
});
});

it('should auto-detect http transport when commandOrUrl is an https URL', async () => {
await parser.parseAsync('add http-server https://example.com/mcp');

expect(mockSetValue).toHaveBeenCalledWith(SettingScope.User, 'mcpServers', {
'http-server': {
httpUrl: 'https://example.com/mcp',
},
});
});

it('should auto-detect http transport when commandOrUrl is an http URL', async () => {
await parser.parseAsync('add http-server http://localhost:8080/mcp');

expect(mockSetValue).toHaveBeenCalledWith(SettingScope.User, 'mcpServers', {
'http-server': {
httpUrl: 'http://localhost:8080/mcp',
},
});
});

it('should respect explicit transport even when commandOrUrl is a URL', async () => {
await parser.parseAsync(
'add --transport sse sse-server https://example.com/sse-endpoint',
);

expect(mockSetValue).toHaveBeenCalledWith(SettingScope.User, 'mcpServers', {
'sse-server': {
url: 'https://example.com/sse-endpoint',
},
});
});

it('should add an sse server to user settings', async () => {
Expand All @@ -96,55 +124,43 @@ describe('mcp add command', () => {
});
});

it('should add an http server to project settings', async () => {
it('should add an http server to user settings by default', async () => {
await parser.parseAsync(
'add --transport http http-server https://example.com/mcp -H "Authorization: Bearer your-token"',
);

expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
'mcpServers',
{
'http-server': {
httpUrl: 'https://example.com/mcp',
headers: { Authorization: 'Bearer your-token' },
},
expect(mockSetValue).toHaveBeenCalledWith(SettingScope.User, 'mcpServers', {
'http-server': {
httpUrl: 'https://example.com/mcp',
headers: { Authorization: 'Bearer your-token' },
},
);
});
});

it('should handle MCP server args with -- separator', async () => {
await parser.parseAsync(
'add my-server npx -- -y http://example.com/some-package',
);

expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
'mcpServers',
{
'my-server': {
command: 'npx',
args: ['-y', 'http://example.com/some-package'],
},
expect(mockSetValue).toHaveBeenCalledWith(SettingScope.User, 'mcpServers', {
'my-server': {
command: 'npx',
args: ['-y', 'http://example.com/some-package'],
},
);
});
});

it('should handle unknown options as MCP server args', async () => {
await parser.parseAsync(
'add test-server npx -y http://example.com/some-package',
);

expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
'mcpServers',
{
'test-server': {
command: 'npx',
args: ['-y', 'http://example.com/some-package'],
},
expect(mockSetValue).toHaveBeenCalledWith(SettingScope.User, 'mcpServers', {
'test-server': {
command: 'npx',
args: ['-y', 'http://example.com/some-package'],
},
);
});
});

describe('when handling scope and directory', () => {
Expand All @@ -166,10 +182,10 @@ describe('mcp add command', () => {
setupMocks('/path/to/project', '/path/to/project');
});

it('should use project scope by default', async () => {
it('should use user scope by default', async () => {
await parser.parseAsync(`add ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
SettingScope.User,
'mcpServers',
expect.any(Object),
);
Expand Down Expand Up @@ -199,10 +215,10 @@ describe('mcp add command', () => {
setupMocks('/path/to/project/subdir', '/path/to/project');
});

it('should use project scope by default', async () => {
it('should use user scope by default', async () => {
await parser.parseAsync(`add ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
SettingScope.User,
'mcpServers',
expect.any(Object),
);
Expand All @@ -214,22 +230,14 @@ describe('mcp add command', () => {
setupMocks('/home/user', '/home/user');
});

it('should show an error by default', async () => {
const mockProcessExit = vi
.spyOn(process, 'exit')
.mockImplementation((() => {
throw new Error('process.exit called');
}) as (code?: number) => never);

await expect(
parser.parseAsync(`add ${serverName} ${command}`),
).rejects.toThrow('process.exit called');

expect(mockWriteStderrLine).toHaveBeenCalledWith(
'Error: Please use --scope user to edit settings in the home directory.',
it('should use user scope by default without error', async () => {
await parser.parseAsync(`add ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.User,
'mcpServers',
expect.any(Object),
);
expect(mockProcessExit).toHaveBeenCalledWith(1);
expect(mockSetValue).not.toHaveBeenCalled();
expect(mockWriteStderrLine).not.toHaveBeenCalled();
});

it('should show an error when --scope=project is used explicitly', async () => {
Expand Down Expand Up @@ -266,16 +274,16 @@ describe('mcp add command', () => {
setupMocks('/home/user/some/dir', '/home/user/some/dir');
});

it('should use project scope by default', async () => {
it('should use user scope by default', async () => {
await parser.parseAsync(`add ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
SettingScope.User,
'mcpServers',
expect.any(Object),
);
});

it('should write to the WORKSPACE scope, not the USER scope', async () => {
it('should write to the USER scope by default', async () => {
await parser.parseAsync(`add my-new-server echo`);

// We expect setValue to be called once.
Expand All @@ -284,8 +292,8 @@ describe('mcp add command', () => {
// We get the scope that setValue was called with.
const calledScope = mockSetValue.mock.calls[0][0];

// We assert that the scope was Workspace, not User.
expect(calledScope).toBe(SettingScope.Workspace);
// We assert that the scope was User by default.
expect(calledScope).toBe(SettingScope.User);
});
});

Expand All @@ -294,10 +302,10 @@ describe('mcp add command', () => {
setupMocks('/tmp/foo', '/tmp/foo');
});

it('should use project scope by default', async () => {
it('should use user scope by default', async () => {
await parser.parseAsync(`add ${serverName} ${command}`);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
SettingScope.User,
'mcpServers',
expect.any(Object),
);
Expand Down Expand Up @@ -328,12 +336,12 @@ describe('mcp add command', () => {
});
});

it('should update the existing server in the project scope', async () => {
it('should update the existing server in the user scope by default', async () => {
await parser.parseAsync(
`add ${serverName} ${updatedCommand} ${updatedArgs.join(' ')}`,
);
expect(mockSetValue).toHaveBeenCalledWith(
SettingScope.Workspace,
SettingScope.User,
'mcpServers',
expect.objectContaining({
[serverName]: expect.objectContaining({
Expand Down
22 changes: 18 additions & 4 deletions packages/cli/src/commands/mcp/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

// File for 'gemini mcp add' command
// File for 'qwen mcp add' command
import type { CommandModule } from 'yargs';
import { loadSettings, SettingScope } from '../../config/settings.js';
import { writeStdoutLine, writeStderrLine } from '../../utils/stdioHelpers.js';
Expand Down Expand Up @@ -159,14 +159,14 @@ export const addCommand: CommandModule = {
alias: 's',
describe: 'Configuration scope (user or project)',
type: 'string',
default: 'project',
default: 'user',
choices: ['user', 'project'],
})
.option('transport', {
alias: 't',
describe: 'Transport type (stdio, sse, http)',
describe:
'Transport type (stdio, sse, http). Auto-detected from URL if not specified.',
type: 'string',
default: 'stdio',
choices: ['stdio', 'sse', 'http'],
})
.option('env', {
Expand Down Expand Up @@ -211,6 +211,20 @@ export const addCommand: CommandModule = {
const existingArgs = (argv['args'] as Array<string | number>) || [];
argv['args'] = [...existingArgs, ...(argv['--'] as string[])];
}

// Auto-detect transport from URL if not explicitly specified
if (!argv['transport']) {
const commandOrUrl = argv['commandOrUrl'] as string;
if (
commandOrUrl &&
(commandOrUrl.startsWith('http://') ||
commandOrUrl.startsWith('https://'))
) {
argv['transport'] = 'http';
} else {
argv['transport'] = 'stdio';
}
}
}),
handler: async (argv) => {
await addMcpServer(
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/mcp/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

// File for 'gemini mcp list' command
// File for 'qwen mcp list' command
import type { CommandModule } from 'yargs';
import { loadSettings } from '../../config/settings.js';
import { writeStdoutLine } from '../../utils/stdioHelpers.js';
Expand Down
Loading
Loading