feat: MCP market

This commit is contained in:
Kadxy
2025-01-09 19:51:01 +08:00
parent 0c14ce6417
commit 7d51bfd42e
14 changed files with 1607 additions and 30 deletions

View File

@@ -7,15 +7,16 @@ import {
Primitive,
} from "./client";
import { MCPClientLogger } from "./logger";
import conf from "./mcp_config.json";
import { McpRequestMessage } from "./types";
import { McpRequestMessage, McpConfig, ServerConfig } from "./types";
import fs from "fs/promises";
import path from "path";
const logger = new MCPClientLogger("MCP Actions");
// Use Map to store all clients
const clientsMap = new Map<
string,
{ client: Client; primitives: Primitive[] }
{ client: Client | null; primitives: Primitive[]; errorMsg: string | null }
>();
// Whether initialized
@@ -24,27 +25,76 @@ let initialized = false;
// Store failed clients
let errorClients: string[] = [];
const CONFIG_PATH = path.join(process.cwd(), "app/mcp/mcp_config.json");
// 获取 MCP 配置
export async function getMcpConfig(): Promise<McpConfig> {
try {
const configStr = await fs.readFile(CONFIG_PATH, "utf-8");
return JSON.parse(configStr);
} catch (error) {
console.error("Failed to read MCP config:", error);
return { mcpServers: {} };
}
}
// 更新 MCP 配置
export async function updateMcpConfig(config: McpConfig): Promise<void> {
try {
await fs.writeFile(CONFIG_PATH, JSON.stringify(config, null, 2));
} catch (error) {
console.error("Failed to write MCP config:", error);
throw error;
}
}
// 重新初始化所有客户端
export async function reinitializeMcpClients() {
logger.info("Reinitializing MCP clients...");
// 遍历所有客户端,关闭
try {
for (const [clientId, clientData] of clientsMap.entries()) {
clientData.client?.close();
}
} catch (error) {
logger.error(`Failed to close clients: ${error}`);
}
// 清空状态
clientsMap.clear();
errorClients = [];
initialized = false;
// 重新初始化
return initializeMcpClients();
}
// Initialize all configured clients
export async function initializeMcpClients() {
// If already initialized, return
if (initialized) {
return;
return { errorClients };
}
logger.info("Starting to initialize MCP clients...");
errorClients = [];
const config = await getMcpConfig();
// Initialize all clients, key is clientId, value is client config
for (const [clientId, config] of Object.entries(conf.mcpServers)) {
for (const [clientId, serverConfig] of Object.entries(config.mcpServers)) {
try {
logger.info(`Initializing MCP client: ${clientId}`);
const client = await createClient(config, clientId);
const client = await createClient(serverConfig as ServerConfig, clientId);
const primitives = await listPrimitives(client);
clientsMap.set(clientId, { client, primitives });
clientsMap.set(clientId, { client, primitives, errorMsg: null });
logger.success(
`Client [${clientId}] initialized, ${primitives.length} primitives supported`,
);
} catch (error) {
errorClients.push(clientId);
clientsMap.set(clientId, {
client: null,
primitives: [],
errorMsg: error instanceof Error ? error.message : String(error),
});
logger.error(`Failed to initialize client ${clientId}: ${error}`);
}
}
@@ -58,8 +108,9 @@ export async function initializeMcpClients() {
}
const availableClients = await getAvailableClients();
logger.info(`Available clients: ${availableClients.join(",")}`);
return { errorClients };
}
// Execute MCP request
@@ -87,9 +138,9 @@ export async function executeMcpAction(
// Get all available client IDs
export async function getAvailableClients() {
return Array.from(clientsMap.keys()).filter(
(clientId) => !errorClients.includes(clientId),
);
return Array.from(clientsMap.entries())
.filter(([_, data]) => data.errorMsg === null)
.map(([clientId]) => clientId);
}
// Get all primitives from all clients
@@ -104,3 +155,62 @@ export async function getAllPrimitives(): Promise<
primitives,
}));
}
// 获取客户端的 Primitives
export async function getClientPrimitives(clientId: string) {
try {
const clientData = clientsMap.get(clientId);
if (!clientData) {
console.warn(`Client ${clientId} not found in map`);
return null;
}
if (clientData.errorMsg) {
console.warn(`Client ${clientId} has error: ${clientData.errorMsg}`);
return null;
}
return clientData.primitives;
} catch (error) {
console.error(`Failed to get primitives for client ${clientId}:`, error);
return null;
}
}
// 重启所有客户端
export async function restartAllClients() {
logger.info("Restarting all MCP clients...");
// 清空状态
clientsMap.clear();
errorClients = [];
initialized = false;
// 重新初始化
await initializeMcpClients();
return {
success: errorClients.length === 0,
errorClients,
};
}
// 获取所有客户端状态
export async function getAllClientStatus(): Promise<
Record<string, string | null>
> {
const status: Record<string, string | null> = {};
for (const [clientId, data] of clientsMap.entries()) {
status[clientId] = data.errorMsg;
}
return status;
}
// 检查客户端状态
export async function getClientErrors(): Promise<
Record<string, string | null>
> {
const errors: Record<string, string | null> = {};
for (const [clientId, data] of clientsMap.entries()) {
errors[clientId] = data.errorMsg;
}
return errors;
}

