#
tokens: 26230/50000 13/13 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .gitignore
├── docs
│   ├── analysis.md
│   ├── code-restructuring.md
│   ├── error-handling-and-logging.md
│   ├── feature-improvements.md
│   ├── implementation-roadmap.md
│   ├── README.md
│   ├── security-and-authentication.md
│   └── testing-strategy.md
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
node_modules/
build/
*.log
.env*
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# ERPNext MCP Server

A Model Context Protocol server for ERPNext integration

This is a TypeScript-based MCP server that provides integration with ERPNext/Frappe API. It enables AI assistants to interact with ERPNext data and functionality through the Model Context Protocol.

## Features

### Resources
- Access ERPNext documents via `erpnext://{doctype}/{name}` URIs
- JSON format for structured data access

### Tools
- `authenticate_erpnext` - Authenticate with ERPNext using username and password
- `get_documents` - Get a list of documents for a specific doctype
- `create_document` - Create a new document in ERPNext
- `update_document` - Update an existing document in ERPNext
- `run_report` - Run an ERPNext report
- `get_doctype_fields` - Get fields list for a specific DocType
- `get_doctypes` - Get a list of all available DocTypes

## Configuration

The server requires the following environment variables:
- `ERPNEXT_URL` - The base URL of your ERPNext instance
- `ERPNEXT_API_KEY` (optional) - API key for authentication
- `ERPNEXT_API_SECRET` (optional) - API secret for authentication

## Development

Install dependencies:
```bash
npm install
```

Build the server:
```bash
npm run build
```

For development with auto-rebuild:
```bash
npm run watch
```

## Installation

To use with Claude Desktop, add the server config:

On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
On Windows: `%APPDATA%/Claude/claude_desktop_config.json`

```json
{
  "mcpServers": {
    "erpnext": {
      "command": "node",
      "args": ["/path/to/erpnext-server/build/index.js"],
      "env": {
        "ERPNEXT_URL": "http://your-erpnext-instance.com",
        "ERPNEXT_API_KEY": "your-api-key",
        "ERPNEXT_API_SECRET": "your-api-secret"
      }
    }
  }
}
```

To use with Claude in VSCode, add the server config to:

On MacOS: `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
On Windows: `%APPDATA%/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`

### Debugging

Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script:

```bash
npm run inspector
```

The Inspector will provide a URL to access debugging tools in your browser.

## Usage Examples

### Authentication
```
<use_mcp_tool>
<server_name>erpnext</server_name>
<tool_name>authenticate_erpnext</tool_name>
<arguments>
{
  "username": "your-username",
  "password": "your-password"
}
</arguments>
</use_mcp_tool>
```

### Get Customer List
```
<use_mcp_tool>
<server_name>erpnext</server_name>
<tool_name>get_documents</tool_name>
<arguments>
{
  "doctype": "Customer"
}
</arguments>
</use_mcp_tool>
```

### Get Customer Details
```
<access_mcp_resource>
<server_name>erpnext</server_name>
<uri>erpnext://Customer/CUSTOMER001</uri>
</access_mcp_resource>
```

### Create New Item
```
<use_mcp_tool>
<server_name>erpnext</server_name>
<tool_name>create_document</tool_name>
<arguments>
{
  "doctype": "Item",
  "data": {
    "item_code": "ITEM001",
    "item_name": "Test Item",
    "item_group": "Products",
    "stock_uom": "Nos"
  }
}
</arguments>
</use_mcp_tool>
```

### Get Item Fields
```
<use_mcp_tool>
<server_name>erpnext</server_name>
<tool_name>get_doctype_fields</tool_name>
<arguments>
{
  "doctype": "Item"
}
</arguments>
</use_mcp_tool>

```

--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------

```markdown
# ERPNext MCP Server Improvement Documentation

This directory contains comprehensive documentation for improving the ERPNext MCP server implementation. These documents analyze the current state of the server and provide detailed recommendations for enhancing its maintainability, reliability, security, and functionality.

## Document Overview

### [Analysis](./analysis.md)
A comprehensive analysis of the current ERPNext MCP server implementation, identifying key areas for improvement including code organization, error handling, authentication, caching, testing, logging, documentation, configuration, rate limiting, tool implementation, security, resource management, and dependency management.

### [Code Restructuring](./code-restructuring.md)
Detailed plan for restructuring the codebase to improve maintainability and testability, including a proposed directory structure, key component implementation examples, and step-by-step implementation plan.

### [Testing Strategy](./testing-strategy.md)
Comprehensive testing approach covering unit tests, integration tests, and end-to-end tests, with code examples, mock implementations, and configuration details for setting up a robust testing framework.

### [Error Handling and Logging](./error-handling-and-logging.md)
Strategy for implementing structured error handling and logging, including error categorization, correlation IDs, contextual logging, and integration with the MCP server.

### [Security and Authentication](./security-and-authentication.md)
Detailed recommendations for enhancing security and authentication, covering input validation, secure configuration management, rate limiting, security middleware, and best practices for credential handling.

### [Implementation Roadmap](./implementation-roadmap.md)
Phased approach for implementing all recommended improvements, with timeline estimates, dependencies, task prioritization, and risk management considerations.

### [Feature Improvements](./feature-improvements.md)
Detailed recommendations for enhancing the server's functionality with new features like advanced querying, bulk operations, webhooks, document workflows, file operations, and more.

## Getting Started

If you're new to this documentation, we recommend starting with the [Analysis](./analysis.md) document to understand the current state and identified areas for improvement. Then, review the [Implementation Roadmap](./implementation-roadmap.md) for a structured approach to implementing the recommended changes.

## Key Improvement Areas

1. **Code Organization**: Transition from a monolithic design to a modular architecture
2. **Error Handling**: Implement structured error handling with proper categorization
3. **Testing**: Add comprehensive test coverage with unit, integration, and E2E tests
4. **Logging**: Enhance logging with structured formats and contextual information
5. **Security**: Improve authentication, add input validation, and implement rate limiting
6. **Performance**: Add caching and optimize API interactions
7. **Maintainability**: Improve documentation and implement best practices

## Implementation Guide

For implementing these improvements, follow the phases outlined in the [Implementation Roadmap](./implementation-roadmap.md). This allows for incremental enhancement while maintaining a functioning system throughout the process.

## Additional Resources

