blade-ai
Version:
🗡️ Blade - 智能 AI 助手命令行工具
1,314 lines (1,304 loc) • 36.1 kB
JavaScript
import {
__require
} from "./chunk-7N7GSU6K.js";
// src/mcp/client/MCPClient.ts
import { EventEmitter } from "events";
import WebSocket from "ws";
var MCPClient = class extends EventEmitter {
constructor() {
super();
this.sessions = /* @__PURE__ */ new Map();
this.connections = /* @__PURE__ */ new Map();
this.messageId = 0;
this.clientInfo = {
name: "blade-ai",
version: "1.2.5",
capabilities: {
sampling: {}
}
};
}
/**
* 连接到 MCP 服务器
*/
async connect(config) {
const sessionId = `${config.name}-${Date.now()}`;
try {
let connection;
switch (config.transport) {
case "ws":
connection = await this.connectWebSocket(config);
break;
case "stdio":
connection = await this.connectStdio(config);
break;
case "sse":
throw new Error("SSE transport not implemented yet");
default:
throw new Error(`Unsupported transport: ${config.transport}`);
}
const session = {
id: sessionId,
config,
connected: true,
lastActivity: /* @__PURE__ */ new Date()
};
this.sessions.set(sessionId, session);
this.connections.set(sessionId, connection);
await this.performHandshake(sessionId);
this.emit("connected", session);
return session;
} catch (error) {
this.emit("error", error);
throw error;
}
}
/**
* 断开连接
*/
async disconnect(sessionId) {
const session = this.sessions.get(sessionId);
const connection = this.connections.get(sessionId);
if (session) {
session.connected = false;
}
if (connection) {
if (connection instanceof WebSocket) {
connection.close();
} else if (connection.kill) {
connection.kill();
}
this.connections.delete(sessionId);
}
this.sessions.delete(sessionId);
this.emit("disconnected", sessionId);
}
/**
* 获取所有会话
*/
getSessions() {
return Array.from(this.sessions.values());
}
/**
* 获取指定会话
*/
getSession(sessionId) {
return this.sessions.get(sessionId);
}
/**
* 列出资源
*/
async listResources(sessionId) {
const response = await this.sendRequest(sessionId, {
method: "resources/list",
params: {}
});
return response.resources || [];
}
/**
* 读取资源内容
*/
async readResource(sessionId, uri) {
const response = await this.sendRequest(sessionId, {
method: "resources/read",
params: { uri }
});
return response.contents[0];
}
/**
* 列出工具
*/
async listTools(sessionId) {
const response = await this.sendRequest(sessionId, {
method: "tools/list",
params: {}
});
return response.tools || [];
}
/**
* 调用工具
*/
async callTool(sessionId, toolCall) {
const response = await this.sendRequest(sessionId, {
method: "tools/call",
params: {
name: toolCall.name,
arguments: toolCall.arguments
}
});
return response;
}
/**
* 发送请求到 MCP 服务器
*/
async sendRequest(sessionId, request) {
const connection = this.connections.get(sessionId);
const session = this.sessions.get(sessionId);
if (!connection || !session?.connected) {
throw new Error(`Session ${sessionId} is not connected`);
}
const message = {
jsonrpc: "2.0",
id: ++this.messageId,
...request
};
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Request timeout"));
}, session.config.timeout || 3e4);
const handleMessage = (data) => {
try {
const response = typeof data === "string" ? JSON.parse(data) : data;
if (response.id === message.id) {
clearTimeout(timeout);
connection.off?.("message", handleMessage);
if (response.error) {
reject(new Error(response.error.message));
} else {
resolve(response.result);
}
}
} catch (error) {
reject(error);
}
};
if (connection instanceof WebSocket) {
connection.on("message", handleMessage);
connection.send(JSON.stringify(message));
} else {
const responseHandler = (data) => {
try {
const response = JSON.parse(data.toString().trim());
if (response.id === message.id) {
clearTimeout(timeout);
connection.stdout?.off("data", responseHandler);
if (response.error) {
reject(new Error(response.error.message));
} else {
resolve(response.result);
}
}
} catch (error) {
}
};
connection.stdout?.on("data", responseHandler);
connection.stdin?.write(JSON.stringify(message) + "\n");
}
});
}
/**
* WebSocket 连接
*/
async connectWebSocket(config) {
if (!config.endpoint) {
throw new Error("WebSocket endpoint is required");
}
const ws = new WebSocket(config.endpoint);
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Connection timeout"));
}, config.timeout || 1e4);
ws.on("open", () => {
clearTimeout(timeout);
resolve(ws);
});
ws.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
ws.on("message", (data) => {
this.handleMessage(data.toString());
});
});
}
/**
* Stdio 连接
*/
async connectStdio(config) {
const { spawn } = await import("child_process");
if (!config.command) {
throw new Error("Command is required for stdio transport");
}
const childProcess = spawn(config.command, config.args || [], {
stdio: ["pipe", "pipe", "pipe"],
env: { ...process.env, ...config.env }
});
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error("Process start timeout"));
}, config.timeout || 1e4);
childProcess.on("spawn", () => {
clearTimeout(timeout);
resolve(childProcess);
});
childProcess.on("error", (error) => {
clearTimeout(timeout);
reject(error);
});
childProcess.stdout?.on("data", (data) => {
this.handleMessage(data.toString());
});
});
}
/**
* 处理消息
*/
handleMessage(data) {
try {
const message = JSON.parse(data);
this.emit("message", message);
} catch (error) {
this.emit("error", new Error(`Invalid JSON message: ${data}`));
}
}
/**
* 执行握手
*/
async performHandshake(sessionId) {
try {
const response = await this.sendRequest(sessionId, {
method: "initialize",
params: {
protocolVersion: "2024-11-05",
capabilities: this.clientInfo.capabilities,
clientInfo: {
name: this.clientInfo.name,
version: this.clientInfo.version
}
}
});
const session = this.sessions.get(sessionId);
if (session) {
session.serverInfo = {
name: response.serverInfo?.name || "Unknown",
version: response.serverInfo?.version || "0.0.0",
capabilities: response.capabilities || {}
};
}
const connection = this.connections.get(sessionId);
if (connection instanceof WebSocket) {
connection.send(
JSON.stringify({
jsonrpc: "2.0",
method: "notifications/initialized"
})
);
} else {
connection.stdin?.write(
JSON.stringify({
jsonrpc: "2.0",
method: "notifications/initialized"
}) + "\n"
);
}
} catch (error) {
throw new Error(`Handshake failed: ${error}`);
}
}
};
// src/mcp/server/MCPServer.ts
import { EventEmitter as EventEmitter3 } from "events";
import { createServer } from "http";
import { WebSocketServer } from "ws";
// src/tools/ToolManager.ts
import { randomUUID } from "crypto";
import { EventEmitter as EventEmitter2 } from "events";
// src/tools/types.ts
var ToolValidationError = class extends Error {
constructor(message, field, value) {
super(message);
this.field = field;
this.value = value;
this.name = "ToolValidationError";
}
};
var ToolExecutionError = class extends Error {
constructor(message, toolName, originalError) {
super(message);
this.toolName = toolName;
this.originalError = originalError;
this.name = "ToolExecutionError";
}
};
var ToolRegistrationError = class extends Error {
constructor(message, toolName) {
super(message);
this.toolName = toolName;
this.name = "ToolRegistrationError";
}
};
// src/tools/validator.ts
var ToolValidator = class {
/**
* 验证工具参数
*/
static validateParameters(parameters, schema, required = []) {
for (const requiredParam of required) {
if (!(requiredParam in parameters) || parameters[requiredParam] === void 0) {
throw new ToolValidationError(`\u7F3A\u5C11\u5FC5\u9700\u53C2\u6570: ${requiredParam}`, requiredParam);
}
}
for (const [key, value] of Object.entries(parameters)) {
const paramSchema = schema[key];
if (!paramSchema) {
throw new ToolValidationError(`\u672A\u77E5\u53C2\u6570: ${key}`, key, value);
}
this.validateValue(value, paramSchema, key);
}
}
/**
* 验证单个值
*/
static validateValue(value, schema, fieldPath) {
if (!this.isValidType(value, schema.type)) {
throw new ToolValidationError(
`\u53C2\u6570 ${fieldPath} \u7C7B\u578B\u9519\u8BEF\uFF0C\u671F\u671B: ${schema.type}\uFF0C\u5B9E\u9645: ${typeof value}`,
fieldPath,
value
);
}
if (schema.enum && !schema.enum.includes(value)) {
throw new ToolValidationError(
`\u53C2\u6570 ${fieldPath} \u503C\u5FC5\u987B\u662F: ${schema.enum.join(", ")} \u4E2D\u7684\u4E00\u4E2A`,
fieldPath,
value
);
}
if (schema.type === "array" && Array.isArray(value) && schema.items) {
value.forEach((item, index) => {
this.validateValue(item, schema.items, `${fieldPath}[${index}]`);
});
}
if (schema.type === "object" && value && typeof value === "object" && schema.properties) {
for (const [key, subValue] of Object.entries(value)) {
const subSchema = schema.properties[key];
if (subSchema) {
this.validateValue(subValue, subSchema, `${fieldPath}.${key}`);
}
}
}
}
/**
* 检查值类型是否匹配
*/
static isValidType(value, expectedType) {
if (value === null || value === void 0) {
return true;
}
switch (expectedType) {
case "string":
return typeof value === "string";
case "number":
return typeof value === "number" && !isNaN(value);
case "boolean":
return typeof value === "boolean";
case "array":
return Array.isArray(value);
case "object":
return typeof value === "object" && !Array.isArray(value);
default:
return false;
}
}
/**
* 应用默认值
*/
static applyDefaults(parameters, schema) {
const result = { ...parameters };
for (const [key, paramSchema] of Object.entries(schema)) {
if (!(key in result) && paramSchema.default !== void 0) {
result[key] = paramSchema.default;
}
}
return result;
}
/**
* 清理参数(移除未定义的参数)
*/
static sanitizeParameters(parameters, schema) {
const result = {};
for (const [key, value] of Object.entries(parameters)) {
if (key in schema && value !== void 0) {
result[key] = value;
}
}
return result;
}
/**
* 生成参数文档
*/
static generateDocumentation(schema, required = []) {
const docs = [];
docs.push("\u53C2\u6570\u8BF4\u660E:");
for (const [key, paramSchema] of Object.entries(schema)) {
const isRequired = required.includes(key);
const requiredMark = isRequired ? " *" : "";
const defaultValue = paramSchema.default !== void 0 ? ` (\u9ED8\u8BA4: ${paramSchema.default})` : "";
const enumValues = paramSchema.enum ? ` (\u53EF\u9009\u503C: ${paramSchema.enum.join(", ")})` : "";
docs.push(` ${key}${requiredMark}: ${paramSchema.type}${defaultValue}${enumValues}`);
if (paramSchema.description) {
docs.push(` ${paramSchema.description}`);
}
}
if (required.length > 0) {
docs.push("");
docs.push("* \u8868\u793A\u5FC5\u9700\u53C2\u6570");
}
return docs.join("\n");
}
};
// src/tools/ToolManager.ts
var ToolManager = class extends EventEmitter2 {
constructor(config = {}) {
super();
this.tools = /* @__PURE__ */ new Map();
this.toolStates = /* @__PURE__ */ new Map();
this.executionHistory = [];
this.runningExecutions = /* @__PURE__ */ new Map();
this.config = {
debug: false,
maxConcurrency: 10,
executionTimeout: 3e4,
// 30秒
logHistory: true,
maxHistorySize: 1e3,
...config
};
this.log("\u5DE5\u5177\u7BA1\u7406\u5668\u5DF2\u521D\u59CB\u5316", { config: this.config });
}
/**
* 注册工具
*/
async registerTool(tool, options = {}) {
try {
this.validateToolDefinition(tool);
if (this.tools.has(tool.name) && !options.override) {
throw new ToolRegistrationError(
`\u5DE5\u5177 "${tool.name}" \u5DF2\u5B58\u5728\uFF0C\u4F7F\u7528 override: true \u5F3A\u5236\u8986\u76D6`,
tool.name
);
}
this.tools.set(tool.name, tool);
this.toolStates.set(tool.name, {
enabled: options.enabled ?? true,
permissions: options.permissions ?? []
});
this.log(`\u5DE5\u5177 "${tool.name}" \u6CE8\u518C\u6210\u529F`, {
version: tool.version,
category: tool.category,
enabled: options.enabled ?? true
});
this.emit("toolRegistered", {
toolName: tool.name,
tool,
options
});
} catch (error) {
this.log(`\u5DE5\u5177 "${tool.name}" \u6CE8\u518C\u5931\u8D25`, { error: error.message });
throw error;
}
}
/**
* 注销工具
*/
unregisterTool(toolName) {
const existed = this.tools.has(toolName);
if (existed) {
this.tools.delete(toolName);
this.toolStates.delete(toolName);
this.log(`\u5DE5\u5177 "${toolName}" \u5DF2\u6CE8\u9500`);
this.emit("toolUnregistered", { toolName });
}
return existed;
}
/**
* 获取所有已注册的工具
*/
getTools() {
return Array.from(this.tools.values());
}
/**
* 获取特定工具
*/
getTool(toolName) {
return this.tools.get(toolName);
}
/**
* 检查工具是否存在
*/
hasTool(toolName) {
return this.tools.has(toolName);
}
/**
* 启用/禁用工具
*/
setToolEnabled(toolName, enabled) {
const state = this.toolStates.get(toolName);
if (!state) {
throw new ToolRegistrationError(`\u5DE5\u5177 "${toolName}" \u4E0D\u5B58\u5728`, toolName);
}
state.enabled = enabled;
this.log(`\u5DE5\u5177 "${toolName}" ${enabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u7981\u7528"}`);
this.emit("toolStateChanged", { toolName, enabled });
}
/**
* 检查工具是否启用
*/
isToolEnabled(toolName) {
const state = this.toolStates.get(toolName);
return state?.enabled ?? false;
}
/**
* 调用工具
*/
async callTool(request) {
const requestId = randomUUID();
const startTime = Date.now();
try {
if (this.runningExecutions.size >= this.config.maxConcurrency) {
throw new ToolExecutionError("\u8FBE\u5230\u6700\u5927\u5E76\u53D1\u6267\u884C\u9650\u5236", request.toolName);
}
const tool = this.tools.get(request.toolName);
if (!tool) {
throw new ToolExecutionError(`\u5DE5\u5177 "${request.toolName}" \u4E0D\u5B58\u5728`, request.toolName);
}
if (!this.isToolEnabled(request.toolName)) {
throw new ToolExecutionError(`\u5DE5\u5177 "${request.toolName}" \u5DF2\u7981\u7528`, request.toolName);
}
const context = {
executionId: requestId,
timestamp: startTime,
...request.context
};
let processedParams = ToolValidator.applyDefaults(request.parameters, tool.parameters);
processedParams = ToolValidator.sanitizeParameters(processedParams, tool.parameters);
ToolValidator.validateParameters(processedParams, tool.parameters, tool.required);
this.log(`\u5F00\u59CB\u6267\u884C\u5DE5\u5177 "${request.toolName}"`, {
requestId,
parameters: processedParams
});
this.emit("toolCallStarted", {
requestId,
toolName: request.toolName,
parameters: processedParams,
context
});
const executionPromise = this.executeToolWithTimeout(tool, processedParams);
this.runningExecutions.set(requestId, executionPromise);
const result = await executionPromise;
result.duration = Date.now() - startTime;
this.log(`\u5DE5\u5177 "${request.toolName}" \u6267\u884C\u5B8C\u6210`, {
requestId,
duration: result.duration,
success: result.success
});
const response = {
requestId,
toolName: request.toolName,
result,
context
};
if (this.config.logHistory) {
this.addToHistory({
executionId: requestId,
toolName: request.toolName,
parameters: processedParams,
result,
context,
createdAt: /* @__PURE__ */ new Date()
});
}
this.emit("toolCallCompleted", response);
return response;
} catch (error) {
const result = {
success: false,
error: error.message,
duration: Date.now() - startTime
};
const response = {
requestId,
toolName: request.toolName,
result,
context: {
executionId: requestId,
timestamp: startTime,
...request.context
}
};
this.log(`\u5DE5\u5177 "${request.toolName}" \u6267\u884C\u5931\u8D25`, {
requestId,
error: error.message
});
this.emit("toolCallFailed", { ...response, error });
return response;
} finally {
this.runningExecutions.delete(requestId);
}
}
/**
* 获取执行历史
*/
getExecutionHistory(limit) {
const history = [...this.executionHistory];
return limit ? history.slice(-limit) : history;
}
/**
* 清空执行历史
*/
clearHistory() {
this.executionHistory = [];
this.log("\u6267\u884C\u5386\u53F2\u5DF2\u6E05\u7A7A");
}
/**
* 获取工具统计信息
*/
getStats() {
const stats = {
totalTools: this.tools.size,
enabledTools: 0,
runningExecutions: this.runningExecutions.size,
totalExecutions: this.executionHistory.length,
successfulExecutions: 0,
failedExecutions: 0
};
for (const state of this.toolStates.values()) {
if (state.enabled) {
stats.enabledTools++;
}
}
for (const history of this.executionHistory) {
if (history.result.success) {
stats.successfulExecutions++;
} else {
stats.failedExecutions++;
}
}
return stats;
}
/**
* 验证工具定义
*/
validateToolDefinition(tool) {
if (!tool.name || typeof tool.name !== "string") {
throw new ToolRegistrationError("\u5DE5\u5177\u540D\u79F0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
}
if (!tool.description || typeof tool.description !== "string") {
throw new ToolRegistrationError("\u5DE5\u5177\u63CF\u8FF0\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
}
if (!tool.parameters || typeof tool.parameters !== "object") {
throw new ToolRegistrationError("\u5DE5\u5177\u53C2\u6570\u5B9A\u4E49\u5FC5\u987B\u662F\u5BF9\u8C61");
}
if (typeof tool.execute !== "function") {
throw new ToolRegistrationError("\u5DE5\u5177\u6267\u884C\u51FD\u6570\u5FC5\u987B\u662F\u51FD\u6570");
}
}
/**
* 执行工具并设置超时
*/
async executeToolWithTimeout(tool, parameters) {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(
new ToolExecutionError(`\u5DE5\u5177\u6267\u884C\u8D85\u65F6 (${this.config.executionTimeout}ms)`, tool.name)
);
}, this.config.executionTimeout);
Promise.resolve(tool.execute(parameters)).then((result) => {
clearTimeout(timeoutId);
const duration = Date.now() - startTime;
if (result && typeof result === "object" && "success" in result) {
resolve({
...result,
duration: result.duration || duration
});
} else {
resolve({
success: true,
data: result,
duration
});
}
}).catch((error) => {
clearTimeout(timeoutId);
reject(new ToolExecutionError(`\u5DE5\u5177\u6267\u884C\u9519\u8BEF: ${error.message}`, tool.name, error));
});
});
}
/**
* 添加到历史记录
*/
addToHistory(history) {
this.executionHistory.push(history);
if (this.executionHistory.length > this.config.maxHistorySize) {
this.executionHistory = this.executionHistory.slice(-this.config.maxHistorySize);
}
}
/**
* 日志记录
*/
log(message, data) {
if (this.config.debug) {
console.log(`[ToolManager] ${message}`, data || "");
}
}
};
// src/mcp/server/MCPServer.ts
var MCPServer = class extends EventEmitter3 {
constructor(config, toolManager) {
super();
this.config = config;
this.clients = /* @__PURE__ */ new Map();
this.messageId = 0;
this.serverInfo = {
name: "blade-ai-server",
version: "1.2.5",
capabilities: {
resources: {
subscribe: true,
listChanged: true
},
tools: {
listChanged: false
},
prompts: {
listChanged: false
}
}
};
this.toolManager = toolManager || new ToolManager();
}
/**
* 启动服务器
*/
async start() {
const port = this.config.port || 3001;
const host = this.config.host || "localhost";
if (this.config.transport === "ws") {
this.server = createServer();
this.wsServer = new WebSocketServer({ server: this.server });
this.wsServer.on("connection", (ws, request) => {
this.handleConnection(ws, request);
});
this.server.listen(port, host, () => {
console.log(`MCP Server listening on ws://${host}:${port}`);
this.emit("started", { host, port });
});
} else if (this.config.transport === "stdio") {
this.handleStdioConnection();
}
}
/**
* 停止服务器
*/
async stop() {
if (this.wsServer) {
this.wsServer.close();
}
if (this.server) {
this.server.close();
}
this.clients.clear();
this.emit("stopped");
}
/**
* 处理 WebSocket 连接
*/
handleConnection(ws, request) {
const clientId = `client-${Date.now()}-${Math.random()}`;
this.clients.set(clientId, ws);
console.log(`Client connected: ${clientId}`);
ws.on("message", async (data) => {
try {
const message = JSON.parse(data.toString());
await this.handleMessage(clientId, message);
} catch (error) {
this.sendError(clientId, -32700, "Parse error", error);
}
});
ws.on("close", () => {
this.clients.delete(clientId);
console.log(`Client disconnected: ${clientId}`);
});
ws.on("error", (error) => {
console.error(`WebSocket error for ${clientId}:`, error);
this.clients.delete(clientId);
});
}
/**
* 处理 Stdio 连接
*/
handleStdioConnection() {
process.stdin.on("data", async (data) => {
try {
const lines = data.toString().trim().split("\n");
for (const line of lines) {
if (line.trim()) {
const message = JSON.parse(line);
await this.handleMessage("stdio", message);
}
}
} catch (error) {
this.sendErrorToStdio(-32700, "Parse error", error);
}
});
console.log("MCP Server listening on stdio");
this.emit("started", { transport: "stdio" });
}
/**
* 处理消息
*/
async handleMessage(clientId, message) {
try {
switch (message.method) {
case "initialize":
await this.handleInitialize(clientId, message);
break;
case "notifications/initialized":
break;
case "resources/list":
await this.handleListResources(clientId, message);
break;
case "resources/read":
await this.handleReadResource(clientId, message);
break;
case "tools/list":
await this.handleListTools(clientId, message);
break;
case "tools/call":
await this.handleCallTool(clientId, message);
break;
case "prompts/list":
await this.handleListPrompts(clientId, message);
break;
case "prompts/get":
await this.handleGetPrompt(clientId, message);
break;
default:
this.sendError(clientId, -32601, `Unknown method: ${message.method}`);
}
} catch (error) {
this.sendError(clientId, -32603, "Internal error", error);
}
}
/**
* 处理初始化请求
*/
async handleInitialize(clientId, message) {
const clientInfo = message.params?.clientInfo;
this.sendResponse(clientId, message.id, {
protocolVersion: "2024-11-05",
capabilities: this.serverInfo.capabilities,
serverInfo: {
name: this.serverInfo.name,
version: this.serverInfo.version
}
});
console.log(
`Client initialized: ${clientInfo?.name || "Unknown"} v${clientInfo?.version || "0.0.0"}`
);
}
/**
* 处理列出资源
*/
async handleListResources(clientId, message) {
const resources = [
{
uri: "file://workspace",
name: "Current Workspace",
description: "Files and directories in the current workspace",
mimeType: "application/json"
},
{
uri: "git://status",
name: "Git Status",
description: "Current git repository status",
mimeType: "application/json"
},
{
uri: "git://log",
name: "Git Log",
description: "Recent git commits",
mimeType: "application/json"
}
];
this.sendResponse(clientId, message.id, { resources });
}
/**
* 处理读取资源
*/
async handleReadResource(clientId, message) {
const uri = message.params?.uri;
try {
let content;
if (uri === "file://workspace") {
const { readdir } = await import("fs/promises");
const files = await readdir(process.cwd());
content = {
uri,
mimeType: "application/json",
text: JSON.stringify(files, null, 2)
};
} else if (uri === "git://status") {
const { execSync } = await import("child_process");
const status = execSync("git status --porcelain", { encoding: "utf-8" });
content = {
uri,
mimeType: "text/plain",
text: status
};
} else if (uri === "git://log") {
const { execSync } = await import("child_process");
const log = execSync("git log --oneline -10", { encoding: "utf-8" });
content = {
uri,
mimeType: "text/plain",
text: log
};
} else {
throw new Error(`Unknown resource URI: ${uri}`);
}
this.sendResponse(clientId, message.id, { contents: [content] });
} catch (error) {
this.sendError(clientId, -32603, `Failed to read resource: ${uri}`, error);
}
}
/**
* 处理列出工具
*/
async handleListTools(clientId, message) {
const bladeTools = this.toolManager.getTools();
const tools = bladeTools.map((tool) => ({
name: tool.name,
description: tool.description,
inputSchema: {
type: "object",
properties: tool.parameters || {},
required: tool.required || []
}
}));
this.sendResponse(clientId, message.id, { tools });
}
/**
* 处理工具调用
*/
async handleCallTool(clientId, message) {
const toolName = message.params?.name;
const toolArgs = message.params?.arguments;
try {
const response = await this.toolManager.callTool({
toolName,
parameters: toolArgs,
context: {
executionId: `mcp-${Date.now()}`,
timestamp: Date.now()
}
});
const mcpResponse = {
content: [
{
type: "text",
text: typeof response.result.data === "string" ? response.result.data : JSON.stringify(response.result.data, null, 2)
}
],
isError: !response.result.success
};
this.sendResponse(clientId, message.id, mcpResponse);
} catch (error) {
const response = {
content: [
{
type: "text",
text: error instanceof Error ? error.message : String(error)
}
],
isError: true
};
this.sendResponse(clientId, message.id, response);
}
}
/**
* 处理列出提示
*/
async handleListPrompts(clientId, message) {
const prompts = [
{
name: "code_review",
description: "Review code for quality, security, and best practices",
arguments: [
{
name: "file_path",
description: "Path to the file to review",
required: true
}
]
},
{
name: "generate_docs",
description: "Generate documentation for code",
arguments: [
{
name: "file_path",
description: "Path to the file to document",
required: true
}
]
}
];
this.sendResponse(clientId, message.id, { prompts });
}
/**
* 处理获取提示
*/
async handleGetPrompt(clientId, message) {
const promptName = message.params?.name;
const args = message.params?.arguments;
this.sendResponse(clientId, message.id, {
description: `Generated prompt for ${promptName}`,
messages: [
{
role: "user",
content: {
type: "text",
text: `Please ${promptName} for the following parameters: ${JSON.stringify(args)}`
}
}
]
});
}
/**
* 发送响应
*/
sendResponse(clientId, messageId, result) {
const response = {
jsonrpc: "2.0",
id: messageId,
result
};
if (clientId === "stdio") {
process.stdout.write(JSON.stringify(response) + "\n");
} else {
const client = this.clients.get(clientId);
if (client) {
client.send(JSON.stringify(response));
}
}
}
/**
* 发送错误
*/
sendError(clientId, code, message, data) {
const response = {
jsonrpc: "2.0",
error: {
code,
message,
data
}
};
if (clientId === "stdio") {
process.stdout.write(JSON.stringify(response) + "\n");
} else {
const client = this.clients.get(clientId);
if (client) {
client.send(JSON.stringify(response));
}
}
}
/**
* 发送错误到 Stdio
*/
sendErrorToStdio(code, message, data) {
this.sendError("stdio", code, message, data);
}
};
// src/mcp/config/MCPConfig.ts
import { existsSync, readFileSync, writeFileSync } from "fs";
import { homedir } from "os";
import { join } from "path";
var MCPConfig = class {
constructor(configPath) {
this.configPath = configPath || join(homedir(), ".blade", "mcp-config.json");
this.loadConfig();
}
/**
* 加载配置
*/
loadConfig() {
if (existsSync(this.configPath)) {
try {
const content = readFileSync(this.configPath, "utf-8");
this.config = JSON.parse(content);
} catch (error) {
console.warn("Failed to load MCP config, using defaults");
this.config = this.getDefaultConfig();
}
} else {
this.config = this.getDefaultConfig();
}
this.config = {
...this.getDefaultConfig(),
...this.config,
servers: {
...this.getDefaultConfig().servers,
...this.config.servers
}
};
}
/**
* 保存配置
*/
saveConfig() {
try {
const dir = this.configPath.substring(0, this.configPath.lastIndexOf("/"));
if (!existsSync(dir)) {
const { mkdirSync } = __require("fs");
mkdirSync(dir, { recursive: true });
}
writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
} catch (error) {
throw new Error(`Failed to save MCP config: ${error}`);
}
}
/**
* 获取默认配置
*/
getDefaultConfig() {
return {
servers: {},
client: {
timeout: 3e4,
retryAttempts: 3,
retryDelay: 1e3
},
server: {
port: 3001,
host: "localhost",
transport: "ws",
auth: {
enabled: false
}
}
};
}
/**
* 添加服务器配置
*/
addServer(name, config) {
this.config.servers[name] = config;
this.saveConfig();
}
/**
* 移除服务器配置
*/
removeServer(name) {
delete this.config.servers[name];
this.saveConfig();
}
/**
* 获取服务器配置
*/
getServer(name) {
return this.config.servers[name];
}
/**
* 获取所有服务器配置
*/
getServers() {
return this.config.servers;
}
/**
* 更新客户端配置
*/
updateClientConfig(config) {
this.config.client = {
...this.config.client,
...config
};
this.saveConfig();
}
/**
* 获取客户端配置
*/
getClientConfig() {
return this.config.client;
}
/**
* 更新服务器配置
*/
updateServerConfig(config) {
this.config.server = {
...this.config.server,
...config
};
this.saveConfig();
}
/**
* 获取服务器配置
*/
getServerConfig() {
return this.config.server;
}
/**
* 验证服务器配置
*/
validateServerConfig(config) {
const errors = [];
if (!config.name) {
errors.push("Server name is required");
}
if (!config.transport) {
errors.push("Transport type is required");
}
if (config.transport === "ws" && !config.endpoint) {
errors.push("WebSocket endpoint is required for ws transport");
}
if (config.transport === "stdio" && !config.command) {
errors.push("Command is required for stdio transport");
}
if (config.timeout && config.timeout < 1e3) {
errors.push("Timeout must be at least 1000ms");
}
return errors;
}
/**
* 导出配置
*/
exportConfig() {
return JSON.parse(JSON.stringify(this.config));
}
/**
* 导入配置
*/
importConfig(config) {
this.config = {
...this.getDefaultConfig(),
...config,
servers: {
...this.getDefaultConfig().servers,
...config.servers
}
};
this.saveConfig();
}
/**
* 重置为默认配置
*/
reset() {
this.config = this.getDefaultConfig();
this.saveConfig();
}
};
var mcpConfig = new MCPConfig();
export {
MCPClient,
ToolValidationError,
ToolExecutionError,
ToolRegistrationError,
ToolValidator,
ToolManager,
MCPServer,
MCPConfig,
mcpConfig
};