mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-04-19 12:01:28 +00:00
- Add provider registry to manage LLM providers (Claude, Codex, Cursor, Gemini). - Create provider routes for MCP server operations (list, upsert, delete, run). - Implement MCP service for handling server operations and validations. - Introduce abstract provider class and MCP provider base for shared functionality. - Add tests for MCP server operations across different providers and scopes. - Define shared interfaces and types for MCP functionality. - Implement utility functions for handling JSON config files and API responses.
111 lines
3.0 KiB
TypeScript
111 lines
3.0 KiB
TypeScript
import os from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
import { McpProvider } from '@/modules/providers/shared/mcp/mcp.provider.js';
|
|
import type { McpScope, ProviderMcpServer, UpsertProviderMcpServerInput } from '@/shared/types.js';
|
|
import {
|
|
AppError,
|
|
readJsonConfig,
|
|
readObjectRecord,
|
|
readOptionalString,
|
|
readStringArray,
|
|
readStringRecord,
|
|
writeJsonConfig,
|
|
} from '@/shared/utils.js';
|
|
|
|
export class GeminiMcpProvider extends McpProvider {
|
|
constructor() {
|
|
super('gemini', ['user', 'project'], ['stdio', 'http', 'sse']);
|
|
}
|
|
|
|
protected async readScopedServers(scope: McpScope, workspacePath: string): Promise<Record<string, unknown>> {
|
|
const filePath = scope === 'user'
|
|
? path.join(os.homedir(), '.gemini', 'settings.json')
|
|
: path.join(workspacePath, '.gemini', 'settings.json');
|
|
const config = await readJsonConfig(filePath);
|
|
return readObjectRecord(config.mcpServers) ?? {};
|
|
}
|
|
|
|
protected async writeScopedServers(
|
|
scope: McpScope,
|
|
workspacePath: string,
|
|
servers: Record<string, unknown>,
|
|
): Promise<void> {
|
|
const filePath = scope === 'user'
|
|
? path.join(os.homedir(), '.gemini', 'settings.json')
|
|
: path.join(workspacePath, '.gemini', 'settings.json');
|
|
const config = await readJsonConfig(filePath);
|
|
config.mcpServers = servers;
|
|
await writeJsonConfig(filePath, config);
|
|
}
|
|
|
|
protected buildServerConfig(input: UpsertProviderMcpServerInput): Record<string, unknown> {
|
|
if (input.transport === 'stdio') {
|
|
if (!input.command?.trim()) {
|
|
throw new AppError('command is required for stdio MCP servers.', {
|
|
code: 'MCP_COMMAND_REQUIRED',
|
|
statusCode: 400,
|
|
});
|
|
}
|
|
|
|
return {
|
|
command: input.command,
|
|
args: input.args ?? [],
|
|
env: input.env ?? {},
|
|
cwd: input.cwd,
|
|
};
|
|
}
|
|
|
|
if (!input.url?.trim()) {
|
|
throw new AppError('url is required for http/sse MCP servers.', {
|
|
code: 'MCP_URL_REQUIRED',
|
|
statusCode: 400,
|
|
});
|
|
}
|
|
|
|
return {
|
|
type: input.transport,
|
|
url: input.url,
|
|
headers: input.headers ?? {},
|
|
};
|
|
}
|
|
|
|
protected normalizeServerConfig(
|
|
scope: McpScope,
|
|
name: string,
|
|
rawConfig: unknown,
|
|
): ProviderMcpServer | null {
|
|
if (!rawConfig || typeof rawConfig !== 'object') {
|
|
return null;
|
|
}
|
|
|
|
const config = rawConfig as Record<string, unknown>;
|
|
if (typeof config.command === 'string') {
|
|
return {
|
|
provider: 'gemini',
|
|
name,
|
|
scope,
|
|
transport: 'stdio',
|
|
command: config.command,
|
|
args: readStringArray(config.args),
|
|
env: readStringRecord(config.env),
|
|
cwd: readOptionalString(config.cwd),
|
|
};
|
|
}
|
|
|
|
if (typeof config.url === 'string') {
|
|
const transport = readOptionalString(config.type) === 'sse' ? 'sse' : 'http';
|
|
return {
|
|
provider: 'gemini',
|
|
name,
|
|
scope,
|
|
transport,
|
|
url: config.url,
|
|
headers: readStringRecord(config.headers),
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|