@toolprint/hypertool-mcp
Version:
HyperTool MCP proxy server for routing requests between clients and multiple underlying MCP servers
206 lines • 6.63 kB
JavaScript
/**
* Base Hypertool MCP server implementation
* Provides core MCP server functionality with transport abstraction
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createChildLogger } from "../utils/logging.js";
const logger = createChildLogger({ module: "server/base" });
import { ListToolsRequestSchema, CallToolRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
import { ServerState, } from "./types.js";
import { EventEmitter } from "events";
import { McpHttpServer } from "./http-server.js";
/**
* Base Hypertool MCP server class
* Handles MCP protocol implementation with pluggable transport layer
*/
export class MetaMCPServer extends EventEmitter {
server;
stdioTransport = null;
httpServer;
config;
state = ServerState.STOPPED;
startTime;
connectedClients = 0;
constructor(config) {
super();
this.config = config;
// Initialize MCP server
this.server = new Server({
name: config.name,
version: config.version,
description: config.description,
}, {
capabilities: {
tools: {},
},
});
this.setupHandlers();
}
/**
* Setup MCP protocol handlers
*/
setupHandlers() {
// Handle list_tools requests
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: await this.getAvailableTools(),
};
});
// Handle call_tool requests
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
return await this.handleToolCall(request.params.name, request.params.arguments);
});
}
/**
* Get available tools from underlying servers
* To be implemented by subclasses
*/
async getAvailableTools() {
return [];
}
/**
* Handle tool call requests
* To be implemented by subclasses
*/
async handleToolCall(name,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_args) {
throw new Error(`Tool "${name}" not implemented`);
}
/**
* Initialize and start the server
*/
async start(options) {
if (this.state !== ServerState.STOPPED) {
throw new Error(`Server is already ${this.state}`);
}
try {
this.setState(ServerState.STARTING);
this.startTime = new Date();
// Initialize transport layer
if (options.transport.type === "stdio") {
this.stdioTransport = new StdioServerTransport();
await this.server.connect(this.stdioTransport);
}
else if (options.transport.type === "http") {
await this.setupHttpServer(options.transport);
}
else {
throw new Error(`Unsupported transport type: ${options.transport.type}`);
}
this.setState(ServerState.RUNNING);
this.emit("started");
}
catch (error) {
this.setState(ServerState.ERROR);
this.emit("error", error);
throw error;
}
}
/**
* Setup Express.js HTTP server for streamable MCP transport
*/
async setupHttpServer(transport) {
const host = transport.host || "localhost";
const port = transport.port || 3000;
this.httpServer = new McpHttpServer(this.server, port, host);
await this.httpServer.start();
this.onClientConnected();
}
/**
* Stop the server
*/
async stop() {
if (this.state === ServerState.STOPPED) {
return;
}
try {
this.setState(ServerState.STOPPING);
if (this.stdioTransport) {
await this.stdioTransport.close();
this.stdioTransport = null;
}
if (this.httpServer) {
await this.httpServer.stop();
this.httpServer = undefined;
}
this.setState(ServerState.STOPPED);
this.emit("stopped");
}
catch (error) {
this.setState(ServerState.ERROR);
this.emit("error", error);
throw error;
}
}
/**
* Get current server status
*/
getStatus() {
const status = {
state: this.state,
connectedClients: this.connectedClients,
};
if (this.startTime && this.state === ServerState.RUNNING) {
status.uptime = Date.now() - this.startTime.getTime();
}
return status;
}
/**
* Get server configuration
*/
getConfig() {
return { ...this.config };
}
/**
* Send tools list changed notification to clients
*/
async notifyToolsChanged() {
if (!this.server) {
return;
}
const toolsChangedNotification = {
method: "notifications/tools/list_changed",
};
try {
logger.info("Sending tools list changed notification");
if (this.stdioTransport) {
// stdio transport - send to single client
await this.server.notification(toolsChangedNotification);
logger.debug("Tools list changed notification sent via stdio transport");
}
else if (this.httpServer) {
// HTTP transport - broadcast to all connected clients
await this.httpServer.broadcastNotification(toolsChangedNotification);
logger.debug("Tools list changed notification broadcasted via HTTP transport");
}
}
catch (error) {
logger.error("Failed to send tools list changed notification:", error);
}
}
/**
* Set server state and emit event
*/
setState(state) {
const oldState = this.state;
this.state = state;
this.emit("stateChanged", { from: oldState, to: state });
}
/**
* Handle client connection events
*/
onClientConnected() {
this.connectedClients++;
this.emit("clientConnected", { count: this.connectedClients });
}
/**
* Handle client disconnection events
*/
onClientDisconnected() {
this.connectedClients = Math.max(0, this.connectedClients - 1);
this.emit("clientDisconnected", { count: this.connectedClients });
}
}
//# sourceMappingURL=base.js.map