@spryker-sdk/spryker-mcp-server
Version:
AI-powered e-commerce server that enables intelligent shopping assistants for Spryker Commerce OS platforms
242 lines (238 loc) • 9.28 kB
JavaScript
/**
* Spryker MCP Server
*
* A Model Context Protocol (MCP) server that provides integration with Spryker e-commerce platform.
* This server exposes Spryker's API functionality through MCP tools, enabling AI assistants
* to interact with products, carts, orders, and customer data.
*
* Features:
* - Product search and management
* - Shopping cart operations
* - Order processing and checkout
* - Customer authentication
* - Guest user support
*/
import { CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, SetLevelRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
import { config } from './config/index.js';
import { logger } from './utils/logger.js';
import { toolRegistry } from './tools/index.js';
import { promptRegistry } from './prompts/index.js';
import { StdioMCPServer } from './servers/stdio-server.js';
import { HttpMCPServer } from './servers/http-server.js';
import { SSEMCPServer } from './servers/sse-server.js';
/**
* Create handler registration function for tools and prompts
*/
function createHandlerRegistrar() {
return (server) => {
// Register tool call handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.info(`Executing tool: ${name}`, { args });
try {
const result = await toolRegistry.callTool(name, args || {});
logger.info(`Tool ${name} executed successfully`);
return result;
}
catch (error) {
logger.error(`Tool ${name} execution failed:`, error instanceof Error ? error : new Error(String(error)));
throw error;
}
});
// Register prompt list handler
server.setRequestHandler(ListPromptsRequestSchema, async () => {
logger.debug('Received list_prompts request');
const prompts = promptRegistry.getMCPPrompts();
logger.info(`Returning ${prompts.length} available prompts`);
return { prompts };
});
// Register prompt get handler
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.info(`Getting prompt: ${name}`, { args });
try {
const content = promptRegistry.generatePromptContent(name, args || {});
logger.info(`Prompt ${name} generated successfully`);
return {
description: promptRegistry.get(name)?.description || '',
messages: [
{
role: 'user',
content: {
type: 'text',
text: content,
},
},
],
};
}
catch (error) {
logger.error(`Prompt ${name} generation failed:`, error instanceof Error ? error : new Error(String(error)));
throw error;
}
});
// Register logging set level handler
server.setRequestHandler(SetLevelRequestSchema, async (request) => {
const { level } = request.params;
logger.info(`Setting log level to: ${level}`);
try {
const previousLevel = logger.getLevel();
logger.setLevel(level);
const newLevel = logger.getLevel();
logger.info(`Log level successfully changed from ${previousLevel} to ${newLevel}`);
return {
message: `Log level changed from '${previousLevel}' to '${newLevel}'`,
previousLevel: previousLevel,
newLevel: newLevel,
requestedLevel: level,
timestamp: new Date().toISOString(),
};
}
catch (error) {
logger.error(`Failed to set log level to ${level}:`, error instanceof Error ? error : new Error(String(error)));
throw error;
}
});
// Register all tools (this will handle ListToolsRequestSchema)
toolRegistry.registerAll(server);
};
}
/**
* Create the appropriate server implementation based on transport type
*/
function createServer(options) {
const registerHandlers = createHandlerRegistrar();
switch (options.transport) {
case 'stdio':
return new StdioMCPServer(options, registerHandlers);
case 'http':
return new HttpMCPServer(options, registerHandlers);
case 'sse':
return new SSEMCPServer(options, registerHandlers);
default:
throw new Error(`Unsupported transport type: ${options.transport}`);
}
}
function parseArgs() {
const args = process.argv.slice(2);
const options = {
transport: config.mcp.transport || 'stdio',
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--transport':
const transport = args[++i];
if (['stdio', 'http', 'sse'].includes(transport)) {
options.transport = transport;
}
else {
logger.error(`Invalid transport: ${transport}. Valid options: stdio, http, sse`);
process.exit(1);
}
break;
case '--port':
const portStr = args[++i];
if (!portStr) {
logger.error('Port number required');
process.exit(1);
}
options.httpPort = parseInt(portStr, 10);
if (isNaN(options.httpPort)) {
logger.error('Invalid port number');
process.exit(1);
}
break;
case '--host':
const host = args[++i];
if (!host) {
logger.error('Host required');
process.exit(1);
}
options.httpHost = host;
break;
case '--endpoint':
const endpoint = args[++i];
if (!endpoint) {
logger.error('Endpoint required');
process.exit(1);
}
options.httpEndpoint = endpoint;
break;
case '--help':
console.error(`
Spryker MCP Server
Usage: spryker-mcp-server [options]
Options:
--transport <type> Transport type: stdio, http, or sse (default: ${config.mcp.transport})
--port <number> HTTP port for http/sse transport (default: ${config.mcp.http.port})
--host <string> HTTP host for http/sse transport (default: ${config.mcp.http.host})
--endpoint <string> HTTP endpoint path (default: ${config.mcp.http.endpoint})
--help Show this help message
Examples:
spryker-mcp-server # Use stdio transport
spryker-mcp-server --transport http --port 3000
spryker-mcp-server --transport sse --port 8080 --host 0.0.0.0
Environment Variables:
SPRYKER_API_BASE_URL - Spryker API base URL
MCP_TRANSPORT - Transport type (stdio|http|sse)
MCP_HTTP_PORT - HTTP port
MCP_HTTP_HOST - HTTP host
MCP_HTTP_ENDPOINT - HTTP endpoint path
`);
process.exit(0);
break;
}
}
return options;
}
/**
* Application entry point
*/
async function main() {
const options = parseArgs();
const server = createServer(options);
// Handle graceful shutdown
process.on('SIGINT', async () => {
await server.shutdown();
process.exit(0);
});
process.on('SIGTERM', async () => {
await server.shutdown();
process.exit(0);
});
// Handle uncaught errors
process.on('uncaughtException', error => {
logger.error('Uncaught exception:', error);
server.shutdown().finally(() => {
process.exit(1);
});
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled rejection:', {
reason: reason instanceof Error
? {
message: reason.message,
stack: reason.stack,
name: reason.name,
}
: String(reason),
promise: promise.toString(),
});
// Don't shut down the server for unhandled rejections
// Just log them and continue running for better stability
// The server should only shut down on critical errors or signals
});
// Start the server
await server.start();
}
// Export for testing, but auto-start when run directly
export { main, createServer, createHandlerRegistrar };
// Auto-start when run as main module
if (process.argv[1] && process.argv[1].endsWith('index.js')) {
main().catch(error => {
logger.error('Failed to start server:', error);
process.exit(1);
});
}
//# sourceMappingURL=index.js.map