View File

@@ -8,13 +8,29 @@
"/Users/kadxy/Desktop"
]
},
"everything": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"]
},
"docker-mcp": {
"command": "uvx",
"args": ["docker-mcp"]
},
"difyworkflow": {
"command": "mcp-difyworkflow-server",
"args": ["-base-url", "23"],
"env": {
"DIFY_WORKFLOW_NAME": "23",
"DIFY_API_KEYS": "23"
}
},
"postgres": {
"command": "docker",
"args": ["run", "-i", "--rm", "mcp/postgres", null]
},
"playwright": {
"command": "npx",
"args": ["-y", "@executeautomation/playwright-mcp-server"]
},
"gdrive": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-gdrive"]
}
}
}

206
app/mcp/preset-server.json Normal file
View File

@@ -0,0 +1,206 @@
[
{
"id": "filesystem",
"name": "Filesystem",
"description": "Secure file operations with configurable access controls",
"command": "npx",
"baseArgs": ["-y", "@modelcontextprotocol/server-filesystem"],
"configurable": true,
"configSchema": {
"properties": {
"paths": {
"type": "array",
"description": "Allowed file system paths",
"required": true,
"minItems": 1
}
}
},
"argsMapping": {
"paths": {
"type": "spread",
"position": 2
}
}
},
{
"id": "github",
"name": "GitHub",
"description": "Repository management, file operations, and GitHub API integration",
"command": "npx",
"baseArgs": ["-y", "@modelcontextprotocol/server-github"],
"configurable": true,
"configSchema": {
"properties": {
"token": {
"type": "string",
"description": "GitHub Personal Access Token",
"required": true
}
}
},
"argsMapping": {
"token": {
"type": "env",
"key": "GITHUB_PERSONAL_ACCESS_TOKEN"
}
}
},
{
"id": "gdrive",
"name": "Google Drive",
"description": "File access and search capabilities for Google Drive",
"command": "npx",
"baseArgs": ["-y", "@modelcontextprotocol/server-gdrive"],
"configurable": false
},
{
"id": "playwright",
"name": "Playwright",
"description": "Browser automation and webscrapping with Playwright",
"command": "npx",
"baseArgs": ["-y", "@executeautomation/playwright-mcp-server"],
"configurable": false
},
{
"id": "mongodb",
"name": "MongoDB",
"description": "Direct interaction with MongoDB databases",
"command": "node",
"baseArgs": ["dist/index.js"],
"configurable": true,
"configSchema": {
"properties": {
"connectionString": {
"type": "string",
"description": "MongoDB connection string",
"required": true
}
}
},
"argsMapping": {
"connectionString": {
"type": "single",
"position": 1
}
}
},
{
"id": "difyworkflow",
"name": "Dify Workflow",
"description": "Tools to query and execute Dify workflows",
"command": "mcp-difyworkflow-server",
"baseArgs": ["-base-url"],
"configurable": true,
"configSchema": {
"properties": {
"baseUrl": {
"type": "string",
"description": "Dify API base URL",
"required": true
},
"workflowName": {
"type": "string",
"description": "Dify workflow name",
"required": true
},
"apiKeys": {
"type": "string",
"description": "Comma-separated Dify API keys",
"required": true
}
}
},
"argsMapping": {
"baseUrl": {
"type": "single",
"position": 1
},
"workflowName": {
"type": "env",
"key": "DIFY_WORKFLOW_NAME"
},
"apiKeys": {
"type": "env",
"key": "DIFY_API_KEYS"
}
}
},
{
"id": "postgres",
"name": "PostgreSQL",
"description": "Read-only database access with schema inspection",
"command": "docker",
"baseArgs": ["run", "-i", "--rm", "mcp/postgres"],
"configurable": true,
"configSchema": {
"properties": {
"connectionString": {
"type": "string",
"description": "PostgreSQL connection string",
"required": true
}
}
},
"argsMapping": {
"connectionString": {
"type": "single",
"position": 4
}
}
},
{
"id": "brave-search",
"name": "Brave Search",
"description": "Web and local search using Brave's Search API",
"command": "npx",
"baseArgs": ["-y", "@modelcontextprotocol/server-brave-search"],
"configurable": true,
"configSchema": {
"properties": {
"apiKey": {
"type": "string",
"description": "Brave Search API Key",
"required": true
}
}
},
"argsMapping": {
"apiKey": {
"type": "env",
"key": "BRAVE_API_KEY"
}
}
},
{
"id": "google-maps",
"name": "Google Maps",
"description": "Location services, directions, and place details",
"command": "npx",
"baseArgs": ["-y", "@modelcontextprotocol/server-google-maps"],
"configurable": true,
"configSchema": {
"properties": {
"apiKey": {
"type": "string",
"description": "Google Maps API Key",
"required": true
}
}
},
"argsMapping": {
"apiKey": {
"type": "env",
"key": "GOOGLE_MAPS_API_KEY"
}
}
},
{
"id": "docker-mcp",
"name": "Docker",
"description": "Run and manage docker containers, docker compose, and logs",
"command": "uvx",
"baseArgs": ["docker-mcp"],
"configurable": false
}
]

