auto-publishing-mcp-server
Version:
Enterprise-grade MCP Server for Auto-Publishing with pre-publish validation, multi-cloud deployment, and monitoring
531 lines (456 loc) • 13.2 kB
JavaScript
/**
* MCP Protocol Handler
* Core MCP protocol implementation for auto-publishing server
*/
import { v4 as uuidv4 } from 'uuid';
import {
getServerCapabilities,
validateClientCapabilities,
negotiateCapabilities,
SERVER_INFO
} from './capabilities.js';
import { ErrorCreators } from '../utils/errors.js';
import { validateInitializeParams, validateToolCallParams } from '../utils/validation.js';
export class McpHandler {
constructor() {
this.sessions = new Map();
this.tools = new Map();
this.resources = new Map();
this.prompts = new Map();
this.subscriptions = new Map();
this.isInitialized = false;
// Initialize built-in tools
this.initializeBuiltinTools();
this.initializeBuiltinResources();
this.initializeBuiltinPrompts();
}
/**
* Handles MCP initialize request
*/
async handleInitialize(params, message) {
try {
validateInitializeParams(params);
const sessionId = message.connectionId || uuidv4();
// Validate client capabilities
const negotiation = negotiateCapabilities(params);
if (!negotiation.validation.compatible) {
throw ErrorCreators.invalidParams({
message: "Client capabilities not compatible",
issues: negotiation.validation.issues
});
}
// Create session
const session = {
id: sessionId,
clientInfo: params.clientInfo,
capabilities: negotiation.negotiated,
initialized: true,
createdAt: new Date().toISOString(),
lastActivity: new Date().toISOString()
};
this.sessions.set(sessionId, session);
this.isInitialized = true;
console.log(`MCP session initialized: ${sessionId}`, {
client: params.clientInfo.name,
version: params.clientInfo.version,
warnings: negotiation.validation.warnings
});
// Log warnings if any
if (negotiation.validation.warnings.length > 0) {
console.warn('Client capability warnings:', negotiation.validation.warnings);
}
return {
protocolVersion: "1.0",
capabilities: getServerCapabilities(),
serverInfo: SERVER_INFO,
sessionId: sessionId,
negotiated: negotiation.negotiated
};
} catch (error) {
console.error('Initialize error:', error);
throw error;
}
}
/**
* Handles tools/list request
*/
async handleToolsList(params, message) {
this.validateSession(message);
const tools = Array.from(this.tools.values()).map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema || {}
}));
return { tools };
}
/**
* Handles tools/call request
*/
async handleToolCall(params, message) {
this.validateSession(message);
validateToolCallParams(params);
const toolName = params.name;
const toolArgs = params.arguments || {};
if (!this.tools.has(toolName)) {
throw ErrorCreators.toolNotFound(toolName);
}
const tool = this.tools.get(toolName);
try {
// Validate tool input if schema is provided
if (tool.inputSchema) {
// This would be validated by the validation middleware
}
// Add execution context
const context = {
sessionId: message.connectionId,
requestId: message.id,
timestamp: new Date().toISOString(),
toolName: toolName
};
// Execute tool
console.log(`Executing tool: ${toolName}`, { args: toolArgs, context });
const result = await tool.handler(toolArgs, context);
// Update session activity
this.updateSessionActivity(message.connectionId);
return {
content: [
{
type: "text",
text: result.output || result.message || "Tool executed successfully"
}
],
isError: false,
toolResult: result
};
} catch (error) {
console.error(`Tool execution error for ${toolName}:`, error);
throw ErrorCreators.toolExecutionError(toolName, error);
}
}
/**
* Handles resources/list request
*/
async handleResourcesList(params, message) {
this.validateSession(message);
const resources = Array.from(this.resources.values()).map(resource => ({
uri: resource.uri,
name: resource.name,
description: resource.description,
mimeType: resource.mimeType
}));
return { resources };
}
/**
* Handles resources/read request
*/
async handleResourceRead(params, message) {
this.validateSession(message);
const uri = params.uri;
if (!uri) {
throw ErrorCreators.invalidParams({ message: "URI is required" });
}
const resource = Array.from(this.resources.values()).find(r => r.uri === uri);
if (!resource) {
throw ErrorCreators.resourceNotFound(uri);
}
try {
const content = await resource.handler(params);
this.updateSessionActivity(message.connectionId);
return {
contents: [
{
uri: uri,
mimeType: resource.mimeType,
text: content
}
]
};
} catch (error) {
console.error(`Resource read error for ${uri}:`, error);
throw ErrorCreators.resourceAccessError(uri, error);
}
}
/**
* Handles prompts/list request
*/
async handlePromptsList(params, message) {
this.validateSession(message);
const prompts = Array.from(this.prompts.values()).map(prompt => ({
name: prompt.name,
description: prompt.description,
arguments: prompt.arguments || []
}));
return { prompts };
}
/**
* Handles prompts/get request
*/
async handlePromptGet(params, message) {
this.validateSession(message);
const promptName = params.name;
if (!this.prompts.has(promptName)) {
throw ErrorCreators.invalidParams({ message: `Prompt '${promptName}' not found` });
}
const prompt = this.prompts.get(promptName);
try {
const content = await prompt.handler(params.arguments || {});
this.updateSessionActivity(message.connectionId);
return {
description: prompt.description,
messages: [
{
role: "user",
content: {
type: "text",
text: content
}
}
]
};
} catch (error) {
console.error(`Prompt generation error for ${promptName}:`, error);
throw ErrorCreators.internalError({
message: `Failed to generate prompt: ${error.message}`
});
}
}
/**
* Registers a new tool
*/
registerTool(name, description, handler, inputSchema = null) {
const tool = {
name,
description,
handler,
inputSchema,
registeredAt: new Date().toISOString()
};
this.tools.set(name, tool);
console.log(`Tool registered: ${name}`);
// Notify subscribed clients about tool changes
this.notifyToolsChanged();
}
/**
* Registers a new resource
*/
registerResource(uri, name, description, handler, mimeType = "text/plain") {
const resource = {
uri,
name,
description,
handler,
mimeType,
registeredAt: new Date().toISOString()
};
this.resources.set(uri, resource);
console.log(`Resource registered: ${uri}`);
// Notify subscribed clients about resource changes
this.notifyResourcesChanged();
}
/**
* Registers a new prompt
*/
registerPrompt(name, description, handler, argumentSchema = []) {
const prompt = {
name,
description,
handler,
arguments: argumentSchema,
registeredAt: new Date().toISOString()
};
this.prompts.set(name, prompt);
console.log(`Prompt registered: ${name}`);
}
/**
* Validates that a session exists and is valid
*/
validateSession(message) {
const sessionId = message.connectionId;
if (!sessionId) {
throw ErrorCreators.authenticationError();
}
const session = this.sessions.get(sessionId);
if (!session || !session.initialized) {
throw ErrorCreators.authenticationError();
}
return session;
}
/**
* Updates session activity timestamp
*/
updateSessionActivity(sessionId) {
const session = this.sessions.get(sessionId);
if (session) {
session.lastActivity = new Date().toISOString();
}
}
/**
* Gets session information
*/
getSessionInfo(sessionId) {
return this.sessions.get(sessionId) || null;
}
/**
* Gets all active sessions
*/
getActiveSessions() {
return Array.from(this.sessions.values());
}
/**
* Removes a session
*/
removeSession(sessionId) {
const removed = this.sessions.delete(sessionId);
if (removed) {
console.log(`Session removed: ${sessionId}`);
}
return removed;
}
/**
* Notifies clients about tool changes
*/
async notifyToolsChanged() {
// This would be implemented with the notification system
console.log('Tools list changed notification would be sent');
}
/**
* Notifies clients about resource changes
*/
async notifyResourcesChanged() {
// This would be implemented with the notification system
console.log('Resources list changed notification would be sent');
}
/**
* Initializes built-in tools
*/
initializeBuiltinTools() {
// Built-in system tools
this.registerTool(
"system/info",
"Get system information and server status",
async (args, context) => {
return {
output: "System information retrieved",
data: {
serverInfo: SERVER_INFO,
uptime: process.uptime(),
memory: process.memoryUsage(),
activeSessions: this.sessions.size,
toolsCount: this.tools.size,
resourcesCount: this.resources.size
}
};
},
{
type: "object",
properties: {
detailed: { type: "boolean", default: false }
}
}
);
this.registerTool(
"system/health",
"Check system health status",
async (args, context) => {
return {
output: "System health check completed",
data: {
status: "healthy",
timestamp: new Date().toISOString(),
checks: {
memory: process.memoryUsage().heapUsed < 500 * 1024 * 1024, // 500MB
sessions: this.sessions.size < 100,
tools: this.tools.size > 0
}
}
};
}
);
}
/**
* Initializes built-in resources
*/
initializeBuiltinResources() {
this.registerResource(
"system://server-info",
"Server Information",
"Current server status and configuration",
async (params) => {
return JSON.stringify({
serverInfo: SERVER_INFO,
capabilities: getServerCapabilities(),
activeSessions: this.sessions.size,
registeredTools: Array.from(this.tools.keys()),
registeredResources: Array.from(this.resources.keys())
}, null, 2);
},
"application/json"
);
this.registerResource(
"system://logs",
"System Logs",
"Recent system log entries",
async (params) => {
// This would integrate with actual logging system
return "System log entries would be retrieved here";
},
"text/plain"
);
}
/**
* Initializes built-in prompts
*/
initializeBuiltinPrompts() {
this.registerPrompt(
"system/status",
"Generate a system status report",
async (args) => {
const sessions = this.getActiveSessions();
const toolCount = this.tools.size;
const resourceCount = this.resources.size;
return `Please provide a comprehensive status report for the Auto-Publishing MCP Server:
Current Status:
- Active Sessions: ${sessions.length}
- Registered Tools: ${toolCount}
- Registered Resources: ${resourceCount}
- Server Uptime: ${Math.floor(process.uptime())} seconds
Please analyze the current system health, active connections, and suggest any optimizations or actions needed.`;
},
[]
);
}
/**
* Gets comprehensive server statistics
*/
getStats() {
return {
sessions: {
active: this.sessions.size,
list: Array.from(this.sessions.values()).map(s => ({
id: s.id,
client: s.clientInfo?.name,
createdAt: s.createdAt,
lastActivity: s.lastActivity
}))
},
tools: {
count: this.tools.size,
list: Array.from(this.tools.keys())
},
resources: {
count: this.resources.size,
list: Array.from(this.resources.keys())
},
prompts: {
count: this.prompts.size,
list: Array.from(this.prompts.keys())
},
server: {
initialized: this.isInitialized,
uptime: process.uptime(),
memory: process.memoryUsage()
}
};
}
}
export default McpHandler;