Skip to main content
Glama

RS.ge Waybill MCP Server

PROJECT_DOCUMENTATION.md23.7 kB
# RS.ge Waybill MCP Server - Project Documentation ## Table of Contents 1. [Project Overview](#project-overview) 2. [Architecture](#architecture) 3. [Project Structure](#project-structure) 4. [Core Components](#core-components) 5. [Data Flow](#data-flow) 6. [Configuration](#configuration) 7. [API Integration](#api-integration) 8. [Error Handling](#error-handling) 9. [Logging](#logging) 10. [Development Workflow](#development-workflow) --- ## Project Overview ### What is This Project? This is an **MCP (Model Context Protocol) server** that integrates the RS.ge Waybill SOAP API with Claude Desktop, allowing users to query and manage waybills through natural language conversations with Claude. ### Key Features ✅ **Natural Language Interface** - Query waybills using conversational language ✅ **Date Range Queries** - Retrieve waybills for specific date ranges ✅ **TIN Lookup** - Get company names from Tax Identification Numbers ✅ **Dictionary Access** - Fetch error codes, akciz codes, waybill types ✅ **Type-Safe** - Full TypeScript implementation with strict typing ✅ **Robust Error Handling** - Retry logic, validation, detailed error messages ✅ **Comprehensive Logging** - Winston-based logging for debugging ### Technology Stack - **Language:** TypeScript 5.x - **Runtime:** Node.js 18+ - **Protocol:** MCP (Model Context Protocol) - **API:** RS.ge SOAP API - **HTTP Client:** Axios - **XML Parser:** fast-xml-parser - **Validation:** Zod - **Logging:** Winston - **Build Tool:** TypeScript Compiler (tsc) --- ## Architecture ### High-Level Architecture ``` ┌─────────────────┐ │ Claude Desktop │ │ │ │ (User Chat) │ └────────┬────────┘ │ MCP Protocol │ (stdio) ▼ ┌─────────────────┐ │ MCP Server │ │ │ │ (This Project) │ └────────┬────────┘ │ SOAP/HTTP │ ▼ ┌─────────────────┐ │ RS.ge API │ │ │ │ (Waybill Service)│ └─────────────────┘ ``` ### Communication Flow 1. **User → Claude Desktop** User types: "Show me waybills from October 19-21, 2025" 2. **Claude Desktop → MCP Server** MCP request: `tools/rs_get_waybills` with parameters 3. **MCP Server → RS.ge API** SOAP request with credentials and date range 4. **RS.ge API → MCP Server** XML response with waybill data 5. **MCP Server → Claude Desktop** Parsed JSON data 6. **Claude Desktop → User** Human-readable response with waybill information --- ## Project Structure ``` MCPWaybill/ ├── src/ # Source code (TypeScript) │ ├── index.ts # MCP server entry point │ ├── config/ # Configuration management │ │ ├── config.ts # Config loader │ │ └── config.schema.ts # Zod schema for validation │ ├── services/ # Business logic │ │ ├── soap-client.ts # RS.ge SOAP API client │ │ └── xml-parser.ts # XML parsing utilities │ ├── tools/ # MCP tools (exposed to Claude) │ │ ├── get-waybills.ts # Waybill retrieval tool │ │ ├── get-dictionaries.ts # Dictionary tools │ │ └── lookup-tin.ts # TIN lookup tool │ ├── types/ # TypeScript type definitions │ │ └── waybill.types.ts # Waybill and API types │ └── utils/ # Utility functions │ ├── logger.ts # Winston logger setup │ └── error-handler.ts # Error handling utilities ├── dist/ # Compiled JavaScript (generated) ├── config/ # Configuration files │ ├── config.json # Main configuration │ ├── config.example.json # Example configuration │ └── config.schema.json # JSON schema ├── docs/ # Documentation │ ├── RS_GE_API_BEST_PRACTICES.md │ ├── PROJECT_DOCUMENTATION.md │ ├── MCP_DEVELOPMENT_GUIDE.md │ └── SETUP_AND_DEPLOYMENT.md ├── logs/ # Log files (generated) ├── .env # Environment variables (git-ignored) ├── .env.example # Example environment variables ├── package.json # Node.js dependencies ├── tsconfig.json # TypeScript configuration └── README.md # Project readme ``` --- ## Core Components ### 1. MCP Server (src/index.ts) **Purpose:** Entry point that exposes tools to Claude Desktop via MCP protocol. **Key Responsibilities:** - Initialize MCP server - Register tools (get_waybills, get_dictionaries, lookup_tin) - Handle tool requests from Claude - Return results in MCP format **Code Structure:** ```typescript // Initialize server const server = new Server({ name: 'rs-waybill-mcp', version: '1.0.0' }, { capabilities: { tools: {} } }); // Register tools server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'rs_get_waybills', description: 'Get waybills from RS.ge for a date range', inputSchema: { /* Zod schema */ } }, // ... other tools ] })); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name === 'rs_get_waybills') { return await getWaybillsTool(args); } // ... other tools }); ``` --- ### 2. SOAP Client (src/services/soap-client.ts) **Purpose:** Handles all communication with RS.ge SOAP API. **Key Methods:** ```typescript class RsWaybillSoapClient { // Get waybills for date range async getWaybillsV1( startDate: string, endDate: string, buyerTin?: string ): Promise<Waybill[]> // Get single waybill by ID async getWaybill(waybillId: string): Promise<Waybill | null> // Get error codes dictionary async getErrorCodes(): Promise<ErrorCode[]> // Get akciz codes async getAkcizCodes(searchText?: string): Promise<AkcizCode[]> // Get waybill types async getWaybillTypes(): Promise<WaybillType[]> // Lookup company name from TIN async getNameFromTin(tin: string): Promise<string> // ... other methods } ``` **Critical Implementation Details:** ```typescript async getWaybillsV1(startDate, endDate, buyerTin) { // 1. Extract seller_un_id from credentials const sellerUnId = this.credentials.user.split(':')[1]; // 2. Convert to ISO datetime and add +1 day to end const startDateTime = `${startDate}T00:00:00`; const endDateObj = new Date(endDate); endDateObj.setDate(endDateObj.getDate() + 1); const endDateTime = endDateObj.toISOString().slice(0, 19); // 3. Call SOAP API const response = await this.callSoap('get_waybills', { su: this.credentials.user, sp: this.credentials.password, seller_un_id: sellerUnId, create_date_s: startDateTime, create_date_e: endDateTime, buyer_tin: buyerTin || '', }); // 4. Extract and return waybills return normalizeToArray(response.WAYBILL_LIST?.WAYBILL); } ``` --- ### 3. XML Parser (src/services/xml-parser.ts) **Purpose:** Parse SOAP XML responses and build SOAP XML requests. **Key Functions:** ```typescript // Build SOAP request export function buildSoapRequest( method: string, params: Record<string, any>, namespace: string = 'http://tempuri.org/' ): string // Parse SOAP response export function parseSoapResponse<T>(xmlData: string): T // Build waybill XML export function buildWaybillXml(waybill: Waybill): string ``` **XML Parsing Logic:** ```typescript export function parseSoapResponse<T>(xmlData: string): T { const parser = new XMLParser({ ignoreAttributes: false, attributeNamePrefix: '@_', removeNSPrefix: true, }); const parsed = parser.parse(xmlData); // Navigate SOAP structure const body = parsed.Envelope.Body; const methodResponse = body[Object.keys(body)[0]]; // Filter out XML attributes (critical!) const resultKeys = Object.keys(methodResponse) .filter(k => !k.startsWith('@_')); // Find Result element const resultKey = resultKeys.find(k => k.endsWith('Result')); if (!resultKey) throw new Error('No Result element'); const result = methodResponse[resultKey]; // Check for API errors if (result.RESULT?.STATUS < 0) { throw new Error(`RS.ge API Error ${result.RESULT.STATUS}`); } return result as T; } ``` --- ### 4. Tools (src/tools/) **Purpose:** MCP tool implementations that Claude Desktop calls. **get-waybills.ts:** ```typescript export async function getWaybillsTool(args: any) { // 1. Validate inputs const { start_date, end_date, buyer_tin } = args; // 2. Call SOAP client const client = new RsWaybillSoapClient(config, credentials); const waybills = await client.getWaybillsV1( start_date, end_date, buyer_tin ); // 3. Format response return { content: [{ type: 'text', text: JSON.stringify({ count: waybills.length, waybills: waybills.map(formatWaybill) }, null, 2) }] }; } ``` --- ### 5. Type Definitions (src/types/waybill.types.ts) **Purpose:** TypeScript interfaces for all API responses and data structures. **Key Types:** ```typescript // Waybill structure export interface Waybill { ID: number; TYPE: number; CREATE_DATE: string; BUYER_TIN: number | string; BUYER_NAME: string; SELLER_TIN: number | string; SELLER_NAME: string; FULL_AMOUNT: number; STATUS: number; // ... 30+ more fields } // API response types export interface GetWaybillsResponse { WAYBILL_LIST?: { WAYBILL: Waybill | Waybill[]; }; } export interface GetErrorCodesResponse { ERROR_CODES?: { ERROR_CODE: ErrorCode | ErrorCode[]; }; } // Helper to normalize single/array responses export function normalizeToArray<T>(data: T | T[] | undefined): T[] { if (!data) return []; return Array.isArray(data) ? data : [data]; } ``` --- ### 6. Configuration (src/config/) **Purpose:** Load and validate configuration from config.json and environment variables. **config.schema.ts:** ```typescript import { z } from 'zod'; export const ConfigSchema = z.object({ server: z.object({ name: z.string(), version: z.string(), description: z.string(), }), api: z.object({ baseUrl: z.string().url(), timeout: z.number().min(1000), retries: z.number().min(0).max(10), retryDelay: z.number().min(100), }), credentials: z.object({ serviceUser: z.string(), servicePassword: z.string(), }), logging: z.object({ level: z.enum(['error', 'warn', 'info', 'debug']), file: z.string(), console: z.boolean(), }), // ... more fields }); ``` **config.ts:** ```typescript export function loadConfig(): Config { // 1. Read config.json const configPath = path.join(__dirname, '../../config/config.json'); const configData = fs.readFileSync(configPath, 'utf-8'); const rawConfig = JSON.parse(configData); // 2. Replace environment variables const processedConfig = replaceEnvVars(rawConfig); // 3. Validate with Zod const config = ConfigSchema.parse(processedConfig); return config; } function replaceEnvVars(obj: any): any { // Recursively replace ${VAR_NAME} with process.env.VAR_NAME if (typeof obj === 'string') { return obj.replace(/\$\{([^}]+)\}/g, (_, varName) => { return process.env[varName] || ''; }); } // ... handle objects and arrays } ``` --- ## Data Flow ### Example: Get Waybills ``` 1. User Input (Claude Desktop) ┌────────────────────────────────────┐ │ "Show me waybills from Oct 19-21" │ └────────────────────────────────────┘ │ ▼ 2. Claude Interprets Intent ┌────────────────────────────────────┐ │ Tool: rs_get_waybills │ │ Args: { │ │ start_date: "2025-10-19", │ │ end_date: "2025-10-21" │ │ } │ └────────────────────────────────────┘ │ ▼ 3. MCP Server (index.ts) ┌────────────────────────────────────┐ │ Receives tool call │ │ Routes to getWaybillsTool() │ └────────────────────────────────────┘ │ ▼ 4. Tool Handler (get-waybills.ts) ┌────────────────────────────────────┐ │ Validates inputs │ │ Calls SOAP client │ └────────────────────────────────────┘ │ ▼ 5. SOAP Client (soap-client.ts) ┌────────────────────────────────────┐ │ Prepares dates: │ │ start: 2025-10-19T00:00:00 │ │ end: 2025-10-22T00:00:00 │ │ Extracts seller_un_id │ │ Builds SOAP request │ └────────────────────────────────────┘ │ ▼ 6. XML Parser (xml-parser.ts) ┌────────────────────────────────────┐ │ Builds SOAP XML: │ │ <get_waybills> │ │ <su>...</su> │ │ <seller_un_id>...</seller_un_id> │ │ <create_date_s>...</> │ │ <create_date_e>...</> │ │ </get_waybills> │ └────────────────────────────────────┘ │ ▼ 7. HTTP Request (Axios) ┌────────────────────────────────────┐ │ POST to RS.ge API │ │ SOAPAction header │ │ Timeout: 30s │ └────────────────────────────────────┘ │ ▼ 8. RS.ge API Response ┌────────────────────────────────────┐ │ <?xml version="1.0"?> │ │ <soap:Envelope> │ │ <soap:Body> │ │ <get_waybillsResponse> │ │ <get_waybillsResult> │ │ <WAYBILL_LIST> │ │ <WAYBILL>...</WAYBILL> │ │ ... │ │ </WAYBILL_LIST> │ │ </get_waybillsResult> │ │ </get_waybillsResponse> │ │ </soap:Body> │ │ </soap:Envelope> │ └────────────────────────────────────┘ │ ▼ 9. XML Parser (xml-parser.ts) ┌────────────────────────────────────┐ │ Parse XML to JSON │ │ Filter @_ attributes │ │ Extract get_waybillsResult │ │ Check for errors │ └────────────────────────────────────┘ │ ▼ 10. SOAP Client (soap-client.ts) ┌────────────────────────────────────┐ │ Normalize WAYBILL array │ │ Return Waybill[] objects │ └────────────────────────────────────┘ │ ▼ 11. Tool Handler (get-waybills.ts) ┌────────────────────────────────────┐ │ Format waybills for display │ │ Return MCP response │ └────────────────────────────────────┘ │ ▼ 12. MCP Server (index.ts) ┌────────────────────────────────────┐ │ Send response to Claude Desktop │ └────────────────────────────────────┘ │ ▼ 13. Claude Desktop ┌────────────────────────────────────┐ │ Format as human-readable text │ │ "Found 61 waybills: │ │ - Oct 19: 12 waybills │ │ - Oct 20: 32 waybills │ │ - Oct 21: 17 waybills" │ └────────────────────────────────────┘ ``` --- ## Configuration ### config.json Structure ```json { "server": { "name": "rs-waybill-mcp", "version": "1.0.0", "description": "MCP server for RS.ge Waybill SOAP API" }, "api": { "baseUrl": "https://services.rs.ge/WayBillService/WayBillService.asmx", "timeout": 30000, "retries": 3, "retryDelay": 2000 }, "credentials": { "serviceUser": "${RS_SERVICE_USER}", "servicePassword": "${RS_SERVICE_PASSWORD}" }, "logging": { "level": "info", "file": "logs/mcp-server.log", "console": true, "maxSize": "10m", "maxFiles": 7 }, "features": { "getWaybills": true, "saveWaybill": false, "sendWaybill": false } } ``` ### Environment Variables Create `.env` file: ```bash RS_SERVICE_USER=4053098841:405309884 RS_SERVICE_PASSWORD=YourPasswordHere ``` **Security Note:** Never commit `.env` to version control! --- ## API Integration ### RS.ge API Endpoints **Base URL:** ``` https://services.rs.ge/WayBillService/WayBillService.asmx ``` **Available Operations:** - `get_waybills` - Get waybills with filters - `get_waybill` - Get single waybill by ID - `save_waybill` - Create/update waybill - `send_waybill` - Submit waybill to RS.ge - `close_waybill` - Close waybill - `confirm_waybill` - Buyer confirms waybill - `reject_waybill` - Buyer rejects waybill - `get_error_codes` - Get error code dictionary - `get_akciz_codes` - Get akciz code dictionary - `get_waybill_types` - Get waybill type dictionary - `get_name_from_tin` - Lookup company name from TIN ### Authentication Uses HTTP Basic Auth embedded in SOAP body: ```xml <su>username:company_id</su> <sp>password</sp> <seller_un_id>company_id</seller_un_id> ``` --- ## Error Handling ### Error Types ```typescript // Custom error classes export class SoapApiError extends Error { constructor(message: string) { super(message); this.name = 'SoapApiError'; } } export class AuthenticationError extends SoapApiError { constructor(message: string) { super(message); this.name = 'AuthenticationError'; } } export class ValidationError extends SoapApiError { constructor(message: string) { super(message); this.name = 'ValidationError'; } } ``` ### Retry Logic ```typescript export async function retryWithBackoff<T>( fn: () => Promise<T>, options: { maxRetries: number; initialDelay: number; shouldRetry?: (error: any) => boolean; } ): Promise<T> { let lastError: any; let delay = options.initialDelay; for (let attempt = 0; attempt <= options.maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error; if (attempt === options.maxRetries) break; if (options.shouldRetry && !options.shouldRetry(error)) { throw error; } await new Promise(resolve => setTimeout(resolve, delay)); delay *= 2; // Exponential backoff } } throw lastError; } ``` ### RS.ge API Error Codes Common error codes: - `-101` - Missing seller_un_id - `-1064` - Date range too large (>3 days) - `-1072` - Date range issue (format or validation) - `401/403` - Authentication failure --- ## Logging ### Winston Logger Configuration ```typescript import winston from 'winston'; export function getLogger(): winston.Logger { return winston.createLogger({ level: config.logging.level, format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), transports: [ new winston.transports.File({ filename: config.logging.file, maxsize: 10485760, // 10MB maxFiles: 7, }), new winston.transports.Console({ format: winston.format.simple(), }), ], }); } ``` ### Log Levels - `error` - Critical errors - `warn` - Warnings and recoverable errors - `info` - General information (API calls, results) - `debug` - Detailed debugging (request/response data) --- ## Development Workflow ### 1. Local Development ```bash # Install dependencies npm install # Build TypeScript npm run build # Run in development mode (with auto-rebuild) npm run dev # Run tests npm test ``` ### 2. Code Organization - Keep business logic in `services/` - Keep MCP tools in `tools/` - Keep types in `types/` - Keep utilities in `utils/` - One responsibility per file ### 3. Type Safety Always use TypeScript strict mode: ```typescript // tsconfig.json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true } } ``` ### 4. Testing Strategy - Unit tests for utilities - Integration tests for SOAP client - End-to-end tests with real API - Mock API for CI/CD --- ## Summary This MCP server provides a robust, type-safe integration between Claude Desktop and the RS.ge Waybill SOAP API. Key architectural decisions: 1. **Separation of Concerns** - Clear separation between MCP layer, business logic, and API integration 2. **Type Safety** - Full TypeScript coverage with Zod validation 3. **Error Resilience** - Retry logic, detailed error messages, graceful degradation 4. **Configuration** - Flexible config system with environment variable support 5. **Logging** - Comprehensive logging for debugging and monitoring **Next:** See `MCP_DEVELOPMENT_GUIDE.md` to learn how to build your own MCP server based on this architecture.

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/BorisSolomonia/MCPWaybill'

If you have feedback or need assistance with the MCP directory API, please join our Discord server