View File

@@ -59,3 +59,41 @@ export const McpNotificationsSchema: z.ZodType<McpNotifications> = z.object({
method: z.string(),
params: z.record(z.unknown()).optional(),
});
// MCP 服务器配置相关类型
export interface ServerConfig {
command: string;
args: string[];
env?: Record<string, string>;
}
export interface McpConfig {
mcpServers: Record<string, ServerConfig>;
}
export interface ArgsMapping {
type: "spread" | "single" | "env";
position?: number;
key?: string;
}
export interface PresetServer {
id: string;
name: string;
description: string;
command: string;
baseArgs: string[];
configurable: boolean;
configSchema?: {
properties: Record<
string,
{
type: string;
description?: string;
required?: boolean;
minItems?: number;
}
>;
};
argsMapping?: Record<string, ArgsMapping>;
}

View File

@@ -1,10 +1,10 @@
export function isMcpJson(content: string) {
return content.match(/```json:mcp:(\w+)([\s\S]*?)```/);
return content.match(/```json:mcp:([^{\s]+)([\s\S]*?)```/);
}
export function extractMcpJson(content: string) {
const match = content.match(/```json:mcp:(\w+)([\s\S]*?)```/);
if (match) {
const match = content.match(/```json:mcp:([^{\s]+)([\s\S]*?)```/);
if (match && match.length === 3) {
return { clientId: match[1], mcp: JSON.parse(match[2]) };
}
return null;