- [Model Context Protocol Documentation](https://github.com/modelcontextprotocol/mcp)
- [ERPNext API Reference](https://frappeframework.com/docs/user/en/api)
- [Frappe Framework Documentation](https://frappeframework.com/docs)

```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./build",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "erpnext-server",
  "version": "0.1.0",
  "description": "A Model Context Protocol server",
  "private": true,
  "type": "module",
  "bin": {
    "erpnext-server": "./build/index.js"
  },
  "files": [
    "build"
  ],
  "scripts": {
    "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
    "prepare": "npm run build",
    "watch": "tsc --watch",
    "inspector": "npx @modelcontextprotocol/inspector build/index.js"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "0.6.0",
    "axios": "^1.8.4"
  },
  "devDependencies": {
    "@types/node": "^20.11.24",
    "typescript": "^5.3.3"
  }
}

```

--------------------------------------------------------------------------------
/docs/analysis.md:
--------------------------------------------------------------------------------

```markdown
# ERPNext MCP Server Analysis

This document provides an analysis of the current ERPNext MCP server implementation and suggests improvements for better reliability, maintainability, and extensibility.

## Current Implementation Overview

The ERPNext MCP server currently provides integration with the ERPNext/Frappe API through:

- **Resource access**: Documents can be accessed via `erpnext://{doctype}/{name}` URIs
- **Tools**: Several tools for authentication, document manipulation, and report running
- **Single-file implementation**: All logic is contained in `src/index.ts`

## Areas for Improvement

### 1. Code Organization

**Issues:**
- The entire server implementation is in a single file, making it difficult to maintain as it grows
- No separation of concerns between API client, MCP server, and request handlers
- Mixed business logic and protocol handling

**Recommendations:**
- Restructure the codebase into multiple modules:
  ```
  src/
  ├── client/
  │   └── erpnext-client.ts           # ERPNext API client logic
  ├── handlers/
  │   ├── resource-handlers.ts        # Resource request handlers 
  │   └── tool-handlers.ts            # Tool request handlers
  ├── models/
  │   └── types.ts                    # TypeScript interfaces and types
  ├── utils/
  │   ├── error-handler.ts            # Error handling utilities
  │   ├── logger.ts                   # Logging utilities
  │   └── config.ts                   # Configuration management
  └── index.ts                        # Server bootstrap
  ```

### 2. Error Handling

**Issues:**
- Basic error handling with limited categorization
- Inconsistent error response formats
- No handling of network-specific errors vs. business logic errors

**Recommendations:**
- Create a dedicated error handling module
- Map ERPNext API errors to appropriate MCP error codes
- Implement consistent error logging with appropriate detail levels
- Add correlation IDs to track errors across requests

### 3. Authentication

**Issues:**
- Only username/password authentication supported
- No token refresh mechanism
- API key/secret handling is basic

**Recommendations:**
- Support modern OAuth-based authentication
- Implement token refresh strategy
- Add secure credential storage options
- Consider implementing session management

### 4. Caching

**Issues:**
- `doctypeCache` is defined but never used
- No caching strategy for frequently accessed data
- Each request makes a fresh API call

**Recommendations:**
- Implement in-memory cache for DocType metadata
- Add time-based cache expiration
- Consider using a dedicated cache library for more complex scenarios
- Add cache invalidation when documents are updated

### 5. Testing

**Issues:**
- No test suite
- No mocking of external dependencies

**Recommendations:**
- Add unit tests for core functionality
- Implement integration tests for ERPNext client
- Create mock ERPNext API for testing
- Set up CI/CD pipeline with test automation

### 6. Logging

**Issues:**
- Basic console logging
- No structured log format
- No log levels for different environments

**Recommendations:**
- Implement structured logging
- Add configurable log levels
- Include request/response details for debugging
- Consider using a dedicated logging library

### 7. Documentation

**Issues:**
- Basic README with limited examples
- No JSDoc comments for most functions
- No detailed API documentation

**Recommendations:**
- Add comprehensive JSDoc comments
- Create detailed API reference
- Add code examples for common scenarios
- Document error codes and their meanings
- Consider generating API docs with TypeDoc

### 8. Configuration

**Issues:**
- Environment variables handled in an ad-hoc way
- No configuration validation
- Limited configuration options

**Recommendations:**
- Create a dedicated configuration module
- Add validation for required configuration
- Support multiple configuration sources (env vars, config files)
- Add configuration schema validation

### 9. Rate Limiting

**Issues:**
- No protection against excessive API calls
- Potential for unintended DoS on ERPNext instance

**Recommendations:**
- Implement rate limiting for API calls
- Add configurable limits per operation
- Provide feedback on rate limit status

### 10. Tool Implementation

**Issues:**
- `get_doctype_fields` has a hacky implementation that loads a sample document
- Limited error handling in tool implementations
- No pagination support for large result sets

**Recommendations:**
- Use proper metadata APIs to get DocType fields
- Implement pagination for list operations
- Add better validation of tool inputs
- Consider adding additional useful tools (e.g., bulk operations)

### 11. Security

**Issues:**
- Passwords are passed directly with no handling recommendations
- No security headers or CORS configuration
- No input sanitization

**Recommendations:**
- Add input validation and sanitization
- Improve credential handling and provide secure usage guidelines
- Document security best practices

### 12. Resource Management

**Issues:**
- Limited resource implementations compared to tools
- No dynamic resource discovery

**Recommendations:**
- Expand resource capabilities
- Implement more resource templates
- Add resource discovery mechanism

### 13. Dependency Management

**Issues:**
- No dependency injection for better testability
- Direct dependency on axios without abstraction

**Recommendations:**
- Implement dependency injection pattern
- Create HTTP client abstraction to allow switching libraries
- Centralize external dependency management

## Conclusion

The ERPNext MCP server provides a solid foundation for integration with ERPNext but could benefit from several improvements to make it more maintainable, robust, and feature-rich. The recommendations provided above would help transform it into a production-ready solution that's easier to maintain and extend.

```

--------------------------------------------------------------------------------
/docs/implementation-roadmap.md:
--------------------------------------------------------------------------------

```markdown
# Implementation Roadmap

This document outlines a comprehensive roadmap for implementing the improvements suggested in the analysis of the ERPNext MCP server.

## Overview

The ERPNext MCP server provides integration with ERPNext through the Model Context Protocol, but several improvements can enhance its maintainability, reliability, and security. This roadmap prioritizes these improvements into phases for systematic implementation.

## Phase 1: Code Restructuring and Basic Improvements

**Objective**: Establish a solid foundation by restructuring the codebase into a more maintainable architecture.

**Duration**: 2-3 weeks

### Tasks

1. **Project Structure Reorganization**
   - Create directory structure as outlined in [`code-restructuring.md`](./code-restructuring.md)
   - Split the monolithic `src/index.ts` into modular components

2. **Core Module Extraction**
   - Extract ERPNext client into `src/client/erpnext-client.ts`
   - Extract resource handlers into `src/handlers/resource-handlers.ts`
   - Extract tool handlers into `src/handlers/tool-handlers.ts`

3. **Configuration Management**
   - Create dedicated configuration module in `src/utils/config.ts`
   - Implement validation for required configuration
   - Add support for multiple configuration sources

4. **Basic Logging**
   - Implement structured logging in `src/utils/logger.ts`
   - Add different log levels for development and production
   - Add contextual information to logs

### Deliverables
- Modular codebase with clear separation of concerns
- Improved configuration management
- Basic structured logging

## Phase 2: Enhanced Error Handling and Testing

**Objective**: Improve reliability through better error handling and establish a testing framework.

**Duration**: 3-4 weeks

### Tasks

1. **Error Handling**
   - Implement error categorization as outlined in [`error-handling-and-logging.md`](./error-handling-and-logging.md)
   - Create error mapping between ERPNext and MCP error codes
   - Add correlation IDs for tracking errors across the system

2. **Testing Framework Setup**
   - Set up Jest for testing
   - Create test directory structure
   - Add test utilities and mocks

3. **Unit Tests**
   - Write unit tests for core modules (client, utils)
   - Achieve at least 70% code coverage

4. **Integration Tests**
   - Add integration tests for API interactions
   - Create mock ERPNext API for testing

### Deliverables
- Robust error handling system
- Test suite with good coverage
- Mock ERPNext API for testing

## Phase 3: Security and Authentication Enhancements

**Objective**: Strengthen security and improve authentication mechanisms.

**Duration**: 2-3 weeks

### Tasks

1. **Input Validation**
   - Create validation module as outlined in [`security-and-authentication.md`](./security-and-authentication.md)
   - Implement schema validation for all inputs
   - Add input sanitization to prevent injection attacks

2. **Authentication Improvements**
   - Implement authentication manager
   - Add token management
   - Implement secure credential handling

3. **Rate Limiting**
   - Add rate limiting middleware
   - Implement different limits for different operations
   - Add protection against brute force attacks

4. **Security Headers**
   - Add security headers to responses
   - Implement secure defaults

### Deliverables
- Secure input validation
- Enhanced authentication
- Protection against common attacks

## Phase 4: Advanced Features and Performance

**Objective**: Add advanced features and optimize performance.

**Duration**: 4-6 weeks

### Tasks

1. **Caching Implementation**
   - Implement in-memory cache
   - Add cache invalidation logic
   - Optimize frequently accessed resources

2. **Documentation**
   - Add comprehensive JSDoc comments
   - Generate API documentation
   - Update README with examples

3. **Pagination Support**
   - Add pagination for list operations
   - Implement cursor-based pagination for large result sets

4. **Resource Expansion**
   - Enhance resource capabilities
   - Add more resource templates
   - Improve discovery mechanism

5. **Performance Optimization**
   - Implement request batching
   - Optimize network requests
   - Add performance metrics

6. **Feature Enhancements** (as outlined in [`feature-improvements.md`](./feature-improvements.md))
   - Implement enhanced DocType discovery and metadata
   - Add advanced querying capabilities
   - Develop bulk operations support
   - Create file operations functionality
   - Add webhook and event support
   - Implement document workflows
   - Develop data synchronization utilities

### Deliverables
- Caching system for improved performance
- Comprehensive documentation
- Enhanced resource capabilities
- Performance metrics and optimizations
- New functional capabilities as detailed in feature improvements

## Phase 5: CI/CD and DevOps

**Objective**: Set up continuous integration and deployment pipeline.

**Duration**: 1-2 weeks

### Tasks

1. **CI Setup**
   - Configure GitHub Actions or similar CI platform
   - Automate testing
   - Implement code quality checks

2. **Automated Builds**
   - Set up automated builds
   - Create release versioning
   - Generate build artifacts

3. **Deployment Automation**
   - Create deployment scripts
   - Add environment configuration templates
   - Document deployment process

### Deliverables
- Automated CI/CD pipeline
- Code quality checks
- Streamlined release process

## Timeline Overview

```
Week 1-3:   Phase 1 - Code Restructuring
Week 4-7:   Phase 2 - Error Handling and Testing
Week 8-10:  Phase 3 - Security Enhancements
Week 11-14: Phase 4 - Advanced Features
Week 15-16: Phase 5 - CI/CD and DevOps
```

## Implementation Priorities

When implementing these improvements, follow these priorities:

1. **Critical**: Code restructuring, basic error handling
2. **High**: Testing setup, security improvements
3. **Medium**: Caching, pagination, documentation
4. **Low**: Advanced features, CI/CD

## Dependencies and Requirements

For implementing these improvements, ensure:

1. Access to ERPNext instance for testing
2. Node.js development environment
3. Knowledge of TypeScript and MCP SDK
4. Understanding of ERPNext API

## Risk Management

Some potential risks to consider:

1. **API Changes**: ERPNext may update its API, requiring adjustments
2. **Backward Compatibility**: Ensure changes don't break existing clients
3. **Performance Impact**: Monitor performance impacts of changes

## Conclusion

Following this roadmap will transform the ERPNext MCP server into a robust, maintainable, and secure system. The phased approach allows for incremental improvements while maintaining a functioning system throughout the process.

```

--------------------------------------------------------------------------------
/docs/code-restructuring.md:
--------------------------------------------------------------------------------

```markdown
# Code Restructuring Plan

This document outlines a plan for restructuring the ERPNext MCP server codebase to improve maintainability, testability, and extensibility.

## Current Structure

Currently, the entire implementation is in a single file (`src/index.ts`), which contains:

- ERPNext API client
- MCP server setup
- Resource handlers
- Tool handlers
- Error handling
- Authentication logic

This monolithic approach makes the code difficult to maintain, test, and extend.

## Proposed Structure

### Directory Structure

```
src/
├── client/
│   └── erpnext-client.ts          # ERPNext API client
├── handlers/
│   ├── resource-handlers.ts       # Resource request handlers
│   └── tool-handlers.ts           # Tool request handlers
├── models/
│   ├── doctype.ts                 # DocType interfaces
│   └── errors.ts                  # Error types
├── utils/
│   ├── cache.ts                   # Caching utilities
│   ├── config.ts                  # Configuration management
│   ├── error-handler.ts           # Error handling utilities
│   └── logger.ts                  # Logging utilities
├── constants/
│   └── error-codes.ts             # Error code definitions
├── middleware/
│   ├── auth.ts                    # Authentication middleware
│   └── rate-limiter.ts            # Rate limiting middleware
└── index.ts                       # Server bootstrap
```

### Key Components

#### 1. ERPNext Client (`src/client/erpnext-client.ts`)

Extract the ERPNext API client into a dedicated module:

```typescript
// src/client/erpnext-client.ts
import axios, { AxiosInstance } from "axios";
import { Logger } from "../utils/logger";
import { Config } from "../utils/config";
import { ERPNextError } from "../models/errors";

export class ERPNextClient {
  private baseUrl: string;
  private axiosInstance: AxiosInstance;
  private authenticated: boolean = false;
  private logger: Logger;

  constructor(config: Config, logger: Logger) {
    this.logger = logger;
    this.baseUrl = config.getERPNextUrl();
    
    // Initialize axios instance
    this.axiosInstance = axios.create({
      baseURL: this.baseUrl,
      withCredentials: true,
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    });
    
    // Configure authentication if credentials provided
    const apiKey = config.getERPNextApiKey();
    const apiSecret = config.getERPNextApiSecret();
    
    if (apiKey && apiSecret) {
      this.axiosInstance.defaults.headers.common['Authorization'] = 
        `token ${apiKey}:${apiSecret}`;
      this.authenticated = true;
      this.logger.info("Initialized with API key authentication");
    }
  }

  // Methods for API operations...
}
```

#### 2. Resource Handlers (`src/handlers/resource-handlers.ts`)

Move the resource-related request handlers to a dedicated module:

```typescript
// src/handlers/resource-handlers.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
  ListResourcesRequestSchema,
  ListResourceTemplatesRequestSchema,
  ReadResourceRequestSchema,
  McpError,
  ErrorCode
} from "@modelcontextprotocol/sdk/types.js";
import { ERPNextClient } from "../client/erpnext-client";
import { Logger } from "../utils/logger";
import { Cache } from "../utils/cache";

export function registerResourceHandlers(
  server: Server, 
  erpnext: ERPNextClient,
  cache: Cache,
  logger: Logger
) {
  // Handler for listing resources
  server.setRequestHandler(ListResourcesRequestSchema, async () => {
    logger.debug("Handling ListResourcesRequest");
    
    const resources = [
      {
        uri: "erpnext://DocTypes",
        name: "All DocTypes",
        mimeType: "application/json",
        description: "List of all available DocTypes in the ERPNext instance"
      }
    ];

    return { resources };
  });

  // Other resource handlers...
}
```

#### 3. Tool Handlers (`src/handlers/tool-handlers.ts`)

Similarly, move the tool-related request handlers:

```typescript
// src/handlers/tool-handlers.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  McpError,
  ErrorCode
} from "@modelcontextprotocol/sdk/types.js";
import { ERPNextClient } from "../client/erpnext-client";
import { Logger } from "../utils/logger";
import { handleErrors } from "../utils/error-handler";

export function registerToolHandlers(
  server: Server, 
  erpnext: ERPNextClient,
  logger: Logger
) {
  // Handler for listing tools
  server.setRequestHandler(ListToolsRequestSchema, async () => {
    logger.debug("Handling ListToolsRequest");
    
    return {
      tools: [
        {
          name: "get_doctypes",
          description: "Get a list of all available DocTypes",
          inputSchema: {
            type: "object",
            properties: {}
          }
        },
        // Other tools...
      ]
    };
  });

  // Handler for tool calls with proper error handling
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    logger.debug(`Handling CallToolRequest: ${request.params.name}`);
    
    try {
      switch (request.params.name) {
        case "authenticate_erpnext":
          return await handleAuthenticateErpnext(request, erpnext, logger);
        // Other tool handlers...
        default:
          throw new McpError(
            ErrorCode.MethodNotFound,
            `Unknown tool: ${request.params.name}`
          );
      }
    } catch (error) {
      return handleErrors(error, logger);
    }
  });
}

// Individual tool handler functions
async function handleAuthenticateErpnext(request, erpnext, logger) {
  // Implementation...
}
```

#### 4. Cache Utility (`src/utils/cache.ts`)

Implement the previously defined but unused cache:

```typescript
// src/utils/cache.ts
export class Cache {
  private cache: Map<string, CacheEntry>;
  private defaultTTLMs: number;
  
  constructor(defaultTTLMs: number = 5 * 60 * 1000) { // 5 minutes default
    this.cache = new Map();
    this.defaultTTLMs = defaultTTLMs;
  }
  
  set(key: string, value: any, ttlMs?: number): void {
    const expiryTime = Date.now() + (ttlMs || this.defaultTTLMs);
    this.cache.set(key, { value, expiryTime });
  }
  
  get<T>(key: string): T | undefined {
    const entry = this.cache.get(key);
    
    if (!entry) {
      return undefined;
    }
    
    if (Date.now() > entry.expiryTime) {
      this.cache.delete(key);
      return undefined;
    }
    
    return entry.value as T;
  }
  
  invalidate(keyPrefix: string): void {
    for (const key of this.cache.keys()) {
      if (key.startsWith(keyPrefix)) {
        this.cache.delete(key);
      }
    }
  }
}

interface CacheEntry {
  value: any;
  expiryTime: number;
}
```

#### 5. Configuration Module (`src/utils/config.ts`)

Create a dedicated configuration module:

```typescript
// src/utils/config.ts
export class Config {
  private erpnextUrl: string;
  private apiKey?: string;
  private apiSecret?: string;
  
  constructor() {
    this.erpnextUrl = this.getRequiredEnv("ERPNEXT_URL");
    // Remove trailing slash if present
    this.erpnextUrl = this.erpnextUrl.replace(/\/$/, '');
    
    this.apiKey = process.env.ERPNEXT_API_KEY;
    this.apiSecret = process.env.ERPNEXT_API_SECRET;
    
    this.validate();
  }
  
  private getRequiredEnv(name: string): string {
    const value = process.env[name];
    if (!value) {
      throw new Error(`${name} environment variable is required`);
    }
    return value;
  }
  
  private validate() {
    if (!this.erpnextUrl.startsWith("http")) {
      throw new Error("ERPNEXT_URL must include protocol (http:// or https://)");
    }
    
    // If one of API key/secret is provided, both must be provided
    if ((this.apiKey && !this.apiSecret) || (!this.apiKey && this.apiSecret)) {
      throw new Error("Both ERPNEXT_API_KEY and ERPNEXT_API_SECRET must be provided if using API key authentication");
    }
  }
  
  getERPNextUrl(): string {
    return this.erpnextUrl;
  }
  
  getERPNextApiKey(): string | undefined {
    return this.apiKey;
  }
  
  getERPNextApiSecret(): string | undefined {
    return this.apiSecret;
  }
}
```

#### 6. Logger Module (`src/utils/logger.ts`)

Implement a proper logging utility:

```typescript
// src/utils/logger.ts
export enum LogLevel {
  ERROR = 0,
  WARN = 1,
  INFO = 2,
  DEBUG = 3,
}

export class Logger {
  private level: LogLevel;
  
  constructor(level: LogLevel = LogLevel.INFO) {
    this.level = level;
  }
  
  setLevel(level: LogLevel) {
    this.level = level;
  }
  
  error(message: string, ...meta: any[]) {
    if (this.level >= LogLevel.ERROR) {
      console.error(`[ERROR] ${message}`, ...meta);
    }
  }
  
  warn(message: string, ...meta: any[]) {
    if (this.level >= LogLevel.WARN) {
      console.warn(`[WARN] ${message}`, ...meta);
    }
  }
  
  info(message: string, ...meta: any[]) {
    if (this.level >= LogLevel.INFO) {
      console.info(`[INFO] ${message}`, ...meta);
    }
  }
  
  debug(message: string, ...meta: any[]) {
    if (this.level >= LogLevel.DEBUG) {
      console.debug(`[DEBUG] ${message}`, ...meta);
    }
  }
}
```

#### 7. Main File (`src/index.ts`)

The main file becomes much simpler:

```typescript
#!/usr/bin/env node

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { Config } from "./utils/config";
import { Logger, LogLevel } from "./utils/logger";
import { Cache } from "./utils/cache";
import { ERPNextClient } from "./client/erpnext-client";
import { registerResourceHandlers } from "./handlers/resource-handlers";
import { registerToolHandlers } from "./handlers/tool-handlers";

async function main() {
  // Initialize components
  const logger = new Logger(
    process.env.DEBUG ? LogLevel.DEBUG : LogLevel.INFO
  );
  
  try {
    logger.info("Initializing ERPNext MCP server");
    
    const config = new Config();
    const cache = new Cache();
    const erpnext = new ERPNextClient(config, logger);
    
    // Create server
    const server = new Server(
      {
        name: "erpnext-server",
        version: "0.1.0"
      },
      {
        capabilities: {
          resources: {},
          tools: {}
        }
      }
    );
    
    // Register handlers
    registerResourceHandlers(server, erpnext, cache, logger);
    registerToolHandlers(server, erpnext, logger);
    
    // Setup error handling
    server.onerror = (error) => {
      logger.error("Server error:", error);
    };
    
    // Start server
    const transport = new StdioServerTransport();
    await server.connect(transport);
    logger.info('ERPNext MCP server running on stdio');
    
    // Handle graceful shutdown
    process.on('SIGINT', async () => {
      logger.info("Shutting down...");
      await server.close();
      process.exit(0);
    });
  } catch (error) {
    logger.error("Failed to start server:", error);
    process.exit(1);
  }
}

main();
```

## Implementation Plan

1. Create the directory structure
2. Move the ERPNext client to its own module
3. Create utility modules (config, logger, cache)
4. Split out the resource and tool handlers
5. Update the main file to use the new modules
6. Add tests for each module
7. Update documentation

This restructuring will make the code more maintainable, easier to test, and facilitate future enhancements.

```

--------------------------------------------------------------------------------
/docs/feature-improvements.md:
--------------------------------------------------------------------------------

```markdown
# Feature Improvements

This document outlines potential feature improvements for the ERPNext MCP server to enhance its functionality, usability, and integration capabilities with ERPNext.

## Current Feature Set

Currently, the ERPNext MCP server provides these core features:

1. **Authentication** with ERPNext using username/password or API keys
2. **Document Operations** (get, list, create, update)
3. **Report Running**
4. **DocType Information** (listing DocTypes and fields)
5. **Basic Resource Access** via URIs

## Proposed Feature Improvements

### 1. Enhanced DocType Discovery and Metadata

**Current Limitation**: Basic DocType listing without metadata or relationships.

**Proposed Improvements**:
- Add metadata about each DocType (description, icon, module)
- Include field type information and validations
- Show relationships between DocTypes (links, child tables)
- Provide DocType-specific operations and permissions

**Implementation**:
```typescript
// src/handlers/tool-handlers.ts
// New tool: get_doctype_metadata

export async function handleGetDoctypeMetadata(request, erpnext, logger) {
  const doctype = request.params.arguments?.doctype;
  
  if (!doctype) {
    throw new McpError(ErrorCode.InvalidParams, "DocType is required");
  }
  
  try {
    // Get DocType metadata including fields with types
    const metadata = await erpnext.getDocTypeMetadata(doctype);
    
    // Get relationship information
    const relationships = await erpnext.getDocTypeRelationships(doctype);
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          doctype,
          metadata,
          relationships,
          operations: getAvailableOperations(doctype)
        }, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}
```

### 2. Bulk Operations

**Current Limitation**: Operations are limited to single documents.

**Proposed Improvements**:
- Add bulk document creation
- Implement batch updates
- Support bulk data import/export
- Add atomic transaction support

**Implementation**:
```typescript
// New tool: bulk_create_documents

export async function handleBulkCreateDocuments(request, erpnext, logger) {
  const doctype = request.params.arguments?.doctype;
  const documents = request.params.arguments?.documents;
  
  if (!doctype || !Array.isArray(documents)) {
    throw new McpError(ErrorCode.InvalidParams, "DocType and array of documents are required");
  }
  
  try {
    // Create documents in a transaction if possible
    const results = await erpnext.bulkCreateDocuments(doctype, documents);
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          message: `Created ${results.length} ${doctype} documents`,
          results
        }, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}
```

### 3. Advanced Querying

**Current Limitation**: Basic filtering without complex queries.

**Proposed Improvements**:
- Support complex query conditions
- Add sorting and grouping options
- Implement flexible field selection
- Add query templates for common operations

**Implementation**:
```typescript
// New tool: advanced_query

export async function handleAdvancedQuery(request, erpnext, logger) {
  const doctype = request.params.arguments?.doctype;
  const query = request.params.arguments?.query || {};
  
  if (!doctype) {
    throw new McpError(ErrorCode.InvalidParams, "DocType is required");
  }
  
  try {
    // Transform query to ERPNext format
    const erpnextQuery = transformQuery(query);
    
    // Execute query
    const results = await erpnext.advancedQuery(doctype, erpnextQuery);
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify(results, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}

function transformQuery(query) {
  // Transform from MCP query format to ERPNext query format
  const result = {
    filters: query.filters || {},
    fields: query.fields || ["*"],
    limit: query.limit,
    offset: query.offset,
    order_by: query.orderBy,
    group_by: query.groupBy
  };
  
  // Handle complex conditions
  if (query.conditions) {
    result.filters = buildComplexFilters(query.conditions);
  }
  
  return result;
}
```

### 4. Webhooks and Events

**Current Limitation**: No support for event-driven operations.

**Proposed Improvements**:
- Add webhook registration for ERPNext events
- Implement event listeners
- Support callback URLs for async operations
- Create notification tools

**Implementation**:
```typescript
// New tool: register_webhook

export async function handleRegisterWebhook(request, erpnext, logger) {
  const doctype = request.params.arguments?.doctype;
  const event = request.params.arguments?.event;
  const callbackUrl = request.params.arguments?.callbackUrl;
  
  if (!doctype || !event || !callbackUrl) {
    throw new McpError(ErrorCode.InvalidParams, "DocType, event, and callbackUrl are required");
  }
  
  try {
    // Register webhook with ERPNext
    const webhook = await erpnext.registerWebhook(doctype, event, callbackUrl);
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          message: `Webhook registered for ${doctype} ${event} events`,
          webhook
        }, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}
```

### 5. Document Workflows

**Current Limitation**: No workflow or process automation.

**Proposed Improvements**:
- Add workflow status tracking
- Implement state transitions
- Support approval processes
- Create multi-step operations

**Implementation**:
```typescript
// New tool: execute_workflow_action

export async function handleExecuteWorkflowAction(request, erpnext, logger) {
  const doctype = request.params.arguments?.doctype;
  const name = request.params.arguments?.name;
  const action = request.params.arguments?.action;
  const comments = request.params.arguments?.comments;
  
  if (!doctype || !name || !action) {
    throw new McpError(ErrorCode.InvalidParams, "DocType, document name, and action are required");
  }
  
  try {
    // Execute workflow action
    const result = await erpnext.executeWorkflowAction(doctype, name, action, comments);
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          message: `Executed ${action} on ${doctype} ${name}`,
          result
        }, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}
```

### 6. File Operations

**Current Limitation**: No file handling capabilities.

**Proposed Improvements**:
- Add file upload/download
- Support attachments to documents
- Implement file metadata management
- Add image processing utilities

**Implementation**:
```typescript
// New tool: upload_attachment

export async function handleUploadAttachment(request, erpnext, logger) {
  const doctype = request.params.arguments?.doctype;
  const name = request.params.arguments?.name;
  const fileName = request.params.arguments?.fileName;
  const fileContent = request.params.arguments?.fileContent; // Base64 encoded
  const fileType = request.params.arguments?.fileType;
  
  if (!doctype || !name || !fileName || !fileContent) {
    throw new McpError(ErrorCode.InvalidParams, "DocType, document name, fileName, and fileContent are required");
  }
  
  try {
    // Upload attachment
    const attachment = await erpnext.uploadAttachment(
      doctype, 
      name, 
      fileName, 
      Buffer.from(fileContent, 'base64'), 
      fileType
    );
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          message: `Attached ${fileName} to ${doctype} ${name}`,
          attachment
        }, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}
```

### 7. Data Synchronization

**Current Limitation**: No synchronization utilities.

**Proposed Improvements**:
- Add data synchronization capabilities
- Implement change tracking
- Support incremental updates
- Create data migration tools

**Implementation**:
```typescript
// New tool: sync_data

export async function handleSyncData(request, erpnext, logger) {
  const doctype = request.params.arguments?.doctype;
  const lastSyncTime = request.params.arguments?.lastSyncTime;
  
  if (!doctype) {
    throw new McpError(ErrorCode.InvalidParams, "DocType is required");
  }
  
  try {
    // Get changes since last sync
    const changes = await erpnext.getChangesSince(doctype, lastSyncTime);
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
        message: `Retrieved ${changes.length} changes for ${doctype} since ${lastSyncTime || 'beginning'}`,
          currentTime: new Date().toISOString(),
          changes
        }, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}
```

### 8. Custom Scripts and Server Actions

**Current Limitation**: No support for custom scripts or actions.

**Proposed Improvements**:
- Add support for executing custom server scripts
- Implement custom actions
- Support script parameters
- Create script management tools

**Implementation**:
```typescript
// New tool: execute_server_script

export async function handleExecuteServerScript(request, erpnext, logger) {
  const scriptName = request.params.arguments?.scriptName;
  const parameters = request.params.arguments?.parameters || {};
  
  if (!scriptName) {
    throw new McpError(ErrorCode.InvalidParams, "Script name is required");
  }
  
  try {
    // Execute server script
    const result = await erpnext.executeServerScript(scriptName, parameters);
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          message: `Executed server script ${scriptName}`,
          result
        }, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}
```

### 9. ERPNext Printing and PDF Generation

**Current Limitation**: No document formatting or printing.

**Proposed Improvements**:
- Add print format rendering
- Implement PDF generation
- Support custom print templates
- Create document export tools

**Implementation**:
```typescript
// New tool: generate_pdf

export async function handleGeneratePdf(request, erpnext, logger) {
  const doctype = request.params.arguments?.doctype;
  const name = request.params.arguments?.name;
  const printFormat = request.params.arguments?.printFormat;
  const letterhead = request.params.arguments?.letterhead;
  
  if (!doctype || !name) {
    throw new McpError(ErrorCode.InvalidParams, "DocType and document name are required");
  }
  
  try {
    // Generate PDF
    const pdfData = await erpnext.generatePdf(doctype, name, printFormat, letterhead);
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          message: `Generated PDF for ${doctype} ${name}`,
          pdf: pdfData // Base64 encoded
        }, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}
```

### 10. Interactive Tools

**Current Limitation**: All operations are direct API calls without user interaction.

**Proposed Improvements**:
- Add support for multi-step interactive operations
- Implement wizard-like flows
- Support form generation
- Create contextual suggestions

**Implementation**:
```typescript
// New tool: start_interactive_flow

export async function handleStartInteractiveFlow(request, erpnext, logger) {
  const flowType = request.params.arguments?.flowType;
  const initialData = request.params.arguments?.initialData || {};
  
  if (!flowType) {
    throw new McpError(ErrorCode.InvalidParams, "Flow type is required");
  }
  
  try {
    // Get flow definition
    const flow = getFlowDefinition(flowType);
    
    // Initialize flow state
    const flowState = initializeFlowState(flow, initialData);
    
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          message: `Started interactive flow: ${flowType}`,
          currentStep: flowState.currentStep,
          steps: flowState.steps,
          totalSteps: flowState.totalSteps,
          data: flowState.data,
          form: generateFormForStep(flowState.currentStep, flow, flowState.data)
        }, null, 2)
      }]
    };
  } catch (error) {
    return handleToolError(error, logger);
  }
}
```

## Implementation Strategy

To implement these feature improvements, we recommend:

1. **Prioritization**: Focus on features that provide the most value with the least implementation complexity first:
   - Enhanced DocType discovery and metadata
   - Advanced querying
   - File operations

2. **Phased Implementation**:
   - Phase 1: Metadata and querying enhancements
   - Phase 2: Bulk operations and file handling
   - Phase 3: Workflows and synchronization
   - Phase 4: Interactive tools and custom scripts

3. **Dependency Handling**:
   - Identify ERPNext API dependencies for each feature
   - Document required permissions and configuration
   - Handle version compatibility

4. **Documentation and Examples**:
   - Provide detailed documentation for each new feature
   - Include usage examples
   - Create tutorials for complex features

## Conclusion

These feature improvements will significantly enhance the ERPNext MCP server's capabilities, making it a more powerful and flexible integration point for ERPNext. By implementing these features in a phased approach, we can incrementally add functionality while maintaining stability and backward compatibility.

```

--------------------------------------------------------------------------------
/docs/error-handling-and-logging.md:
--------------------------------------------------------------------------------

```markdown
# Error Handling and Logging Improvements

This document outlines recommendations for improving error handling and logging in the ERPNext MCP server.

## Current Status

The current implementation has several limitations in its error handling and logging approach:

1. Basic error handling with inconsistent patterns
2. Console logging without structured format
3. No distinct log levels for different environments
4. No dedicated error mapping between ERPNext and MCP error codes
5. Limited contextual information in error messages

## Proposed Improvements

### 1. Structured Error Handling

#### Create a dedicated Error Handling Module

```typescript
// src/utils/error-handler.ts
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import { AxiosError } from "axios";
import { Logger } from "./logger";

export enum ERPNextErrorType {
  Authentication = "authentication",
  Permission = "permission",
  NotFound = "not_found",
  Validation = "validation",
  Server = "server",
  Network = "network",
  Unknown = "unknown"
}

export class ERPNextError extends Error {
  constructor(
    public readonly type: ERPNextErrorType,
    message: string,
    public readonly originalError?: Error
  ) {
    super(message);
    this.name = "ERPNextError";
  }
}

/**
 * Maps ERPNext errors to MCP error codes
 */
export function mapToMcpError(error: ERPNextError): McpError {
  switch (error.type) {
    case ERPNextErrorType.Authentication:
      return new McpError(ErrorCode.Unauthorized, error.message);
    case ERPNextErrorType.Permission:
      return new McpError(ErrorCode.Forbidden, error.message);
    case ERPNextErrorType.NotFound:
      return new McpError(ErrorCode.NotFound, error.message);
    case ERPNextErrorType.Validation:
      return new McpError(ErrorCode.InvalidParams, error.message);
    case ERPNextErrorType.Server:
      return new McpError(ErrorCode.InternalError, error.message);
    case ERPNextErrorType.Network:
      return new McpError(ErrorCode.InternalError, `Network error: ${error.message}`);
    default:
      return new McpError(ErrorCode.InternalError, `Unknown error: ${error.message}`);
  }
}

/**
 * Categorizes an error based on its properties and returns an ERPNextError
 */
export function categorizeError(error: any): ERPNextError {
  if (error instanceof ERPNextError) {
    return error;
  }
  
  // Handle Axios errors
  if (error?.isAxiosError) {
    const axiosError = error as AxiosError;
    const statusCode = axiosError.response?.status;
    const responseData = axiosError.response?.data as any;
    const message = responseData?.message || responseData?.error || axiosError.message;
    
    if (!statusCode) {
      return new ERPNextError(ERPNextErrorType.Network, 
        `Network error connecting to ERPNext: ${message}`, 
        error);
    }
    
    switch (statusCode) {
      case 401:
        return new ERPNextError(ERPNextErrorType.Authentication, 
          `Authentication failed: ${message}`, 
          error);
      case 403:
        return new ERPNextError(ERPNextErrorType.Permission, 
          `Permission denied: ${message}`, 
          error);
      case 404:
        return new ERPNextError(ERPNextErrorType.NotFound, 
          `Resource not found: ${message}`, 
          error);
      case 400:
        return new ERPNextError(ERPNextErrorType.Validation, 
          `Validation error: ${message}`, 
          error);
      case 500:
      case 502:
      case 503:
      case 504:
        return new ERPNextError(ERPNextErrorType.Server, 
          `ERPNext server error: ${message}`, 
          error);
      default:
        return new ERPNextError(ERPNextErrorType.Unknown, 
          `Unknown error: ${message}`, 
          error);
    }
  }
  
  // Handle generic errors
  return new ERPNextError(
    ERPNextErrorType.Unknown, 
    error?.message || 'An unknown error occurred',
    error instanceof Error ? error : undefined
  );
}

/**
 * Handles errors in tool handlers, returning appropriate response format
 */
export function handleToolError(error: any, logger: Logger) {
  const erpError = categorizeError(error);
  
  // Log the error with appropriate context
  if (erpError.originalError) {
    logger.error(`${erpError.message}`, {
      errorType: erpError.type,
      originalError: erpError.originalError.message,
      stack: erpError.originalError.stack
    });
  } else {
    logger.error(`${erpError.message}`, { 
      errorType: erpError.type,
      stack: erpError.stack
    });
  }
  
  // Return a formatted error response
  return {
    content: [{
      type: "text",
      text: erpError.message
    }],
    isError: true
  };
}

/**
 * Creates a correlation ID for tracking requests through the system
 */
export function createCorrelationId(): string {
  return `mcp-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
}
```

### 2. Enhanced Logging System

#### Structured Logger Implementation

```typescript
// src/utils/logger.ts
export enum LogLevel {
  ERROR = 0,
  WARN = 1,
  INFO = 2,
  DEBUG = 3
}

export interface LogMetadata {
  [key: string]: any;
}

export interface LogEntry {
  timestamp: string;
  level: string;
  message: string;
  correlationId?: string;
  metadata?: LogMetadata;
}

export class Logger {
  private level: LogLevel;
  private serviceName: string;
  
  constructor(level: LogLevel = LogLevel.INFO, serviceName: string = "erpnext-mcp") {
    this.level = level;
    this.serviceName = serviceName;
  }
  
  setLevel(level: LogLevel) {
    this.level = level;
  }
  
  private formatLog(level: string, message: string, metadata?: LogMetadata): LogEntry {
    return {
      timestamp: new Date().toISOString(),
      level,
      message,
      correlationId: metadata?.correlationId,
      metadata: metadata ? { ...metadata, service: this.serviceName } : { service: this.serviceName }
    };
  }
  
  private outputLog(logEntry: LogEntry) {
    // In production, you might want to use a more sophisticated logging system
    // For now, we'll use console with JSON formatting
    const logJson = JSON.stringify(logEntry);
    
    switch (logEntry.level) {
      case 'ERROR':
        console.error(logJson);
        break;
      case 'WARN':
        console.warn(logJson);
        break;
      case 'INFO':
        console.info(logJson);
        break;
      case 'DEBUG':
        console.debug(logJson);
        break;
      default:
        console.log(logJson);
    }
  }
  
  error(message: string, metadata?: LogMetadata) {
    if (this.level >= LogLevel.ERROR) {
      this.outputLog(this.formatLog('ERROR', message, metadata));
    }
  }
  
  warn(message: string, metadata?: LogMetadata) {
    if (this.level >= LogLevel.WARN) {
      this.outputLog(this.formatLog('WARN', message, metadata));
    }
  }
  
  info(message: string, metadata?: LogMetadata) {
    if (this.level >= LogLevel.INFO) {
      this.outputLog(this.formatLog('INFO', message, metadata));
    }
  }
  
  debug(message: string, metadata?: LogMetadata) {
    if (this.level >= LogLevel.DEBUG) {
      this.outputLog(this.formatLog('DEBUG', message, metadata));
    }
  }
}
```

### 3. Request Context and Correlation

To track requests through the system:

```typescript
// src/middleware/context.ts
import { createCorrelationId } from "../utils/error-handler";

export interface RequestContext {
  correlationId: string;
  startTime: number;
  [key: string]: any;
}

export class RequestContextManager {
  private static contextMap = new Map<string, RequestContext>();
  
  static createContext(requestId: string): RequestContext {
    const context: RequestContext = {
      correlationId: createCorrelationId(),
      startTime: Date.now()
    };
    
    this.contextMap.set(requestId, context);
    return context;
  }
  
  static getContext(requestId: string): RequestContext | undefined {
    return this.contextMap.get(requestId);
  }
  
  static removeContext(requestId: string): void {
    this.contextMap.delete(requestId);
  }
}
```

### 4. Integration with MCP Server

Use the error handling and logging systems with the MCP server:

```typescript
// src/index.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { Config } from "./utils/config";
import { Logger, LogLevel } from "./utils/logger";
import { RequestContextManager } from "./middleware/context";

async function main() {
  // Initialize logger
  const logger = new Logger(
    process.env.DEBUG ? LogLevel.DEBUG : LogLevel.INFO
  );
  
  try {
    logger.info("Initializing ERPNext MCP server");
    
    // Create server
    const server = new Server(
      {
        name: "erpnext-server",
        version: "0.1.0"
      },
      {
        capabilities: {
          resources: {},
          tools: {}
        }
      }
    );
    
    // Set up request interceptors for correlation and context
    server.onRequest = (request) => {
      const context = RequestContextManager.createContext(request.id);
      logger.debug(`Received request: ${request.method}`, {
        correlationId: context.correlationId,
        request: {
          id: request.id,
          method: request.method,
          params: JSON.stringify(request.params)
        }
      });
    };
    
    // Set up response interceptors for timing and cleanup
    server.onResponse = (response) => {
      const context = RequestContextManager.getContext(response.id);
      if (context) {
        const duration = Date.now() - context.startTime;
        logger.debug(`Sending response`, {
          correlationId: context.correlationId,
          response: {
            id: response.id,
            duration: `${duration}ms`,
            hasError: !!response.error
          }
        });
        
        // Clean up context
        RequestContextManager.removeContext(response.id);
      }
    };
    
    // Set up error handler for server errors
    server.onerror = (error) => {
      logger.error("Server error", { error: error?.message, stack: error?.stack });
    };
    
    // Connect server with stdio transport
    const transport = new StdioServerTransport();
    await server.connect(transport);
    logger.info('ERPNext MCP server running on stdio');
    
  } catch (error) {
    logger.error("Failed to start server", { error: error?.message, stack: error?.stack });
    process.exit(1);
  }
}

main();
```

### 5. Integrating with Client

Update the ERPNext client to use the enhanced error handling:

```typescript
// src/client/erpnext-client.ts
import axios, { AxiosInstance } from "axios";
import { Logger } from "../utils/logger";
import { Config } from "../utils/config";
import { categorizeError, ERPNextError, ERPNextErrorType } from "../utils/error-handler";

export class ERPNextClient {
  private baseUrl: string;
  private axiosInstance: AxiosInstance;
  private authenticated: boolean = false;
  private logger: Logger;

  constructor(config: Config, logger: Logger) {
    this.logger = logger;
    this.baseUrl = config.getERPNextUrl();
    
    // Initialize axios instance
    this.axiosInstance = axios.create({
      baseURL: this.baseUrl,
      withCredentials: true,
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    });
    
    // Add request interceptor for logging
    this.axiosInstance.interceptors.request.use(
      (config) => {
        this.logger.debug(`Sending ${config.method?.toUpperCase()} request to ${config.url}`, {
          api: {
            method: config.method,
            url: config.url,
            hasData: !!config.data,
            hasParams: !!config.params
          }
        });
        return config;
      },
      (error) => {
        this.logger.error(`Request error: ${error.message}`);
        return Promise.reject(error);
      }
    );
    
    // Add response interceptor for error handling
    this.axiosInstance.interceptors.response.use(
      (response) => {
        return response;
      },
      (error) => {
        const erpError = categorizeError(error);
        this.logger.error(`API error: ${erpError.message}`, {
          errorType: erpError.type,
          statusCode: error.response?.status,
          data: error.response?.data
        });
        return Promise.reject(erpError);
      }
    );
    
    // Configure authentication if credentials provided
    const apiKey = config.getERPNextApiKey();
    const apiSecret = config.getERPNextApiSecret();
    
    if (apiKey && apiSecret) {
      this.axiosInstance.defaults.headers.common['Authorization'] = 
        `token ${apiKey}:${apiSecret}`;
      this.authenticated = true;
      this.logger.info("Initialized with API key authentication");
    }
  }

  // Client methods would use the same error handling pattern...
  async login(username: string, password: string): Promise<void> {
    try {
      this.logger.info(`Attempting login for user ${username}`);
      const response = await this.axiosInstance.post('/api/method/login', {
        usr: username,
        pwd: password
      });
      
      if (response.data.message === 'Logged In') {
        this.authenticated = true;
        this.logger.info(`Successfully authenticated user ${username}`);
      } else {
        throw new ERPNextError(
          ERPNextErrorType.Authentication,
          "Login response did not confirm successful authentication"
        );
      }
    } catch (error) {
      // Let the interceptor handle the error categorization
      this.authenticated = false;
      throw error;
    }
  }

  // Other methods would follow the same pattern...
}
```

## Benefits of Improved Error Handling and Logging

1. **Better Diagnostics**: Structured logging with correlation IDs makes it easier to track requests through the system and diagnose issues.

2. **Consistent Error Responses**: Standardized error handling ensures that clients receive consistent and informative error messages.

3. **Categorized Errors**: Errors are properly categorized, making it easier to handle different types of failures appropriately.

4. **Contextual Information**: Additional metadata is included with logs, providing more context for troubleshooting.

5. **Performance Tracking**: Request timing information helps identify performance bottlenecks.

6. **Environment-specific Logging**: Different log levels can be used in development vs. production environments.

## Implementation Plan

1. Create the error handling utility module
2. Implement the structured logger
3. Update the client to use the enhanced error handling
4. Update resource and tool handlers to leverage the new error system
5. Add request context management
6. Update the main server to integrate all components

These changes will significantly improve the reliability and maintainability of the ERPNext MCP server by making errors more traceable and logs more informative.

```

--------------------------------------------------------------------------------
/docs/testing-strategy.md:
--------------------------------------------------------------------------------

```markdown
# Testing Strategy

This document outlines a comprehensive testing strategy for the ERPNext MCP server to ensure reliability, correctness, and maintainability.

## Current Status

The current implementation lacks any automated testing, which poses several risks:

- No way to verify that changes don't break existing functionality
- Difficulty in detecting regressions
- Challenges in maintaining code quality as the codebase grows
- Decreased confidence when making changes or adding features

## Proposed Testing Structure

### Directory Structure

```
tests/
├── unit/
│   ├── client/
│   │   └── erpnext-client.test.ts
│   ├── handlers/
│   │   ├── resource-handlers.test.ts
│   │   └── tool-handlers.test.ts
│   └── utils/
│       ├── cache.test.ts
│       ├── config.test.ts
│       └── logger.test.ts
├── integration/
│   └── api-integration.test.ts
├── e2e/
│   └── full-workflow.test.ts
└── mocks/
    ├── erpnext-api.ts
    └── test-data.ts
```

## Testing Levels

### 1. Unit Tests

Unit tests should focus on testing individual components in isolation, mocking external dependencies. These tests should be fast and focused.

#### Example: Testing the Cache Utility

```typescript
// tests/unit/utils/cache.test.ts
import { Cache } from '../../../src/utils/cache';
import { jest } from '@jest/globals';

describe('Cache', () => {
  let cache: Cache;
  
  beforeEach(() => {
    cache = new Cache();
    jest.useFakeTimers();
  });
  
  afterEach(() => {
    jest.useRealTimers();
  });
  
  test('should store and retrieve values', () => {
    // Arrange
    const key = 'test-key';
    const value = { data: 'test-data' };
    
    // Act
    cache.set(key, value);
    const result = cache.get(key);
    
    // Assert
    expect(result).toEqual(value);
  });
  
  test('should expire values after TTL', () => {
    // Arrange
    const key = 'test-key';
    const value = { data: 'test-data' };
    const ttl = 1000; // 1 second
    
    // Act
    cache.set(key, value, ttl);
    
    // Move time forward
    jest.advanceTimersByTime(1001);
    
    const result = cache.get(key);
    
    // Assert
    expect(result).toBeUndefined();
  });
  
  test('should invalidate keys with prefix', () => {
    // Arrange
    cache.set('prefix:key1', 'value1');
    cache.set('prefix:key2', 'value2');
    cache.set('other:key', 'value3');
    
    // Act
    cache.invalidate('prefix:');
    
    // Assert
    expect(cache.get('prefix:key1')).toBeUndefined();
    expect(cache.get('prefix:key2')).toBeUndefined();
    expect(cache.get('other:key')).toBe('value3');
  });
});
```

#### Example: Testing the ERPNext Client

```typescript
// tests/unit/client/erpnext-client.test.ts
import { ERPNextClient } from '../../../src/client/erpnext-client';
import { Config } from '../../../src/utils/config';
import { Logger } from '../../../src/utils/logger';
import axios from 'axios';
import { jest } from '@jest/globals';

// Mock axios
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('ERPNextClient', () => {
  let client: ERPNextClient;
  let mockConfig: jest.Mocked<Config>;
  let mockLogger: jest.Mocked<Logger>;
  
  beforeEach(() => {
    // Setup mock config
    mockConfig = {
      getERPNextUrl: jest.fn().mockReturnValue('https://test-erpnext.com'),
      getERPNextApiKey: jest.fn().mockReturnValue('test-key'),
      getERPNextApiSecret: jest.fn().mockReturnValue('test-secret')
    } as unknown as jest.Mocked<Config>;
    
    // Setup mock logger
    mockLogger = {
      info: jest.fn(),
      error: jest.fn(),
      debug: jest.fn(),
      warn: jest.fn()
    } as unknown as jest.Mocked<Logger>;
    
    // Setup axios mock
    mockedAxios.create.mockReturnValue({
      defaults: { headers: { common: {} } },
      get: jest.fn(),
      post: jest.fn(),
      put: jest.fn()
    } as any);
    
    // Create client instance
    client = new ERPNextClient(mockConfig, mockLogger);
  });
  
  test('should initialize with API key authentication', () => {
    expect(mockLogger.info).toHaveBeenCalledWith(
      expect.stringContaining('API key authentication')
    );
    expect(client.isAuthenticated()).toBe(true);
  });
  
  test('login should authenticate the client', async () => {
    // Setup mock response
    const axiosInstance = mockedAxios.create.mock.results[0].value;
    axiosInstance.post.mockResolvedValue({
      data: { message: 'Logged In' }
    });
    
    // Act
    await client.login('testuser', 'testpass');
    
    // Assert
    expect(axiosInstance.post).toHaveBeenCalledWith(
      '/api/method/login',
      { usr: 'testuser', pwd: 'testpass' }
    );
    expect(client.isAuthenticated()).toBe(true);
  });
  
  test('login should handle authentication failure', async () => {
    // Setup mock response for failure
    const axiosInstance = mockedAxios.create.mock.results[0].value;
    axiosInstance.post.mockRejectedValue(new Error('Authentication failed'));
    
    // Act & Assert
    await expect(client.login('testuser', 'wrongpass')).rejects.toThrow('Authentication failed');
    expect(client.isAuthenticated()).toBe(false);
  });
});
```

### 2. Integration Tests

Integration tests should verify that different components work correctly together. For the ERPNext MCP server, integration tests should focus on the interaction between the server, handlers, and the ERPNext client.

```typescript
// tests/integration/api-integration.test.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { MemoryTransport } from "@modelcontextprotocol/sdk/server/memory.js";
import { ERPNextClient } from '../../src/client/erpnext-client';
import { registerResourceHandlers } from '../../src/handlers/resource-handlers';
import { registerToolHandlers } from '../../src/handlers/tool-handlers';
import { Logger } from '../../src/utils/logger';
import { Cache } from '../../src/utils/cache';
import { jest } from '@jest/globals';

// Use mock ERPNext client
jest.mock('../../src/client/erpnext-client');

describe('MCP Server Integration', () => {
  let server: Server;
  let transport: MemoryTransport;
  let mockErpnextClient: jest.Mocked<ERPNextClient>;
  let logger: Logger;
  let cache: Cache;
  
  beforeEach(async () => {
    // Setup mocks
    mockErpnextClient = {
      isAuthenticated: jest.fn().mockReturnValue(true),
      login: jest.fn(),
      getDocument: jest.fn(),
      getDocList: jest.fn(),
      createDocument: jest.fn(),
      updateDocument: jest.fn(),
      runReport: jest.fn(),
      getAllDocTypes: jest.fn()
    } as unknown as jest.Mocked<ERPNextClient>;
    
    logger = new Logger();
    cache = new Cache();
    
    // Create server
    server = new Server(
      {
        name: "erpnext-server-test",
        version: "0.1.0"
      },
      {
        capabilities: {
          resources: {},
          tools: {}
        }
      }
    );
    
    // Register handlers
    registerResourceHandlers(server, mockErpnextClient, cache, logger);
    registerToolHandlers(server, mockErpnextClient, logger);
    
    // Setup transport
    transport = new MemoryTransport();
    await server.connect(transport);
  });
  
  afterEach(async () => {
    await server.close();
  });
  
  test('List tools should return all tools', async () => {
    // Act
    const response = await transport.sendRequestAndWaitForResponse({
      jsonrpc: "2.0",
      id: "test-id",
      method: "mcp.listTools",
      params: {}
    });
    
    // Assert
    expect(response.result).toBeDefined();
    expect(response.result.tools).toBeInstanceOf(Array);
    expect(response.result.tools.length).toBeGreaterThan(0);
    expect(response.result.tools.map(t => t.name)).toContain('authenticate_erpnext');
  });
  
  test('Get documents tool should fetch documents', async () => {
    // Arrange
    const mockDocuments = [
      { name: "CUST-001", customer_name: "Test Customer" }
    ];
    
    mockErpnextClient.getDocList.mockResolvedValue(mockDocuments);
    
    // Act
    const response = await transport.sendRequestAndWaitForResponse({
      jsonrpc: "2.0",
      id: "test-id",
      method: "mcp.callTool",
      params: {
        name: "get_documents",
        arguments: {
          doctype: "Customer"
        }
      }
    });
    
    // Assert
    expect(mockErpnextClient.getDocList).toHaveBeenCalledWith(
      "Customer", undefined, undefined, undefined
    );
    expect(response.result).toBeDefined();
    expect(JSON.parse(response.result.content[0].text)).toEqual(mockDocuments);
  });
});
```

### 3. End-to-End Tests

E2E tests should verify the entire system works correctly from the user's perspective. These tests should use a real or mock ERPNext server and execute complete workflows.

```typescript
// tests/e2e/full-workflow.test.ts
import { spawn, ChildProcess } from 'child_process';
import axios from 'axios';
import { jest } from '@jest/globals';
import path from 'path';
import { startMockErpnextServer, stopMockErpnextServer } from '../mocks/erpnext-api';

describe('E2E Tests', () => {
  let serverProcess: ChildProcess;
  let mockServerUrl: string;
  
  beforeAll(async () => {
    // Start mock ERPNext server
    mockServerUrl = await startMockErpnextServer();
    
    // Start MCP server with environment pointing to mock server
    const serverPath = path.resolve(__dirname, '../../build/index.js');
    serverProcess = spawn('node', [serverPath], {
      env: {
        ...process.env,
        ERPNEXT_URL: mockServerUrl,
        ERPNEXT_API_KEY: 'test-key',
        ERPNEXT_API_SECRET: 'test-secret',
        DEBUG: 'true'
      },
      stdio: ['pipe', 'pipe', 'pipe']
    });
    
    // Wait for server to start
    await new Promise(resolve => setTimeout(resolve, 1000));
  });
  
  afterAll(async () => {
    // Terminate the server process
    if (serverProcess) {
      serverProcess.kill();
    }
    
    // Stop mock server
    await stopMockErpnextServer();
  });
  
  test('Complete workflow test', async () => {
    // Setup a test client that communicates with the MCP server
    
    // This would test a complete workflow:
    // 1. Authentication
    // 2. Document retrieval
    // 3. Document creation
    // 4. Document update
    // 5. Running a report
  });
});
```

## Test Mocks

Creating proper mocks is essential for effective testing:

```typescript
// tests/mocks/erpnext-api.ts
import express from 'express';
import http from 'http';
import bodyParser from 'body-parser';

let server: http.Server;
let app: express.Express;

// Mock data storage
const mockData: Record<string, any[]> = {
  'Customer': [
    {
      name: 'CUST-001',
      customer_name: 'Test Customer 1',
      customer_type: 'Company',
      customer_group: 'Commercial',
      territory: 'United States'
    }
  ],
  'Item': [
    {
      name: 'ITEM-001',
      item_code: 'ITEM-001',
      item_name: 'Test Item 1',
      item_group: 'Products',
      stock_uom: 'Nos'
    }
  ]
};

export async function startMockErpnextServer(): Promise<string> {
  app = express();
  app.use(bodyParser.json());
  
  // Setup API endpoints that mimic ERPNext
  
  // Login endpoint
  app.post('/api/method/login', (req, res) => {
    const { usr, pwd } = req.body;
    
    if (usr === 'testuser' && pwd === 'testpass') {
      res.json({ message: 'Logged In' });
    } else {
      res.status(401).json({ message: 'Authentication failed' });
    }
  });
  
  // Document listing
  app.get('/api/resource/:doctype', (req, res) => {
    const doctype = req.params.doctype;
    res.json({ data: mockData[doctype] || [] });
  });
  
  // Document retrieval
  app.get('/api/resource/:doctype/:name', (req, res) => {
    const { doctype, name } = req.params;
    const docs = mockData[doctype] || [];
    const doc = docs.find(d => d.name === name);
    
    if (doc) {
      res.json({ data: doc });
    } else {
      res.status(404).json({ message: 'Not found' });
    }
  });
  
  // Document creation
  app.post('/api/resource/:doctype', (req, res) => {
    const { doctype } = req.params;
    const data = req.body.data;
    
    if (!mockData[doctype]) {
      mockData[doctype] = [];
    }
    
    mockData[doctype].push(data);
    res.json({ data });
  });
  
  // Document update
  app.put('/api/resource/:doctype/:name', (req, res) => {
    const { doctype, name } = req.params;
    const data = req.body.data;
    
    if (!mockData[doctype]) {
      return res.status(404).json({ message: 'DocType not found' });
    }
    
    const index = mockData[doctype].findIndex(d => d.name === name);
    if (index === -1) {
      return res.status(404).json({ message: 'Document not found' });
    }
    
    mockData[doctype][index] = { ...mockData[doctype][index], ...data };
    res.json({ data: mockData[doctype][index] });
  });
  
  // Start server on a random port
  return new Promise<string>((resolve) => {
    server = app.listen(0, () => {
      const address = server.address();
      const port = typeof address === 'object' ? address?.port : 0;
      resolve(`http://localhost:${port}`);
    });
  });
}

export async function stopMockErpnextServer(): Promise<void> {
  return new Promise<void>((resolve) => {
    if (server) {
      server.close(() => resolve());
    } else {
      resolve();
    }
  });
}
```

## Setting Up Testing Infrastructure

### 1. Dependencies

Add the following development dependencies to `package.json`:

```json
"devDependencies": {
  "@types/jest": "^29.5.0",
  "@types/express": "^4.17.17",
  "body-parser": "^1.20.2",
  "express": "^4.18.2",
  "jest": "^29.5.0",
  "ts-jest": "^29.1.0",
  "@types/node": "^20.11.24",
  "typescript": "^5.3.3"
}
```

### 2. Jest Configuration

Add Jest configuration in `package.json`:

```json
"jest": {
  "preset": "ts-jest",
  "testEnvironment": "node",
  "roots": [
    "<rootDir>/tests"
  ],
  "collectCoverage": true,
  "collectCoverageFrom": [
    "src/**/*.ts"
  ],
  "coverageThreshold": {
    "global": {
      "branches": 80,
      "functions": 80,
      "lines": 80,
      "statements": 80
    }
  }
}
```

### 3. Add Test Scripts

Add test scripts to `package.json`:

```json
"scripts": {
  "test": "jest",
  "test:watch": "jest --watch",
  "test:coverage": "jest --coverage",
  "test:unit": "jest tests/unit",
  "test:integration": "jest tests/integration",
  "test:e2e": "jest tests/e2e"
}
```

## CI/CD Integration

Implement continuous integration using GitHub Actions or similar CI platform:

```yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        
    - name: Install dependencies
      run: npm ci
      
    - name: Run tests
      run: npm test
      
    - name: Upload coverage
      uses: codecov/codecov-action@v3
```

## Testing Best Practices

1. **Write tests before code** - Consider test-driven development (TDD) for new features
2. **Test edge cases** - Ensure error scenarios and unusual inputs are handled correctly
3. **Keep tests independent** - Each test should run in isolation
4. **Use descriptive test names** - Tests should document what functionality is being verified
5. **Mock external dependencies** - Don't rely on external services in unit tests
6. **Aim for high coverage** - But focus on meaningful coverage rather than arbitrary metrics
7. **Maintain tests** - Update tests when functionality changes
8. **Run tests regularly** - Integrate in CI/CD pipeline and run locally before commits

## Conclusion

Implementing a comprehensive testing strategy will significantly improve the reliability and maintainability of the ERPNext MCP server. By using a combination of unit, integration, and end-to-end tests, we can ensure that the server behaves correctly under different scenarios and that changes don't introduce regressions.

```

--------------------------------------------------------------------------------
/docs/security-and-authentication.md:
--------------------------------------------------------------------------------

```markdown
# Security and Authentication Improvements

This document outlines recommendations for enhancing security and authentication in the ERPNext MCP server.

## Current Status

The current implementation has several limitations in its security and authentication approach:

1. Simple username/password authentication without token refresh mechanisms
2. Basic API key/secret handling without proper validation
3. Passwords transmitted and handled in plain text
4. No input validation or sanitization
5. No token/session management
6. Limited security headers
7. No protection against common security vulnerabilities

## Proposed Improvements

### 1. Enhanced Authentication System

#### Robust Authentication Mechanisms

```typescript
// src/auth/authenticator.ts
import { Logger } from "../utils/logger";
import { Config } from "../utils/config";
import { ERPNextClient } from "../client/erpnext-client";
import { ERPNextError, ERPNextErrorType } from "../utils/error-handler";

export interface AuthTokens {
  accessToken: string;
  refreshToken?: string;
  expiresAt?: number;
}

export class AuthManager {
  private tokens: AuthTokens | null = null;
  private authenticated: boolean = false;
  private refreshTimer: NodeJS.Timeout | null = null;
  private client: ERPNextClient;
  private logger: Logger;
  
  constructor(
    client: ERPNextClient, 
    logger: Logger,
    private readonly config: Config
  ) {
    this.client = client;
    this.logger = logger;
    
    // Initialize with API key/secret if available
    const apiKey = this.config.getERPNextApiKey();
    const apiSecret = this.config.getERPNextApiSecret();
    
    if (apiKey && apiSecret) {
      this.authenticated = true;
      this.logger.info("Initialized with API key authentication");
    }
  }
  
  /**
   * Check if the client is authenticated
   */
  isAuthenticated(): boolean {
    return this.authenticated;
  }
  
  /**
   * Authenticate using username and password
   */
  async authenticate(username: string, password: string): Promise<void> {
    try {
      if (!username || !password) {
        throw new ERPNextError(
          ERPNextErrorType.Authentication, 
          "Username and password are required"
        );
      }
      
      // Mask password in logs
      this.logger.info(`Attempting to authenticate user: ${username}`);
      
      // Authenticate with ERPNext
      await this.client.login(username, password);
      
      this.authenticated = true;
      this.logger.info(`Authentication successful for user: ${username}`);
      
      // In a real implementation, we would store tokens and set up refresh
      // ERPNext doesn't have a standard token-based auth, but we're 
      // setting up the structure for future enhancement
    } catch (error) {
      this.authenticated = false;
      this.logger.error(`Authentication failed for user: ${username}`);
      throw error;
    }
  }
  
  /**
   * Set up token refresh mechanism
   * This is a placeholder for future enhancement as ERPNext's
   * standard API doesn't use refresh tokens, but the structure
   * is here for custom implementations or future changes
   */
  private setupTokenRefresh(tokens: AuthTokens): void {
    // Clear any existing refresh timer
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
    }
    
    // If no expiry, don't set up refresh
    if (!tokens.expiresAt || !tokens.refreshToken) {
      return;
    }
    
    // Calculate time until refresh (5 minutes before expiry)
    const now = Date.now();
    const expiryTime = tokens.expiresAt;
    const timeUntilRefresh = Math.max(0, expiryTime - now - 5 * 60 * 1000);
    
    this.logger.debug(`Setting up token refresh in ${timeUntilRefresh / 1000} seconds`);
    
    this.refreshTimer = setTimeout(async () => {
      try {
        // Here we would implement the token refresh logic
        this.logger.debug("Refreshing authentication token");
        
        // For future implementation:
        // const newTokens = await this.client.refreshToken(tokens.refreshToken);
        // this.setTokens(newTokens);
      } catch (error) {
        this.logger.error("Failed to refresh token", { error });
        this.authenticated = false;
      }
    }, timeUntilRefresh);
  }
  
  /**
   * Store authentication tokens
   */
  private setTokens(tokens: AuthTokens): void {
    this.tokens = tokens;
    this.authenticated = true;
    
    // Set up token refresh if applicable
    this.setupTokenRefresh(tokens);
  }
  
  /**
   * Get current auth token for API requests
   */
  getAuthToken(): string | null {
    return this.tokens?.accessToken || null;
  }
  
  /**
   * Log out and clear authentication
   */
  logout(): void {
    this.authenticated = false;
    this.tokens = null;
    
    if (this.refreshTimer) {
      clearTimeout(this.refreshTimer);
      this.refreshTimer = null;
    }
    
    this.logger.info("User logged out");
  }
}
```

### 2. Input Validation and Sanitization

Create a dedicated validation module to ensure inputs are properly validated before being used:

```typescript
// src/utils/validation.ts
import { z } from "zod"; // Using Zod for schema validation

// Schema for authentication credentials
export const AuthCredentialsSchema = z.object({
  username: z.string().min(1, "Username is required"),
  password: z.string().min(1, "Password is required")
});

// Schema for doctype
export const DoctypeSchema = z.string().min(1, "DocType is required");

// Schema for document name
export const DocumentNameSchema = z.string().min(1, "Document name is required");

// Schema for document data
export const DocumentDataSchema = z.record(z.unknown());

// Schema for document filters
export const FiltersSchema = z.record(z.unknown()).optional();

// Schema for field list
export const FieldsSchema = z.array(z.string()).optional();

// Schema for pagination
export const PaginationSchema = z.object({
  limit: z.number().positive().optional(),
  page: z.number().positive().optional()
});

// Schema for report
export const ReportSchema = z.object({
  report_name: z.string().min(1, "Report name is required"),
  filters: z.record(z.unknown()).optional()
});

/**
 * Validates input against a schema and returns the validated data
 * or throws an error if validation fails
 */
export function validateInput<T>(schema: z.Schema<T>, data: unknown): T {
  try {
    return schema.parse(data);
  } catch (error) {
    if (error instanceof z.ZodError) {
      // Convert zod error to a more user-friendly format
      const issues = error.errors.map(err => `${err.path.join('.')}: ${err.message}`).join(", ");
      throw new Error(`Validation error: ${issues}`);
    }
    throw error;
  }
}

/**
 * Sanitizes string input to prevent injection attacks
 */
export function sanitizeString(input: string): string {
  // Basic sanitization to prevent script injection
  return input
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

/**
 * Sanitizes an object by applying string sanitization to all string properties
 */
export function sanitizeObject(obj: Record<string, any>): Record<string, any> {
  const result: Record<string, any> = {};
  
  Object.entries(obj).forEach(([key, value]) => {
    if (typeof value === 'string') {
      result[key] = sanitizeString(value);
    } else if (value && typeof value === 'object' && !Array.isArray(value)) {
      result[key] = sanitizeObject(value);
    } else {
      result[key] = value;
    }
  });
  
  return result;
}
```

### 3. Secure Configuration Management

Enhance the configuration module with better security practices:

```typescript
// src/utils/config.ts
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';

export class Config {
  private config: Record<string, string> = {};
  
  constructor(configPath?: string) {
    // Load environment variables from .env file if it exists
    if (configPath && fs.existsSync(configPath)) {
      const envConfig = dotenv.parse(fs.readFileSync(configPath));
      this.config = { ...this.config, ...envConfig };
    }
    
    // Override with actual environment variables
    Object.assign(this.config, process.env);
    
    // Validate required configuration
    this.validateConfig();
  }
  
  private validateConfig() {
    // Validate required configuration
    const requiredVars = ['ERPNEXT_URL'];
    const missing = requiredVars.filter(key => !this.get(key));
    
    if (missing.length > 0) {
      throw new Error(`Missing required configuration: ${missing.join(', ')}`);
    }
    
    // Validate URL format
    try {
      new URL(this.getERPNextUrl());
    } catch (error) {
      throw new Error(`Invalid ERPNEXT_URL: ${this.getERPNextUrl()}`);
    }
    
    // Validate API key and secret (both or neither)
    const hasApiKey = !!this.get('ERPNEXT_API_KEY');
    const hasApiSecret = !!this.get('ERPNEXT_API_SECRET');
    
    if ((hasApiKey && !hasApiSecret) || (!hasApiKey && hasApiSecret)) {
      throw new Error('Both ERPNEXT_API_KEY and ERPNEXT_API_SECRET must be provided if using API key authentication');
    }
  }
  
  /**
   * Get a configuration value
   */
  get(key: string): string | undefined {
    return this.config[key];
  }
  
  /**
   * Get a configuration value or throw if not found
   */
  getRequired(key: string): string {
    const value = this.get(key);
    if (value === undefined) {
      throw new Error(`Required configuration "${key}" not found`);
    }
    return value;
  }
  
  /**
   * Get ERPNext URL, ensuring it doesn't end with a trailing slash
   */
  getERPNextUrl(): string {
    return this.getRequired('ERPNEXT_URL').replace(/\/$/, '');
  }
  
  /**
   * Get ERPNext API key
   */
  getERPNextApiKey(): string | undefined {
    return this.get('ERPNEXT_API_KEY');
  }
  
  /**
   * Get ERPNext API secret
   */
  getERPNextApiSecret(): string | undefined {
    return this.get('ERPNEXT_API_SECRET');
  }
  
  /**
   * Get log level (defaults to "info")
   */
  getLogLevel(): string {
    return this.get('LOG_LEVEL') || 'info';
  }
}
```

### 4. Security Middleware

Add security middleware to protect against common vulnerabilities:

```typescript
// src/middleware/security.ts
import { Logger } from "../utils/logger";

export interface RateLimitConfig {
  windowMs: number;
  maxRequests: number;
}

/**
 * Simple rate limiting implementation for tools
 */
export class RateLimiter {
  private windowMs: number;
  private maxRequests: number;
  private requests: Map<string, number[]> = new Map();
  private logger: Logger;
  
  constructor(config: RateLimitConfig, logger: Logger) {
    this.windowMs = config.windowMs;
    this.maxRequests = config.maxRequests;
    this.logger = logger;
  }
  
  /**
   * Check if a request should be rate limited
   * @param key Identifier for the rate limit bucket (e.g. tool name, user ID)
   * @returns Whether the request should be allowed
   */
  allowRequest(key: string): boolean {
    const now = Date.now();
    
    // Get existing timestamps for this key
    let timestamps = this.requests.get(key) || [];
    
    // Filter out timestamps outside the current window
    timestamps = timestamps.filter(time => now - time < this.windowMs);
    
    // Check if we've exceeded the limit
    if (timestamps.length >= this.maxRequests) {
      this.logger.warn(`Rate limit exceeded for ${key}`);
      return false;
    }
    
    // Add the new timestamp and update the map
    timestamps.push(now);
    this.requests.set(key, timestamps);
    
    return true;
  }
  
  /**
   * Reset rate limit for a specific key
   */
  reset(key: string): void {
    this.requests.delete(key);
  }
}

/**
 * Security headers to add to responses
 */
export const securityHeaders = {
  'X-Content-Type-Options': 'nosniff',
  'X-Frame-Options': 'DENY',
  'Content-Security-Policy': "default-src 'none'",
  'X-XSS-Protection': '1; mode=block',
  'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
};
```

### 5. Secure Resource and Tool Handlers

Update the tool handlers to include validation, rate limiting, and better credential handling:

```typescript
// src/handlers/tool-handlers.ts
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  McpError,
  ErrorCode
} from "@modelcontextprotocol/sdk/types.js";
import { ERPNextClient } from "../client/erpnext-client";
import { AuthManager } from "../auth/authenticator";
import { Logger } from "../utils/logger";
import { handleToolError } from "../utils/error-handler";
import { RateLimiter } from "../middleware/security";
import * as validation from "../utils/validation";
import { RequestContextManager } from "../middleware/context";

export function registerToolHandlers(
  server: Server, 
  erpnext: ERPNextClient,
  auth: AuthManager,
  logger: Logger
) {
  // Create rate limiter for authentication attempts
  const authRateLimiter = new RateLimiter({
    windowMs: 60 * 1000, // 1 minute
    maxRequests: 5 // 5 attempts per minute
  }, logger);
  
  // Create rate limiter for other operations
  const toolRateLimiter = new RateLimiter({
    windowMs: 60 * 1000, // 1 minute
    maxRequests: 30 // 30 requests per minute
  }, logger);

  // Handler for listing tools
  server.setRequestHandler(ListToolsRequestSchema, async () => {
    logger.debug("Handling ListToolsRequest");
    
    return {
      tools: [
        {
          name: "get_doctypes",
          description: "Get a list of all available DocTypes",
          inputSchema: {
            type: "object",
            properties: {}
          }
        },
        {
          name: "authenticate_erpnext",
          description: "Authenticate with ERPNext using username and password",
          inputSchema: {
            type: "object",
            properties: {
              username: {
                type: "string",
                description: "ERPNext username"
              },
              password: {
                type: "string",
                description: "ERPNext password"
              }
            },
            required: ["username", "password"]
          }
        },
        // Other tools...
      ]
    };
  });

  // Handler for tool calls
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const requestId = request.id;
    const context = RequestContextManager.getContext(requestId);
    const correlationId = context?.correlationId;
    
    logger.debug(`Handling CallToolRequest: ${request.params.name}`, { 
      correlationId,
      tool: request.params.name
    });
    
    try {
      // Apply rate limiting based on tool
      const toolName = request.params.name;
      
      if (toolName === 'authenticate_erpnext') {
        // Use stricter rate limiting for authentication
        if (!authRateLimiter.allowRequest(toolName)) {
          throw new McpError(
            ErrorCode.TooManyRequests,
            "Too many authentication attempts. Please try again later."
          );
        }
      } else {
        // Use standard rate limiting for other tools
        if (!toolRateLimiter.allowRequest(toolName)) {
          throw new McpError(
            ErrorCode.TooManyRequests,
            "Too many requests. Please try again later."
          );
        }
      }
      
      // Handle specific tool requests with proper validation
      switch (toolName) {
        case "authenticate_erpnext": {
          const credentials = validation.validateInput(
            validation.AuthCredentialsSchema, 
            request.params.arguments
          );
          
          try {
            await auth.authenticate(credentials.username, credentials.password);
            
            return {
              content: [{
                type: "text",
                text: `Successfully authenticated with ERPNext as ${credentials.username}`
              }]
            };
          } catch (error) {
            // Don't expose details of authentication failures
            return {
              content: [{
                type: "text",
                text: "Authentication failed. Please check your credentials and try again."
              }],
              isError: true
            };
          }
        }
        
        case "get_documents": {
          // First check authentication
          if (!auth.isAuthenticated()) {
            throw new McpError(
              ErrorCode.Unauthorized,
              "Not authenticated with ERPNext. Use the authenticate_erpnext tool first."
            );
          }
          
          // Validate doctype
          const doctype = validation.validateInput(
            validation.DoctypeSchema,
            request.params.arguments?.doctype
          );
          
          // Validate optional parameters
          const fields = request.params.arguments?.fields ? 
            validation.validateInput(validation.FieldsSchema, request.params.arguments.fields) : 
            undefined;
          
          const filters = request.params.arguments?.filters ?
            validation.validateInput(validation.FiltersSchema, request.params.arguments.filters) :
            undefined;
          
          const limit = request.params.arguments?.limit as number | undefined;
          
          // Get documents
          const documents = await erpnext.getDocList(doctype, filters, fields, limit);
          
          return {
            content: [{
              type: "text",
              text: JSON.stringify(documents, null, 2)
            }]
          };
        }
        
        // Other tool handlers...
        
        default:
          throw new McpError(
            ErrorCode.MethodNotFound,
            `Unknown tool: ${request.params.name}`
          );
      }
    } catch (error) {
      return handleToolError(error, logger);
    }
  });
}
```

## Security Best Practices

### 1. Credential Handling

- Never log passwords or sensitive information
- Use secure environment variables for storing secrets
- Implement token refresh mechanisms where possible
- Consider using a secrets manager for production deployments

### 2. Input Validation

- Validate all inputs with proper schemas
- Sanitize inputs where necessary to prevent injection attacks
- Apply validation early in the request flow

### 3. Rate Limiting

- Apply rate limits to prevent abuse
- Use stricter limits for sensitive operations like authentication
- Provide informative feedback when limits are exceeded

### 4. Secure Configuration

- Validate configuration at startup
- Support multiple configuration sources (env vars, config files)
- Use a secure method for loading credentials

### 5. Authentication Best Practices

- Implement proper token handling
- Support multiple authentication methods
- Add contextual information for authentication events
- Log authentication events (success, failure) without exposing sensitive details

## Implementation Plan

1. Add the validation utility module
2. Implement the secure configuration module
3. Create the authentication manager
4. Add security middleware (rate limiting)
5. Update tool handlers to use validation and rate limiting
6. Add security headers to responses

These security improvements will help protect the ERPNext MCP server against common security vulnerabilities and provide better protection for user credentials and data.

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

/**
 * ERPNext MCP Server
 * This server provides integration with the ERPNext/Frappe API, allowing:
 * - Authentication with ERPNext
 * - Fetching documents from ERPNext
 * - Querying lists of documents
 * - Creating and updating documents
 * - Running reports
 */

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ErrorCode,
  ListResourcesRequestSchema,
  ListResourceTemplatesRequestSchema,
  ListToolsRequestSchema,
  McpError,
  ReadResourceRequestSchema
} from "@modelcontextprotocol/sdk/types.js";
import axios, { AxiosInstance } from "axios";

// ERPNext API client configuration
class ERPNextClient {
  private baseUrl: string;
  private axiosInstance: AxiosInstance;
  private authenticated: boolean = false;

  constructor() {
    // Get ERPNext configuration from environment variables
    this.baseUrl = process.env.ERPNEXT_URL || '';
    
    // Validate configuration
    if (!this.baseUrl) {
      throw new Error("ERPNEXT_URL environment variable is required");
    }
    
    // Remove trailing slash if present
    this.baseUrl = this.baseUrl.replace(/\/$/, '');
    
    // Initialize axios instance
    this.axiosInstance = axios.create({
      baseURL: this.baseUrl,
      withCredentials: true,
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      }
    });
    
    // Configure authentication if credentials provided
    const apiKey = process.env.ERPNEXT_API_KEY;
    const apiSecret = process.env.ERPNEXT_API_SECRET;
    
    if (apiKey && apiSecret) {
      this.axiosInstance.defaults.headers.common['Authorization'] = 
        `token ${apiKey}:${apiSecret}`;
      this.authenticated = true;
    }
  }

  isAuthenticated(): boolean {
    return this.authenticated;
  }

  // Get a document by doctype and name
  async getDocument(doctype: string, name: string): Promise<any> {
    try {
      const response = await this.axiosInstance.get(`/api/resource/${doctype}/${name}`);
      return response.data.data;
    } catch (error: any) {
      throw new Error(`Failed to get ${doctype} ${name}: ${error?.message || 'Unknown error'}`);
    }
  }

  // Get list of documents for a doctype
  async getDocList(doctype: string, filters?: Record<string, any>, fields?: string[], limit?: number): Promise<any[]> {
    try {
      let params: Record<string, any> = {};
      
      if (fields && fields.length) {
        params['fields'] = JSON.stringify(fields);
      }
      
      if (filters) {
        params['filters'] = JSON.stringify(filters);
      }
      
      if (limit) {
        params['limit_page_length'] = limit;
      }
      
      const response = await this.axiosInstance.get(`/api/resource/${doctype}`, { params });
      return response.data.data;
    } catch (error: any) {
      throw new Error(`Failed to get ${doctype} list: ${error?.message || 'Unknown error'}`);
    }
  }

  // Create a new document
  async createDocument(doctype: string, doc: Record<string, any>): Promise<any> {
    try {
      const response = await this.axiosInstance.post(`/api/resource/${doctype}`, {
        data: doc
      });
      return response.data.data;
    } catch (error: any) {
      throw new Error(`Failed to create ${doctype}: ${error?.message || 'Unknown error'}`);
    }
  }

  // Update an existing document
  async updateDocument(doctype: string, name: string, doc: Record<string, any>): Promise<any> {
    try {
      const response = await this.axiosInstance.put(`/api/resource/${doctype}/${name}`, {
        data: doc
      });
      return response.data.data;
    } catch (error: any) {
      throw new Error(`Failed to update ${doctype} ${name}: ${error?.message || 'Unknown error'}`);
    }
  }

  // Run a report
  async runReport(reportName: string, filters?: Record<string, any>): Promise<any> {
    try {
      const response = await this.axiosInstance.get(`/api/method/frappe.desk.query_report.run`, {
        params: {
          report_name: reportName,
          filters: filters ? JSON.stringify(filters) : undefined
        }
      });
      return response.data.message;
    } catch (error: any) {
      throw new Error(`Failed to run report ${reportName}: ${error?.message || 'Unknown error'}`);
    }
  }

  // Get all available DocTypes
  async getAllDocTypes(): Promise<string[]> {
    try {
      // Use the standard REST API to fetch DocTypes
      const response = await this.axiosInstance.get('/api/resource/DocType', {
        params: {
          fields: JSON.stringify(["name"]),
          limit_page_length: 500 // Get more doctypes at once
        }
      });
      
      if (response.data && response.data.data) {
        return response.data.data.map((item: any) => item.name);
      }
      
      return [];
    } catch (error: any) {
      console.error("Failed to get DocTypes:", error?.message || 'Unknown error');
      
      // Try an alternative approach if the first one fails
      try {
        // Try using the method API to get doctypes
        const altResponse = await this.axiosInstance.get('/api/method/frappe.desk.search.search_link', {
          params: {
            doctype: 'DocType',
            txt: '',
            limit: 500
          }
        });
        
        if (altResponse.data && altResponse.data.results) {
          return altResponse.data.results.map((item: any) => item.value);
        }
        
        return [];
      } catch (altError: any) {
        console.error("Alternative DocType fetch failed:", altError?.message || 'Unknown error');
        
        // Fallback: Return a list of common DocTypes
        return [
          "Customer", "Supplier", "Item", "Sales Order", "Purchase Order",
          "Sales Invoice", "Purchase Invoice", "Employee", "Lead", "Opportunity",
          "Quotation", "Payment Entry", "Journal Entry", "Stock Entry"
        ];
      }
    }
  }
}

// Cache for doctype metadata
const doctypeCache = new Map<string, any>();

// Initialize ERPNext client
const erpnext = new ERPNextClient();

// Create an MCP server with capabilities for resources and tools
const server = new Server(
  {
    name: "erpnext-server",
    version: "0.1.0"
  },
  {
    capabilities: {
      resources: {},
      tools: {}
    }
  }
);

/**
 * Handler for listing available ERPNext resources.
 * Exposes DocTypes list as a resource and common doctypes as individual resources.
 */
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  // List of common DocTypes to expose as individual resources
  const commonDoctypes = [
    "Customer",
    "Supplier",
    "Item",
    "Sales Order",
    "Purchase Order",
    "Sales Invoice",
    "Purchase Invoice",
    "Employee"
  ];

  const resources = [
    // Add a resource to get all doctypes
    {
      uri: "erpnext://DocTypes",
      name: "All DocTypes",
      mimeType: "application/json",
      description: "List of all available DocTypes in the ERPNext instance"
    }
  ];

  return {
    resources
  };
});

/**
 * Handler for resource templates.
 * Allows querying ERPNext documents by doctype and name.
 */
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
  const resourceTemplates = [
    {
      uriTemplate: "erpnext://{doctype}/{name}",
      name: "ERPNext Document",
      mimeType: "application/json",
      description: "Fetch an ERPNext document by doctype and name"
    }
  ];

  return { resourceTemplates };
});

/**
 * Handler for reading ERPNext resources.
 */
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
  if (!erpnext.isAuthenticated()) {
    throw new McpError(
      ErrorCode.InvalidRequest,
      "Not authenticated with ERPNext. Please configure API key authentication."
    );
  }

  const uri = request.params.uri;
  let result: any;

  // Handle special resource: erpnext://DocTypes (list of all doctypes)
  if (uri === "erpnext://DocTypes") {
    try {
      const doctypes = await erpnext.getAllDocTypes();
      result = { doctypes };
    } catch (error: any) {
      throw new McpError(
        ErrorCode.InternalError,
        `Failed to fetch DocTypes: ${error?.message || 'Unknown error'}`
      );
    }
  } else {
    // Handle document access: erpnext://{doctype}/{name}
    const documentMatch = uri.match(/^erpnext:\/\/([^\/]+)\/(.+)$/);
    if (documentMatch) {
      const doctype = decodeURIComponent(documentMatch[1]);
      const name = decodeURIComponent(documentMatch[2]);
      
      try {
        result = await erpnext.getDocument(doctype, name);
      } catch (error: any) {
        throw new McpError(
          ErrorCode.InvalidRequest,
          `Failed to fetch ${doctype} ${name}: ${error?.message || 'Unknown error'}`
        );
      }
    }
  }

  if (!result) {
    throw new McpError(
      ErrorCode.InvalidRequest,
      `Invalid ERPNext resource URI: ${uri}`
    );
  }

  return {
    contents: [{
      uri: request.params.uri,
      mimeType: "application/json",
      text: JSON.stringify(result, null, 2)
    }]
  };
});

/**
 * Handler that lists available tools.
 */
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "get_doctypes",
        description: "Get a list of all available DocTypes",
        inputSchema: {
          type: "object",
          properties: {}
        }
      },
      {
        name: "get_doctype_fields",
        description: "Get fields list for a specific DocType",
        inputSchema: {
          type: "object",
          properties: {
            doctype: {
              type: "string",
              description: "ERPNext DocType (e.g., Customer, Item)"
            }
          },
            required: ["doctype"]
        }
      },
      {
        name: "get_documents",
        description: "Get a list of documents for a specific doctype",
        inputSchema: {
          type: "object",
          properties: {
            doctype: {
              type: "string",
              description: "ERPNext DocType (e.g., Customer, Item)"
            },
            fields: {
              type: "array",
              items: {
                type: "string"
              },
              description: "Fields to include (optional)"
            },
            filters: {
              type: "object",
              additionalProperties: true,
              description: "Filters in the format {field: value} (optional)"
            },
            limit: {
              type: "number",
              description: "Maximum number of documents to return (optional)"
            }
          },
          required: ["doctype"]
        }
      },
      {
        name: "create_document",
        description: "Create a new document in ERPNext",
        inputSchema: {
          type: "object",
          properties: {
            doctype: {
              type: "string",
              description: "ERPNext DocType (e.g., Customer, Item)"
            },
            data: {
              type: "object",
              additionalProperties: true,
              description: "Document data"
            }
          },
          required: ["doctype", "data"]
        }
      },
      {
        name: "update_document",
        description: "Update an existing document in ERPNext",
        inputSchema: {
          type: "object",
          properties: {
            doctype: {
              type: "string",
              description: "ERPNext DocType (e.g., Customer, Item)"
            },
            name: {
              type: "string",
              description: "Document name/ID"
            },
            data: {
              type: "object",
              additionalProperties: true,
              description: "Document data to update"
            }
          },
          required: ["doctype", "name", "data"]
        }
      },
      {
        name: "run_report",
        description: "Run an ERPNext report",
        inputSchema: {
          type: "object",
          properties: {
            report_name: {
              type: "string",
              description: "Name of the report"
            },
            filters: {
              type: "object",
              additionalProperties: true,
              description: "Report filters (optional)"
            }
          },
          required: ["report_name"]
        }
      }
    ]
  };
});

/**
 * Handler for tool calls.
 */
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  switch (request.params.name) {
    case "get_documents": {
      if (!erpnext.isAuthenticated()) {
        return {
          content: [{
            type: "text",
            text: "Not authenticated with ERPNext. Please configure API key authentication."
          }],
          isError: true
        };
      }
      
      const doctype = String(request.params.arguments?.doctype);
      const fields = request.params.arguments?.fields as string[] | undefined;
      const filters = request.params.arguments?.filters as Record<string, any> | undefined;
      const limit = request.params.arguments?.limit as number | undefined;
      
      if (!doctype) {
        throw new McpError(
          ErrorCode.InvalidParams,
          "Doctype is required"
        );
      }
      
      try {
        const documents = await erpnext.getDocList(doctype, filters, fields, limit);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(documents, null, 2)
          }]
        };
      } catch (error: any) {
        return {
          content: [{
            type: "text",
            text: `Failed to get ${doctype} documents: ${error?.message || 'Unknown error'}`
          }],
          isError: true
        };
      }
    }
    
    case "create_document": {
      if (!erpnext.isAuthenticated()) {
        return {
          content: [{
            type: "text",
            text: "Not authenticated with ERPNext. Please configure API key authentication."
          }],
          isError: true
        };
      }
      
      const doctype = String(request.params.arguments?.doctype);
      const data = request.params.arguments?.data as Record<string, any> | undefined;
      
      if (!doctype || !data) {
        throw new McpError(
          ErrorCode.InvalidParams,
          "Doctype and data are required"
        );
      }
      
      try {
        const result = await erpnext.createDocument(doctype, data);
        return {
          content: [{
            type: "text",
            text: `Created ${doctype}: ${result.name}\n\n${JSON.stringify(result, null, 2)}`
          }]
        };
      } catch (error: any) {
        return {
          content: [{
            type: "text",
            text: `Failed to create ${doctype}: ${error?.message || 'Unknown error'}`
          }],
          isError: true
        };
      }
    }
    
    case "update_document": {
      if (!erpnext.isAuthenticated()) {
        return {
          content: [{
            type: "text",
            text: "Not authenticated with ERPNext. Please configure API key authentication."
          }],
          isError: true
        };
      }
      
      const doctype = String(request.params.arguments?.doctype);
      const name = String(request.params.arguments?.name);
      const data = request.params.arguments?.data as Record<string, any> | undefined;
      
      if (!doctype || !name || !data) {
        throw new McpError(
          ErrorCode.InvalidParams,
          "Doctype, name, and data are required"
        );
      }
      
      try {
        const result = await erpnext.updateDocument(doctype, name, data);
        return {
          content: [{
            type: "text",
            text: `Updated ${doctype} ${name}\n\n${JSON.stringify(result, null, 2)}`
          }]
        };
      } catch (error: any) {
        return {
          content: [{
            type: "text",
            text: `Failed to update ${doctype} ${name}: ${error?.message || 'Unknown error'}`
          }],
          isError: true
        };
      }
    }
    
    case "run_report": {
      if (!erpnext.isAuthenticated()) {
        return {
          content: [{
            type: "text",
            text: "Not authenticated with ERPNext. Please configure API key authentication."
          }],
          isError: true
        };
      }
      
      const reportName = String(request.params.arguments?.report_name);
      const filters = request.params.arguments?.filters as Record<string, any> | undefined;
      
      if (!reportName) {
        throw new McpError(
          ErrorCode.InvalidParams,
          "Report name is required"
        );
      }
      
      try {
        const result = await erpnext.runReport(reportName, filters);
        return {
          content: [{
            type: "text",
            text: JSON.stringify(result, null, 2)
          }]
        };
      } catch (error: any) {
        return {
          content: [{
            type: "text",
            text: `Failed to run report ${reportName}: ${error?.message || 'Unknown error'}`
          }],
          isError: true
        };
      }
    }
    
    case "get_doctype_fields": {
      if (!erpnext.isAuthenticated()) {
        return {
          content: [{
            type: "text",
            text: "Not authenticated with ERPNext. Please configure API key authentication."
          }],
          isError: true
        };
      }
      
      const doctype = String(request.params.arguments?.doctype);
      
      if (!doctype) {
        throw new McpError(
          ErrorCode.InvalidParams,
          "Doctype is required"
        );
      }
      
      try {
        // Get a sample document to understand the fields
        const documents = await erpnext.getDocList(doctype, {}, ["*"], 1);
        
        if (!documents || documents.length === 0) {
          return {
            content: [{
              type: "text",
              text: `No documents found for ${doctype}. Cannot determine fields.`
            }],
            isError: true
          };
        }
        
        // Extract field names from the first document
        const sampleDoc = documents[0];
        const fields = Object.keys(sampleDoc).map(field => ({
          fieldname: field,
          value: typeof sampleDoc[field],
          sample: sampleDoc[field]?.toString()?.substring(0, 50) || null
        }));
        
        return {
          content: [{
            type: "text",
            text: JSON.stringify(fields, null, 2)
          }]
        };
      } catch (error: any) {
        return {
          content: [{
            type: "text",
            text: `Failed to get fields for ${doctype}: ${error?.message || 'Unknown error'}`
          }],
          isError: true
        };
      }
    }
    
    case "get_doctypes": {
      if (!erpnext.isAuthenticated()) {
        return {
          content: [{
            type: "text",
            text: "Not authenticated with ERPNext. Please configure API key authentication."
          }],
          isError: true
        };
      }
      
      try {
        const doctypes = await erpnext.getAllDocTypes();
        return {
          content: [{
            type: "text",
            text: JSON.stringify(doctypes, null, 2)
          }]
        };
      } catch (error: any) {
        return {
          content: [{
            type: "text",
            text: `Failed to get DocTypes: ${error?.message || 'Unknown error'}`
          }],
          isError: true
        };
      }
    }
      
    default:
      throw new McpError(
        ErrorCode.MethodNotFound,
        `Unknown tool: ${request.params.name}`
      );
  }
});

/**
 * Start the server using stdio transport.
 */
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('ERPNext MCP server running on stdio');
}

main().catch((error) => {
  console.error("Server error:", error);
  process.exit(1);
});

```