@rhofkens/mcp-quotes-server-claude-code
Version:
Model Context Protocol (MCP) server for managing and serving quotes
195 lines • 7.76 kB
JavaScript
/**
* MCP Quotes Server
*
* Main server implementation using MCP SDK
*/
import { readFileSync } from 'fs';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
import { resourceRegistry } from './resources/index.js';
import { toolRegistry } from './tools/index.js';
import { HttpServerTransport } from './transports/http.js';
import { getConfig } from './utils/config.js';
import { logger } from './utils/logger.js';
// Load package.json
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const packageJson = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
export class QuotesServer {
server;
transport;
httpTransport;
constructor() {
const config = getConfig();
logger.info('Creating MCP Quotes Server', {
name: packageJson.name,
version: packageJson.version,
transport: config.transport,
});
// Initialize MCP Server
this.server = new Server({
name: packageJson.name,
version: packageJson.version,
}, {
capabilities: {
tools: {},
resources: {},
},
});
// Initialize transport based on configuration
if (config.transport === 'http') {
this.httpTransport = new HttpServerTransport({
port: config.httpPort,
host: config.httpHost,
path: config.httpPath,
});
this.transport = this.httpTransport;
}
else {
this.transport = new StdioServerTransport();
}
// Register handlers
this.registerHandlers();
}
registerHandlers() {
// Tool call handler
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.info('Tool call received', { tool: name, args });
const tool = toolRegistry[name];
if (!tool) {
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
}
try {
const result = await tool.handler(args);
logger.info('Tool call succeeded', { tool: name });
return {
content: [
{
type: 'text',
text: JSON.stringify(result, null, 2),
},
],
};
}
catch (error) {
logger.error('Tool call failed', { tool: name, error });
throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`);
}
});
// Resource list handler
this.server.setRequestHandler(ListResourcesRequestSchema, () => {
logger.info('Listing resources');
return {
resources: Object.values(resourceRegistry).map((r) => r.definition),
};
});
// Resource read handler
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
logger.info('Resource read requested', { uri });
const resource = resourceRegistry[uri];
if (!resource) {
throw new McpError(ErrorCode.MethodNotFound, `Unknown resource: ${uri}`);
}
try {
const result = await resource.handler(uri);
logger.info('Resource read succeeded', { uri });
return {
contents: [
{
uri,
mimeType: resource.definition.mimeType || 'application/json',
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
},
],
};
}
catch (error) {
logger.error('Resource read failed', { uri, error });
throw new McpError(ErrorCode.InternalError, `Resource read failed: ${error instanceof Error ? error.message : String(error)}`);
}
});
// List tools handler
this.server.setRequestHandler(ListToolsRequestSchema, () => {
logger.info('Listing tools');
return {
tools: Object.values(toolRegistry).map((t) => t.definition),
};
});
// List resource templates handler
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, () => {
logger.info('Listing resource templates');
return {
resourceTemplates: [], // No resource templates defined yet
};
});
// Register all tools
const toolList = Object.values(toolRegistry).map((t) => t.definition);
toolList.forEach((tool) => {
logger.info('Registered tool', { name: tool.name });
});
// Register all resources
const resourceList = Object.values(resourceRegistry).map((r) => r.definition);
resourceList.forEach((resource) => {
logger.info('Registered resource', { uri: resource.uri });
});
}
async start() {
const config = getConfig();
logger.info('Starting MCP Quotes Server...', { transport: config.transport });
try {
// For HTTP transport, start the HTTP server first
if (this.httpTransport) {
await this.httpTransport.start();
}
// Connect the transport to the server
await this.server.connect(this.transport);
logger.info('MCP Quotes Server started successfully', {
transport: config.transport,
...(config.transport === 'http'
? {
url: `http://${config.httpHost}:${config.httpPort}${config.httpPath}`,
httpPort: config.httpPort,
httpHost: config.httpHost,
httpPath: config.httpPath,
}
: {}),
tools: Object.keys(toolRegistry),
resources: Object.keys(resourceRegistry),
});
// For HTTP transport, the endpoint is already logged by the transport
if (config.transport === 'stdio') {
logger.info('MCP server running in STDIO mode');
}
}
catch (error) {
logger.error('Failed to start server', error);
throw error;
}
}
async stop() {
logger.info('Stopping MCP Quotes Server...');
try {
await this.server.close();
// Close HTTP transport if used
if (this.httpTransport) {
await this.httpTransport.close();
}
logger.info('MCP Quotes Server stopped successfully');
}
catch (error) {
logger.error('Error stopping server', error);
throw error;
}
}
}
/**
* Factory function to create a quotes server instance
*/
export function createQuotesServer() {
return new QuotesServer();
}
//# sourceMappingURL=server.js.map