Improve MCP settings auth flow, remote config support, and diagnostics UX (#953)

* feat: improve MCP settings auth workflow

* fix: complete MCP settings auth flow

* fix: harden MCP settings auth flow

* fix: add MCP settings refresh control

* fix: stabilize MCP authorization and status handling

* fix: clarify MCP advanced remote options toggle

* fix: improve MCP import and diagnostics

* feat: improve MCP settings panel visual hierarchy and UX

* fix: expose MCP auth actions in connected state

* fix: remove MCP import snippet helper text

* fix: address MCP review feedback

* fix: correct MCP page transport layout after rebase
This commit is contained in:
Dave Otero
2026-04-21 20:46:51 +03:00
committed by GitHub
parent b1a96c7b36
commit d73edc672e
16 changed files with 2554 additions and 131 deletions
+74 -2
View File
@@ -118,6 +118,11 @@ function createMcpConfig(name, mcpConfig, workingDirectory, scope) {
function updateMcpConfig(name, updates, workingDirectory) {
const layers = readConfigLayers(workingDirectory);
const source = getJsonEntrySource(layers, 'mcp', name);
if (!source.exists) {
throw new Error(`MCP server "${name}" not found`);
}
const targetPath = source.path || CONFIG_FILE;
const config = source.config || (fs.existsSync(targetPath) ? readConfigFile(targetPath) : {});
@@ -125,7 +130,7 @@ function updateMcpConfig(name, updates, workingDirectory) {
config.mcp = {};
}
const existing = config.mcp[name] ?? {};
const existing = config.mcp[name];
const { name: _ignoredName, ...updateData } = updates;
config.mcp[name] = buildMcpEntry({ ...existing, ...updateData });
@@ -161,7 +166,12 @@ function deleteMcpConfig(name, workingDirectory) {
* Build a clean MCP entry object, omitting undefined/null values
*/
function buildMcpEntry(data) {
const entry = {};
const entry = (data && typeof data === 'object' && !Array.isArray(data))
? { ...data }
: {};
delete entry.name;
delete entry.scope;
// type is required
entry.type = data.type === 'remote' ? 'remote' : 'local';
@@ -170,11 +180,69 @@ function buildMcpEntry(data) {
// command must be a non-empty array of strings
if (Array.isArray(data.command) && data.command.length > 0) {
entry.command = data.command.map(String);
} else {
delete entry.command;
}
delete entry.url;
delete entry.headers;
delete entry.oauth;
delete entry.timeout;
} else {
// remote: url required
if (data.url && typeof data.url === 'string') {
entry.url = data.url.trim();
} else {
delete entry.url;
}
delete entry.command;
if (data.headers && typeof data.headers === 'object' && !Array.isArray(data.headers)) {
const cleaned = {};
for (const [k, v] of Object.entries(data.headers)) {
if (k && v !== undefined && v !== null) {
cleaned[k] = String(v);
}
}
if (Object.keys(cleaned).length > 0) {
entry.headers = cleaned;
} else {
delete entry.headers;
}
} else if (data.headers === undefined) {
delete entry.headers;
}
if (data.oauth === false) {
entry.oauth = false;
} else if (data.oauth && typeof data.oauth === 'object' && !Array.isArray(data.oauth)) {
const oauth = {};
if (typeof data.oauth.clientId === 'string' && data.oauth.clientId.trim()) {
oauth.clientId = data.oauth.clientId.trim();
}
if (typeof data.oauth.clientSecret === 'string' && data.oauth.clientSecret.trim()) {
oauth.clientSecret = data.oauth.clientSecret.trim();
}
if (typeof data.oauth.scope === 'string' && data.oauth.scope.trim()) {
oauth.scope = data.oauth.scope.trim();
}
if (typeof data.oauth.redirectUri === 'string' && data.oauth.redirectUri.trim()) {
oauth.redirectUri = data.oauth.redirectUri.trim();
}
if (Object.keys(oauth).length > 0) {
entry.oauth = oauth;
} else {
delete entry.oauth;
}
} else if (data.oauth === undefined) {
delete entry.oauth;
}
if (typeof data.timeout === 'number' && Number.isFinite(data.timeout) && data.timeout > 0) {
entry.timeout = data.timeout;
} else if (data.timeout === undefined || data.timeout === null || data.timeout === '') {
delete entry.timeout;
}
}
@@ -188,7 +256,11 @@ function buildMcpEntry(data) {
}
if (Object.keys(cleaned).length > 0) {
entry.environment = cleaned;
} else {
delete entry.environment;
}
} else if (data.environment === undefined) {
delete entry.environment;
}
// enabled defaults to true