@prsna_ai/mcp-server
Version:
Model Context Protocol server for PRSNA personality profiles and communication insights
159 lines • 5.43 kB
JavaScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { config } from 'dotenv';
import { logger } from './utils/logger.js';
import { CONFIG } from './utils/constants.js';
import { allTools, toolHandlers } from './tools/index.js';
import { safeJSONStringify } from './utils/json.js';
// Load environment variables
config();
// Validate required environment variables
if (!process.env.PRSNA_API_TOKEN) {
logger.error('PRSNA_API_TOKEN environment variable is required');
process.exit(1);
}
// Create MCP server instance
const server = new Server({
name: CONFIG.MCP.NAME,
version: CONFIG.MCP.VERSION,
}, {
capabilities: {
tools: {},
},
});
// Register tool list handler
server.setRequestHandler(ListToolsRequestSchema, async () => {
logger.debug('Listing available tools', { toolCount: allTools.length });
try {
const toolsResponse = {
tools: allTools.map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema
}))
};
// Validate the response can be serialized properly
const serialized = safeJSONStringify(toolsResponse);
if (!serialized || serialized.length === 0) {
throw new Error('Failed to serialize tools response');
}
logger.debug('Tools list serialized successfully', {
responseLength: serialized.length,
toolCount: toolsResponse.tools.length
});
return toolsResponse;
}
catch (error) {
logger.error('Failed to list tools', { error: error.message });
// Return a minimal response if there's an error
return {
tools: []
};
}
});
// Register tool call handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.info('Tool called', { toolName: name, args });
// Find the tool handler
const handler = toolHandlers[name];
if (!handler) {
logger.error('Unknown tool called', { toolName: name });
throw new Error(`Unknown tool: ${name}`);
}
try {
// Call the tool handler
const result = await handler(args);
// Validate result format
if (!result || typeof result !== 'object' || !result.content || !Array.isArray(result.content)) {
throw new Error('Invalid tool response format');
}
// Validate content array
for (const content of result.content) {
if (!content || typeof content !== 'object' || content.type !== 'text' || typeof content.text !== 'string') {
throw new Error('Invalid content format in tool response');
}
}
logger.info('Tool execution completed', {
toolName: name,
success: true
});
return result;
}
catch (error) {
const errorMessage = error.message;
logger.error('Tool execution failed', {
toolName: name,
error: errorMessage
});
// Return error response in the expected format using safe JSON stringify
return {
content: [{
type: "text",
text: safeJSONStringify({
success: false,
error: errorMessage,
tool: name
}, 2)
}]
};
}
});
// Handle server lifecycle
const transport = new StdioServerTransport();
// Error handlers
process.on('uncaughtException', (error) => {
logger.error('Uncaught exception', { error: error.message });
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled rejection', { reason, promise });
process.exit(1);
});
process.on('SIGINT', () => {
logger.info('Received SIGINT, shutting down gracefully');
server.close();
process.exit(0);
});
process.on('SIGTERM', () => {
logger.info('Received SIGTERM, shutting down gracefully');
server.close();
process.exit(0);
});
// Start the server
async function main() {
try {
logger.info('Starting PRSNA MCP server', {
name: CONFIG.MCP.NAME,
version: CONFIG.MCP.VERSION,
nodeEnv: process.env.NODE_ENV || 'development',
apiUrl: CONFIG.API.BASE_URL,
toolCount: allTools.length
});
// Log available tools for debugging
logger.debug('Available tools:', {
tools: allTools.map(tool => ({
name: tool.name,
description: tool.description
}))
});
await server.connect(transport);
logger.info('PRSNA MCP server started successfully');
// Keep the process running
await new Promise(() => { });
}
catch (error) {
logger.error('Failed to start MCP server', {
error: error.message
});
process.exit(1);
}
}
// Start the server
main().catch((error) => {
logger.error('Server startup failed', { error: error.message });
process.exit(1);
});
//# sourceMappingURL=index.js.map