mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-21 04:51:31 +00:00
chore(api): remove unused backend endpoints after MCP audit
Remove legacy backend routes that no longer have frontend or internal callers, including the old Claude/Codex MCP APIs, unused Cursor and Codex helper endpoints, stale TaskMaster detection/next/initialize routes, and unused command/project helpers. This reduces duplicated MCP behavior now handled by the provider-based MCP API, shrinks the exposed backend surface, and removes probe/service code that only existed for deleted endpoints. Add an MCP settings API audit document to capture the route-usage analysis and explain why the legacy MCP endpoints were considered safe to remove.
This commit is contained in:
@@ -180,19 +180,6 @@ router.post(
|
||||
}),
|
||||
);
|
||||
|
||||
router.put(
|
||||
'/:provider/mcp/servers/:name',
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const provider = parseProvider(req.params.provider);
|
||||
const payload = parseMcpUpsertPayload({
|
||||
...((req.body && typeof req.body === 'object') ? req.body as Record<string, unknown> : {}),
|
||||
name: readPathParam(req.params.name, 'name'),
|
||||
});
|
||||
const server = await providerMcpService.upsertProviderMcpServer(provider, payload);
|
||||
res.json(createApiSuccessResponse({ server }));
|
||||
}),
|
||||
);
|
||||
|
||||
router.delete(
|
||||
'/:provider/mcp/servers/:name',
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
@@ -208,22 +195,6 @@ router.delete(
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/:provider/mcp/servers/:name/run',
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
const provider = parseProvider(req.params.provider);
|
||||
const body = (req.body as Record<string, unknown> | undefined) ?? {};
|
||||
const scope = parseMcpScope(body.scope ?? req.query.scope);
|
||||
const workspacePath = readOptionalQueryString(body.workspacePath ?? req.query.workspacePath);
|
||||
const result = await providerMcpService.runProviderMcpServer(provider, {
|
||||
name: readPathParam(req.params.name, 'name'),
|
||||
scope,
|
||||
workspacePath,
|
||||
});
|
||||
res.json(createApiSuccessResponse(result));
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/mcp/servers/global',
|
||||
asyncHandler(async (req: Request, res: Response) => {
|
||||
|
||||
@@ -60,25 +60,6 @@ export const providerMcpService = {
|
||||
return provider.mcp.removeServer(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Runs one provider MCP server probe.
|
||||
*/
|
||||
async runProviderMcpServer(
|
||||
providerName: string,
|
||||
input: { name: string; scope?: McpScope; workspacePath?: string },
|
||||
): Promise<{
|
||||
provider: LLMProvider;
|
||||
name: string;
|
||||
scope: McpScope;
|
||||
transport: 'stdio' | 'http' | 'sse';
|
||||
reachable: boolean;
|
||||
statusCode?: number;
|
||||
error?: string;
|
||||
}> {
|
||||
const provider = providerRegistry.resolveProvider(providerName);
|
||||
return provider.mcp.runServer(input);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds one HTTP/stdio MCP server to every provider.
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { once } from 'node:events';
|
||||
import path from 'node:path';
|
||||
|
||||
import spawn from 'cross-spawn';
|
||||
|
||||
import type { IProviderMcp } from '@/shared/interfaces.js';
|
||||
import type { LLMProvider, McpScope, McpTransport, ProviderMcpServer, UpsertProviderMcpServerInput } from '@/shared/types.js';
|
||||
import { AppError } from '@/shared/utils.js';
|
||||
@@ -22,74 +19,6 @@ const normalizeServerName = (name: string): string => {
|
||||
return normalized;
|
||||
};
|
||||
|
||||
const runStdioServerProbe = async (
|
||||
server: ProviderMcpServer,
|
||||
workspacePath: string,
|
||||
): Promise<{ reachable: boolean; error?: string }> => {
|
||||
if (!server.command) {
|
||||
return { reachable: false, error: 'Missing stdio command.' };
|
||||
}
|
||||
|
||||
try {
|
||||
const child = spawn(server.command, server.args ?? [], {
|
||||
cwd: server.cwd ? path.resolve(workspacePath, server.cwd) : workspacePath,
|
||||
env: {
|
||||
...process.env,
|
||||
...(server.env ?? {}),
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (!child.killed && child.exitCode === null) {
|
||||
child.kill('SIGTERM');
|
||||
}
|
||||
}, 1_500);
|
||||
|
||||
const errorPromise = once(child, 'error').then(([error]) => {
|
||||
throw error;
|
||||
});
|
||||
const closePromise = once(child, 'close');
|
||||
await Promise.race([closePromise, errorPromise]);
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (typeof child.exitCode === 'number' && child.exitCode !== 0) {
|
||||
return {
|
||||
reachable: false,
|
||||
error: `Process exited with code ${child.exitCode}.`,
|
||||
};
|
||||
}
|
||||
|
||||
return { reachable: true };
|
||||
} catch (error) {
|
||||
return {
|
||||
reachable: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to start stdio process',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const runHttpServerProbe = async (
|
||||
url: string,
|
||||
): Promise<{ reachable: boolean; statusCode?: number; error?: string }> => {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 3_000);
|
||||
try {
|
||||
const response = await fetch(url, { method: 'GET', signal: controller.signal });
|
||||
clearTimeout(timeout);
|
||||
return {
|
||||
reachable: true,
|
||||
statusCode: response.status,
|
||||
};
|
||||
} catch (error) {
|
||||
clearTimeout(timeout);
|
||||
return {
|
||||
reachable: false,
|
||||
error: error instanceof Error ? error.message : 'Network probe failed',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shared MCP provider for provider-specific config readers/writers.
|
||||
*/
|
||||
@@ -182,63 +111,6 @@ export abstract class McpProvider implements IProviderMcp {
|
||||
return { removed, provider: this.provider, name: normalizedName, scope };
|
||||
}
|
||||
|
||||
async runServer(
|
||||
input: { name: string; scope?: McpScope; workspacePath?: string },
|
||||
): Promise<{
|
||||
provider: LLMProvider;
|
||||
name: string;
|
||||
scope: McpScope;
|
||||
transport: McpTransport;
|
||||
reachable: boolean;
|
||||
statusCode?: number;
|
||||
error?: string;
|
||||
}> {
|
||||
const scope = input.scope ?? 'project';
|
||||
this.assertScope(scope);
|
||||
|
||||
const workspacePath = resolveWorkspacePath(input.workspacePath);
|
||||
const normalizedName = normalizeServerName(input.name);
|
||||
const scopedServers = await this.readScopedServers(scope, workspacePath);
|
||||
const rawConfig = scopedServers[normalizedName];
|
||||
if (!rawConfig || typeof rawConfig !== 'object') {
|
||||
throw new AppError(`MCP server "${normalizedName}" was not found.`, {
|
||||
code: 'MCP_SERVER_NOT_FOUND',
|
||||
statusCode: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const normalized = this.normalizeServerConfig(scope, normalizedName, rawConfig);
|
||||
if (!normalized) {
|
||||
throw new AppError(`MCP server "${normalizedName}" has an invalid configuration.`, {
|
||||
code: 'MCP_SERVER_INVALID_CONFIG',
|
||||
statusCode: 400,
|
||||
});
|
||||
}
|
||||
|
||||
if (normalized.transport === 'stdio') {
|
||||
const result = await runStdioServerProbe(normalized, workspacePath);
|
||||
return {
|
||||
provider: this.provider,
|
||||
name: normalizedName,
|
||||
scope,
|
||||
transport: normalized.transport,
|
||||
reachable: result.reachable,
|
||||
error: result.error,
|
||||
};
|
||||
}
|
||||
|
||||
const result = await runHttpServerProbe(normalized.url ?? '');
|
||||
return {
|
||||
provider: this.provider,
|
||||
name: normalizedName,
|
||||
scope,
|
||||
transport: normalized.transport,
|
||||
reachable: result.reachable,
|
||||
statusCode: result.statusCode,
|
||||
error: result.error,
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract readScopedServers(
|
||||
scope: McpScope,
|
||||
workspacePath: string,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import fs from 'node:fs/promises';
|
||||
import http from 'node:http';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import test from 'node:test';
|
||||
@@ -292,62 +291,3 @@ test('providerMcpService global adder writes to all providers and rejects unsupp
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* This test covers "run" behavior for both stdio and http MCP servers.
|
||||
*/
|
||||
test('providerMcpService runProviderServer probes stdio and http MCP servers', { concurrency: false }, async () => {
|
||||
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'llm-mcp-run-'));
|
||||
const workspacePath = path.join(tempRoot, 'workspace');
|
||||
await fs.mkdir(workspacePath, { recursive: true });
|
||||
|
||||
const restoreHomeDir = patchHomeDir(tempRoot);
|
||||
const server = http.createServer((_req, res) => {
|
||||
res.statusCode = 200;
|
||||
res.end('ok');
|
||||
});
|
||||
|
||||
try {
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', () => resolve()));
|
||||
const address = server.address();
|
||||
assert.ok(address && typeof address === 'object');
|
||||
const url = `http://127.0.0.1:${address.port}/mcp`;
|
||||
|
||||
await providerMcpService.upsertProviderMcpServer('gemini', {
|
||||
name: 'probe-http',
|
||||
scope: 'project',
|
||||
transport: 'http',
|
||||
url,
|
||||
workspacePath,
|
||||
});
|
||||
|
||||
await providerMcpService.upsertProviderMcpServer('cursor', {
|
||||
name: 'probe-stdio',
|
||||
scope: 'project',
|
||||
transport: 'stdio',
|
||||
command: process.execPath,
|
||||
args: ['-e', 'process.exit(0)'],
|
||||
workspacePath,
|
||||
});
|
||||
|
||||
const httpProbe = await providerMcpService.runProviderMcpServer('gemini', {
|
||||
name: 'probe-http',
|
||||
scope: 'project',
|
||||
workspacePath,
|
||||
});
|
||||
assert.equal(httpProbe.reachable, true);
|
||||
assert.equal(httpProbe.transport, 'http');
|
||||
|
||||
const stdioProbe = await providerMcpService.runProviderMcpServer('cursor', {
|
||||
name: 'probe-stdio',
|
||||
scope: 'project',
|
||||
workspacePath,
|
||||
});
|
||||
assert.equal(stdioProbe.reachable, true);
|
||||
assert.equal(stdioProbe.transport, 'stdio');
|
||||
} finally {
|
||||
server.close();
|
||||
restoreHomeDir();
|
||||
await fs.rm(tempRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user