-
Notifications
You must be signed in to change notification settings - Fork 3k
Expand file tree
/
Copy pathapprove.ts
More file actions
132 lines (122 loc) · 3.85 KB
/
Copy pathapprove.ts
File metadata and controls
132 lines (122 loc) · 3.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
// Files for 'qwen mcp approve' / 'qwen mcp reject' commands (issue #4615).
import type { CommandModule } from 'yargs';
import type { MCPServerConfig } from '@qwen-code/qwen-code-core';
import { isGatedMcpScope } from '@qwen-code/qwen-code-core';
import { writeStdoutLine } from '../../utils/stdioHelpers.js';
import { loadSettings } from '../../config/settings.js';
import { assembleMcpServers } from '../../config/mcpServers.js';
import {
loadMcpApprovals,
type McpApprovalStatus,
} from '../../config/mcpApprovals.js';
/**
* All gated (approval-requiring) servers visible from `cwd` — project
* `.mcp.json` plus workspace `.qwen/settings.json` (#4615). Non-gated sources
* (user/system/extension) never need approval and are excluded.
*/
function loadGatedServers(cwd: string): Record<string, MCPServerConfig> {
const settings = loadSettings(cwd);
const all = assembleMcpServers(settings.merged.mcpServers, cwd);
const gated: Record<string, MCPServerConfig> = {};
for (const [serverName, config] of Object.entries(all)) {
if (isGatedMcpScope(config.scope)) {
gated[serverName] = config;
}
}
return gated;
}
async function setProjectServerStatus(
name: string | undefined,
status: McpApprovalStatus,
all: boolean,
): Promise<void> {
const cwd = process.cwd();
const servers = loadGatedServers(cwd);
const names = Object.keys(servers);
if (names.length === 0) {
writeStdoutLine(
'No approval-requiring MCP servers found (looked in .mcp.json and .qwen/settings.json).',
);
return;
}
const verb = status === 'approved' ? 'Approved' : 'Rejected';
const approvals = loadMcpApprovals();
const targets = all ? names : name ? [name] : [];
if (targets.length === 0) {
writeStdoutLine('Specify a server name or pass --all.');
return;
}
for (const target of targets) {
const config = servers[target];
if (!config) {
writeStdoutLine(
`Server "${target}" not found. Available: ${names.join(', ')}`,
);
continue;
}
// The decision binds to this exact config's hash: editing the server in
// its source file later returns it to pending (issue #4615).
await approvals.setState(cwd, target, config, status);
writeStdoutLine(
`${verb} MCP server "${target}" (bound to its current config).`,
);
}
if (status === 'approved') {
writeStdoutLine(
'Approved servers connect in your next interactive session.',
);
}
}
export const approveCommand: CommandModule = {
command: 'approve [name]',
describe:
'Approve a gated MCP server (.mcp.json or workspace .qwen/settings.json)',
builder: (yargs) =>
yargs
.usage('Usage: qwen mcp approve [options] [name]')
.positional('name', {
describe: 'Name of the gated server to approve',
type: 'string',
})
.option('all', {
describe: 'Approve all gated servers in this workspace',
type: 'boolean',
default: false,
}),
handler: async (argv) => {
await setProjectServerStatus(
argv['name'] as string | undefined,
'approved',
argv['all'] as boolean,
);
},
};
export const rejectCommand: CommandModule = {
command: 'reject [name]',
describe:
'Reject a gated MCP server (.mcp.json or workspace .qwen/settings.json)',
builder: (yargs) =>
yargs
.usage('Usage: qwen mcp reject [options] [name]')
.positional('name', {
describe: 'Name of the gated server to reject',
type: 'string',
})
.option('all', {
describe: 'Reject all gated servers in this workspace',
type: 'boolean',
default: false,
}),
handler: async (argv) => {
await setProjectServerStatus(
argv['name'] as string | undefined,
'rejected',
argv['all'] as boolean,
);
},
};