PROJECT_DOCUMENTATION.md•23.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.