Skip to content

Commit

Permalink
feat: Create all MCP Servers at startup
Browse files Browse the repository at this point in the history
  • Loading branch information
Kadxy committed Dec 28, 2024
1 parent c3108ad commit 664879b
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 165 deletions.
3 changes: 2 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
public/serviceWorker.js
public/serviceWorker.js
app/mcp/mcp_config.json
72 changes: 58 additions & 14 deletions app/mcp/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,76 @@

import { createClient, executeRequest } from "./client";
import { MCPClientLogger } from "./logger";
import { MCP_CONF } from "@/app/mcp/mcp_config";
import conf from "./mcp_config.json";

const logger = new MCPClientLogger("MCP Server");

let fsClient: any = null;
// Use Map to store all clients
const clientsMap = new Map<string, any>();

async function initFileSystemClient() {
if (!fsClient) {
fsClient = await createClient(MCP_CONF.filesystem, "fs");
logger.success("FileSystem client initialized");
// Whether initialized
let initialized = false;

// Store failed clients
let errorClients: string[] = [];

// Initialize all configured clients
export async function initializeMcpClients() {
// If already initialized, return
if (initialized) {
return;
}

logger.info("Starting to initialize MCP clients...");

// Initialize all clients, key is clientId, value is client config
for (const [clientId, config] of Object.entries(conf.mcpServers)) {
try {
logger.info(`Initializing MCP client: ${clientId}`);
const client = await createClient(config, clientId);
clientsMap.set(clientId, client);
logger.success(`Client ${clientId} initialized`);
} catch (error) {
errorClients.push(clientId);
logger.error(`Failed to initialize client ${clientId}: ${error}`);
}
}
return fsClient;
}

export async function executeMcpAction(request: any) {
"use server";
initialized = true;

if (errorClients.length > 0) {
logger.warn(`Failed to initialize clients: ${errorClients.join(", ")}`);
} else {
logger.success("All MCP clients initialized");
}

const availableClients = await getAvailableClients();

logger.info(`Available clients: ${availableClients.join(",")}`);
}

// Execute MCP request
export async function executeMcpAction(clientId: string, request: any) {
try {
if (!fsClient) {
await initFileSystemClient();
// Find the corresponding client
const client = clientsMap.get(clientId);
if (!client) {
logger.error(`Client ${clientId} not found`);
return;
}

logger.info("Executing MCP request for fs");
return await executeRequest(fsClient, request);
logger.info(`Executing MCP request for ${clientId}`);
// Execute request and return result
return await executeRequest(client, request);
} catch (error) {
logger.error(`MCP execution error: ${error}`);
throw error;
}
}

// Get all available client IDs
export async function getAvailableClients() {
return Array.from(clientsMap.keys()).filter(
(clientId) => !errorClients.includes(clientId),
);
}
13 changes: 5 additions & 8 deletions app/mcp/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ export async function createClient(
},
{
capabilities: {
roots: {
// listChanged indicates whether the client will emit notifications when the list of roots changes.
// listChanged 指示客户端在根列表更改时是否发出通知。
listChanged: true,
},
// roots: {
// listChanged: true,
// },
},
},
);
Expand Down Expand Up @@ -80,8 +78,7 @@ export async function listPrimitives(client: Client) {
return primitives;
}

/** Execute a request */
export async function executeRequest(client: Client, request: any) {
const r = client.request(request, z.any());
console.log(r);
return r;
return client.request(request, z.any());
}
73 changes: 6 additions & 67 deletions app/mcp/example.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,28 @@
import { createClient, listPrimitives } from "@/app/mcp/client";
import { MCPClientLogger } from "@/app/mcp/logger";
import { z } from "zod";
import { MCP_CONF } from "@/app/mcp/mcp_config";
import conf from "./mcp_config.json";

const logger = new MCPClientLogger("MCP FS Example", true);

const ListAllowedDirectoriesResultSchema = z.object({
content: z.array(
z.object({
type: z.string(),
text: z.string(),
}),
),
});

const ReadFileResultSchema = z.object({
content: z.array(
z.object({
type: z.string(),
text: z.string(),
}),
),
});
const logger = new MCPClientLogger("MCP Server Example", true);

async function main() {
logger.info("Connecting to server...");

const client = await createClient(MCP_CONF.filesystem, "fs");
const client = await createClient(conf.mcpServers.everything, "everything");
const primitives = await listPrimitives(client);

logger.success(`Connected to server fs`);
logger.success(`Connected to server everything`);

logger.info(
`server capabilities: ${Object.keys(
client.getServerCapabilities() ?? [],
).join(", ")}`,
);

logger.debug("Server supports the following primitives:");
logger.info("Server supports the following primitives:");

primitives.forEach((primitive) => {
logger.debug("\n" + JSON.stringify(primitive, null, 2));
logger.info("\n" + JSON.stringify(primitive, null, 2));
});

const listAllowedDirectories = async () => {
const result = await client.request(
{
method: "tools/call",
params: {
name: "list_allowed_directories",
arguments: {},
},
},
ListAllowedDirectoriesResultSchema,
);
logger.success(`Allowed directories: ${result.content[0].text}`);
return result;
};

const readFile = async (path: string) => {
const result = await client.request(
{
method: "tools/call",
params: {
name: "read_file",
arguments: {
path: path,
},
},
},
ReadFileResultSchema,
);
logger.success(`File contents for ${path}:\n${result.content[0].text}`);
return result;
};

try {
logger.info("Example 1: List allowed directories\n");
await listAllowedDirectories();

logger.info("\nExample 2: Read a file\n");
await readFile("/users/kadxy/desktop/test.txt");
} catch (error) {
logger.error(`Error executing examples: ${error}`);
}
}

main().catch((error) => {
Expand Down
29 changes: 17 additions & 12 deletions app/mcp/logger.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ANSI color codes for terminal output
const colors = {
reset: "\x1b[0m",
bright: "\x1b[1m",
Expand All @@ -21,40 +22,44 @@ export class MCPClientLogger {
}

info(message: any) {
this.log(colors.blue, message);
this.print(colors.blue, message);
}

success(message: any) {
this.log(colors.green, message);
this.print(colors.green, message);
}

error(message: any) {
const formattedMessage = this.formatMessage(message);
console.error(
`${colors.red}${colors.bright}[${this.prefix}]${colors.reset} ${formattedMessage}`,
);
this.print(colors.red, message);
}

warn(message: any) {
this.log(colors.yellow, message);
this.print(colors.yellow, message);
}

debug(message: any) {
if (this.debugMode) {
this.log(colors.dim, message);
this.print(colors.dim, message);
}
}

/**
* Format message to string, if message is object, convert to JSON string
*/
private formatMessage(message: any): string {
return typeof message === "object"
? JSON.stringify(message, null, 2)
: message;
}

private log(color: string, message: any) {
/**
* Print formatted message to console
*/
private print(color: string, message: any) {
const formattedMessage = this.formatMessage(message);
console.log(
`${color}${colors.bright}[${this.prefix}]${colors.reset} ${formattedMessage}`,
);
const logMessage = `${color}${colors.bright}[${this.prefix}]${colors.reset} ${formattedMessage}`;

// 只使用 console.log,这样日志会显示在 Tauri 的终端中
console.log(logMessage);
}
}
16 changes: 16 additions & 0 deletions app/mcp/mcp_config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/Users/kadxy/Desktop"
]
},
"everything": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"]
}
}
}
40 changes: 0 additions & 40 deletions app/mcp/mcp_config.ts

This file was deleted.

5 changes: 3 additions & 2 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { Analytics } from "@vercel/analytics/react";

import { Home } from "./components/home";

import { getServerSideConfig } from "./config/server";
import { initializeMcpClients } from "./mcp/actions";

const serverConfig = getServerSideConfig();

export default async function App() {
await initializeMcpClients();

return (
<>
<Home />
Expand Down
Loading

0 comments on commit 664879b

Please sign in to comment.