index.tsโข6.49 kB
#!/usr/bin/env node
/**
* RS.ge Waybill MCP Server
*
* Main entry point for the MCP server that connects Claude Desktop to RS.ge Waybill API
*
* ๐ TEACHING: MCP Server Architecture
* ====================================
*
* This server:
* 1. Loads configuration from config.json
* 2. Initializes logging and error handling
* 3. Creates SOAP client for RS.ge API
* 4. Registers MCP tools that Claude can invoke
* 5. Listens for tool calls from Claude Desktop via stdio
*
* Communication Flow:
* Claude Desktop โ stdio โ This MCP Server โ SOAP โ RS.ge API
*/
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 { loadConfig, getCredentials, getLoggingConfig, getFeaturesConfig } from './config/config.js';
import { initLogger } from './utils/logger.js';
import { formatErrorForUser } from './utils/error-handler.js';
import { RsWaybillSoapClient } from './services/soap-client.js';
// Import tools
import { getWaybillsTool, executeGetWaybills } from './tools/get-waybills.js';
import { lookupTinTool, executeLookupTin } from './tools/lookup-tin.js';
import {
getWaybillTypesTool,
executeGetWaybillTypes,
getErrorCodesTool,
executeGetErrorCodes,
getAkcizCodesTool,
executeGetAkcizCodes,
} from './tools/get-dictionaries.js';
/**
* Main function - Initialize and start MCP server
*/
async function main() {
let logger: any;
try {
// Load configuration
const config = loadConfig();
// Initialize logger
const loggingConfig = getLoggingConfig(config);
logger = initLogger(loggingConfig as any);
logger.info('RS.ge Waybill MCP Server starting', {
version: config.server.version,
name: config.server.name,
});
// Get credentials
const credentials = getCredentials(config);
logger.info('Credentials loaded', {
user: credentials.user.substring(0, 10) + '...',
});
// Get feature flags
const features = getFeaturesConfig(config) || {
getWaybills: true,
saveWaybill: true,
sendWaybill: true,
closeWaybill: true,
confirmWaybill: true,
rejectWaybill: true,
getErrorCodes: true,
getAkcizCodes: true,
getNameFromTin: true,
};
// Create SOAP client
const soapClient = new RsWaybillSoapClient(config.api, credentials);
logger.info('SOAP client initialized');
// Verify credentials
logger.info('Verifying credentials with RS.ge...');
const authResult = await soapClient.checkServiceUser();
if (!authResult) {
throw new Error('Authentication failed. Please check your RS.ge credentials.');
}
logger.info('โ
Authentication successful');
// Create MCP server
const server = new Server(
{
name: config.server.name,
version: config.server.version,
},
{
capabilities: {
tools: {},
},
}
);
/**
* Handle: List available tools
*/
server.setRequestHandler(ListToolsRequestSchema, async () => {
const tools = [];
// Add tools based on feature flags
if (features.getWaybills) {
tools.push(getWaybillsTool);
}
if (features.getNameFromTin) {
tools.push(lookupTinTool);
}
if (features.getErrorCodes) {
tools.push(getWaybillTypesTool);
tools.push(getErrorCodesTool);
}
if (features.getAkcizCodes) {
tools.push(getAkcizCodesTool);
}
logger.debug('Listed tools', { count: tools.length });
return { tools };
});
/**
* Handle: Execute tool
*/
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
logger.info('Tool called', { name, args });
try {
let result: string;
switch (name) {
case 'get_waybills':
if (!features.getWaybills) {
throw new Error('get_waybills tool is disabled');
}
result = await executeGetWaybills(soapClient, args);
break;
case 'lookup_tin':
if (!features.getNameFromTin) {
throw new Error('lookup_tin tool is disabled');
}
result = await executeLookupTin(soapClient, args);
break;
case 'get_waybill_types':
if (!features.getErrorCodes) {
throw new Error('get_waybill_types tool is disabled');
}
result = await executeGetWaybillTypes(soapClient);
break;
case 'get_error_codes':
if (!features.getErrorCodes) {
throw new Error('get_error_codes tool is disabled');
}
result = await executeGetErrorCodes(soapClient);
break;
case 'get_akciz_codes':
if (!features.getAkcizCodes) {
throw new Error('get_akciz_codes tool is disabled');
}
result = await executeGetAkcizCodes(soapClient, args);
break;
default:
throw new Error(`Unknown tool: ${name}`);
}
logger.info('Tool executed successfully', { name });
return {
content: [
{
type: 'text',
text: result,
},
],
};
} catch (error) {
logger.error('Tool execution failed', { name, error });
return {
content: [
{
type: 'text',
text: formatErrorForUser(error),
},
],
isError: true,
};
}
});
// Connect to stdio transport
const transport = new StdioServerTransport();
await server.connect(transport);
logger.info('โ
MCP server started successfully on stdio');
const enabledTools = Object.entries(features)
.filter(([_, v]) => v)
.map(([k]) => k)
.join(', ');
logger.info(`Enabled tools: ${enabledTools}`);
} catch (error) {
if (logger) {
logger.error('Fatal error during startup', { error });
} else {
console.error('Fatal error during startup:', error);
}
process.exit(1);
}
}
// Start the server
main().catch((error) => {
console.error('Unhandled error:', error);
process.exit(1);
});