server.js•25.5 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import Database from './database.js';
import Calculator from './calculator.js';
class MCPServer {
constructor() {
this.server = new Server(
{
name: 'mcp-example-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
resources: {},
prompts: {},
},
}
);
this.database = new Database();
this.calculator = new Calculator();
this.setupTools();
this.setupResources();
this.setupPrompts();
}
setupTools() {
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'query_database':
return await this.handleDatabaseQuery(args);
case 'get_all_users':
return await this.handleGetAllUsers(args);
case 'get_user_by_id':
return await this.handleGetUserById(args);
case 'add_user':
return await this.handleAddUser(args);
case 'update_user':
return await this.handleUpdateUser(args);
case 'delete_user':
return await this.handleDeleteUser(args);
case 'calculate':
return await this.handleCalculate(args);
case 'calculate_expression':
return await this.handleCalculateExpression(args);
case 'calculate_stats':
return await this.handleCalculateStats(args);
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
return {
method: "tools/call",
content: [
{
type: 'text',
text: `Error: ${error.message}`,
},
],
};
}
});
// Handle tool listing
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
method: "tools/list",
tools: [
{
name: 'query_database',
description: 'Execute a custom SQL query on the database',
inputSchema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'SQL query to execute',
},
},
required: ['query'],
},
},
{
name: 'get_all_users',
description: 'Retrieve all users from the database',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of users to return (optional)',
},
},
},
},
{
name: 'get_user_by_id',
description: 'Retrieve a specific user by ID',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'User ID to retrieve',
},
},
required: ['id'],
},
},
{
name: 'add_user',
description: 'Add a new user to the database',
inputSchema: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'User name',
},
email: {
type: 'string',
description: 'User email',
},
age: {
type: 'number',
description: 'User age',
},
},
required: ['name', 'email'],
},
},
{
name: 'update_user',
description: 'Update an existing user',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'User ID to update',
},
name: {
type: 'string',
description: 'New user name',
},
email: {
type: 'string',
description: 'New user email',
},
age: {
type: 'number',
description: 'New user age',
},
},
required: ['id'],
},
},
{
name: 'delete_user',
description: 'Delete a user from the database',
inputSchema: {
type: 'object',
properties: {
id: {
type: 'number',
description: 'User ID to delete',
},
},
required: ['id'],
},
},
{
name: 'calculate',
description: 'Perform mathematical calculations',
inputSchema: {
type: 'object',
properties: {
operation: {
type: 'string',
description: 'Mathematical operation (add, subtract, multiply, divide, power, sqrt, sin, cos, tan, log, abs, round, floor, ceil)',
enum: ['add', 'subtract', 'multiply', 'divide', 'power', 'sqrt', 'sin', 'cos', 'tan', 'log', 'abs', 'round', 'floor', 'ceil'],
},
numbers: {
type: 'array',
items: { type: 'number' },
description: 'Numbers to perform the operation on',
},
},
required: ['operation', 'numbers'],
},
},
{
name: 'calculate_expression',
description: 'Evaluate a mathematical expression',
inputSchema: {
type: 'object',
properties: {
expression: {
type: 'string',
description: 'Mathematical expression to evaluate (e.g., "2 + 3 * 4")',
},
},
required: ['expression'],
},
},
{
name: 'calculate_stats',
description: 'Calculate statistical measures for a set of numbers',
inputSchema: {
type: 'object',
properties: {
numbers: {
type: 'array',
items: { type: 'number' },
description: 'Array of numbers to calculate statistics for',
},
},
required: ['numbers'],
},
},
],
};
});
}
setupResources() {
// Handle resource listing
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
return {
method: "resources/list",
resources: [
{
uri: "mcp://database/users.json",
name: "Users Database Export",
description: "All users from the database in JSON format",
mimeType: "application/json"
},
{
uri: "mcp://database/users.csv",
name: "Users CSV Export",
description: "All users from the database in CSV format",
mimeType: "text/csv"
},
{
uri: "mcp://database/stats.json",
name: "Database Statistics",
description: "Database statistics and metrics",
mimeType: "application/json"
},
{
uri: "mcp://calculator/operations.json",
name: "Calculator Operations",
description: "Available calculator operations and their descriptions",
mimeType: "application/json"
},
{
uri: "mcp://system/info.json",
name: "System Information",
description: "Server system information and status",
mimeType: "application/json"
}
],
};
});
// Handle resource reading
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
try {
switch (uri) {
case "mcp://database/users.json":
return await this.handleUsersJsonResource();
case "mcp://database/users.csv":
return await this.handleUsersCsvResource();
case "mcp://database/stats.json":
return await this.handleDatabaseStatsResource();
case "mcp://calculator/operations.json":
return await this.handleCalculatorOperationsResource();
case "mcp://system/info.json":
return await this.handleSystemInfoResource();
default:
throw new Error(`Resource not found: ${uri}`);
}
} catch (error) {
return {
method: "resources/read",
contents: [
{
uri: uri,
mimeType: "text/plain",
text: `Error reading resource: ${error.message}`,
},
],
};
}
});
}
setupPrompts() {
// Handle prompt listing
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
return {
method: "prompts/list",
prompts: [
{
name: "user-analysis",
description: "Analyze user data and provide insights",
arguments: {
type: "object",
properties: {
analysisType: {
type: "string",
enum: ["demographics", "activity", "trends"],
description: "Type of analysis to perform"
},
includeStats: {
type: "boolean",
description: "Include statistical calculations"
}
},
required: ["analysisType"]
}
},
{
name: "database-report",
description: "Generate a comprehensive database report",
arguments: {
type: "object",
properties: {
reportType: {
type: "string",
enum: ["summary", "detailed", "export"],
description: "Type of report to generate"
},
format: {
type: "string",
enum: ["json", "csv", "markdown"],
description: "Output format for the report"
}
},
required: ["reportType"]
}
},
{
name: "calculator-tutorial",
description: "Generate a tutorial for calculator operations",
arguments: {
type: "object",
properties: {
operation: {
type: "string",
enum: ["basic", "advanced", "trigonometric", "utility"],
description: "Category of operations to cover"
},
includeExamples: {
type: "boolean",
description: "Include practical examples"
}
},
required: ["operation"]
}
}
],
};
});
// Handle prompt retrieval
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case "user-analysis":
return await this.handleUserAnalysisPrompt(args);
case "database-report":
return await this.handleDatabaseReportPrompt(args);
case "calculator-tutorial":
return await this.handleCalculatorTutorialPrompt(args);
default:
throw new Error(`Prompt not found: ${name}`);
}
} catch (error) {
return {
method: "prompts/get",
prompt: {
name: name,
description: `Error generating prompt: ${error.message}`,
arguments: {},
messages: [
{
role: "system",
content: `Error: ${error.message}`
}
]
},
};
}
});
}
// Resource handlers
async handleUsersJsonResource() {
const users = await this.database.all('SELECT * FROM users');
return {
method: "resources/read",
contents: [
{
uri: "mcp://database/users.json",
mimeType: "application/json",
text: JSON.stringify(users, null, 2),
},
],
};
}
async handleUsersCsvResource() {
const users = await this.database.all('SELECT * FROM users');
// Convert to CSV
const headers = ['id', 'name', 'email', 'age', 'created_at'];
const csvRows = [headers.join(',')];
users.forEach(user => {
const row = headers.map(header => {
const value = user[header];
return typeof value === 'string' ? `"${value}"` : value;
});
csvRows.push(row.join(','));
});
return {
method: "resources/read",
contents: [
{
uri: "mcp://database/users.csv",
mimeType: "text/csv",
text: csvRows.join('\n'),
},
],
};
}
async handleDatabaseStatsResource() {
const totalUsers = await this.database.get('SELECT COUNT(*) as count FROM users');
const avgAge = await this.database.get('SELECT AVG(age) as avg_age FROM users WHERE age IS NOT NULL');
const ageStats = await this.database.get('SELECT MIN(age) as min_age, MAX(age) as max_age FROM users WHERE age IS NOT NULL');
const stats = {
totalUsers: totalUsers.count,
averageAge: avgAge.avg_age ? Math.round(avgAge.avg_age * 100) / 100 : null,
ageRange: {
min: ageStats.min_age,
max: ageStats.max_age
},
databaseInfo: {
type: "SQLite",
location: this.database.dbPath,
lastUpdated: new Date().toISOString()
}
};
return {
method: "resources/read",
contents: [
{
uri: "mcp://database/stats.json",
mimeType: "application/json",
text: JSON.stringify(stats, null, 2),
},
],
};
}
async handleCalculatorOperationsResource() {
const operations = this.calculator.getAvailableOperations();
const operationDetails = operations.map(op => ({
name: op,
description: this.getOperationDescription(op),
examples: this.getOperationExamples(op)
}));
const calculatorInfo = {
availableOperations: operationDetails,
totalOperations: operations.length,
categories: {
basic: ['add', 'subtract', 'multiply', 'divide'],
advanced: ['power', 'sqrt', 'log'],
trigonometric: ['sin', 'cos', 'tan'],
utility: ['abs', 'round', 'floor', 'ceil']
}
};
return {
method: "resources/read",
contents: [
{
uri: "mcp://calculator/operations.json",
mimeType: "application/json",
text: JSON.stringify(calculatorInfo, null, 2),
},
],
};
}
async handleSystemInfoResource() {
const systemInfo = {
server: {
name: 'mcp-example-server',
version: '1.0.0',
uptime: process.uptime(),
startTime: new Date(Date.now() - process.uptime() * 1000).toISOString()
},
capabilities: {
tools: true,
resources: true,
prompts: true
},
tools: {
database: 6,
calculator: 3,
total: 9
},
resources: {
database: 3,
calculator: 1,
system: 1,
total: 5
},
prompts: {
analysis: 2,
reporting: 1,
total: 3
},
timestamp: new Date().toISOString()
};
return {
method: "resources/read",
contents: [
{
uri: "mcp://system/info.json",
mimeType: "application/json",
text: JSON.stringify(systemInfo, null, 2),
},
],
};
}
getOperationDescription(operation) {
const descriptions = {
add: "Add two or more numbers",
subtract: "Subtract numbers",
multiply: "Multiply numbers",
divide: "Divide numbers (with zero check)",
power: "Raise a number to a power",
sqrt: "Calculate square root",
sin: "Calculate sine (in radians)",
cos: "Calculate cosine (in radians)",
tan: "Calculate tangent (in radians)",
log: "Calculate natural logarithm",
abs: "Calculate absolute value",
round: "Round to nearest integer",
floor: "Round down to integer",
ceil: "Round up to integer"
};
return descriptions[operation] || "Mathematical operation";
}
getOperationExamples(operation) {
const examples = {
add: ["add(5, 3) = 8", "add(1, 2, 3, 4) = 10"],
subtract: ["subtract(10, 3) = 7"],
multiply: ["multiply(4, 5) = 20"],
divide: ["divide(10, 2) = 5"],
power: ["power(2, 3) = 8"],
sqrt: ["sqrt(16) = 4"],
sin: ["sin(0) = 0", "sin(Math.PI/2) = 1"],
cos: ["cos(0) = 1", "cos(Math.PI) = -1"],
tan: ["tan(0) = 0"],
log: ["log(Math.E) = 1"],
abs: ["abs(-5) = 5"],
round: ["round(3.7) = 4"],
floor: ["floor(3.7) = 3"],
ceil: ["ceil(3.2) = 4"]
};
return examples[operation] || [];
}
// Database handlers
async handleDatabaseQuery(args) {
const { query } = args;
const result = await this.database.all(query);
return {
method: "tools/call",
content: [
{
type: 'text',
text: `Query executed successfully. Found ${result.length} rows:\n${JSON.stringify(result, null, 2)}`,
},
],
};
}
async handleGetAllUsers(args) {
const { limit } = args;
const query = limit ? 'SELECT * FROM users LIMIT ?' : 'SELECT * FROM users';
const params = limit ? [limit] : [];
const users = await this.database.all(query, params);
return {
method: "tools/call",
content: [
{
type: 'text',
text: `Retrieved ${users.length} users:\n${JSON.stringify(users, null, 2)}`,
},
],
};
}
async handleGetUserById(args) {
const { id } = args;
const user = await this.database.get('SELECT * FROM users WHERE id = ?', [id]);
if (!user) {
return {
method: "tools/call",
content: [
{
type: 'text',
text: `User with ID ${id} not found.`,
},
],
};
}
return {
method: "tools/call",
content: [
{
type: 'text',
text: `User found:\n${JSON.stringify(user, null, 2)}`,
},
],
};
}
async handleAddUser(args) {
const { name, email, age } = args;
const result = await this.database.run(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
[name, email, age || null]
);
return {
method: "tools/call",
content: [
{
type: 'text',
text: `User added successfully with ID: ${result.id}`,
},
],
};
}
async handleUpdateUser(args) {
const { id, name, email, age } = args;
const updates = [];
const params = [];
if (name !== undefined) {
updates.push('name = ?');
params.push(name);
}
if (email !== undefined) {
updates.push('email = ?');
params.push(email);
}
if (age !== undefined) {
updates.push('age = ?');
params.push(age);
}
if (updates.length === 0) {
return {
method: "tools/call",
content: [
{
type: 'text',
text: 'No fields to update provided.',
},
],
};
}
params.push(id);
const result = await this.database.run(
`UPDATE users SET ${updates.join(', ')} WHERE id = ?`,
params
);
return {
method: "tools/call",
content: [
{
type: 'text',
text: `User updated successfully. ${result.changes} row(s) affected.`,
},
],
};
}
async handleDeleteUser(args) {
const { id } = args;
const result = await this.database.run('DELETE FROM users WHERE id = ?', [id]);
return {
method: "tools/call",
content: [
{
type: 'text',
text: `User deleted successfully. ${result.changes} row(s) affected.`,
},
],
};
}
// Calculator handlers
async handleCalculate(args) {
const { operation, numbers } = args;
const result = this.calculator.calculate(operation, ...numbers);
return {
method: "tools/call",
content: [
{
type: 'text',
text: `Calculation result: ${JSON.stringify(result, null, 2)}`,
},
],
};
}
async handleCalculateExpression(args) {
const { expression } = args;
const result = this.calculator.calculateExpression(expression);
return {
method: "tools/call",
content: [
{
type: 'text',
text: `Expression result: ${JSON.stringify(result, null, 2)}`,
},
],
};
}
async handleCalculateStats(args) {
const { numbers } = args;
const result = this.calculator.calculateStats(numbers);
return {
method: "tools/call",
content: [
{
type: 'text',
text: `Statistical analysis: ${JSON.stringify(result, null, 2)}`,
},
],
};
}
// Prompt handlers
async handleUserAnalysisPrompt(args) {
const { analysisType, includeStats = false } = args;
let content = `You are a data analyst. Analyze the user database and provide insights about ${analysisType}.`;
if (includeStats) {
content += "\n\nInclude statistical analysis such as averages, distributions, and trends.";
}
const messages = [
{
role: "system",
content: content
},
{
role: "user",
content: `Please analyze the user data focusing on ${analysisType}. ${includeStats ? 'Include detailed statistics.' : 'Provide a general overview.'}`
}
];
return {
method: "prompts/get",
prompt: {
name: "user-analysis",
description: `Analyze user data for ${analysisType}${includeStats ? ' with statistics' : ''}`,
arguments: args,
messages: messages
},
};
}
async handleDatabaseReportPrompt(args) {
const { reportType, format = "json" } = args;
let content = `You are a database administrator. Generate a ${reportType} report of the database in ${format} format.`;
if (reportType === "detailed") {
content += "\n\nInclude detailed information about tables, relationships, and data quality.";
} else if (reportType === "export") {
content += "\n\nProvide export-ready data with proper formatting.";
}
const messages = [
{
role: "system",
content: content
},
{
role: "user",
content: `Generate a ${reportType} database report in ${format} format.`
}
];
return {
method: "prompts/get",
prompt: {
name: "database-report",
description: `Generate ${reportType} database report in ${format} format`,
arguments: args,
messages: messages
},
};
}
async handleCalculatorTutorialPrompt(args) {
const { operation, includeExamples = true } = args;
let content = `You are a mathematics tutor. Create a tutorial for ${operation} calculator operations.`;
if (includeExamples) {
content += "\n\nInclude practical examples and step-by-step explanations.";
}
const messages = [
{
role: "system",
content: content
},
{
role: "user",
content: `Create a tutorial for ${operation} calculator operations. ${includeExamples ? 'Include examples.' : 'Focus on theory.'}`
}
];
return {
method: "prompts/get",
prompt: {
name: "calculator-tutorial",
description: `Tutorial for ${operation} calculator operations${includeExamples ? ' with examples' : ''}`,
arguments: args,
messages: messages
},
};
}
async start() {
try {
// Initialize database
await this.database.initialize();
// Start MCP server
const transport = new StdioServerTransport();
await this.server.connect(transport);
// Don't log to stdout as it interferes with JSON-RPC
// console.log('MCP server started successfully');
} catch (error) {
console.error('Failed to start MCP server:', error);
process.exit(1);
}
}
}
// Start the server
const server = new MCPServer();
server.start().catch(console.error);