diff --git a/examples/mcp/.env.example b/examples/mcp/.env.example new file mode 100644 index 0000000..4fefbba --- /dev/null +++ b/examples/mcp/.env.example @@ -0,0 +1,55 @@ + +# General settings +ENVIRONMENT=development +ENABLE_TELEMETRY=false +ENABLE_AUTH=false +# Model Context Protocol (MCP) +MCP_ENABLE=false +MCP_EXPOSE=false +MCP_SERVERS= +MCP_CLIENT_TIMEOUT=5s +MCP_DIAL_TIMEOUT=3s +MCP_TLS_HANDSHAKE_TIMEOUT=3s +MCP_RESPONSE_HEADER_TIMEOUT=3s +MCP_EXPECT_CONTINUE_TIMEOUT=1s +MCP_REQUEST_TIMEOUT=5s +# OpenID Connect +OIDC_ISSUER_URL=http://keycloak:8080/realms/inference-gateway-realm +OIDC_CLIENT_ID=inference-gateway-client +OIDC_CLIENT_SECRET= +# Server settings +SERVER_HOST=0.0.0.0 +SERVER_PORT=8080 +SERVER_READ_TIMEOUT=30s +SERVER_WRITE_TIMEOUT=30s +SERVER_IDLE_TIMEOUT=120s +SERVER_TLS_CERT_PATH= +SERVER_TLS_KEY_PATH= +# Client settings +CLIENT_TIMEOUT=30s +CLIENT_MAX_IDLE_CONNS=20 +CLIENT_MAX_IDLE_CONNS_PER_HOST=20 +CLIENT_IDLE_CONN_TIMEOUT=30s +CLIENT_TLS_MIN_VERSION=TLS12 +# Providers +ANTHROPIC_API_URL=https://api.anthropic.com/v1 +ANTHROPIC_API_KEY= +CLOUDFLARE_API_URL=https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/ai +CLOUDFLARE_API_KEY= +COHERE_API_URL=https://api.cohere.ai +COHERE_API_KEY= +GROQ_API_URL=https://api.groq.com/openai/v1 +GROQ_API_KEY= +OLLAMA_API_URL=http://ollama:8080/v1 +OLLAMA_API_KEY= +OPENAI_API_URL=https://api.openai.com/v1 +OPENAI_API_KEY= +DEEPSEEK_API_URL=https://api.deepseek.com +DEEPSEEK_API_KEY= + +# Chosen provider +PROVIDER=deepseek +LLM=deepseek-chat + +# Brave search +BRAVE_API_KEY= diff --git a/examples/mcp/README.md b/examples/mcp/README.md index 8b6d1a3..bf40d81 100644 --- a/examples/mcp/README.md +++ b/examples/mcp/README.md @@ -1,578 +1,178 @@ -# MCP Example +# MCP Examples -This example demonstrates how to use the Inference Gateway SDK with Model Context Protocol (MCP) tools in a multi-provider architecture. It showcases how to connect to MCP servers, discover available tools, and use them in AI conversations. +This directory contains comprehensive examples demonstrating how to use the Inference Gateway SDK with Model Context Protocol (MCP) tools in a multi-provider architecture. Each example showcases different aspects of MCP tool integration. -Please ensure you have no containers running before starting this example, as it uses Docker Compose to set up the necessary infrastructure. +## πŸš€ Quick Start -## Features Demonstrated - -1. **MCP Tool Discovery** - List and explore available MCP tools -2. **File Operations** - Use filesystem MCP server for file operations -3. **Web Scraping** - Fetch content from URLs using MCP tools -4. **Multi-Tool Conversations** - Combine multiple MCP tools in single conversations -5. **Tool Function Calling** - Stream responses with real-time tool execution -6. **Data Analysis** - Analyze sample data files with AI assistance - -## Architecture - -This example uses Docker Compose to orchestrate: - -- **Inference Gateway** - Main API gateway with MCP support enabled -- **MCP Filesystem Server** - Provides file system operations (restricted to `/shared` and `/tmp`) -- **MCP Web Search Server** - Simulated web search and URL fetching - -## Important: Filesystem Access - -The MCP filesystem server is configured with restricted access for security: - -- **`/shared`** - Read-only directory with sample data files -- **`/tmp`** - Read-write directory for temporary files - -The AI will only be able to access these directories. This prevents unauthorized access to system files. - -## Sample Data - -The `/shared` directory contains example files for testing: - -- `mcp-filesystem-example.txt` - Basic example file -- `sample_sales_data.csv` - Sales data for analysis exercises -- `README.md` - Documentation about available files - -## Getting Started - -### Prerequisites - -- Docker and Docker Compose installed -- API key for at least one provider (OpenAI, Groq, Anthropic, etc.) - -Make sure the environment is configured: - -```bash -# From the .env file grab one of the providers keys and export it -export OPENAI_API_KEY=your_openai_api_key -``` - -### 1. Start the MCP Infrastructure - -Start all services using Docker Compose: - -```bash -npm run compose:up -``` - -This will start: - -- Inference Gateway on port 8080 -- MCP Filesystem server on port 3000 -- MCP Web Search server on port 3001 - -### 2. Install Dependencies - -```bash -npm install -``` - -### 3. Configure Provider and Model - -Set your preferred provider and model: - -```bash -export PROVIDER=groq -export LLM=qwen-qwq-32b -``` - -Or for OpenAI: +### Spin up the Inference Gateway and the MCP Server ```bash -export PROVIDER=openai -export LLM=gpt-4o +docker compose -f docker-compose.yml up --build ``` -### 4. Verify MCP Setup +### Run Specific Examples -Test that MCP tools are working correctly: +On another terminal, you can run specific examples using the Inference Gateway: ```bash -npm run example:mcp:remotetools +# Run interactive specialized agents +docker compose -f docker-compose-agents.yml run --rm -it --build nextjs-agent # πŸ€– Next.js development agent +docker compose -f docker-compose-agents.yml run --rm -it --build vite-agent # ⚑ Vite application agent +docker compose -f docker-compose-agents.yml run --rm -it --build kubernetes-agent # ☸️ Kubernetes operations agent +docker compose -f docker-compose-agents.yml run --rm -it --build marketing-agent # πŸ“ˆ Marketing research agent ``` -### 5. Run Examples - -Run the main example: - -```bash -npm start -``` - -Or run the focused filesystem demo: - -```bash -npx tsx filesystem-demo.ts -``` - -## Available Examples - -- `index.ts` - Complete MCP example with multiple scenarios -- `filesystem-demo.ts` - Focused demonstration of filesystem operations -- `test-mcp-tools.ts` - Simple verification that MCP tools are working -- `advanced-example.ts` - More complex MCP usage patterns +## 🧠 Memory & Error Recovery -## Available Commands +The **Memory MCP Server** provides persistent state management for AI agents, enabling them to recover gracefully from HTTP errors and continue from where they left off. This is particularly useful for long-running tasks that may encounter temporary network issues or API failures. -- `npm start` - Run the main MCP example -- `npm run compose:up` - Start all services in background -- `npm run compose:down` - Stop all services -- `npm run compose:logs` - View logs from all services +### Key Features -## Example Prompts to Try +- **State Persistence**: Save arbitrary state objects with session IDs +- **Error State Recovery**: Special handling for HTTP error scenarios +- **Session Management**: List, restore, and clear saved sessions +- **File-based Storage**: Persistent storage using JSON files -Once the example is running, you can ask the AI: +### Memory Tools Integration -1. **List available data:** +All agents (Next.js, Vite, and Kubernetes) now include memory recovery capabilities: - ``` - "Can you show me what files are available in the /shared directory?" - ``` +1. **Save State Before Risky Operations**: Before making HTTP requests, agents save their current progress +2. **Handle Errors Gracefully**: When HTTP errors occur, agents save the error state with context +3. **Resume from Last Checkpoint**: On restart, agents check for saved state and continue from the last successful step +4. **Memory Management**: Agents can list, restore, and clear saved sessions -2. **Analyze sample data:** +### Available Memory Tools - ``` - "Read the sales data from /shared/sample_sales_data.csv and give me a summary of the top-selling products" - ``` +- `save-state`: Save current progress/state with a session ID +- `save-error-state`: Save state when HTTP errors occur for recovery +- `restore-state`: Restore previously saved state by session ID +- `list-sessions`: List all saved sessions +- `clear-session`: Remove a saved session -3. **Create reports:** +All agents will automatically use these tools when encountering HTTP errors, ensuring robust error recovery and task continuation. - ``` - "Based on the sales data, create a summary report and save it to /tmp/sales_report.txt" - ``` +## πŸ” MCP Inspector -4. **File operations:** - ``` - "Create a todo list with 5 tasks and save it to /tmp/todo.txt, then read it back to me" - ``` +The **MCP Inspector** is a visual debugging tool that allows you to inspect and test your MCP servers interactively. It provides a web-based interface to: -## Example Output +- Connect to and inspect MCP servers +- View available tools and their schemas +- Test tool execution with custom parameters +- Debug server responses and error messages +- Monitor real-time server communication -The example will demonstrate: +### Accessing the Inspector -``` -=== MCP Tools Example === - -πŸ” Checking gateway health... -Gateway health: βœ… Healthy - -πŸ“‹ Listing available MCP tools... -Found 9 MCP tools: - -1. read_file - Description: Read content from a file - Server: http://mcp-filesystem:3000/mcp - Schema: { - "type": "object", - "properties": { - "file_path": { - "type": "string", - "description": "Path to the file to read" - } - }, - "required": ["file_path"] - } - -2. write_file - Description: Write content to a file - Server: http://mcp-filesystem:3000/mcp - Schema: { - "type": "object", - "properties": { - "file_path": { - "type": "string", - "description": "Path to the file to write" - }, - "content": { - "type": "string", - "description": "Content to write to the file" - } - }, - "required": ["file_path", "content"] - } - -3. fetch_url - Description: Fetch content from a URL - Server: http://mcp-web-search:3001/mcp - Schema: { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "The URL to fetch content from" - }, - "timeout": { - "type": "number", - "description": "Request timeout in milliseconds" - } - }, - "required": ["url"] - } - -4. search_web - Description: Search the web and return results - Server: http://mcp-web-search:3001/mcp - Schema: { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "Search query" - }, - "limit": { - "type": "number", - "description": "Number of results to return" - } - }, - "required": ["query"] - } - -=== Example 1: File Operations with MCP === - -πŸš€ Starting file reading example... -I'll help you read the contents of /tmp/example.txt file. - -πŸ”§ Tool called: read_file -πŸ“ Arguments: {"file_path": "/tmp/example.txt"} - -The file contains: -Hello from MCP filesystem server! -This is a sample file for testing MCP file operations. -Created at: Mon May 27 10:30:00 UTC 2025 - -βœ… File reading example completed -``` - -## MCP Servers Included - -### Filesystem Server - -- **Purpose**: File system operations (read, write, list directories) -- **Port**: 3000 -- **Tools**: `read_file`, `write_file`, `list_directory` -- **Allowed Directories**: `/shared`, `/tmp` - -### Web Search Server - -- **Purpose**: Web content fetching and search simulation -- **Port**: 3001 -- **Tools**: `fetch_url`, `search_web` -- **Features**: HTTP requests, basic content extraction - -## Supported Providers +The MCP Inspector is available at: **http://localhost:6274** -All Inference Gateway providers work with MCP tools: +You can connect to any of the running MCP servers: -- `openai` - GPT models with excellent tool calling -- `groq` - Fast inference with Llama and Mixtral models -- `anthropic` - Claude models with strong reasoning -- `ollama` - Local models (use `--profile with-ollama`) -- `cohere` - Command models -- `deepseek` - DeepSeek models -- `cloudflare` - Workers AI models +- **Filesystem Server**: `http://mcp-filesystem:3000/mcp` +- **Web Search Server**: `http://mcp-web-search:3001/mcp` +- **Context7 Server**: `http://mcp-context7:3002/mcp` +- **NPM Server**: `http://mcp-npm:3003/mcp` +- **Memory Server**: `http://mcp-memory:3004/mcp` -## Using with Local Models (Ollama) - -To include Ollama for local model inference: - -```bash -docker-compose --profile with-ollama up -d -``` - -Then pull a model: - -```bash -docker exec -it mcp_ollama_1 ollama pull llama3.2 -``` +### Quick Inspector URLs -Set environment variables: +For convenience, you can use these pre-configured URLs: ```bash -export PROVIDER=ollama -export LLM=llama3.2 -``` - -## Troubleshooting - -### MCP Tools Not Available - -If you see "No MCP tools available": - -1. Check if MCP servers are running: - - ```bash - docker-compose ps - ``` - -2. Verify MCP server logs: - - ```bash - npm run compose:logs - ``` - -3. Ensure the Inference Gateway can reach MCP servers: - ```bash - curl -X POST http://localhost:8080/mcp/tools/list -H "Content-Type: application/json" -d '{}' - ``` - -### Gateway Health Check Fails - -If the gateway appears unhealthy: - -1. Check gateway logs: - - ```bash - docker-compose logs inference-gateway - ``` - -2. Verify API keys are set in `.env` file -3. Test direct connection: - ```bash - curl http://localhost:8080/health - ``` - -### Tool Calls Not Working - -If tool calls fail during conversations: - -1. Verify the model supports tool calling (GPT-4, Claude, etc.) -2. Check MCP server connectivity through the gateway: - - ```bash - curl -X POST http://localhost:8080/mcp/tools/list \ - -H "Content-Type: application/json" \ - -d '{}' - ``` - -3. Enable debug logging by adding to docker-compose.yml: - ```yaml - environment: - - LOG_LEVEL=debug - ``` - -## Custom MCP Servers - -To add your own MCP server: - -1. Add service to `docker-compose.yml`: - - ```yaml - my-custom-mcp: - image: my-mcp-server:latest - ports: - - '3002:3002' - networks: - - inference-network - ``` - -2. Update Inference Gateway environment: - - ```yaml - MCP_SERVERS: 'http://mcp-filesystem:3000/mcp,http://mcp-web-search:3001/mcp,http://my-custom-mcp:3002/mcp' - ``` - -3. Restart services: - ```bash - npm run compose:down - npm run compose:up - ``` - -## Notes - -- MCP tools are called automatically by AI models when relevant to the conversation -- Tool schemas are defined by the MCP servers and exposed through the `/mcp/tools` endpoint -- Each MCP server can provide multiple tools with different capabilities -- The Inference Gateway acts as a bridge between AI models and MCP tools -- Tool execution is streamed in real-time during conversations -- File operations are sandboxed to allowed directories for security - -## Resources +# Connect to Filesystem server +http://localhost:6274/?transport=streamable-http&serverUrl=http://mcp-filesystem:3000/mcp -- [Model Context Protocol Specification](https://modelcontextprotocol.io/) -- [Inference Gateway Documentation](https://github.com/inference-gateway/inference-gateway) -- [Official MCP Servers](https://github.com/modelcontextprotocol/servers) +# Connect to Web Search server +http://localhost:6274/?transport=streamable-http&serverUrl=http://mcp-web-search:3001/mcp -## HTTP Examples - -This section provides practical HTTP examples for interacting with MCP tools through the Inference Gateway. These examples are useful for testing, debugging, or integrating with other systems. **Note: Always interact with MCP tools through the Inference Gateway, not directly with MCP servers.** - -### Prerequisites for HTTP Examples - -Make sure the MCP infrastructure is running with MCP tools exposed: - -```bash -npm run compose:up +# Connect to NPM server +http://localhost:6274/?transport=streamable-http&serverUrl=http://mcp-npm:3003/mcp ``` -**Important:** Ensure the Inference Gateway is started with `EXPOSE_MCP=true` environment variable to enable MCP endpoints. +### Using the Inspector -### 1. Health Checks +1. **Start the services**: `docker compose up --build` +2. **Open the inspector**: Navigate to http://localhost:6274 +3. **Connect to a server**: Enter an MCP server URL (e.g., `http://mcp-filesystem:3000/mcp`) +4. **Explore tools**: Browse available tools and their parameters +5. **Test execution**: Run tools with custom parameters and view results -#### Check Inference Gateway Health +### Troubleshooting -```bash -curl -X GET http://localhost:8080/health -``` +If you have to debug an MCP server, you can either use the MCP Inspector or standard curl commands. -**Response:** - -```json -{ - "status": "healthy", - "timestamp": "2025-05-28T10:30:00Z", - "version": "1.0.0" -} -``` - -### 2. MCP Tool Discovery - -#### List All Available MCP Tools via Inference Gateway +1. First go inside the a container that is attached to the same inference gateway network: ```bash -curl -X GET http://localhost:8080/v1/mcp/tools -``` - -**Response:** - -```json -{ - "tools": [ - { - "name": "read_file", - "description": "Read content from a file", - "input_schema": { - "type": "object", - "properties": { - "file_path": { - "type": "string", - "description": "Path to the file to read" - } - }, - "required": ["file_path"] - } - }, - { - "name": "write_file", - "description": "Write content to a file", - "input_schema": { - "type": "object", - "properties": { - "file_path": { - "type": "string", - "description": "Path to the file to write" - }, - "content": { - "type": "string", - "description": "Content to write to the file" - } - }, - "required": ["file_path", "content"] - } - }, - { - "name": "fetch_url", - "description": "Fetch content from a URL", - "input_schema": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "The URL to fetch content from" - }, - "timeout": { - "type": "number", - "description": "Request timeout in milliseconds" - } - }, - "required": ["url"] - } - } - ] -} +docker run -it --rm alpine:latest sh -c "apk add --no-cache curl && sh" ``` -### 3. Using MCP Tools in Chat Completions +2. For example let's check if mcp-web-search works as expected. We will get into the container: -MCP tools are executed automatically by AI models during chat completions. You don't call them directly - instead, you include them in the `tools` array of a chat completion request. +```sh +docker compose exec mcp-web-search sh -#### Basic Chat Completion with MCP Tools - -```bash -curl -X POST http://localhost:8080/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": "groq/llama-3.3-70b-versatile", - "messages": [ - { - "role": "system", - "content": "You are a helpful assistant that can read files using available tools." - }, - { - "role": "user", - "content": "Can you read the contents of /tmp/example.txt?" - } - ] - }' +export SERVER_URL="http://127.0.0.1" +export SERVER_PORT="" +export TOOL_NAME="" ``` -#### Web Scraping with MCP Tools +3. Fetch a session ID by initializing the MCP server: -```bash -curl -X POST http://localhost:8080/v1/chat/completions \ +```sh +SESSION_ID=$(curl -v -X POST "${SERVER_URL}:${SERVER_PORT}/mcp" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ -d '{ - "model": "openai/gpt-4o", - "messages": [ - { - "role": "system", - "content": "You are a helpful assistant that can fetch web content." - }, - { - "role": "user", - "content": "Can you fetch the content from https://httpbin.org/json?" + "jsonrpc": "2.0", + "id": 1, + "method": "initialize", + "params": { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": { + "name": "test-client", + "version": "1.0.0" } - ] - }' + } + }' 2>&1 | grep -i "< mcp-session-id:" | cut -d' ' -f3 | tr -d '\r') +echo "mcp-session-id: $SESSION_ID" ``` -### 4. Streaming Chat Completions with MCP Tools +4. List available tools to verify the server is running correctly: -```bash -curl -X POST http://localhost:8080/v1/chat/completions \ +```sh +curl -v -X POST "${SERVER_URL}:${SERVER_PORT}/mcp" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -H "mcp-session-id: $SESSION_ID" \ -d '{ - "model": "anthropic/claude-3-haiku-20240307", - "messages": [ - { - "role": "user", - "content": "Help me analyze the file /shared/data.csv by reading it first" - } - ], - "stream": true + "jsonrpc": "2.0", + "id": 2, + "method": "tools/list", + "params": {} }' ``` -### 5. Multi-Tool Conversations +5. Call a specific tool, for example, `search_web` to search the web: -```bash -curl -X POST http://localhost:8080/v1/chat/completions \ +```sh +curl -X POST "${SERVER_URL}:${SERVER_PORT}/mcp" \ -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -H "mcp-session-id: $SESSION_ID" \ -d '{ - "model": "groq/mixtral-8x7b-32768", - "messages": [ - { - "role": "system", - "content": "You are a research assistant with access to file operations and web content fetching." - }, - { - "role": "user", - "content": "Research information about AI from https://openai.com and save a summary to /tmp/ai-research.txt" + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "'"$TOOL_NAME"'", + "arguments": { + "sessionId": "'"$SESSION_ID"'", + "state": { + "query": "What is the capital of France?" + } } - ] + } }' ``` diff --git a/examples/mcp/agents/kubernetes/Dockerfile b/examples/mcp/agents/kubernetes/Dockerfile new file mode 100644 index 0000000..0312a97 --- /dev/null +++ b/examples/mcp/agents/kubernetes/Dockerfile @@ -0,0 +1,23 @@ +FROM ubuntu:24.04 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + bash \ + curl \ + git \ + wget \ + ca-certificates + +RUN curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \ + && apt-get install -y nodejs + +RUN curl -LO "https://dl.k8s.io/release/$(curl -sL https://dl.k8s.io/release/stable.txt)/bin/linux/$(dpkg --print-architecture)/kubectl" \ + && install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl \ + && rm kubectl +RUN curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + +WORKDIR /app +COPY package.json ./ +RUN npm install +COPY . . +CMD ["npm", "start"] + diff --git a/examples/mcp/agents/kubernetes/index.ts b/examples/mcp/agents/kubernetes/index.ts new file mode 100644 index 0000000..9563e8d --- /dev/null +++ b/examples/mcp/agents/kubernetes/index.ts @@ -0,0 +1,1253 @@ +/** + * Interactive Kubernetes Operations Agent + * + * This agent specializes in Kubernetes cluster management, deployment automation, + * and container orchestration using Context7 MCP tools for up-to-date K8s documentation. + */ + +import { + InferenceGatewayClient, + MessageRole, + Provider, +} from '@inference-gateway/sdk'; +import { randomUUID } from 'crypto'; +import * as dotenv from 'dotenv'; +import * as path from 'path'; +import * as readline from 'readline'; +import { clearTimeout, setTimeout } from 'timers'; + +dotenv.config({ path: path.join(__dirname, '.env') }); + +declare const require: any; +declare const module: any; + +interface ErrorRecord { + timestamp: string; + iterationCount: number; + errorType: 'stream_error' | 'tool_error' | 'mcp_error' | 'memory_error'; + errorMessage: string; + context: string; + toolName?: string; + toolId?: string; + toolArguments?: any; + userInput?: string; + recoveryAttempted: boolean; +} + +interface AgentConfig { + client: InferenceGatewayClient; + provider: Provider; + model: string; + conversationHistory: Array<{ role: MessageRole; content: string }>; + maxRetries: number; + retryDelayMs: number; + iterationCount: number; + totalTokensUsed: number; + maxTokensPerRequest: number; + maxHistoryLength: number; + sessionId: string; + memoryEnabled: boolean; + abortController: globalThis.AbortController; + errorHistory: ErrorRecord[]; + lastFailedToolCall?: { + name: string; + id: string; + arguments: any; + timestamp: string; + }; +} + +class KubernetesAgent { + private config: AgentConfig; + private rl: readline.Interface; + + constructor() { + console.log('πŸ”§ Debug - Environment variables:'); + console.log(' PROVIDER:', process.env.PROVIDER); + console.log(' LLM:', process.env.LLM); + + this.config = { + client: new InferenceGatewayClient({ + baseURL: 'http://inference-gateway:8080/v1', + timeout: 120000, + }), + provider: (process.env.PROVIDER as Provider) || Provider.groq, + model: process.env.LLM || 'llama-3.3-70b-versatile', + conversationHistory: [], + maxRetries: 5, + retryDelayMs: 5000, + iterationCount: 0, + totalTokensUsed: 0, + maxTokensPerRequest: 2500, + maxHistoryLength: 6, + sessionId: process.env.SESSION_ID || randomUUID(), + memoryEnabled: true, + abortController: new globalThis.AbortController(), + errorHistory: [], + lastFailedToolCall: undefined, + }; + + this.rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + this.config.conversationHistory.push({ + role: MessageRole.system, + content: this.getSystemPrompt(), + }); + } + + private async delay(ms: number): Promise { + return new Promise((resolve, reject) => { + if (this.config.abortController.signal.aborted) { + reject(new Error('Aborted')); + return; + } + + const timeout = global.setTimeout(() => resolve(), ms); + + const abortHandler = () => { + global.clearTimeout(timeout); + reject(new Error('Aborted')); + }; + + this.config.abortController.signal.addEventListener( + 'abort', + abortHandler, + { once: true } + ); + }); + } + + private async waitForKubernetesOperation(): Promise { + console.log( + '⏳ Waiting 10 seconds for Kubernetes operation to complete...' + ); + await this.delay(10000); + console.log('βœ… Kubernetes operation wait period completed.\n'); + } + + /** + * Record an error to both local history and memory + */ + private async recordError( + errorType: ErrorRecord['errorType'], + errorMessage: string, + context: string, + toolCall?: any, + userInput?: string + ): Promise { + const errorRecord: ErrorRecord = { + timestamp: new Date().toISOString(), + iterationCount: this.config.iterationCount, + errorType, + errorMessage, + context, + toolName: toolCall?.function?.name, + toolId: toolCall?.id, + toolArguments: toolCall?.function?.arguments + ? JSON.parse(toolCall.function.arguments) + : undefined, + userInput: userInput?.substring(0, 100), + recoveryAttempted: false, + }; + + this.config.errorHistory.push(errorRecord); + + // Keep only last 10 errors to prevent memory bloat + if (this.config.errorHistory.length > 10) { + this.config.errorHistory = this.config.errorHistory.slice(-10); + } + + console.log(`πŸ“ Recording ${errorType}: ${errorMessage}`); + + // Save to memory if enabled + if (this.config.memoryEnabled) { + await this.saveErrorToMemory(errorRecord); + } + } + + /** + * Save error state to memory with detailed context + */ + private async saveErrorToMemory(errorRecord: ErrorRecord): Promise { + if (!this.config.memoryEnabled) return; + + try { + const errorState = { + conversationHistory: this.config.conversationHistory.slice(-3), + iterationCount: this.config.iterationCount, + totalTokensUsed: this.config.totalTokensUsed, + timestamp: new Date().toISOString(), + errorHistory: this.config.errorHistory, + lastError: errorRecord, + }; + + console.log( + `🚨 Saving error state to memory for session: ${this.config.sessionId}` + ); + + let toolCallDetected = false; + let saveSuccessful = false; + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: [ + { + role: MessageRole.system, + content: `You are a memory manager handling error recovery. You MUST call the save-error-state tool immediately with the error details below. + +CRITICAL ERROR OCCURRED: +Error Type: ${errorRecord.errorType} +Error Message: ${errorRecord.errorMessage} +Context: ${errorRecord.context} +Tool: ${errorRecord.toolName || 'N/A'} +Tool ID: ${errorRecord.toolId || 'N/A'} +User Input: ${errorRecord.userInput || 'N/A'} +Timestamp: ${errorRecord.timestamp} + +SessionID: ${this.config.sessionId} +Error State: ${JSON.stringify(errorState)} + +Call save-error-state tool immediately with sessionId="${this.config.sessionId}" and the error state above.`, + }, + { + role: MessageRole.user, + content: `URGENT: Save error state now using save-error-state tool for session "${this.config.sessionId}". Include full error context.`, + }, + ], + max_tokens: this.config.maxTokensPerRequest, + }, + { + onMCPTool: (toolCall) => { + toolCallDetected = true; + console.log( + `πŸ“± Error memory tool called: ${toolCall.function.name}` + ); + + if ( + toolCall.function.name === 'save-error-state' || + toolCall.function.name === 'save-state' + ) { + saveSuccessful = true; + console.log('βœ… Error state save tool invoked successfully'); + } + }, + onReasoning: () => { + // Suppress reasoning output for error saves to reduce noise + }, + onContent: () => { + // Suppress content output for error saves to reduce noise + }, + onError: (error) => { + console.warn('⚠️ Error memory save failed:', error.error); + }, + onFinish: () => { + if (toolCallDetected && saveSuccessful) { + console.log('βœ… Error state saved to memory successfully'); + } else { + console.warn('⚠️ Failed to save error state to memory'); + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + } catch (error) { + console.warn( + '⚠️ Failed to save error state to memory:', + (error as Error).message + ); + } + } + + /** + * Load and process previous errors from memory + */ + private async loadErrorHistory(): Promise { + if (!this.config.memoryEnabled) return; + + try { + console.log( + `πŸ“₯ Loading error history for session: ${this.config.sessionId}` + ); + + let errorData: any = null; + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: [ + { + role: MessageRole.system, + content: `You have access to memory management tools. Check for and restore any saved error states for session "${this.config.sessionId}". If errors were previously recorded, provide details about what went wrong.`, + }, + { + role: MessageRole.user, + content: `Check memory for previous errors in session "${this.config.sessionId}" and restore any error states found.`, + }, + ], + max_tokens: this.config.maxTokensPerRequest, + }, + { + onContent: (content) => { + if (content.includes('error') || content.includes('Error')) { + console.log( + `πŸ“‹ Previous error context: ${content.substring(0, 200)}...` + ); + } + if (content.includes('{') && content.includes('}')) { + try { + const jsonMatch = content.match(/\{[\s\S]*\}/); + if (jsonMatch) { + errorData = JSON.parse(jsonMatch[0]); + } + } catch { + // Ignore parsing errors for error recovery + } + } + }, + onMCPTool: (toolCall) => { + console.log( + `πŸ“± Error recovery tool called: ${toolCall.function.name}` + ); + }, + onError: () => { + console.log('ℹ️ No previous error state found'); + }, + onFinish: () => { + if (errorData && errorData.errorHistory) { + this.config.errorHistory = errorData.errorHistory || []; + console.log( + `βœ… Restored ${this.config.errorHistory.length} previous error records` + ); + + if (this.config.errorHistory.length > 0) { + const lastError = + this.config.errorHistory[this.config.errorHistory.length - 1]; + console.log( + `πŸ” Last error was: ${lastError.errorType} - ${lastError.errorMessage}` + ); + console.log(` Context: ${lastError.context}`); + if (lastError.toolName) { + console.log( + ` Failed tool: ${lastError.toolName} (${lastError.toolId})` + ); + } + } + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + } catch (error) { + console.log(`ℹ️ No error history found: ${(error as Error).message}`); + } + } + + private getSystemPrompt(): string { + let errorHistoryPrompt = ''; + + if (this.config.errorHistory.length > 0) { + const recentErrors = this.config.errorHistory.slice(-2); + errorHistoryPrompt = `\nERROR CONTEXT: Previous ${recentErrors.length} errors encountered. Adapt approach accordingly.\n`; + } + + return `You are a Kubernetes operations assistant. Help with cluster management and container orchestration.${errorHistoryPrompt} + +CORE RULES: +- Work only in /tmp directory +- If K8s configs exist, enhance them - don't recreate +- Use Context7 tools for documentation lookup +- Tools called automatically - just describe what you need +- Follow security-first approach with RBAC and network policies +- Wait 10 seconds after applying K8s configurations + +AVAILABLE TOOLS: Context7 docs, filesystem, memory tools for recovery + +WORKFLOW: 1) Clarify requirements 2) Use Context7 for K8s docs 3) Create manifests in /tmp 4) Follow best practices`; + } + + async initialize(): Promise { + console.log( + `☸️ Kubernetes Operations Agent initialized using ${this.config.model} on ${this.config.provider}\n` + ); + + let attempt = 0; + while (attempt < this.config.maxRetries) { + try { + // Health check + const isHealthy = await this.config.client.healthCheck(); + if (!isHealthy) { + console.error('❌ Gateway unhealthy. Run: docker-compose up --build'); + process.exit(1); + } + + const tools = await this.config.client.listTools(); + const realContext7Tools = tools.data.filter((tool) => + ['c41_resolve-library-id', 'c41_get-library-docs'].includes(tool.name) + ); + const mockContext7Tools = tools.data.filter((tool) => + [ + 'search_libraries', + 'get_library_details', + 'get_documentation', + ].includes(tool.name) + ); + + const memoryTools = tools.data.filter((tool) => + ['save-state', 'restore-state', 'list-sessions'].includes(tool.name) + ); + + if (memoryTools.length > 0) { + console.info( + `🧠 Found ${memoryTools.length} memory management tools` + ); + // Skip slow memory restoration for faster startup - only load on demand + console.log('πŸ“₯ Skipping memory restoration for faster startup'); + } else { + console.info( + '⚠️ No memory tools available. State persistence disabled.' + ); + this.config.memoryEnabled = false; + } + + const context7Tools = [...realContext7Tools, ...mockContext7Tools]; + + if (context7Tools.length === 0) { + console.error( + '⚠️ No Context7 MCP tools available. Make sure a Context7 MCP server is running.' + ); + console.error( + ' For real Context7: npx -y @upstash/context7-mcp@latest' + ); + console.error( + ' For local mock: docker-compose up --build (already included in this project)' + ); + process.exit(1); + } + + const usingRealContext7 = realContext7Tools.length > 0; + const toolType = usingRealContext7 ? 'real Context7' : 'mock Context7'; + + console.info( + `πŸ“‹ Found ${context7Tools.length} ${toolType} tools available:` + ); + context7Tools.forEach((tool, index) => { + console.info(` ${index + 1}. ${tool.name} - ${tool.description}`); + }); + + if (!usingRealContext7) { + console.info('πŸ’‘ Using local mock Context7 server for demonstration'); + console.info( + ' To use real Context7 with latest docs, install: npx -y @upstash/context7-mcp@latest' + ); + } + console.info(''); + + this.showWelcomeMessage(); + await this.startInteractiveSession(); + break; + } catch (error) { + attempt++; + console.error( + `❌ Initialization Error (attempt ${attempt}/${this.config.maxRetries}):`, + (error as Error).message + ); + + if (attempt < this.config.maxRetries) { + console.log( + `⏳ Retrying in ${this.config.retryDelayMs / 1000} seconds...` + ); + await this.delay(this.config.retryDelayMs); + } else { + console.error( + `❌ Failed to initialize after ${this.config.maxRetries} attempts` + ); + console.log( + '\nπŸ’‘ Make sure the Context7 MCP server is running on port 3002' + ); + console.log(' and the Inference Gateway is running on port 8080'); + process.exit(1); + } + } + } + } + + private showWelcomeMessage(): void { + console.log('☸️ Welcome to Kubernetes Operations Agent!'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log( + '\nπŸ’‘ I specialize in Kubernetes cluster operations and container orchestration.' + ); + console.log( + " Just describe what you want to deploy or manage, and I'll use Context7" + ); + console.log( + ' to get up-to-date K8s documentation and create production-ready solutions.' + ); + console.log('\nπŸ“ Example requests:'); + console.log( + ' β€’ "Deploy a scalable web application with load balancing and auto-scaling"' + ); + console.log( + ' β€’ "Create a microservices architecture with service mesh and monitoring"' + ); + console.log( + ' β€’ "Set up a CI/CD pipeline with GitOps and automated deployments"' + ); + console.log( + ' β€’ "Configure RBAC and network policies for multi-tenant cluster"' + ); + console.log( + ' β€’ "Deploy a database cluster with persistent storage and backups"' + ); + console.log('\n⚑ Commands:'); + console.log(' β€’ Type your request to start building'); + console.log(' β€’ Use "clear" to reset conversation history'); + console.log(' β€’ Use "exit" or "quit" to end the session'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + } + + private async startInteractiveSession(): Promise { + while (true) { + const userInput = await this.getUserInput( + '☸️ What would you like to deploy or manage in Kubernetes? ' + ); + + if (this.handleSpecialCommands(userInput)) { + continue; + } + + if (userInput.trim()) { + await this.processUserRequestWithRetry(userInput); + } + } + } + + private async getUserInput(prompt: string): Promise { + return new Promise((resolve) => { + this.rl.question(prompt, (answer) => { + resolve(answer); + }); + }); + } + + private handleSpecialCommands(input: string): boolean { + const command = input.trim().toLowerCase(); + + switch (command) { + case 'exit': + case 'quit': + console.log('\nπŸ‘‹ Thank you for using Kubernetes Agent! Goodbye!'); + + if (this.config.memoryEnabled) { + console.log('πŸ’Ύ Saving session state before exit...'); + this.saveStateToMemoryForced('Manual exit via user command') + .then(() => { + console.log('βœ… Session state saved successfully'); + this.rl.close(); + process.exit(0); + }) + .catch((error: Error) => { + console.warn('⚠️ Failed to save session state:', error.message); + this.rl.close(); + process.exit(0); + }); + } else { + this.rl.close(); + process.exit(0); + } + return true; + + case 'clear': + this.config.conversationHistory = [ + { + role: MessageRole.system, + content: this.getSystemPrompt(), + }, + ]; + console.log('\n🧹 Conversation history cleared. Starting fresh!\n'); + return true; + + case 'help': + this.showWelcomeMessage(); + return true; + + default: + return false; + } + } + + private async processUserRequestWithRetry(userInput: string): Promise { + let attempt = 0; + + while (attempt < this.config.maxRetries) { + try { + await this.processUserRequest(userInput); + break; + } catch (error) { + attempt++; + console.error( + `❌ Request failed (attempt ${attempt}/${this.config.maxRetries}):`, + (error as Error).message + ); + + if (attempt < this.config.maxRetries) { + console.log( + `⏳ Retrying in ${this.config.retryDelayMs / 1000} seconds...` + ); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + await this.delay(this.config.retryDelayMs); + } else { + console.error( + `❌ Failed to process request after ${this.config.maxRetries} attempts` + ); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + } + } + } + } + + private async processUserRequest(userInput: string): Promise { + console.log(`\nπŸ” Processing Kubernetes request: "${userInput}"`); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + this.config.iterationCount++; + const iterationStartTime = Date.now(); + + console.log(`πŸ”„ Starting Iteration #${this.config.iterationCount}`); + console.log( + `πŸ“ User Input: "${userInput.substring(0, 100)}${userInput.length > 100 ? '...' : ''}"` + ); + console.log(`⏰ Start Time: ${new Date().toLocaleTimeString()}`); + console.log('─'.repeat(60)); + + this.config.conversationHistory.push({ + role: MessageRole.user, + content: userInput, + }); + + if (this.config.memoryEnabled) { + await this.saveStateToMemory( + `Before processing request: "${userInput.substring(0, 50)}..."` + ); + } + + this.resetAbortController(); + + let assistantResponse = ''; + let shouldWaitForOperation = false; + + console.log( + `πŸ”§ Debug - Using provider: ${this.config.provider}, model: ${this.config.model}` + ); + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: this.getOptimizedConversationHistory(), + max_tokens: this.config.maxTokensPerRequest, + }, + { + onOpen: () => { + console.log( + '\nπŸ”— Starting Kubernetes operations session with Context7...\n' + ); + }, + onReasoning: (reasoning) => { + process.stdout.write(reasoning); + }, + onContent: (content) => { + process.stdout.write(content); + assistantResponse += content; + }, + onUsageMetrics: (usage) => { + const iterationDuration = Date.now() - iterationStartTime; + this.config.totalTokensUsed += usage.total_tokens; + + console.log( + `\n\nπŸ’° Iteration #${this.config.iterationCount} Token Usage:` + ); + console.log( + ` πŸ“Š Prompt tokens: ${usage.prompt_tokens.toLocaleString()}` + ); + console.log( + ` ✍️ Completion tokens: ${usage.completion_tokens.toLocaleString()}` + ); + console.log( + ` 🎯 Total tokens: ${usage.total_tokens.toLocaleString()}` + ); + console.log(` ⏱️ Duration: ${iterationDuration}ms`); + console.log( + ` πŸš€ Tokens/sec: ${Math.round((usage.total_tokens / iterationDuration) * 1000)}` + ); + + console.log(`\nπŸ“ˆ Cumulative Session Usage:`); + console.log(` πŸ”’ Total Iterations: ${this.config.iterationCount}`); + console.log( + ` 🎯 Total Tokens Used: ${this.config.totalTokensUsed.toLocaleString()}` + ); + console.log( + ` πŸ“ˆ Average Tokens per Iteration: ${Math.round(this.config.totalTokensUsed / this.config.iterationCount).toLocaleString()}` + ); + + const estimatedCost = this.config.totalTokensUsed * 0.000001; + console.log( + ` πŸ’° Estimated Total Cost: $${estimatedCost.toFixed(6)}` + ); + console.log('─'.repeat(60)); + }, + onMCPTool: (toolCall: any) => { + console.log(`\nπŸ› οΈ Context7 Tool: ${toolCall.function.name}`); + try { + const args = JSON.parse(toolCall.function.arguments); + console.log(`πŸ“ Arguments:`, JSON.stringify(args, null, 2)); + } catch { + console.log(`πŸ“ Raw Arguments: ${toolCall.function.arguments}`); + } + console.log(`πŸ” Tool ID: ${toolCall.id}\n`); + + // Store the tool call details for error recovery if needed + this.config.lastFailedToolCall = { + name: toolCall.function.name, + id: toolCall.id, + arguments: toolCall.function.arguments, + timestamp: new Date().toISOString(), + }; + + if ( + toolCall.function.name.toLowerCase().includes('kubernetes') || + toolCall.function.name.toLowerCase().includes('k8s') || + toolCall.function.name.toLowerCase().includes('kubectl') || + toolCall.function.name.toLowerCase().includes('deploy') || + toolCall.function.name.toLowerCase().includes('create') || + toolCall.function.name.toLowerCase().includes('apply') || + toolCall.function.name.toLowerCase().includes('helm') || + toolCall.function.name === 'write_file' || + toolCall.function.name === 'create_directory' + ) { + console.log( + '☸️ Kubernetes operation detected - will wait 10 seconds after completion' + ); + shouldWaitForOperation = true; + } + }, + onError: async (error) => { + // Check if this is a "terminated" or stream failure error + const errorMsg = error.error || ''; + if ( + errorMsg.includes('terminated') || + errorMsg.includes('stream') || + errorMsg.includes('context canceled') || + errorMsg.includes('upstream provider error') + ) { + // This will be handled by the retry logic in the main loop + console.error(`\n❌ Stream Error (retryable): ${errorMsg}`); + throw new Error(`Retryable stream error: ${errorMsg}`); + } + + console.error(`\n❌ Stream Error: ${errorMsg}`); + + // Record the error with detailed context + await this.recordError( + 'stream_error', + errorMsg || 'Unknown stream error', + `Stream error during iteration ${this.config.iterationCount}. Last tool call: ${this.config.lastFailedToolCall?.name || 'none'}`, + this.config.lastFailedToolCall, + userInput + ); + + throw new Error( + `Stream error: ${errorMsg || 'Unknown stream error'}` + ); + }, + onFinish: async () => { + console.log('\n\nβœ… Kubernetes operations session completed!\n'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + if (shouldWaitForOperation) { + await this.waitForKubernetesOperation(); + } + + // Add assistant response to conversation history + if (assistantResponse.trim()) { + this.config.conversationHistory.push({ + role: MessageRole.assistant, + content: assistantResponse, + }); + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + } + + async shutdown(): Promise { + if (this.config.memoryEnabled) { + console.log('πŸ’Ύ Saving session state before shutdown...'); + try { + const shutdownTimeout = setTimeout(() => { + console.warn('⚠️ Shutdown timeout reached, forcing exit...'); + process.exit(1); + }, 10000); + + await this.saveStateToMemoryForced( + 'Manual shutdown via SIGINT/SIGTERM signal' + ); + + clearTimeout(shutdownTimeout); + console.log('βœ… Session state saved successfully'); + } catch (error) { + console.warn( + '⚠️ Failed to save session state:', + (error as Error).message + ); + } + } + + this.rl.close(); + } + + abortOperations(): void { + if (!this.config.abortController.signal.aborted) { + this.config.abortController.abort('Shutdown signal received'); + } + } + + /** + * Save current state to memory MCP server + */ + private async saveStateToMemory(context: string): Promise { + if (!this.config.memoryEnabled) return; + + try { + const state = { + conversationHistory: this.config.conversationHistory.slice(-5), + iterationCount: this.config.iterationCount, + totalTokensUsed: this.config.totalTokensUsed, + timestamp: new Date().toISOString(), + }; + + console.log( + `πŸ’Ύ Saving state to memory for session: ${this.config.sessionId}` + ); + + let toolCallDetected = false; + let saveSuccessful = false; + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: [ + { + role: MessageRole.system, + content: `You are a memory manager. You MUST call the save-state tool now with the provided data. Don't explain - just call the tool immediately. + +SessionID: ${this.config.sessionId} +State: ${JSON.stringify(state)} +Context: ${context} + +Call save-state tool immediately with sessionId="${this.config.sessionId}" and the state object above.`, + }, + { + role: MessageRole.user, + content: `Call save-state tool now with sessionId="${this.config.sessionId}"`, + }, + ], + max_tokens: this.config.maxTokensPerRequest, + }, + { + onMCPTool: (toolCall) => { + toolCallDetected = true; + console.log(`πŸ“± Memory tool called: ${toolCall.function.name}`); + + if ( + toolCall.function.name === 'save-state' || + toolCall.function.name === 'save-error-state' + ) { + saveSuccessful = true; + console.log('βœ… State save tool invoked successfully'); + } + }, + onReasoning: (reasoning) => { + process.stdout.write(reasoning); + }, + onContent: (content) => { + process.stdout.write(content); + }, + onError: (error) => { + console.warn('⚠️ Memory save failed:', error.error); + }, + onFinish: () => { + if (toolCallDetected && saveSuccessful) { + console.log('βœ… Memory save completed successfully'); + } else if (!toolCallDetected) { + console.warn( + '⚠️ No memory tool was called - memory may not be available' + ); + } else { + console.warn('⚠️ Memory tool called but save may have failed'); + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + } catch (error) { + console.warn( + '⚠️ Failed to save state to memory:', + (error as Error).message + ); + } + } + + /** + * Save current state to memory MCP server with forced tool usage + */ + private async saveStateToMemoryForced(context: string): Promise { + if (!this.config.memoryEnabled) return; + + try { + const state = { + conversationHistory: this.config.conversationHistory.slice(-5), + iterationCount: this.config.iterationCount, + totalTokensUsed: this.config.totalTokensUsed, + timestamp: new Date().toISOString(), + }; + + console.log( + `πŸ’Ύ Forcing memory save for session: ${this.config.sessionId}` + ); + + let toolCallDetected = false; + let saveSuccessful = false; + const maxAttempts = 3; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + if (this.config.abortController.signal.aborted) { + console.warn('⚠️ Memory save aborted during shutdown'); + throw new Error('Memory save aborted'); + } + + console.log(`πŸ”„ Memory save attempt ${attempt}/${maxAttempts}`); + + try { + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: [ + { + role: MessageRole.system, + content: `You are a memory manager. You MUST call the save-state tool immediately. No explanations, no acknowledgments - just call the tool. + +CRITICAL: You MUST call save-state tool with these exact parameters: +- sessionId: "${this.config.sessionId}" +- state: ${JSON.stringify(state)} +- context: "${context}" + +Call the save-state tool now.`, + }, + { + role: MessageRole.user, + content: `Call save-state tool immediately with sessionId="${this.config.sessionId}". Do not respond with text - only call the tool.`, + }, + ], + max_tokens: this.config.maxTokensPerRequest, + }, + { + onMCPTool: (toolCall) => { + toolCallDetected = true; + console.log(`πŸ“± Memory tool called: ${toolCall.function.name}`); + + if ( + toolCall.function.name === 'save-state' || + toolCall.function.name === 'save-error-state' + ) { + saveSuccessful = true; + console.log('βœ… Memory tool invoked successfully'); + try { + const args = JSON.parse(toolCall.function.arguments); + console.log( + `πŸ“ Tool arguments:`, + JSON.stringify(args, null, 2) + ); + } catch { + console.error( + `πŸ“ Raw tool arguments: ${toolCall.function.arguments}` + ); + } + } + }, + onContent: (content) => { + process.stdout.write(content); + }, + onError: (error) => { + console.warn( + `⚠️ Memory save attempt ${attempt} failed:`, + error.error + ); + }, + onFinish: () => { + if (toolCallDetected && saveSuccessful) { + console.log( + `βœ… Memory save completed successfully on attempt ${attempt}` + ); + } else if (!toolCallDetected) { + console.warn( + `⚠️ Attempt ${attempt}: No memory tool was called` + ); + } else { + console.warn( + `⚠️ Attempt ${attempt}: Memory tool called but save may have failed` + ); + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + + if (toolCallDetected && saveSuccessful) { + break; + } + + if (attempt < maxAttempts) { + if (this.config.abortController.signal.aborted) { + console.warn('⚠️ Memory save aborted during retry wait'); + throw new Error('Memory save aborted'); + } + console.log(`⏳ Waiting 2 seconds before retry...`); + await this.delay(2000); + } + } catch (attemptError) { + console.warn( + `⚠️ Memory save attempt ${attempt} error:`, + (attemptError as Error).message + ); + if (attempt === maxAttempts) { + throw attemptError; + } + } + } + + if (!toolCallDetected || !saveSuccessful) { + console.error( + `❌ Failed to save memory after ${maxAttempts} attempts - memory tools may not be available` + ); + } + } catch (error) { + console.warn( + '⚠️ Failed to save state to memory:', + (error as Error).message + ); + } + } + + /** + * Combined fast memory state and error history loading + */ + private async quickLoadStateAndErrorHistory(): Promise { + if (!this.config.memoryEnabled) return; + + try { + console.log( + `πŸ“₯ Attempting to restore state for session: ${this.config.sessionId}` + ); + + // Single LLM call to check both state and errors + let restoredData: any = null; + let errorData: any = null; + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: [ + { + role: MessageRole.system, + content: `You have access to memory management tools. Quickly restore the saved state for session "${this.config.sessionId}" and check for any previous errors. Use the restore-state tool once.`, + }, + { + role: MessageRole.user, + content: `Restore session state and error history for "${this.config.sessionId}" using restore-state tool.`, + }, + ], + max_tokens: 2000, // Reduced token limit for faster response + }, + { + onContent: (content) => { + if (content.includes('{') && content.includes('}')) { + try { + const jsonMatch = content.match(/\{[\s\S]*\}/); + if (jsonMatch) { + const data = JSON.parse(jsonMatch[0]); + if (data.state) { + restoredData = data; + } + if (data.errorHistory || data.lastError) { + errorData = data; + } + } + } catch { + // Ignore parsing errors for quick startup + } + } + }, + onMCPTool: (toolCall) => { + console.log(`πŸ“± Memory tool called: ${toolCall.function.name}`); + }, + onError: () => { + console.log('ℹ️ No previous state found'); + }, + onFinish: () => { + // Restore state if found + if (restoredData && restoredData.state) { + this.config.conversationHistory = + restoredData.state.conversationHistory || []; + this.config.iterationCount = + restoredData.state.iterationCount || 0; + this.config.totalTokensUsed = + restoredData.state.totalTokensUsed || 0; + + console.log(`βœ… Restored state from ${restoredData.timestamp}`); + console.log( + `πŸ“Š Restored ${this.config.conversationHistory.length} messages` + ); + console.log( + `πŸ”’ Restored iteration count: ${this.config.iterationCount}` + ); + } + + // Restore error history if found + if (errorData && errorData.errorHistory) { + this.config.errorHistory = errorData.errorHistory || []; + console.log( + `βœ… Restored ${this.config.errorHistory.length} previous error records` + ); + + if (this.config.errorHistory.length > 0) { + const lastError = + this.config.errorHistory[this.config.errorHistory.length - 1]; + console.log( + `πŸ” Last error was: ${lastError.errorType} - ${lastError.errorMessage}` + ); + } + } + + if (!restoredData && !errorData) { + console.log('ℹ️ No previous state found'); + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + } catch (error) { + console.log(`ℹ️ No previous state found: ${(error as Error).message}`); + } + } + + /** + * Truncate conversation history to stay within token limits + */ + private truncateConversationHistory(): void { + if ( + this.config.conversationHistory.length <= + this.config.maxHistoryLength + 1 + ) { + return; + } + + console.log( + `βœ‚οΈ Truncating conversation history from ${this.config.conversationHistory.length} to ${this.config.maxHistoryLength + 1} messages` + ); + + const systemPrompt = this.config.conversationHistory[0]; + + const recentMessages = this.config.conversationHistory.slice( + -this.config.maxHistoryLength + ); + + const truncatedMessages = this.config.conversationHistory.slice( + 1, + -this.config.maxHistoryLength + ); + + if (truncatedMessages.length > 0) { + this.saveStateToMemoryForced( + `Truncated ${truncatedMessages.length} older messages` + ).catch((error) => { + console.warn( + '⚠️ Failed to save truncated messages to memory:', + (error as Error).message + ); + }); + } + + this.config.conversationHistory = [systemPrompt, ...recentMessages]; + } + + /** + * Estimate token count for a message (rough approximation) + */ + private estimateTokenCount(text: string): number { + // Rough approximation: 1 token β‰ˆ 4 characters + return Math.ceil(text.length / 4); + } + + /** + * Get optimized conversation history for the current request + */ + private getOptimizedConversationHistory(): Array<{ + role: MessageRole; + content: string; + }> { + this.truncateConversationHistory(); + + const totalEstimatedTokens = this.config.conversationHistory.reduce( + (sum, msg) => sum + this.estimateTokenCount(msg.content), + 0 + ); + + console.log(`πŸ“Š Estimated tokens in conversation: ${totalEstimatedTokens}`); + + return this.config.conversationHistory; + } + + private resetAbortController(): void { + if (!this.config.abortController.signal.aborted) { + this.config.abortController.abort('Starting new request'); + } + this.config.abortController = new globalThis.AbortController(); + } +} + +async function runKubernetesAgent(): Promise { + const agent = new KubernetesAgent(); + + process.on('SIGINT', async () => { + console.log('\n\nπŸ‘‹ Shutting down Kubernetes Agent...'); + agent.abortOperations(); + await agent.shutdown(); + process.exit(0); + }); + + process.on('SIGTERM', async () => { + console.log('\n\nπŸ‘‹ Shutting down Kubernetes Agent...'); + agent.abortOperations(); + await agent.shutdown(); + process.exit(0); + }); + + await agent.initialize(); +} + +if (require.main === module || process.argv[1].endsWith('index.ts')) { + runKubernetesAgent().catch(console.error); +} + +export { KubernetesAgent, runKubernetesAgent }; diff --git a/examples/mcp/package-lock.json b/examples/mcp/agents/kubernetes/package-lock.json similarity index 60% rename from examples/mcp/package-lock.json rename to examples/mcp/agents/kubernetes/package-lock.json index 436dc9e..8226e08 100644 --- a/examples/mcp/package-lock.json +++ b/examples/mcp/agents/kubernetes/package-lock.json @@ -9,7 +9,9 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "@inference-gateway/sdk": "^0.7.1" + "@inference-gateway/sdk": "^0.7.3", + "axios": "^1.9.0", + "dotenv": "^16.5.0" }, "devDependencies": { "tsx": "^4.19.4" @@ -441,9 +443,9 @@ } }, "node_modules/@inference-gateway/sdk": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@inference-gateway/sdk/-/sdk-0.7.1.tgz", - "integrity": "sha512-O6wHlmB5XmQApASaw6yhTaRHMFkSzLUl9DNGb2RYN3/0wK5Bdlymed8HCl69dbATfkEh3eXU9SiZ8FG/pww7Lg==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@inference-gateway/sdk/-/sdk-0.7.3.tgz", + "integrity": "sha512-UCbapsrblks9A0armTyXIdBM1NHy+8blZNkSP4AeTlHqH8CpVHH2gcgNRyn2SgqewF61kPXkijj0fh1XLZM7OA==", "license": "MIT", "engines": { "node": ">=22.12.0", @@ -458,6 +460,128 @@ } } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -499,6 +623,41 @@ "@esbuild/win32-x64": "0.25.5" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -514,6 +673,52 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-tsconfig": { "version": "4.10.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", @@ -527,6 +732,93 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", diff --git a/examples/mcp/agents/kubernetes/package.json b/examples/mcp/agents/kubernetes/package.json new file mode 100644 index 0000000..cd8dcdf --- /dev/null +++ b/examples/mcp/agents/kubernetes/package.json @@ -0,0 +1,21 @@ +{ + "name": "mcp", + "version": "1.0.0", + "description": "This example demonstrates how to use the Inference Gateway SDK with Model Context Protocol (MCP) tools in a multi-provider architecture.", + "main": "index.js", + "private": true, + "scripts": { + "start": "tsx index.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@inference-gateway/sdk": "^0.7.3", + "axios": "^1.9.0", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "tsx": "^4.19.4" + } +} diff --git a/examples/mcp/agents/marketing/.env.example b/examples/mcp/agents/marketing/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/examples/mcp/agents/marketing/Dockerfile b/examples/mcp/agents/marketing/Dockerfile new file mode 100644 index 0000000..4034602 --- /dev/null +++ b/examples/mcp/agents/marketing/Dockerfile @@ -0,0 +1,10 @@ +FROM node:20-alpine + +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +EXPOSE 3005 +ENV HOST=0.0.0.0 + +CMD ["npm", "start"] diff --git a/examples/mcp/agents/marketing/README.md b/examples/mcp/agents/marketing/README.md new file mode 100644 index 0000000..dc23972 --- /dev/null +++ b/examples/mcp/agents/marketing/README.md @@ -0,0 +1,191 @@ +# Marketing Agent with Brave Search + +This example demonstrates how to use the Marketing Intelligence Agent with Brave Search capabilities for comprehensive market research and competitive analysis. + +## Setup + +1. **Get a Brave Search API Key** + + - Visit [Brave Search API](https://api.search.brave.com/) + - Sign up and get your API key + - Add it to your `.env` file: + + ```bash + BRAVE_API_KEY=your_brave_api_key_here + ``` + +2. **Set up environment variables** + + ```bash + # Copy the example environment file + cp .env.example .env + + # Edit .env and add your API keys: + BRAVE_API_KEY=your_brave_api_key_here + OPENAI_API_KEY=your_openai_key_here # Optional + GROQ_API_KEY=your_groq_key_here # Optional + ANTHROPIC_API_KEY=your_anthropic_key_here # Optional + ``` + +3. **Start the services** + + ```bash + docker-compose up --build + ``` + +4. **Run the marketing agent** + ```bash + # In a new terminal + cd agents/marketing + npm install + npm start + ``` + +## Features + +### πŸ” Brave Search Integration + +- **Web Search**: Comprehensive market research using `brave_web_search` +- **News Search**: Real-time news monitoring with `brave_news_search` +- **Marketing Research**: Automated competitive analysis with `marketing_research` + +### πŸ“Š Marketing Capabilities + +- Market trend analysis +- Competitive intelligence +- Brand monitoring +- Content research +- Sentiment tracking +- Strategic recommendations + +### 🧠 Advanced Features + +- Memory persistence for research sessions +- Report generation and file saving +- Context7 integration for best practices +- Error recovery and retry mechanisms + +## Example Queries + +Here are some example queries you can try with the marketing agent: + +### Market Research + +``` +Research the electric vehicle market trends for 2024 +``` + +### Competitive Analysis + +``` +Compare Tesla vs BMW in the luxury EV segment +``` + +### Brand Monitoring + +``` +Find recent news and sentiment about Apple's latest iPhone launch +``` + +### Content Strategy + +``` +What are the top marketing trends for SaaS companies in 2024? +``` + +### Industry Analysis + +``` +Research the AI startup ecosystem and recent funding rounds +``` + +## Tool Examples + +### Using brave_web_search + +```json +{ + "query": "artificial intelligence marketing trends 2024", + "count": 15, + "country": "US", + "safesearch": "moderate", + "freshness": "pw" +} +``` + +### Using brave_news_search + +```json +{ + "query": "tech startup funding", + "count": 10, + "country": "US", + "freshness": "pd" +} +``` + +### Using marketing_research + +```json +{ + "brand": "Tesla", + "competitors": ["BMW", "Mercedes", "Audi"], + "topics": ["reviews", "pricing", "features", "sustainability"], + "country": "US" +} +``` + +## Expected Workflow + +1. **Initialize**: The agent starts and checks available tools +2. **Research**: User provides a marketing research objective +3. **Analysis**: Agent uses Brave Search to gather comprehensive data +4. **Insights**: Agent provides structured analysis and recommendations +5. **Persistence**: Results are saved to memory and optionally to files + +## Troubleshooting + +### Brave Search API Issues + +- Ensure your API key is valid and not expired +- Check API rate limits if getting 429 errors +- Verify the BRAVE_API_KEY environment variable is set + +### Docker Issues + +```bash +# Rebuild if there are issues +docker-compose down +docker-compose up --build + +# Check logs +docker-compose logs mcp-brave-search +``` + +### Agent Connection Issues + +```bash +# Verify services are running +docker-compose ps + +# Check gateway health +curl http://localhost:8080/health +``` + +## API Documentation + +The marketing agent uses the official `mcp/brave-search` Docker image which provides: + +- **brave_web_search**: Web search with filtering options +- **brave_news_search**: News search with recency filters +- **marketing_research**: Automated competitive research + +For detailed API documentation, see the [Brave Search API docs](https://api.search.brave.com/app/documentation/web-search/get-started). + +## Best Practices + +1. **Specific Queries**: Use targeted search terms for better results +2. **Country Filtering**: Specify regions for localized insights +3. **Freshness Filters**: Use recent data for trend analysis +4. **Batch Research**: Combine multiple search types for comprehensive analysis +5. **Save Results**: Use memory tools to persist valuable research diff --git a/examples/mcp/agents/marketing/index.ts b/examples/mcp/agents/marketing/index.ts new file mode 100644 index 0000000..2e1c408 --- /dev/null +++ b/examples/mcp/agents/marketing/index.ts @@ -0,0 +1,680 @@ +/** + * Interactive Marketing Agent + * + * This agent specializes in marketing research, competitive analysis, + * brand monitoring, and market intelligence using Brave Search and other MCP tools. + */ + +import { + InferenceGatewayClient, + MessageRole, + Provider, + SchemaChatCompletionMessageToolCall, +} from '@inference-gateway/sdk'; +import { randomUUID } from 'crypto'; +import * as dotenv from 'dotenv'; +import * as path from 'path'; +import * as readline from 'readline'; + +dotenv.config({ path: path.join(__dirname, '.env') }); + +interface ErrorRecord { + timestamp: string; + iterationCount: number; + errorType: 'stream_error' | 'tool_error' | 'mcp_error' | 'memory_error'; + errorMessage: string; + context: string; + toolName?: string; + toolId?: string; + toolArguments?: any; + userInput?: string; + recoveryAttempted: boolean; +} + +interface AgentConfig { + client: InferenceGatewayClient; + provider: Provider; + model: string; + conversationHistory: Array<{ role: MessageRole; content: string }>; + maxRetries: number; + retryDelayMs: number; + iterationCount: number; + totalTokensUsed: number; + maxTokensPerRequest: number; + maxHistoryLength: number; + sessionId: string; + memoryEnabled: boolean; + abortController: globalThis.AbortController; + errorHistory: ErrorRecord[]; + lastFailedToolCall?: { + name: string; + id: string; + arguments: any; + timestamp: string; + }; +} + +class MarketingAgent { + private config: AgentConfig; + private rl: readline.Interface; + + constructor() { + console.log('πŸ”§ Debug - Environment variables:'); + console.log(' PROVIDER:', process.env.PROVIDER); + console.log(' LLM:', process.env.LLM); + + this.config = { + client: new InferenceGatewayClient({ + baseURL: 'http://inference-gateway:8080/v1', + timeout: 120000, + }), + provider: (process.env.PROVIDER as Provider) || Provider.groq, + model: process.env.LLM || 'llama-3.3-70b-versatile', + conversationHistory: [], + maxRetries: 5, + retryDelayMs: 5000, + iterationCount: 0, + totalTokensUsed: 0, + maxTokensPerRequest: 2500, + maxHistoryLength: 6, + sessionId: process.env.SESSION_ID || randomUUID(), + memoryEnabled: true, + abortController: new globalThis.AbortController(), + errorHistory: [], + lastFailedToolCall: undefined, + }; + + this.rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + this.config.conversationHistory.push({ + role: MessageRole.system, + content: this.getSystemPrompt(), + }); + } + + private async delay(ms: number): Promise { + return new Promise((resolve, reject) => { + if (this.config.abortController.signal.aborted) { + reject(new Error('Aborted')); + return; + } + + const timeout = global.setTimeout(() => resolve(), ms); + + const abortHandler = () => { + global.clearTimeout(timeout); + reject(new Error('Aborted')); + }; + + this.config.abortController.signal.addEventListener( + 'abort', + abortHandler, + { once: true } + ); + }); + } + + /** + * Extract error message from various error types + */ + private getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } + if (typeof error === 'string') { + return error; + } + if (error && typeof error === 'object') { + const errorObj = error as any; + if (errorObj.message) return String(errorObj.message); + if (errorObj.error) return String(errorObj.error); + if (errorObj.reason) return String(errorObj.reason); + return JSON.stringify(error); + } + return String(error); + } + + /** + * Record an error to both local history and memory + */ + private async recordError( + errorType: ErrorRecord['errorType'], + errorMessage: string, + context: string, + toolCall?: any, + userInput?: string + ): Promise { + const errorRecord: ErrorRecord = { + timestamp: new Date().toISOString(), + iterationCount: this.config.iterationCount, + errorType, + errorMessage, + context, + toolName: toolCall?.function?.name, + toolId: toolCall?.id, + toolArguments: toolCall?.function?.arguments + ? (() => { + try { + return JSON.parse(toolCall.function.arguments); + } catch { + return toolCall.function.arguments; + } + })() + : undefined, + userInput: userInput?.substring(0, 100), + recoveryAttempted: false, + }; + + this.config.errorHistory.push(errorRecord); + + if (this.config.errorHistory.length > 10) { + this.config.errorHistory = this.config.errorHistory.slice(-10); + } + + console.log(`πŸ“ Recording ${errorType}: ${errorMessage}`); + + if (this.config.memoryEnabled) { + try { + await this.saveErrorToMemory(errorRecord); + } catch (memoryError) { + console.log( + `⚠️ Failed to save error to memory: ${this.getErrorMessage(memoryError)}` + ); + } + } + } + + /** + * Save error state to memory with detailed context + */ + private async saveErrorToMemory(errorRecord: ErrorRecord): Promise { + if (!this.config.memoryEnabled) return; + + try { + const errorState = { + conversationHistory: this.config.conversationHistory.slice(-3), + iterationCount: this.config.iterationCount, + totalTokensUsed: this.config.totalTokensUsed, + timestamp: new Date().toISOString(), + errorHistory: this.config.errorHistory, + lastError: errorRecord, + }; + + console.log( + `🚨 Saving error state to memory for session: ${this.config.sessionId}` + ); + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: [ + { + role: MessageRole.system, + content: `You are a memory manager handling error recovery. You MUST call the save-error-state tool immediately with the error details below. + +ERROR DETAILS: +- Type: ${errorRecord.errorType} +- Message: ${errorRecord.errorMessage} +- Context: ${errorRecord.context} +- Tool: ${errorRecord.toolName || 'none'} +- Timestamp: ${errorRecord.timestamp} + +CRITICAL: Call save-error-state tool NOW with this data.`, + }, + { + role: MessageRole.user, + content: `Save error state for session ${this.config.sessionId}: ${JSON.stringify(errorState)}`, + }, + ], + max_tokens: this.config.maxTokensPerRequest, + }, + { + onReasoning: (reasoning) => { + process.stdout.write(reasoning); + }, + onContent: (content) => { + process.stdout.write(content); + }, + onError: (error) => { + console.error( + `❌ Memory save error: ${this.getErrorMessage(error)}` + ); + }, + }, + this.config.provider, + this.config.abortController.signal + ); + + console.log('πŸ’Ύ Error state saved to memory system'); + } catch (memoryError) { + console.log( + `⚠️ Failed to save error to memory: ${this.getErrorMessage(memoryError)}` + ); + } + } + + private getSystemPrompt(): string { + let errorHistoryPrompt = ''; + + if (this.config.errorHistory.length > 0) { + const recentErrors = this.config.errorHistory.slice(-2); + errorHistoryPrompt = `\nERROR CONTEXT: Previous ${recentErrors.length} errors encountered. Adapt approach accordingly.\n`; + } + + return `You are a Marketing Intelligence Agent specializing in market research, competitive analysis, brand monitoring, and marketing strategy development.${errorHistoryPrompt} + +Today is ${new Date().toLocaleDateString()}. + +IMPORTANT: NEVER use XML for tool calls. Always use JSON format. + +CORE CAPABILITIES: +- Market research and trend analysis using Brave Search +- Competitive intelligence and brand monitoring +- Content research and marketing insights +- News monitoring and sentiment tracking +- Marketing strategy recommendations + +AVAILABLE TOOLS: +- brave_web_search: Comprehensive web search for market research +- brave_news_search: Real-time news search for trend monitoring +- marketing_research: Automated competitive analysis and brand research +- Context7 tools: Latest marketing best practices and documentation +- Memory tools: Session persistence and research history +- Filesystem tools: Save research reports and analysis + +WORKFLOW GUIDELINES: +1. Always start by understanding the marketing objective or research goal +2. Use Brave Search tools for comprehensive market intelligence +3. Cross-reference findings with news search for current trends +4. Save important research to memory for future reference +5. Provide actionable insights and strategic recommendations + +SEARCH STRATEGY: +- Use specific, targeted queries for better results +- Include competitors in research for comparative analysis +- Filter by country/region when relevant for localized insights +- Use freshness filters to get the most current information +- Combine web and news search for comprehensive coverage + +Always provide structured, actionable marketing insights with clear next steps.`; + } + + async initialize(): Promise { + console.log( + `πŸ“ˆ Marketing Intelligence Agent initialized using ${this.config.model} on ${this.config.provider}\n` + ); + + let attempt = 0; + while (attempt < this.config.maxRetries) { + try { + const isHealthy = await this.config.client.healthCheck(); + if (!isHealthy) { + console.error('❌ Gateway unhealthy. Run: docker-compose up --build'); + process.exit(1); + } + + const tools = await this.config.client.listTools(); + + const braveSearchTools = tools.data.filter((tool) => + [ + 'brave_web_search', + 'brave_news_search', + 'marketing_research', + ].includes(tool.name) + ); + + const context7Tools = tools.data.filter((tool) => + [ + 'c41_resolve-library-id', + 'c41_get-library-docs', + 'search_libraries', + 'get_library_details', + 'get_documentation', + ].includes(tool.name) + ); + + const memoryTools = tools.data.filter((tool) => + ['save-state', 'restore-state', 'list-sessions'].includes(tool.name) + ); + + const filesystemTools = tools.data.filter((tool) => + ['write_file', 'read_file', 'list_directory'].includes(tool.name) + ); + + if (braveSearchTools.length === 0) { + console.error( + '⚠️ No Brave Search tools available. Make sure the Brave Search MCP server is running.' + ); + console.error( + ' Required: brave_web_search, brave_news_search, marketing_research' + ); + console.error(' Run: docker-compose up --build'); + process.exit(1); + } + + console.info(`πŸ” Found ${braveSearchTools.length} Brave Search tools:`); + braveSearchTools.forEach((tool, index) => { + console.info(` ${index + 1}. ${tool.name} - ${tool.description}`); + }); + + if (context7Tools.length > 0) { + console.info( + `πŸ“š Found ${context7Tools.length} Context7 documentation tools` + ); + } + + if (memoryTools.length > 0) { + console.info( + `🧠 Found ${memoryTools.length} memory management tools` + ); + this.config.memoryEnabled = true; + console.log('πŸ“₯ Memory persistence enabled'); + } else { + console.info( + '⚠️ No memory tools available. State persistence disabled.' + ); + this.config.memoryEnabled = false; + } + + if (filesystemTools.length > 0) { + console.info( + `πŸ“ Found ${filesystemTools.length} filesystem tools for report saving` + ); + } + + console.info(''); + this.showWelcomeMessage(); + break; + } catch (error) { + attempt++; + const errorMessage = this.getErrorMessage(error); + console.error( + `❌ Initialization attempt ${attempt}/${this.config.maxRetries} failed:`, + errorMessage + ); + + if (attempt >= this.config.maxRetries) { + console.error('🚫 Max initialization attempts reached. Exiting.'); + process.exit(1); + } + + console.log(`⏳ Retrying in ${this.config.retryDelayMs}ms...`); + await this.delay(this.config.retryDelayMs); + } + } + } + + private showWelcomeMessage(): void { + console.log(` +╔═════════════════════════════════════════════════════════════════╗ +β•‘ πŸ“ˆ Marketing Intelligence Agent β•‘ +β•‘ β•‘ +β•‘ 🎯 Market Research & Competitive Analysis β•‘ +β•‘ πŸ“Š Brand Monitoring & Trend Analysis β•‘ +β•‘ πŸ“° News Monitoring & Sentiment Tracking β•‘ +β•‘ πŸ’‘ Marketing Strategy & Insights β•‘ +β•‘ β•‘ +β•‘ Commands: β•‘ +β•‘ - Research [brand/topic] - Comprehensive market research β•‘ +β•‘ - Analyze [brand] vs [competitor] - Competitive analysis β•‘ +β•‘ - Monitor [brand] news - News and trend monitoring β•‘ +β•‘ - Trends [industry] - Industry trend analysis β•‘ +β•‘ - Help - Show available commands and examples β•‘ +β•‘ - Exit/Quit - Quit the agent β•‘ +β•‘ β•‘ +β•‘ πŸ” Powered by Brave Search API for comprehensive market intel β•‘ +β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• + +Examples: +β€’ "Research Tesla's market position and customer sentiment" +β€’ "Analyze Apple vs Samsung smartphone market share" +β€’ "Monitor OpenAI news and competitor responses" +β€’ "Trends in sustainable fashion industry" + +Ready for your marketing research request! πŸš€ +`); + } + + private trimConversationHistory(): void { + if (this.config.conversationHistory.length > this.config.maxHistoryLength) { + const systemMessage = this.config.conversationHistory[0]; + const recentMessages = this.config.conversationHistory.slice( + -(this.config.maxHistoryLength - 1) + ); + this.config.conversationHistory = [systemMessage, ...recentMessages]; + console.log( + 'πŸ”„ Trimmed conversation history to prevent context overflow' + ); + } + } + + async processUserInput(userInput: string): Promise { + if (this.config.abortController.signal.aborted) { + console.log('πŸ›‘ Operation aborted by user'); + return; + } + + this.config.iterationCount++; + console.log(`\n--- Iteration ${this.config.iterationCount} ---`); + + // Handle special commands + const normalizedInput = userInput.toLowerCase().trim(); + if (normalizedInput === 'exit' || normalizedInput === 'quit') { + this.shutdown(); + return; + } + + if (normalizedInput === 'help') { + this.showHelpMessage(); + return; + } + + this.config.conversationHistory.push({ + role: MessageRole.user, + content: userInput, + }); + + this.trimConversationHistory(); + + let attempt = 0; + while (attempt < this.config.maxRetries) { + try { + let response = ''; + let toolCallResults: any[] = []; + let currentToolCall: any = null; + let assistantResponse = ''; + + console.log('πŸ€” Analyzing your marketing research request...\n'); + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: this.config.conversationHistory, + max_tokens: this.config.maxTokensPerRequest, + }, + { + onContent(content) { + process.stdout.write(content); + response += content; + }, + onMCPTool: async ( + toolCall: SchemaChatCompletionMessageToolCall + ) => { + console.log(`\nπŸ› οΈ Marketing Tool: ${toolCall.function.name}`); + try { + const args = JSON.parse(toolCall.function.arguments); + console.log(`πŸ“ Arguments:`, JSON.stringify(args, null, 2)); + } catch { + console.log(`πŸ“ Raw Arguments: ${toolCall.function.arguments}`); + } + console.log(`πŸ” Tool ID: ${toolCall.id}\n`); + }, + onFinish: async () => { + console.log('\n\nβœ… Development session completed!\n'); + console.log( + '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n' + ); + + if (assistantResponse.trim()) { + this.config.conversationHistory.push({ + role: MessageRole.assistant, + content: assistantResponse, + }); + } + }, + onError: async (error) => { + const errorMessage = this.getErrorMessage(error); + console.error(`❌ Stream error: ${errorMessage}`); + await this.recordError( + 'stream_error', + errorMessage, + 'Streaming chat completion failed', + currentToolCall, + userInput + ); + throw error; + }, + }, + this.config.provider, + this.config.abortController.signal + ); + + // Add assistant response to history + if (response.trim()) { + this.config.conversationHistory.push({ + role: MessageRole.assistant, + content: response, + }); + } + + // Add tool results to history + if (toolCallResults.length > 0) { + this.config.conversationHistory.push(...toolCallResults); + } + + console.log('\n'); + break; + } catch (error) { + attempt++; + const errorMessage = this.getErrorMessage(error); + console.error( + `❌ Attempt ${attempt}/${this.config.maxRetries} failed:`, + errorMessage + ); + + await this.recordError( + 'stream_error', + errorMessage, + `Processing attempt ${attempt} failed`, + undefined, + userInput + ); + + if (attempt >= this.config.maxRetries) { + console.error( + '🚫 Max retries reached. Please try a different approach.' + ); + return; + } + + console.log(`⏳ Retrying in ${this.config.retryDelayMs}ms...`); + await this.delay(this.config.retryDelayMs); + } + } + } + + private showHelpMessage(): void { + console.log(` +╔══════════════════════════════════════════════════════════════════════════════╗ +β•‘ πŸ“ˆ Marketing Intelligence Agent Help β•‘ +╠══════════════════════════════════════════════════════════════════════════════╣ +β•‘ β•‘ +β•‘ 🎯 MARKET RESEARCH COMMANDS: β•‘ +β•‘ β€’ Research [brand/company] - Comprehensive market analysis β•‘ +β•‘ β€’ Analyze [brand] vs [competitor] - Competitive comparison β•‘ +β•‘ β€’ Monitor [brand] news - Real-time news and sentiment tracking β•‘ +β•‘ β€’ Trends [industry/topic] - Industry trend analysis β•‘ +β•‘ β•‘ +β•‘ πŸ“Š EXAMPLE QUERIES: β•‘ +β•‘ β€’ "Research Tesla's market position in electric vehicles" β•‘ +β•‘ β€’ "Analyze Netflix vs Disney+ streaming market share" β•‘ +β•‘ β€’ "Monitor Apple news and competitor responses this week" β•‘ +β•‘ β€’ "Trends in artificial intelligence and machine learning" β•‘ +β•‘ β€’ "Compare pricing strategies of Spotify vs Apple Music" β•‘ +β•‘ β€’ "Research customer sentiment for Samsung Galaxy phones" β•‘ +β•‘ β•‘ +β•‘ πŸ” ADVANCED RESEARCH: β•‘ +β•‘ β€’ "Multi-competitor analysis of ride-sharing market" β•‘ +β•‘ β€’ "Brand perception analysis for sustainable fashion brands" β•‘ +β•‘ β€’ "Market entry strategy research for fintech in Europe" β•‘ +β•‘ β€’ "Social media sentiment tracking for crypto exchanges" β•‘ +β•‘ β•‘ +β•‘ πŸ› οΈ SYSTEM COMMANDS: β•‘ +β•‘ β€’ Help - Show this help message β•‘ +β•‘ β€’ Exit/Quit - Close the agent β•‘ +β•‘ β•‘ +β•‘ πŸ’‘ TIPS: β•‘ +β•‘ - Be specific about your research goals β•‘ +β•‘ - Mention target markets or regions for localized insights β•‘ +β•‘ - Include timeframes (e.g., "this month", "Q4 2024") β•‘ +β•‘ - Ask for actionable recommendations β•‘ +β•‘ β•‘ +β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β• +`); + } + + async run(): Promise { + await this.initialize(); + + while (true) { + try { + const userInput = await new Promise((resolve) => { + this.rl.question('πŸ’¬ Your marketing research request: ', resolve); + }); + + if (userInput.trim()) { + await this.processUserInput(userInput.trim()); + } + } catch (error) { + if (this.config.abortController.signal.aborted) { + break; + } + console.error('❌ Error in main loop:', this.getErrorMessage(error)); + } + } + } + + shutdown(): void { + console.log('\nπŸ‘‹ Thanks for using Marketing Intelligence Agent!'); + console.log( + `πŸ“Š Session stats: ${this.config.iterationCount} interactions, ${this.config.totalTokensUsed} tokens used` + ); + + this.config.abortController.abort(); + this.rl.close(); + process.exit(0); + } +} + +// Create and start the agent +const agent = new MarketingAgent(); + +// Handle graceful shutdown +process.on('SIGINT', () => { + console.log('\nπŸ›‘ Received interrupt signal...'); + agent.shutdown(); +}); + +process.on('SIGTERM', () => { + console.log('\nπŸ›‘ Received terminate signal...'); + agent.shutdown(); +}); + +// Start the agent +agent.run().catch((error) => { + console.error('πŸ’₯ Fatal error:', error); + process.exit(1); +}); diff --git a/examples/mcp/agents/marketing/package-lock.json b/examples/mcp/agents/marketing/package-lock.json new file mode 100644 index 0000000..e954cc5 --- /dev/null +++ b/examples/mcp/agents/marketing/package-lock.json @@ -0,0 +1,886 @@ +{ + "name": "marketing-agent", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "marketing-agent", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@inference-gateway/sdk": "^0.7.3", + "axios": "^1.9.0", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.19.4", + "typescript": "^5.3.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@inference-gateway/sdk": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@inference-gateway/sdk/-/sdk-0.7.3.tgz", + "integrity": "sha512-UCbapsrblks9A0armTyXIdBM1NHy+8blZNkSP4AeTlHqH8CpVHH2gcgNRyn2SgqewF61kPXkijj0fh1XLZM7OA==", + "license": "MIT", + "engines": { + "node": ">=22.12.0", + "npm": ">=10.9.0" + }, + "peerDependencies": { + "node-fetch": "^2.7.0" + }, + "peerDependenciesMeta": { + "node-fetch": { + "optional": true + } + } + }, + "node_modules/@types/node": { + "version": "20.17.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz", + "integrity": "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.19.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", + "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/examples/mcp/agents/marketing/package.json b/examples/mcp/agents/marketing/package.json new file mode 100644 index 0000000..1180b69 --- /dev/null +++ b/examples/mcp/agents/marketing/package.json @@ -0,0 +1,30 @@ +{ + "name": "marketing-agent", + "version": "1.0.0", + "description": "Marketing Intelligence Agent using Brave Search and MCP tools for comprehensive market research and competitive analysis.", + "main": "index.js", + "private": true, + "scripts": { + "start": "tsx index.ts", + "dev": "tsx watch index.ts" + }, + "keywords": [ + "marketing", + "research", + "brave-search", + "mcp", + "intelligence" + ], + "author": "Inference Gateway Team", + "license": "MIT", + "dependencies": { + "@inference-gateway/sdk": "^0.7.3", + "axios": "^1.9.0", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "@types/node": "^20.10.0", + "tsx": "^4.19.4", + "typescript": "^5.3.0" + } +} diff --git a/examples/mcp/agents/nextjs/Dockerfile b/examples/mcp/agents/nextjs/Dockerfile new file mode 100644 index 0000000..29566e3 --- /dev/null +++ b/examples/mcp/agents/nextjs/Dockerfile @@ -0,0 +1,10 @@ +FROM node:20-alpine + +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +EXPOSE 3000 +ENV HOST=0.0.0.0 + +CMD ["npm", "start"] diff --git a/examples/mcp/agents/nextjs/index.ts b/examples/mcp/agents/nextjs/index.ts new file mode 100644 index 0000000..52a7e07 --- /dev/null +++ b/examples/mcp/agents/nextjs/index.ts @@ -0,0 +1,948 @@ +/** + * Interactive NextJS Agent + * + */ + +import { + InferenceGatewayClient, + MessageRole, + Provider, +} from '@inference-gateway/sdk'; +import { randomUUID } from 'crypto'; +import * as dotenv from 'dotenv'; +import * as path from 'path'; +import * as readline from 'readline'; +import { clearTimeout, setTimeout } from 'timers'; + +dotenv.config({ path: path.join(__dirname, '.env') }); + +declare const require: any; +declare const module: any; + +interface AgentConfig { + client: InferenceGatewayClient; + provider: Provider; + model: string; + conversationHistory: Array<{ role: MessageRole; content: string }>; + maxRetries: number; + retryDelayMs: number; + iterationCount: number; + totalTokensUsed: number; + maxTokensPerRequest: number; + maxHistoryLength: number; + sessionId: string; + memoryEnabled: boolean; + abortController: globalThis.AbortController; +} + +class NextJSAgent { + private config: AgentConfig; + private rl: readline.Interface; + + constructor() { + console.log('πŸ”§ Debug - Environment variables:'); + console.log(' PROVIDER:', process.env.PROVIDER); + console.log(' LLM:', process.env.LLM); + + this.config = { + client: new InferenceGatewayClient({ + baseURL: 'http://inference-gateway:8080/v1', + timeout: 120000, + }), + provider: (process.env.PROVIDER as Provider) || Provider.groq, + model: process.env.LLM || 'llama-3.3-70b-versatile', + conversationHistory: [], + maxRetries: 5, + retryDelayMs: 5000, + iterationCount: 0, + totalTokensUsed: 0, + maxTokensPerRequest: 2500, + maxHistoryLength: 6, + sessionId: process.env.SESSION_ID || randomUUID(), + memoryEnabled: true, + abortController: new globalThis.AbortController(), + }; + + this.rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + this.config.conversationHistory.push({ + role: MessageRole.system, + content: this.getSystemPrompt(), + }); + } + + private async delay(ms: number): Promise { + return new Promise((resolve, reject) => { + if (this.config.abortController.signal.aborted) { + reject(new Error('Aborted')); + return; + } + + const timeout = global.setTimeout(() => resolve(), ms); + + const abortHandler = () => { + global.clearTimeout(timeout); + reject(new Error('Aborted')); + }; + + this.config.abortController.signal.addEventListener( + 'abort', + abortHandler, + { once: true } + ); + }); + } + + private async waitForProjectCreation(): Promise { + console.log( + '⏳ Waiting 30 seconds for Next.js project creation to complete...' + ); + await this.delay(30000); + console.log('βœ… Project creation wait period completed.\n'); + } + + private getSystemPrompt(): string { + return `You are a NextJS development assistant. Create modern applications using current technologies. + +CORE RULES: +- Work only in /tmp directory +- Use Next.js App Router (/app), never Pages Router +- Wait 30 seconds after creating Next.js projects +- If project exists, enhance it - don't recreate +- Use Context7 tools for documentation lookup +- Tools called automatically - just describe what you need + +AVAILABLE TOOLS: Context7 docs, web search, filesystem, npm commands, memory tools + +WORKFLOW: 1) Clarify requirements 2) Use Context7 for docs 3) Build in /tmp 4) Follow modern conventions`; + } + + async initialize(): Promise { + console.log( + `πŸš€ NextJS Interactive Agent initialized using ${this.config.model} on ${this.config.provider}\n` + ); + + let attempt = 0; + while (attempt < this.config.maxRetries) { + try { + const isHealthy = await this.config.client.healthCheck(); + if (!isHealthy) { + console.error('❌ Gateway unhealthy. Run: docker-compose up --build'); + process.exit(1); + } + + const tools = await this.config.client.listTools(); + const realContext7Tools = tools.data.filter((tool) => + ['c41_resolve-library-id', 'c41_get-library-docs'].includes(tool.name) + ); + const mockContext7Tools = tools.data.filter((tool) => + [ + 'search_libraries', + 'get_library_details', + 'get_documentation', + ].includes(tool.name) + ); + + const memoryTools = tools.data.filter((tool) => + ['save-state', 'restore-state', 'list-sessions'].includes(tool.name) + ); + + if (memoryTools.length > 0) { + console.info( + `🧠 Found ${memoryTools.length} memory management tools` + ); + // Skip slow memory restoration for faster startup + console.log('πŸ“₯ Skipping memory restoration for faster startup'); + } else { + console.info( + '⚠️ No memory tools available. State persistence disabled.' + ); + this.config.memoryEnabled = false; + } + + const context7Tools = [...realContext7Tools, ...mockContext7Tools]; + + if (context7Tools.length === 0) { + console.error( + '⚠️ No Context7 MCP tools available. Make sure a Context7 MCP server is running.' + ); + console.error( + ' For real Context7: npx -y @upstash/context7-mcp@latest' + ); + console.error( + ' For local mock: docker-compose up --build (already included in this project)' + ); + process.exit(1); + } + + const usingRealContext7 = realContext7Tools.length > 0; + const toolType = usingRealContext7 ? 'real Context7' : 'mock Context7'; + + console.info( + `πŸ“‹ Found ${context7Tools.length} ${toolType} tools available:` + ); + context7Tools.forEach((tool, index) => { + console.info(` ${index + 1}. ${tool.name} - ${tool.description}`); + }); + + if (!usingRealContext7) { + console.info('πŸ’‘ Using local mock Context7 server for demonstration'); + console.info( + ' To use real Context7 with latest docs, install: npx -y @upstash/context7-mcp@latest' + ); + } + console.info(''); + + this.showWelcomeMessage(); + await this.startInteractiveSession(); + break; + } catch (error) { + attempt++; + console.error( + `❌ Initialization Error (attempt ${attempt}/${this.config.maxRetries}):`, + (error as Error).message + ); + + if (attempt < this.config.maxRetries) { + console.log( + `⏳ Retrying in ${this.config.retryDelayMs / 1000} seconds...` + ); + await this.delay(this.config.retryDelayMs); + } else { + console.error( + `❌ Failed to initialize after ${this.config.maxRetries} attempts` + ); + console.log( + '\nπŸ’‘ Make sure the Context7 MCP server is running on port 3002' + ); + console.log(' and the Inference Gateway is running on port 8080'); + process.exit(1); + } + } + } + } + + private showWelcomeMessage(): void { + console.log('πŸ€– Welcome to NextJS Interactive Development Agent!'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log( + '\nπŸ’‘ I can help you create modern applications using the latest technologies.' + ); + console.log( + " Just describe what you want to build, and I'll use Context7 to get" + ); + console.log( + ' up-to-date documentation and create a complete solution for you.' + ); + console.log('\nπŸ“ Example requests:'); + console.log( + ' β€’ "Create a Next.js blog with TypeScript and Tailwind CSS"' + ); + console.log( + ' β€’ "Build a React dashboard with charts and data visualization"' + ); + console.log( + ' β€’ "Make a Node.js API with Express and MongoDB integration"' + ); + console.log( + ' β€’ "Create a Vue.js e-commerce frontend with cart functionality"' + ); + console.log('\n⚑ Commands:'); + console.log(' β€’ Type your request to start building'); + console.log(' β€’ Use "clear" to reset conversation history'); + console.log(' β€’ Use "exit" or "quit" to end the session'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + } + + private async startInteractiveSession(): Promise { + while (true) { + const userInput = await this.getUserInput( + 'πŸ”¨ What would you like to build? ' + ); + + if (this.handleSpecialCommands(userInput)) { + continue; + } + + if (userInput.trim()) { + await this.processUserRequestWithRetry(userInput); + } + } + } + + private async getUserInput(prompt: string): Promise { + return new Promise((resolve) => { + this.rl.question(prompt, (answer) => { + resolve(answer); + }); + }); + } + + private handleSpecialCommands(input: string): boolean { + const command = input.trim().toLowerCase(); + + switch (command) { + case 'exit': + case 'quit': + console.log('\nπŸ‘‹ Thank you for using NextJS Agent! Goodbye!'); + + if (this.config.memoryEnabled) { + console.log('πŸ’Ύ Saving session state before exit...'); + this.saveStateToMemoryForced('Manual exit via user command') + .then(() => { + console.log('βœ… Session state saved successfully'); + this.rl.close(); + process.exit(0); + }) + .catch((error) => { + console.warn( + '⚠️ Failed to save session state:', + (error as Error).message + ); + this.rl.close(); + process.exit(0); + }); + } else { + this.rl.close(); + process.exit(0); + } + return true; + + case 'clear': + this.config.conversationHistory = [ + { + role: MessageRole.system, + content: this.getSystemPrompt(), + }, + ]; + console.log('\n🧹 Conversation history cleared. Starting fresh!\n'); + return true; + + case 'help': + this.showWelcomeMessage(); + return true; + + default: + return false; + } + } + + private async processUserRequestWithRetry(userInput: string): Promise { + let attempt = 0; + + while (attempt < this.config.maxRetries) { + try { + await this.processUserRequest(userInput); + break; + } catch (error) { + attempt++; + console.error( + `❌ Request failed (attempt ${attempt}/${this.config.maxRetries}):`, + (error as Error).message + ); + + if (attempt < this.config.maxRetries) { + console.log( + `⏳ Retrying in ${this.config.retryDelayMs / 1000} seconds...` + ); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + await this.delay(this.config.retryDelayMs); + } else { + console.error( + `❌ Failed to process request after ${this.config.maxRetries} attempts` + ); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + } + } + } + } + + private async processUserRequest(userInput: string): Promise { + console.log(`\nπŸ” Processing request: "${userInput}"`); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + this.config.iterationCount++; + const iterationStartTime = Date.now(); + + console.log(`πŸ”„ Starting Iteration #${this.config.iterationCount}`); + console.log( + `πŸ“ User Input: "${userInput.substring(0, 100)}${userInput.length > 100 ? '...' : ''}"` + ); + console.log(`⏰ Start Time: ${new Date().toLocaleTimeString()}`); + console.log('─'.repeat(60)); + + this.config.conversationHistory.push({ + role: MessageRole.user, + content: userInput, + }); + + if (this.config.memoryEnabled) { + await this.saveStateToMemory( + `Before processing request: "${userInput.substring(0, 50)}..."` + ); + } + + this.resetAbortController(); + + let assistantResponse = ''; + let shouldWaitForProject = false; + + console.log( + `πŸ”§ Debug - Using provider: ${this.config.provider}, model: ${this.config.model}` + ); + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: this.getOptimizedConversationHistory(), + max_tokens: this.config.maxTokensPerRequest, + }, + { + onOpen: () => { + console.log( + '\nπŸ”— Starting development session with NextJS Agent...\n' + ); + }, + onReasoning: (reasoning) => { + console.log(`\nπŸ€” Agent Reasoning: ${reasoning}`); + }, + onContent: (content) => { + process.stdout.write(content); + assistantResponse += content; + }, + onUsageMetrics: (usage) => { + const iterationDuration = Date.now() - iterationStartTime; + this.config.totalTokensUsed += usage.total_tokens; + + console.log( + `\n\nπŸ’° Iteration #${this.config.iterationCount} Token Usage:` + ); + console.log( + ` πŸ“Š Prompt tokens: ${usage.prompt_tokens.toLocaleString()}` + ); + console.log( + ` ✍️ Completion tokens: ${usage.completion_tokens.toLocaleString()}` + ); + console.log( + ` 🎯 Total tokens: ${usage.total_tokens.toLocaleString()}` + ); + console.log(` ⏱️ Duration: ${iterationDuration}ms`); + console.log( + ` πŸš€ Tokens/sec: ${Math.round((usage.total_tokens / iterationDuration) * 1000)}` + ); + + console.log(`\nπŸ“ˆ Cumulative Session Usage:`); + console.log(` πŸ”’ Total Iterations: ${this.config.iterationCount}`); + console.log( + ` 🎯 Total Tokens Used: ${this.config.totalTokensUsed.toLocaleString()}` + ); + console.log( + ` πŸ“ˆ Average Tokens per Iteration: ${Math.round(this.config.totalTokensUsed / this.config.iterationCount).toLocaleString()}` + ); + + const estimatedCost = this.config.totalTokensUsed * 0.000001; + console.log( + ` πŸ’° Estimated Total Cost: $${estimatedCost.toFixed(6)}` + ); + console.log('─'.repeat(60)); + }, + onMCPTool: (toolCall) => { + console.log(`\nπŸ› οΈ NextJS Tool: ${toolCall.function.name}`); + try { + const args = JSON.parse(toolCall.function.arguments); + console.log(`πŸ“ Arguments:`, JSON.stringify(args, null, 2)); + } catch { + console.log(`πŸ“ Raw Arguments: ${toolCall.function.arguments}`); + } + console.log(`πŸ” Tool ID: ${toolCall.id}\n`); + + if ( + toolCall.function.name === 'create_next_project' || + toolCall.function.name === 'create_nextjs_project' || + toolCall.function.name === 'create_new_workspace' || + toolCall.function.name.toLowerCase().includes('next') + ) { + console.log( + '🎯 Next.js project creation detected - will wait 30 seconds after completion' + ); + shouldWaitForProject = true; + } + }, + onError: (error) => { + console.error(`\n❌ Stream Error: ${error.error}`); + + if (this.config.memoryEnabled) { + this.saveStateToMemory( + `Error occurred during request processing: ${error.error}` + ).catch(console.warn); + } + + throw new Error(`Stream error: ${error.error}`); + }, + onFinish: async () => { + console.log('\n\nβœ… Development session completed!\n'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + if (shouldWaitForProject) { + await this.waitForProjectCreation(); + } + + if (assistantResponse.trim()) { + this.config.conversationHistory.push({ + role: MessageRole.assistant, + content: assistantResponse, + }); + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + } + + /** + * Save current state to memory MCP server + */ + private async saveStateToMemory(context: string): Promise { + if (!this.config.memoryEnabled) return; + + try { + const state = { + conversationHistory: this.config.conversationHistory.slice(-5), + iterationCount: this.config.iterationCount, + totalTokensUsed: this.config.totalTokensUsed, + timestamp: new Date().toISOString(), + }; + + console.log( + `πŸ’Ύ Saving state to memory for session: ${this.config.sessionId}` + ); + + let toolCallDetected = false; + let saveSuccessful = false; + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: [ + { + role: MessageRole.system, + content: `You are a memory manager. You MUST call the save-state tool now with the provided data. Don't explain - just call the tool immediately. + +SessionID: ${this.config.sessionId} +State: ${JSON.stringify(state)} +Context: ${context} + +Call save-state tool immediately with sessionId="${this.config.sessionId}" and the state object above.`, + }, + { + role: MessageRole.user, + content: `Call save-state tool now with sessionId="${this.config.sessionId}"`, + }, + ], + max_tokens: this.config.maxTokensPerRequest, + }, + { + onMCPTool: (toolCall) => { + toolCallDetected = true; + console.log(`πŸ“± Memory tool called: ${toolCall.function.name}`); + + if ( + toolCall.function.name === 'save-state' || + toolCall.function.name === 'save-error-state' + ) { + saveSuccessful = true; + console.log('βœ… State save tool invoked successfully'); + } + }, + onReasoning: (reasoning) => { + console.log(`\nπŸ€” Memory Reasoning: ${reasoning}`); + }, + onContent: (content) => { + process.stdout.write(content); + }, + onError: (error) => { + console.warn('⚠️ Memory save failed:', error.error); + }, + onFinish: () => { + if (toolCallDetected && saveSuccessful) { + console.log('βœ… Memory save completed successfully'); + } else if (!toolCallDetected) { + console.warn( + '⚠️ No memory tool was called - memory may not be available' + ); + } else { + console.warn('⚠️ Memory tool called but save may have failed'); + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + } catch (error) { + console.warn( + '⚠️ Failed to save state to memory:', + (error as Error).message + ); + } + } + + /** + * Save current state to memory MCP server with forced tool usage + */ + private async saveStateToMemoryForced(context: string): Promise { + if (!this.config.memoryEnabled) return; + + try { + const state = { + conversationHistory: this.config.conversationHistory.slice(-5), + iterationCount: this.config.iterationCount, + totalTokensUsed: this.config.totalTokensUsed, + timestamp: new Date().toISOString(), + }; + + console.log( + `πŸ’Ύ Forcing memory save for session: ${this.config.sessionId}` + ); + + let toolCallDetected = false; + let saveSuccessful = false; + const maxAttempts = 3; + + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + if (this.config.abortController.signal.aborted) { + console.warn('⚠️ Memory save aborted during shutdown'); + throw new Error('Memory save aborted'); + } + + console.log(`πŸ”„ Memory save attempt ${attempt}/${maxAttempts}`); + + try { + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: [ + { + role: MessageRole.system, + content: `You are a memory manager. You MUST call the save-state tool immediately. No explanations, no acknowledgments - just call the tool. + +CRITICAL: You MUST call save-state tool with these exact parameters: +- sessionId: "${this.config.sessionId}" +- state: ${JSON.stringify(state)} +- context: "${context}" + +Call the save-state tool now.`, + }, + { + role: MessageRole.user, + content: `Call save-state tool immediately with sessionId="${this.config.sessionId}". Do not respond with text - only call the tool.`, + }, + ], + max_tokens: this.config.maxTokensPerRequest, + }, + { + onMCPTool: (toolCall) => { + toolCallDetected = true; + console.log(`πŸ“± Memory tool called: ${toolCall.function.name}`); + + if ( + toolCall.function.name === 'save-state' || + toolCall.function.name === 'save-error-state' + ) { + saveSuccessful = true; + console.log('βœ… Memory tool invoked successfully'); + try { + const args = JSON.parse(toolCall.function.arguments); + console.log( + `πŸ“ Tool arguments:`, + JSON.stringify(args, null, 2) + ); + } catch { + console.error( + `πŸ“ Raw tool arguments: ${toolCall.function.arguments}` + ); + } + } + }, + onContent: (content) => { + process.stdout.write(content); + }, + onError: (error) => { + console.warn( + `⚠️ Memory save attempt ${attempt} failed:`, + error.error + ); + }, + onFinish: () => { + if (toolCallDetected && saveSuccessful) { + console.log( + `βœ… Memory save completed successfully on attempt ${attempt}` + ); + } else if (!toolCallDetected) { + console.warn( + `⚠️ Attempt ${attempt}: No memory tool was called` + ); + } else { + console.warn( + `⚠️ Attempt ${attempt}: Memory tool called but save may have failed` + ); + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + + if (toolCallDetected && saveSuccessful) { + break; + } + + if (attempt < maxAttempts) { + if (this.config.abortController.signal.aborted) { + console.warn('⚠️ Memory save aborted during retry wait'); + throw new Error('Memory save aborted'); + } + console.log(`⏳ Waiting 2 seconds before retry...`); + await this.delay(2000); + } + } catch (attemptError) { + console.warn( + `⚠️ Memory save attempt ${attempt} error:`, + (attemptError as Error).message + ); + if (attempt === maxAttempts) { + throw attemptError; + } + } + } + + if (!toolCallDetected || !saveSuccessful) { + console.error( + `❌ Failed to save memory after ${maxAttempts} attempts - memory tools may not be available` + ); + } + } catch (error) { + console.warn( + '⚠️ Failed to save state to memory:', + (error as Error).message + ); + } + } + + /** + * Load state from memory MCP server (via chat completion) + */ + private async loadStateFromMemory(): Promise { + if (!this.config.memoryEnabled) return false; + + try { + console.log( + `πŸ“₯ Attempting to restore state for session: ${this.config.sessionId}` + ); + + let restoredData: any = null; + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: [ + { + role: MessageRole.system, + content: `You have access to memory management tools. Restore the saved state for session "${this.config.sessionId}".`, + }, + { + role: MessageRole.user, + content: `Please restore the session state using the restore-state tool and provide the restored data.`, + }, + ], + max_tokens: this.config.maxTokensPerRequest, + }, + { + onReasoning: (reasoning) => { + console.log(`\nπŸ€” Memory Reasoning: ${reasoning}`); + }, + onContent: (content) => { + if (content.includes('{') && content.includes('}')) { + try { + const jsonMatch = content.match(/\{[\s\S]*\}/); + if (jsonMatch) { + restoredData = JSON.parse(jsonMatch[0]); + } + } catch { + console.error(`⚠️ Failed to parse restored data: ${content}`); + } + } + }, + onMCPTool: (toolCall) => { + console.log(`πŸ“± Memory tool called: ${toolCall.function.name}`); + }, + onError: () => { + console.log('ℹ️ No previous state found'); + }, + onFinish: () => { + if (restoredData && restoredData.state) { + this.config.conversationHistory = + restoredData.state.conversationHistory || []; + this.config.iterationCount = + restoredData.state.iterationCount || 0; + this.config.totalTokensUsed = + restoredData.state.totalTokensUsed || 0; + + console.log( + `βœ… Restored state from ${restoredData.state.timestamp}` + ); + console.log( + `πŸ“Š Restored ${this.config.conversationHistory.length} messages` + ); + console.log( + `πŸ”’ Restored iteration count: ${this.config.iterationCount}` + ); + } + }, + }, + this.config.provider, + this.config.abortController.signal + ); + + return !!restoredData; + } catch (error) { + console.log(`ℹ️ No previous state found: ${(error as Error).message}`); + return false; + } + } + + /** + * Truncate conversation history to stay within token limits + */ + private truncateConversationHistory(): void { + if ( + this.config.conversationHistory.length <= + this.config.maxHistoryLength + 1 + ) { + return; + } + + console.log( + `βœ‚οΈ Truncating conversation history from ${this.config.conversationHistory.length} to ${this.config.maxHistoryLength + 1} messages` + ); + + const systemPrompt = this.config.conversationHistory[0]; + + const recentMessages = this.config.conversationHistory.slice( + -this.config.maxHistoryLength + ); + + const truncatedMessages = this.config.conversationHistory.slice( + 1, + -this.config.maxHistoryLength + ); + + if (truncatedMessages.length > 0) { + this.saveStateToMemoryForced( + `Truncated ${truncatedMessages.length} older messages` + ).catch((error) => { + console.warn( + '⚠️ Failed to save truncated messages to memory:', + (error as Error).message + ); + }); + } + + this.config.conversationHistory = [systemPrompt, ...recentMessages]; + } + + /** + * Estimate token count for a message (rough approximation) + */ + private estimateTokenCount(text: string): number { + // Rough approximation: 1 token β‰ˆ 4 characters + return Math.ceil(text.length / 4); + } + + /** + * Get optimized conversation history for the current request + */ + private getOptimizedConversationHistory(): Array<{ + role: MessageRole; + content: string; + }> { + this.truncateConversationHistory(); + + const totalEstimatedTokens = this.config.conversationHistory.reduce( + (sum, msg) => sum + this.estimateTokenCount(msg.content), + 0 + ); + + console.log(`πŸ“Š Estimated tokens in conversation: ${totalEstimatedTokens}`); + + return this.config.conversationHistory; + } + + private resetAbortController(): void { + if (!this.config.abortController.signal.aborted) { + this.config.abortController.abort('Starting new request'); + } + this.config.abortController = new globalThis.AbortController(); + } + + async shutdown(): Promise { + if (this.config.memoryEnabled) { + console.log('πŸ’Ύ Saving session state before shutdown...'); + try { + const shutdownTimeout = setTimeout(() => { + console.warn('⚠️ Shutdown timeout reached, forcing exit...'); + process.exit(1); + }, 10000); // 10 second timeout + + await this.saveStateToMemoryForced( + 'Manual shutdown via SIGINT/SIGTERM signal' + ); + + clearTimeout(shutdownTimeout); + console.log('βœ… Session state saved successfully'); + } catch (error) { + console.warn( + '⚠️ Failed to save session state:', + (error as Error).message + ); + } + } + + this.rl.close(); + } + + abortOperations(): void { + if (!this.config.abortController.signal.aborted) { + this.config.abortController.abort('Shutdown signal received'); + } + } +} + +async function runNextJSAgent(): Promise { + const agent = new NextJSAgent(); + + process.on('SIGINT', async () => { + console.log('\n\nπŸ‘‹ Shutting down NextJS Agent...'); + agent.abortOperations(); + await agent.shutdown(); + process.exit(0); + }); + + process.on('SIGTERM', async () => { + console.log('\n\nπŸ‘‹ Shutting down NextJS Agent...'); + agent.abortOperations(); + await agent.shutdown(); + process.exit(0); + }); + + await agent.initialize(); +} + +if (require.main === module || process.argv[1].endsWith('index.ts')) { + runNextJSAgent().catch(console.error); +} + +export { NextJSAgent, runNextJSAgent }; diff --git a/examples/mcp/agents/nextjs/package-lock.json b/examples/mcp/agents/nextjs/package-lock.json new file mode 100644 index 0000000..8226e08 --- /dev/null +++ b/examples/mcp/agents/nextjs/package-lock.json @@ -0,0 +1,853 @@ +{ + "name": "mcp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@inference-gateway/sdk": "^0.7.3", + "axios": "^1.9.0", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "tsx": "^4.19.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@inference-gateway/sdk": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@inference-gateway/sdk/-/sdk-0.7.3.tgz", + "integrity": "sha512-UCbapsrblks9A0armTyXIdBM1NHy+8blZNkSP4AeTlHqH8CpVHH2gcgNRyn2SgqewF61kPXkijj0fh1XLZM7OA==", + "license": "MIT", + "engines": { + "node": ">=22.12.0", + "npm": ">=10.9.0" + }, + "peerDependencies": { + "node-fetch": "^2.7.0" + }, + "peerDependenciesMeta": { + "node-fetch": { + "optional": true + } + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.19.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", + "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + } + } +} diff --git a/examples/mcp/agents/nextjs/package.json b/examples/mcp/agents/nextjs/package.json new file mode 100644 index 0000000..cd8dcdf --- /dev/null +++ b/examples/mcp/agents/nextjs/package.json @@ -0,0 +1,21 @@ +{ + "name": "mcp", + "version": "1.0.0", + "description": "This example demonstrates how to use the Inference Gateway SDK with Model Context Protocol (MCP) tools in a multi-provider architecture.", + "main": "index.js", + "private": true, + "scripts": { + "start": "tsx index.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "@inference-gateway/sdk": "^0.7.3", + "axios": "^1.9.0", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "tsx": "^4.19.4" + } +} diff --git a/examples/mcp/agents/vite/Dockerfile b/examples/mcp/agents/vite/Dockerfile new file mode 100644 index 0000000..26ba3a5 --- /dev/null +++ b/examples/mcp/agents/vite/Dockerfile @@ -0,0 +1,10 @@ +FROM node:20-alpine + +WORKDIR /app +COPY package*.json ./ +RUN npm install +COPY . . +EXPOSE 5173 +ENV HOST=0.0.0.0 + +CMD ["npm", "run", "dev"] diff --git a/examples/mcp/agents/vite/index.ts b/examples/mcp/agents/vite/index.ts new file mode 100644 index 0000000..eafe31f --- /dev/null +++ b/examples/mcp/agents/vite/index.ts @@ -0,0 +1,583 @@ +/** + * Interactive Vite Development Agent + * + * This agent specializes in creating modern Vite-based applications with up-to-date + * documentation and best practices using Context7 MCP tools. + */ + +import { + InferenceGatewayClient, + MessageRole, + Provider, +} from '@inference-gateway/sdk'; +import * as dotenv from 'dotenv'; +import * as path from 'path'; +import * as readline from 'readline'; + +dotenv.config({ path: path.join(__dirname, '.env') }); + +declare const require: any; +declare const module: any; + +interface AgentConfig { + client: InferenceGatewayClient; + provider: Provider; + model: string; + conversationHistory: Array<{ role: MessageRole; content: string }>; + maxRetries: number; + retryDelayMs: number; + iterationCount: number; + totalTokensUsed: number; +} + +class ViteAgent { + private config: AgentConfig; + private rl: readline.Interface; + + constructor() { + this.config = { + client: new InferenceGatewayClient({ + baseURL: 'http://localhost:8080/v1', + }), + provider: (process.env.PROVIDER as Provider) || Provider.groq, + model: process.env.LLM || 'llama-3.3-70b-versatile', + conversationHistory: [], + maxRetries: 3, + retryDelayMs: 60000, + iterationCount: 0, + totalTokensUsed: 0, + }; + + this.rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + this.config.conversationHistory.push({ + role: MessageRole.system, + content: this.getSystemPrompt(), + }); + } + + private async delay(ms: number): Promise { + return new Promise((resolve) => { + global.setTimeout(() => resolve(), ms); + }); + } + + private async waitForViteProjectCreation(): Promise { + console.log( + '⏳ Waiting 15 seconds for Vite project creation to complete...' + ); + await this.delay(15000); + console.log('βœ… Vite project creation wait period completed.\n'); + } + + private getSystemPrompt(): string { + return ` +You are an expert Vite development assistant with access to Context7 MCP tools for library documentation and research. Today is **June 1, 2025**. + +--- + +### πŸ”§ CORE RESPONSIBILITIES + +You help users create **modern, lightning-fast Vite applications** by: + +1. Understanding user requirements and recommending the best Vite-based stack +2. Using **Context7 tools** to retrieve up-to-date Vite documentation and best practices +3. Building complete projects with proper Vite configuration and optimization +4. Following modern development conventions and Vite-specific patterns +5. Creating fast, responsive, and well-structured applications + +--- + +### 🧰 AVAILABLE TOOLS + +You have access to several MCP tool categories: + +**Context7 Tools (@upstash/context7-mcp):** + +* c41_resolve-library-id: Resolve technology names to Context7-compatible IDs +* c41_get-library-docs: Fetch full documentation, usage examples, and best practices + +**Mock Tools (for local/demo use):** + +* search_libraries: Search for libraries by name or functionality +* get_library_details: Fetch library metadata and features +* get_documentation: Fetch usage examples and implementation patterns + +**Memory Tools (for error recovery):** + +* save-state: Save current progress/state with a session ID +* save-error-state: Save state when HTTP errors occur for recovery +* restore-state: Restore previously saved state by session ID +* list-sessions: List all saved sessions +* clear-session: Remove a saved session + +**File System Tools:** + +* Available for file operations in /tmp directory + +--- + +### πŸ›‘οΈ ERROR RECOVERY STRATEGY + +When encountering HTTP errors or failures: + +1. Immediately save state using save-error-state with: + - Unique session ID (e.g., "vite-task-{timestamp}") + - Current progress/context + - Error details +2. In subsequent runs, check for existing sessions with list-sessions +3. Restore state if needed and continue from where you left off +4. Clear sessions when tasks complete successfully + +--- + +### πŸ“‚ FILE SYSTEM RULES + +* All projects and generated files must **use the /tmp directory exclusively**. +* If a **Vite project already exists in /tmp**, continue working within it instead of creating a new one. +* You must **never overwrite** an existing project unless explicitly asked. + +--- + +### βš™οΈ DEVELOPMENT WORKFLOW + +**Always use Context7 tools before coding:** + +**Always list the files in a directory before creating new files.** + +**When creating a Vite project, always wait 15 seconds after project creation.** + +1. Clarify requirements and tech stack +2. Lookup Vite and related technologies using Context7 tools +3. Retrieve current documentation and patterns +4. Scaffold or enhance projects under /tmp, maintaining clean structure +5. Follow Vite conventions and optimization patterns +6. Include proper build configuration, testing, and development scripts +7. Prioritize maintainability, performance, and developer experience + +--- + +### ⚑ VITE PROJECT RULES + +* **Use the latest Vite configuration patterns and best practices** +* **Optimize for development speed and build performance** +* **Structure should include:** + * vite.config.js/ts – main configuration file + * index.html – entry point + * src/main.js/ts – application entry + * src/App.vue/jsx/tsx – main component + * public/ - static assets + * src/components/, src/assets/, etc. as needed + +**Supported Frameworks with Vite:** +* React (with TypeScript/JavaScript) +* Vue 3 (with TypeScript/JavaScript) +* Svelte/SvelteKit +* Vanilla JavaScript/TypeScript +* Preact +* Lit +* Solid + +If a Vite project exists: +* Validate configuration and structure +* Extend or modify as needed based on the request +* Optimize build and development settings + +--- + +### πŸ§ͺ VITE ECOSYSTEM (verify latest versions with Context7) + +**Core:** Vite, Rollup, ES Modules, Hot Module Replacement (HMR) +**Frontend Frameworks:** React, Vue, Svelte, Preact, Lit, Solid +**Styling:** Tailwind CSS, PostCSS, CSS Modules, Sass/SCSS, Styled Components +**Testing:** Vitest, Playwright, Cypress, Jest +**Build Tools:** ESBuild, SWC, Rollup plugins +**Utilities:** TypeScript, ESLint, Prettier, Autoprefixer +**Package Managers:** npm, yarn, pnpm, bun + +--- + +### πŸš€ COMMON VITE FEATURES TO LEVERAGE + +* **Fast Development Server** with HMR +* **Optimized Build** with code splitting +* **Plugin Ecosystem** for extensibility +* **TypeScript Support** out of the box +* **CSS Preprocessing** and PostCSS +* **Asset Optimization** and bundling +* **Environment Variables** management +* **Proxy Configuration** for API development + +--- + +### βœ… SUMMARY + +* Always work in /tmp +* If a Vite project exists, enhance it β€” don't recreate +* Use Context7 tools for everything: Vite decisions, patterns, and examples +* Leverage Vite's speed and modern tooling advantages +* Adhere to modern best practices in project setup, UI/UX, and code quality +`; + } + + async initialize(): Promise { + console.log( + `⚑ Vite Development Agent initialized using ${this.config.model} on ${this.config.provider}\n` + ); + + let attempt = 0; + while (attempt < this.config.maxRetries) { + try { + // Health check + const isHealthy = await this.config.client.healthCheck(); + if (!isHealthy) { + console.error('❌ Gateway unhealthy. Run: docker-compose up --build'); + process.exit(1); + } + + const tools = await this.config.client.listTools(); + const realContext7Tools = tools.data.filter((tool) => + ['c41_resolve-library-id', 'c41_get-library-docs'].includes(tool.name) + ); + const mockContext7Tools = tools.data.filter((tool) => + [ + 'search_libraries', + 'get_library_details', + 'get_documentation', + ].includes(tool.name) + ); + + const context7Tools = [...realContext7Tools, ...mockContext7Tools]; + + if (context7Tools.length === 0) { + console.error( + '⚠️ No Context7 MCP tools available. Make sure a Context7 MCP server is running.' + ); + console.error( + ' For real Context7: npx -y @upstash/context7-mcp@latest' + ); + console.error( + ' For local mock: docker-compose up --build (already included in this project)' + ); + process.exit(1); + } + + const usingRealContext7 = realContext7Tools.length > 0; + const toolType = usingRealContext7 ? 'real Context7' : 'mock Context7'; + + console.info( + `πŸ“‹ Found ${context7Tools.length} ${toolType} tools available:` + ); + context7Tools.forEach((tool, index) => { + console.info(` ${index + 1}. ${tool.name} - ${tool.description}`); + }); + + if (!usingRealContext7) { + console.info('πŸ’‘ Using local mock Context7 server for demonstration'); + console.info( + ' To use real Context7 with latest docs, install: npx -y @upstash/context7-mcp@latest' + ); + } + console.info(''); + + this.showWelcomeMessage(); + await this.startInteractiveSession(); + break; + } catch (error) { + attempt++; + console.error( + `❌ Initialization Error (attempt ${attempt}/${this.config.maxRetries}):`, + (error as Error).message + ); + + if (attempt < this.config.maxRetries) { + console.log( + `⏳ Retrying in ${this.config.retryDelayMs / 1000} seconds...` + ); + await this.delay(this.config.retryDelayMs); + } else { + console.error( + `❌ Failed to initialize after ${this.config.maxRetries} attempts` + ); + console.log( + '\nπŸ’‘ Make sure the Context7 MCP server is running on port 3002' + ); + console.log(' and the Inference Gateway is running on port 8080'); + process.exit(1); + } + } + } + } + + private showWelcomeMessage(): void { + console.log('⚑ Welcome to Vite Interactive Development Agent!'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'); + console.log( + '\nπŸ’‘ I specialize in creating lightning-fast Vite applications with modern tooling.' + ); + console.log( + " Just describe what you want to build, and I'll use Context7 to get" + ); + console.log( + ' up-to-date Vite documentation and create an optimized solution for you.' + ); + console.log('\nπŸ“ Example requests:'); + console.log( + ' β€’ "Create a React + TypeScript app with Vite and Tailwind CSS"' + ); + console.log( + ' β€’ "Build a Vue 3 dashboard with Vite, Vitest, and component library"' + ); + console.log( + ' β€’ "Make a Svelte SPA with Vite and optimal build configuration"' + ); + console.log( + ' β€’ "Create a vanilla TypeScript app with Vite and modern tooling"' + ); + console.log('\n⚑ Commands:'); + console.log(' β€’ Type your request to start building'); + console.log(' β€’ Use "clear" to reset conversation history'); + console.log(' β€’ Use "exit" or "quit" to end the session'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + } + + private async startInteractiveSession(): Promise { + while (true) { + const userInput = await this.getUserInput( + '⚑ What Vite application would you like to build? ' + ); + + if (this.handleSpecialCommands(userInput)) { + continue; + } + + if (userInput.trim()) { + await this.processUserRequestWithRetry(userInput); + } + } + } + + private async getUserInput(prompt: string): Promise { + return new Promise((resolve) => { + this.rl.question(prompt, (answer) => { + resolve(answer); + }); + }); + } + + private handleSpecialCommands(input: string): boolean { + const command = input.trim().toLowerCase(); + + switch (command) { + case 'exit': + case 'quit': + console.log('\nπŸ‘‹ Thank you for using Vite Agent! Goodbye!'); + this.rl.close(); + process.exit(0); + return true; + + case 'clear': + this.config.conversationHistory = [ + { + role: MessageRole.system, + content: this.getSystemPrompt(), + }, + ]; + console.log('\n🧹 Conversation history cleared. Starting fresh!\n'); + return true; + + case 'help': + this.showWelcomeMessage(); + return true; + + default: + return false; + } + } + + private async processUserRequestWithRetry(userInput: string): Promise { + let attempt = 0; + + while (attempt < this.config.maxRetries) { + try { + await this.processUserRequest(userInput); + break; + } catch (error) { + attempt++; + console.error( + `❌ Request failed (attempt ${attempt}/${this.config.maxRetries}):`, + (error as Error).message + ); + + if (attempt < this.config.maxRetries) { + console.log( + `⏳ Retrying in ${this.config.retryDelayMs / 1000} seconds...` + ); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + await this.delay(this.config.retryDelayMs); + } else { + console.error( + `❌ Failed to process request after ${this.config.maxRetries} attempts` + ); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + } + } + } + } + + private async processUserRequest(userInput: string): Promise { + console.log(`\nπŸ” Processing Vite request: "${userInput}"`); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + // Increment iteration count and start timing + this.config.iterationCount++; + const iterationStartTime = Date.now(); + + console.log(`πŸ”„ Starting Iteration #${this.config.iterationCount}`); + console.log( + `πŸ“ User Input: "${userInput.substring(0, 100)}${userInput.length > 100 ? '...' : ''}"` + ); + console.log(`⏰ Start Time: ${new Date().toLocaleTimeString()}`); + console.log('─'.repeat(60)); + + this.config.conversationHistory.push({ + role: MessageRole.user, + content: userInput, + }); + + let assistantResponse = ''; + let shouldWaitForProject = false; + + await this.config.client.streamChatCompletion( + { + model: this.config.model, + messages: this.config.conversationHistory, + max_tokens: 2000, + }, + { + onOpen: () => { + console.log( + 'πŸ”— Starting Vite development session with Context7...\n' + ); + }, + onReasoning: (reasoning) => { + console.log(`\nπŸ€” Agent Reasoning: ${reasoning}`); + }, + onContent: (content) => { + process.stdout.write(content); + assistantResponse += content; + }, + onUsageMetrics: (usage) => { + const iterationDuration = Date.now() - iterationStartTime; + this.config.totalTokensUsed += usage.total_tokens; + + console.log( + `\n\nπŸ’° Iteration #${this.config.iterationCount} Token Usage:` + ); + console.log( + ` πŸ“Š Prompt tokens: ${usage.prompt_tokens.toLocaleString()}` + ); + console.log( + ` ✍️ Completion tokens: ${usage.completion_tokens.toLocaleString()}` + ); + console.log( + ` 🎯 Total tokens: ${usage.total_tokens.toLocaleString()}` + ); + console.log(` ⏱️ Duration: ${iterationDuration}ms`); + console.log( + ` πŸš€ Tokens/sec: ${Math.round((usage.total_tokens / iterationDuration) * 1000)}` + ); + + console.log(`\nπŸ“ˆ Cumulative Session Usage:`); + console.log(` πŸ”’ Total Iterations: ${this.config.iterationCount}`); + console.log( + ` 🎯 Total Tokens Used: ${this.config.totalTokensUsed.toLocaleString()}` + ); + console.log( + ` πŸ“ˆ Average Tokens per Iteration: ${Math.round(this.config.totalTokensUsed / this.config.iterationCount).toLocaleString()}` + ); + + // Calculate estimated cost (example rates - adjust based on provider) + const estimatedCost = this.config.totalTokensUsed * 0.000001; + console.log( + ` πŸ’° Estimated Total Cost: $${estimatedCost.toFixed(6)}` + ); + console.log('─'.repeat(60)); + }, + onMCPTool: (toolCall: any) => { + console.log(`\nπŸ› οΈ Context7 Tool: ${toolCall.function.name}`); + try { + const args = JSON.parse(toolCall.function.arguments); + console.log(`πŸ“ Arguments:`, JSON.stringify(args, null, 2)); + } catch { + console.log(`πŸ“ Raw Arguments: ${toolCall.function.arguments}`); + } + console.log(`πŸ” Tool ID: ${toolCall.id}\n`); + + if ( + toolCall.function.name === 'create_vite_project' || + toolCall.function.name === 'create_new_workspace' || + toolCall.function.name.toLowerCase().includes('vite') || + toolCall.function.name.toLowerCase().includes('project') + ) { + console.log( + '⚑ Vite project creation detected - will wait 15 seconds after completion' + ); + shouldWaitForProject = true; + } + }, + onError: (error) => { + console.error(`\n❌ Stream Error: ${error.error}`); + throw new Error(`Stream error: ${error.error}`); + }, + onFinish: async () => { + console.log('\n\nβœ… Vite development session completed!\n'); + console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'); + + if (shouldWaitForProject) { + await this.waitForViteProjectCreation(); + } + + // Add assistant response to conversation history + if (assistantResponse.trim()) { + this.config.conversationHistory.push({ + role: MessageRole.assistant, + content: assistantResponse, + }); + } + }, + } + ); + } + + async shutdown(): Promise { + this.rl.close(); + } +} + +async function runViteAgent(): Promise { + const agent = new ViteAgent(); + + process.on('SIGINT', async () => { + console.log('\n\nπŸ‘‹ Shutting down Vite Agent...'); + await agent.shutdown(); + process.exit(0); + }); + + process.on('SIGTERM', async () => { + console.log('\n\nπŸ‘‹ Shutting down Vite Agent...'); + await agent.shutdown(); + process.exit(0); + }); + + await agent.initialize(); +} + +if (require.main === module || process.argv[1].endsWith('index.ts')) { + runViteAgent().catch(console.error); +} + +export { runViteAgent, ViteAgent }; diff --git a/examples/mcp/agents/vite/package-lock.json b/examples/mcp/agents/vite/package-lock.json new file mode 100644 index 0000000..8226e08 --- /dev/null +++ b/examples/mcp/agents/vite/package-lock.json @@ -0,0 +1,853 @@ +{ + "name": "mcp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@inference-gateway/sdk": "^0.7.3", + "axios": "^1.9.0", + "dotenv": "^16.5.0" + }, + "devDependencies": { + "tsx": "^4.19.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@inference-gateway/sdk": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@inference-gateway/sdk/-/sdk-0.7.3.tgz", + "integrity": "sha512-UCbapsrblks9A0armTyXIdBM1NHy+8blZNkSP4AeTlHqH8CpVHH2gcgNRyn2SgqewF61kPXkijj0fh1XLZM7OA==", + "license": "MIT", + "engines": { + "node": ">=22.12.0", + "npm": ">=10.9.0" + }, + "peerDependencies": { + "node-fetch": "^2.7.0" + }, + "peerDependenciesMeta": { + "node-fetch": { + "optional": true + } + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.19.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", + "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + } + } +} diff --git a/examples/mcp/package.json b/examples/mcp/agents/vite/package.json similarity index 61% rename from examples/mcp/package.json rename to examples/mcp/agents/vite/package.json index 15712a6..38612e4 100644 --- a/examples/mcp/package.json +++ b/examples/mcp/agents/vite/package.json @@ -5,9 +5,10 @@ "main": "index.js", "private": true, "scripts": { - "start": "tsx index.ts", - "example:mcp:remotetools": "tsx example-mcp-tools.ts", - "example:mcp:withlocaltools": "tsx example-mcp-with-tools.ts", + "clean": "rm -rf shared/next-app shared/update-check", + "example:nextjs-agent": "tsx example-nextjs-agent.ts", + "example:vite-agent": "tsx example-vite-agent.ts", + "example:kubernetes-agent": "tsx example-kubernetes-agent.ts", "compose:up": "docker-compose up -d", "compose:down": "docker-compose down", "compose:logs": "docker-compose logs -f" @@ -16,7 +17,9 @@ "author": "", "license": "ISC", "dependencies": { - "@inference-gateway/sdk": "^0.7.1" + "@inference-gateway/sdk": "^0.7.3", + "axios": "^1.9.0", + "dotenv": "^16.5.0" }, "devDependencies": { "tsx": "^4.19.4" diff --git a/examples/mcp/docker-compose-agents.yml b/examples/mcp/docker-compose-agents.yml new file mode 100644 index 0000000..ea2a3c0 --- /dev/null +++ b/examples/mcp/docker-compose-agents.yml @@ -0,0 +1,52 @@ +services: + kubernetes-agent: + build: + context: ./agents/kubernetes + dockerfile: Dockerfile + env_file: + - .env + volumes: + - shared-data:/tmp + networks: + - inference-network + + vite-agent: + build: + context: ./agents/vite + dockerfile: Dockerfile + env_file: + - .env + volumes: + - shared-data:/tmp + networks: + - inference-network + + nextjs-agent: + build: + context: ./agents/nextjs + dockerfile: Dockerfile + env_file: + - .env + volumes: + - shared-data:/tmp + networks: + - inference-network + + marketing-agent: + build: + context: ./agents/marketing + dockerfile: Dockerfile + env_file: + - .env + volumes: + - shared-data:/tmp + networks: + - inference-network + +volumes: + shared-data: + driver: local + +networks: + inference-network: + driver: bridge diff --git a/examples/mcp/docker-compose.yml b/examples/mcp/docker-compose.yml index c3bb0c2..5da7264 100644 --- a/examples/mcp/docker-compose.yml +++ b/examples/mcp/docker-compose.yml @@ -10,7 +10,7 @@ services: # Enable MCP support MCP_ENABLE: 'true' MCP_EXPOSE: 'true' - MCP_SERVERS: 'http://mcp-filesystem:3000/mcp,http://mcp-web-search:3001/mcp' + MCP_SERVERS: 'http://mcp-filesystem:3000/mcp,http://mcp-context7:3002/mcp,http://mcp-npm:3003/mcp,http://mcp-memory:3004/mcp,http://mcp-brave-search:3005/mcp' # Server settings SERVER_HOST: '0.0.0.0' @@ -29,22 +29,27 @@ services: DEEPSEEK_API_URL: 'https://api.deepseek.com/v1' DEEPSEEK_API_KEY: '${DEEPSEEK_API_KEY:-}' + COHERE_API_URL: 'https://api.cohere.ai' + COHERE_API_KEY: '${COHERE_API_KEY:-}' + # Optional: Ollama for local models OLLAMA_API_URL: 'http://ollama:11434/v1' OLLAMA_API_KEY: '' depends_on: mcp-filesystem: condition: service_healthy - mcp-web-search: + # mcp-web-search: + # condition: service_healthy + mcp-context7: + condition: service_healthy + mcp-npm: + condition: service_healthy + mcp-memory: + condition: service_healthy + mcp-brave-search: condition: service_healthy networks: - inference-network - healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:8080/health'] - interval: 30s - timeout: 10s - retries: 5 - start_period: 60s pull_policy: always restart: unless-stopped @@ -52,7 +57,7 @@ services: build: context: ./mcp-servers/filesystem dockerfile_inline: | - FROM node:18-alpine + FROM node:22-alpine WORKDIR /app RUN apk add --no-cache curl COPY package.json ./ @@ -77,26 +82,187 @@ services: start_period: 45s restart: unless-stopped - mcp-web-search: + # mcp-web-search: + # build: + # context: ./mcp-servers/web-search + # dockerfile_inline: | + # FROM node:22-alpine + # WORKDIR /app + # RUN apk add --no-cache curl + # COPY package.json ./ + # RUN npm install + # COPY . . + # EXPOSE 3001 + # CMD ["npm", "start"] + # environment: + # NODE_ENV: 'production' + # MCP_SERVER_NAME: 'web-search' + # MCP_SERVER_VERSION: '1.0.0' + # PORT: '3001' + # HOST: '0.0.0.0' + # networks: + # - inference-network + # healthcheck: + # test: ['CMD', 'curl', '-f', 'http://localhost:3001/health'] + # interval: 30s + # timeout: 10s + # retries: 5 + # start_period: 45s + # restart: unless-stopped + + mcp-context7: build: - context: ./mcp-servers/web-search + context: ./mcp-servers/context7 dockerfile_inline: | - FROM node:18-alpine + FROM node:22-alpine WORKDIR /app RUN apk add --no-cache curl COPY package.json ./ RUN npm install COPY . . - EXPOSE 3001 - CMD ["node", "index-http.js"] + EXPOSE 3002 + CMD ["npm", "start"] environment: NODE_ENV: 'production' - MCP_SERVER_NAME: 'web-search' + MCP_SERVER_NAME: 'context7' MCP_SERVER_VERSION: '1.0.0' + PORT: '3002' + networks: + - inference-network + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3002/health'] + interval: 30s + timeout: 10s + retries: 5 + start_period: 45s + restart: unless-stopped + + mcp-npm: + build: + context: ./mcp-servers/npm + dockerfile_inline: | + FROM node:18-slim + WORKDIR /app + RUN apt-get update && apt-get install -y \ + curl \ + git \ + && rm -rf /var/lib/apt/lists/* + COPY package.json ./ + RUN npm install + COPY . . + EXPOSE 3003 + CMD ["npm", "start"] + environment: + NODE_ENV: 'production' + MCP_SERVER_NAME: 'npm' + MCP_SERVER_VERSION: '1.0.0' + PORT: '3003' + ALLOWED_DIRECTORIES: '/tmp' + networks: + - inference-network + volumes: + - ./shared:/tmp + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3003/health'] + interval: 30s + timeout: 10s + retries: 5 + start_period: 45s + restart: unless-stopped + + mcp-memory: + build: + context: ./mcp-servers/memory + dockerfile_inline: | + FROM node:22-alpine + WORKDIR /app + RUN apk add --no-cache curl + COPY package.json ./ + RUN npm install + COPY . . + EXPOSE 3004 + CMD ["npm", "start"] + environment: + NODE_ENV: 'production' + MCP_SERVER_NAME: 'memory' + MCP_SERVER_VERSION: '1.0.0' + PORT: '3004' + MEMORY_DIR: '/tmp/memory' + networks: + - inference-network + volumes: + - ./shared:/tmp + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:3004/health'] + interval: 30s + timeout: 10s + retries: 5 + start_period: 45s + restart: unless-stopped + + # mcp-inspector: + # build: + # context: . + # dockerfile_inline: | + # FROM node:22-alpine + # WORKDIR /app + # RUN apk add --no-cache curl git + # RUN npm install -g @modelcontextprotocol/inspector + # EXPOSE 6274 + # CMD ["npx", "@modelcontextprotocol/inspector"] + # environment: + # NODE_ENV: 'production' + # CLIENT_PORT: '6274' + # ports: + # - '6274:6274' + # networks: + # - inference-network + # depends_on: + # mcp-filesystem: + # condition: service_healthy + # mcp-web-search: + # condition: service_healthy + # mcp-context7: + # condition: service_healthy + # mcp-npm: + # condition: service_healthy + # mcp-memory: + # condition: service_healthy + # mcp-brave-search: + # condition: service_healthy + # healthcheck: + # test: ['CMD', 'curl', '-f', 'http://localhost:6274'] + # interval: 30s + # timeout: 10s + # retries: 5 + # start_period: 45s + # restart: unless-stopped + + mcp-brave-search: + build: + context: ./mcp-servers/brave-search + dockerfile_inline: | + FROM node:22-alpine + WORKDIR /app + RUN apk add --no-cache curl + COPY package.json ./ + RUN npm install + COPY . . + EXPOSE 3005 + CMD ["npm", "start"] + environment: + NODE_ENV: 'production' + MCP_SERVER_NAME: 'brave-search' + MCP_SERVER_VERSION: '1.0.0' + MCP_TRANSPORT: 'streamableHttp' + PORT: '3005' + BRAVE_API_KEY: '${BRAVE_API_KEY:-}' + ports: + - '3005:3005' networks: - inference-network healthcheck: - test: ['CMD', 'curl', '-f', 'http://localhost:3001/health'] + test: ['CMD', 'curl', '-f', 'http://localhost:3005/health'] interval: 30s timeout: 10s retries: 5 diff --git a/examples/mcp/example-mcp-tools.ts b/examples/mcp/example-mcp-tools.ts deleted file mode 100644 index 23611a7..0000000 --- a/examples/mcp/example-mcp-tools.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { - InferenceGatewayClient, - MessageRole, - Provider, -} from '../../src/index.js'; - -/** - * Demonstration of MCP filesystem operations with proper directory access - * This example shows how to work with the /shared and /tmp directories - * and demonstrates the onMCPTool callback for tracking tool usage - */ -(async () => { - const client = new InferenceGatewayClient({ - baseURL: 'http://localhost:8080/v1', - }); - - const provider = (process.env.PROVIDER as Provider) || Provider.groq; - const model = process.env.LLM || 'llama-3.3-70b-versatile'; - - console.info(`Using model: ${model}`); - console.info(`Using provider: ${provider}\n`); - - console.info('=== MCP Tool Usage Demo ===\n'); - - try { - // Check gateway health - console.info('πŸ” Checking gateway health...'); - const isHealthy = await client.healthCheck(); - console.info( - `Gateway health: ${isHealthy ? 'βœ… Healthy' : '❌ Unhealthy'}\n` - ); - - if (!isHealthy) { - console.info( - 'Please ensure the Inference Gateway is running with Docker Compose.' - ); - process.exit(1); - } - - // List available models for the provider - console.info('πŸ” Checking available models...'); - try { - const models = await client.listModels(provider); - console.info(`Found ${models.data.length} models for ${provider}:`); - models.data.forEach((modelInfo, index) => { - console.info(`${index + 1}. ${modelInfo.id}`); - }); - - // Check if the requested model is available - const isModelAvailable = models.data.some( - (m) => m.id === `${provider}/${model}` - ); - if (!isModelAvailable) { - console.info( - `⚠️ Model '${model}' not found for provider '${provider}'` - ); - console.info( - 'Consider using one of the available models listed above.' - ); - console.info( - 'You can set the LLM environment variable to use a different model.\n' - ); - } - } catch (modelError) { - console.info('⚠️ Could not retrieve model list:', modelError); - } - console.info(''); - - // List available MCP tools - console.info('πŸ“‹ Listing available MCP tools...'); - const tools = await client.listTools(); - console.info(`Found ${tools.data.length} MCP tools:\n`); - - const fileTools = tools.data.filter((tool) => - ['read_file', 'write_file', 'list_directory'].includes(tool.name) - ); - - if (fileTools.length === 0) { - console.info('⚠️ No filesystem MCP tools available.'); - return; - } - - console.info('πŸ“ Available filesystem tools:'); - fileTools.forEach((tool, index) => { - console.info(`${index + 1}. ${tool.name} - ${tool.description}`); - }); - console.info(''); - - // Track MCP tool calls for demonstration - const toolCallTracker = { - totalCalls: 0, - toolsUsed: new Set(), - filesAccessed: new Set(), - }; - - // Example: Analyze highest revenue from sales data - console.info('=== Highest Revenue Analysis with MCP Tool Tracking ===\n'); - - await client.streamChatCompletion( - { - model, - messages: [ - { - role: MessageRole.system, - content: `You are a data analyst with filesystem access. You have access to tools that can read files from /tmp directory and write files to /tmp directory. When analyzing data, be thorough and provide specific insights.`, - }, - { - role: MessageRole.user, - content: - 'Please read the sample_sales_data.csv file from the /tmp directory, analyze it to find the highest revenue transactions, and create a detailed summary report. Save the summary to /tmp/revenue_analysis.txt.', - }, - ], - max_tokens: 1500, - }, - { - onOpen: () => { - console.info('πŸš€ Starting revenue analysis...'); - console.info('πŸ“Š MCP Tool usage will be tracked below:\n'); - }, - onContent: (content) => process.stdout.write(content), - onMCPTool: (toolCall) => { - toolCallTracker.totalCalls++; - toolCallTracker.toolsUsed.add(toolCall.function.name); - - console.info( - `\nπŸ”§ [TOOL CALL #${toolCallTracker.totalCalls}] ${toolCall.function.name}` - ); - - const args = JSON.parse(toolCall.function.arguments); - - switch (toolCall.function.name) { - case 'read_file': - console.info(` πŸ“„ Reading file: ${args.path}`); - toolCallTracker.filesAccessed.add(args.path); - break; - case 'write_file': - console.info(` πŸ’Ύ Writing file: ${args.path}`); - console.info( - ` πŸ“ Content length: ${ - args.content ? args.content.length : 0 - } characters` - ); - toolCallTracker.filesAccessed.add(args.path); - break; - case 'list_directory': - console.info(` πŸ“‚ Listing directory: ${args.path}`); - break; - default: - console.info( - ` βš™οΈ Arguments: ${JSON.stringify(args, null, 2)}` - ); - } - console.info(` πŸ†” Tool ID: ${toolCall.id}`); - }, - onFinish: () => { - console.info('\n\nβœ… Revenue analysis completed!\n'); - - // Display tool usage summary - console.info('πŸ“ˆ MCP Tool Usage Summary:'); - console.info(` Total tool calls: ${toolCallTracker.totalCalls}`); - console.info( - ` Tools used: ${Array.from(toolCallTracker.toolsUsed).join(', ')}` - ); - console.info( - ` Files accessed: ${Array.from( - toolCallTracker.filesAccessed - ).join(', ')}` - ); - console.info(''); - }, - onError: (error) => console.error('❌ Error:', error), - }, - provider - ); - - console.info('πŸŽ‰ MCP Tool Usage Demo completed successfully!'); - console.info('\nπŸ’‘ Key takeaways:'); - console.info( - '- The onMCPTool callback provides detailed tracking of tool usage' - ); - console.info( - '- Track total tool calls, tool types used, and files accessed' - ); - console.info( - '- Each tool call includes function name, arguments, and unique ID' - ); - console.info( - '- Perfect for debugging, monitoring, and understanding AI tool usage patterns' - ); - console.info( - '- LLM can read CSV data and perform complex analysis with file operations\n' - ); - } catch (error) { - if ( - error instanceof Error && - error.message.includes('MCP tools endpoint is not exposed') - ) { - console.error( - '❌ MCP tools are not exposed. Please ensure the Inference Gateway is started with MCP_EXPOSE=true' - ); - console.info('\nπŸ’‘ To fix this, restart the gateway with:'); - console.info(' docker-compose up --build'); - } else { - console.error('❌ Error:', error); - } - } -})(); diff --git a/examples/mcp/index.ts b/examples/mcp/index.ts deleted file mode 100644 index 85eecc7..0000000 --- a/examples/mcp/index.ts +++ /dev/null @@ -1,436 +0,0 @@ -import { - InferenceGatewayClient, - MessageRole, - Provider, -} from '../../src/index.js'; - -// Token tracking interface -interface TokenTracker { - totalPromptTokens: number; - totalCompletionTokens: number; - totalTokens: number; - requestCount: number; -} - -(async () => { - const client = new InferenceGatewayClient({ - baseURL: 'http://localhost:8080/v1', - }); - - const provider = (process.env.PROVIDER as Provider) || Provider.groq; - const model = process.env.LLM || 'llama-3.3-70b-versatile'; - - // Initialize token tracker - const tokenTracker: TokenTracker = { - totalPromptTokens: 0, - totalCompletionTokens: 0, - totalTokens: 0, - requestCount: 0, - }; - - // Helper function to update token tracking - const updateTokens = (usage: { - prompt_tokens: number; - completion_tokens: number; - total_tokens: number; - }) => { - tokenTracker.totalPromptTokens += usage.prompt_tokens; - tokenTracker.totalCompletionTokens += usage.completion_tokens; - tokenTracker.totalTokens += usage.total_tokens; - tokenTracker.requestCount++; - }; - - // Helper function to display current token usage - const displayTokenUsage = (label: string) => { - console.info(`\nπŸ’° Token Usage for ${label}:`); - console.info( - ` πŸ“Š Prompt tokens: ${tokenTracker.totalPromptTokens.toLocaleString()}` - ); - console.info( - ` ✍️ Completion tokens: ${tokenTracker.totalCompletionTokens.toLocaleString()}` - ); - console.info( - ` 🎯 Total tokens: ${tokenTracker.totalTokens.toLocaleString()}` - ); - console.info( - ` πŸ“ˆ Average tokens per request: ${Math.round( - tokenTracker.totalTokens / Math.max(tokenTracker.requestCount, 1) - ).toLocaleString()}` - ); - }; - - console.info(`Using model: ${model}`); - console.info(`Using provider: ${provider}\n`); - - console.info('=== MCP Tools Example with Token Tracking ===\n'); - - try { - // First, let's check if the gateway is healthy - console.info('πŸ” Checking gateway health...'); - const isHealthy = await client.healthCheck(); - console.info( - `Gateway health: ${isHealthy ? 'βœ… Healthy' : '❌ Unhealthy'}\n` - ); - - if (!isHealthy) { - console.info( - 'Please ensure the Inference Gateway is running with Docker Compose.' - ); - process.exit(1); - } - - // List available MCP tools - console.info('πŸ“‹ Listing available MCP tools...'); - const tools = await client.listTools(); - console.info(`Found ${tools.data.length} MCP tools:\n`); - - // tools.data.forEach((tool, index) => { - // console.info(`${index + 1}. ${tool.name}`); - // console.info(` Description: ${tool.description}`); - // console.info(` Server: ${tool.server}`); - // console.info(` Schema: ${JSON.stringify(tool.input_schema, null, 2)}\n`); - // }); - - if (tools.data.length === 0) { - console.info( - '⚠️ No MCP tools available. Ensure MCP servers are configured and running.' - ); - return; - } - - // Example 0: Simple test without tools first - console.info('=== Example 0: Simple Test (No Tools) ===\n'); - console.info('Testing basic streaming without tools first...\n'); - - await client.streamChatCompletion( - { - model, - messages: [ - { - role: MessageRole.user, - content: 'Hello! Please tell me about the weather.', - }, - ], - max_tokens: 50, - }, - { - onOpen: () => console.info('πŸš€ Starting simple test...'), - onContent: (content) => process.stdout.write(content), - onTool: (toolCall) => { - console.info(`\nπŸ”§ Tool called: ${toolCall.function.name}`); - console.info(`πŸ“ Arguments: ${toolCall.function.arguments}`); - }, - onUsageMetrics: (usage) => { - updateTokens(usage); - }, - onFinish: () => { - console.info('\nβœ… Simple test completed'); - displayTokenUsage('Simple Test'); - console.info(''); - }, - onError: (error) => console.error('❌ Error:', error), - }, - provider - ); - - // Example 1: Automatic tool discovery and usage - console.info('=== Example 1: Automatic Tool Discovery ===\n'); - console.info( - 'The gateway automatically detects and uses available MCP tools based on context.\n' - ); - - await client.streamChatCompletion( - { - model, - messages: [ - { - role: MessageRole.system, - content: - 'You are a helpful assistant with access to various tools.', - }, - { - role: MessageRole.user, - content: - 'What time is it now? Also, if you can, find some information about artificial intelligence.', - }, - ], - max_tokens: 200, - }, - { - onOpen: () => console.info('πŸš€ Starting automatic tool discovery...'), - onContent: (content) => process.stdout.write(content), - onTool: (toolCall) => { - console.info( - `\nπŸ”§ Tool automatically called: ${toolCall.function.name}` - ); - console.info(`πŸ“ Arguments: ${toolCall.function.arguments}`); - }, - onUsageMetrics: (usage) => { - updateTokens(usage); - }, - onFinish: () => { - console.info('\nβœ… Automatic tool discovery completed'); - displayTokenUsage('Automatic Tool Discovery'); - console.info(''); - }, - onError: (error) => console.error('❌ Error:', error), - }, - provider - ); - - // Example 2: Use MCP tools for file operations (if filesystem MCP server is available) - const fileReadTool = tools.data.find((tool) => tool.name === 'read_file'); - if (fileReadTool) { - console.info('=== Example 2: File Operations with MCP ===\n'); - - await client.streamChatCompletion( - { - model, - messages: [ - { - role: MessageRole.system, - content: - 'You are a helpful assistant with access to filesystem operations. Available directory is /tmp.', - }, - { - role: MessageRole.user, - content: - 'Can you read the contents of /tmp/mcp-filesystem-example.txt and tell me what it contains?', - }, - ], - max_tokens: 200, - }, - { - onOpen: () => console.info('πŸš€ Starting file reading example...'), - onContent: (content) => process.stdout.write(content), - onTool: (toolCall) => { - console.info(`\nπŸ”§ Tool called: ${toolCall.function.name}`); - console.info(`πŸ“ Arguments: ${toolCall.function.arguments}`); - }, - onUsageMetrics: (usage) => { - updateTokens(usage); - }, - onFinish: () => { - console.info('\nβœ… File reading example completed'); - displayTokenUsage('File Reading Example'); - console.info(''); - }, - onError: (error) => console.error('❌ Error:', error), - }, - provider - ); - } - - // Example 3: Use MCP tools for web scraping (if web scraper MCP server is available) - const webScrapeTool = tools.data.find( - (tool) => tool.name.includes('fetch') || tool.name.includes('scrape') - ); - if (webScrapeTool) { - console.info('=== Example 3: Web Scraping with MCP ===\n'); - - await client.streamChatCompletion( - { - model, - messages: [ - { - role: MessageRole.system, - content: - 'You are a helpful assistant with access to web search capabilities.', - }, - { - role: MessageRole.user, - content: - 'Can you fetch information from https://httpbin.org/json and tell me what you find?', - }, - ], - max_tokens: 200, - }, - { - onOpen: () => console.info('πŸš€ Starting web scraping example...'), - onContent: (content) => process.stdout.write(content), - onTool: (toolCall) => { - console.info(`\nπŸ”§ Tool called: ${toolCall.function.name}`); - console.info(`πŸ“ Arguments: ${toolCall.function.arguments}`); - }, - onUsageMetrics: (usage) => { - updateTokens(usage); - }, - onFinish: () => { - console.info('\nβœ… Web scraping example completed'); - displayTokenUsage('Web Scraping Example'); - console.info(''); - }, - onError: (error) => console.error('❌ Error:', error), - }, - provider - ); - } - - // Example 4: Generic MCP tool usage - use the first available tool - if (tools.data.length > 0 && !fileReadTool && !webScrapeTool) { - console.info('=== Example 4: Generic MCP Tool Usage ===\n'); - - const firstTool = tools.data[0]; - console.info(`Using tool: ${firstTool.name}\n`); - - await client.streamChatCompletion( - { - model, - messages: [ - { - role: MessageRole.system, - content: `You are a helpful assistant with access to various tools including ${firstTool.name}.`, - }, - { - role: MessageRole.user, - content: `Can you help me use the ${firstTool.name} tool? What can it do?`, - }, - ], - max_tokens: 200, - }, - { - onOpen: () => console.info('πŸš€ Starting generic tool example...'), - onContent: (content) => process.stdout.write(content), - onTool: (toolCall) => { - console.info(`\nπŸ”§ Tool called: ${toolCall.function.name}`); - console.info(`πŸ“ Arguments: ${toolCall.function.arguments}`); - }, - onUsageMetrics: (usage) => { - updateTokens(usage); - }, - onFinish: () => { - console.info('\nβœ… Generic tool example completed'); - displayTokenUsage('Generic Tool Example'); - console.info(''); - }, - onError: (error) => console.error('❌ Error:', error), - }, - provider - ); - } - - // Example 5: Data Analysis with File Operations - if (tools.data.length > 1) { - console.info('=== Example 5: Data Analysis with File Operations ===\n'); - - await client.streamChatCompletion( - { - model, - messages: [ - { - role: MessageRole.system, - content: `You are a helpful data analysis assistant with access to filesystem tools. Available directory is /tmp. You can read, write, and analyze files. The /tmp directory contains sample data files for analysis.`, - }, - { - role: MessageRole.user, - content: - 'I need help with data analysis. First, can you check what files are available in the /tmp directory? Then create a simple CSV file with sample sales data in /tmp/sales_data.csv and analyze it.', - }, - ], - max_tokens: 400, - }, - { - onOpen: () => console.info('πŸš€ Starting data analysis example...'), - onContent: (content) => process.stdout.write(content), - onTool: (toolCall) => { - console.info(`\nπŸ”§ Tool called: ${toolCall.function.name}`); - console.info(`πŸ“ Arguments: ${toolCall.function.arguments}`); - }, - onUsageMetrics: (usage) => { - updateTokens(usage); - }, - onFinish: () => { - console.info('\nβœ… Data analysis example completed'); - displayTokenUsage('Data Analysis Example'); - console.info(''); - }, - onError: (error) => console.error('❌ Error:', error), - }, - provider - ); - } - - // Example 6: File Creation and Manipulation - console.info('=== Example 6: File Creation and Manipulation ===\n'); - - await client.streamChatCompletion( - { - model, - messages: [ - { - role: MessageRole.system, - content: `You are a helpful assistant with filesystem access. Available directory is /tmp. You can create, read, write, and manage files in this directory.`, - }, - { - role: MessageRole.user, - content: - 'Can you create a simple todo list file at /tmp/todo.txt with 3 sample tasks, then read it back to me?', - }, - ], - max_tokens: 300, - }, - { - onOpen: () => console.info('πŸš€ Starting file manipulation example...'), - onReasoning: (content) => process.stdout.write(content), - onContent: (content) => process.stdout.write(content), - onTool: (toolCall) => { - console.info(`\nπŸ”§ Tool called: ${toolCall.function.name}`); - console.info(`πŸ“ Arguments: ${toolCall.function.arguments}`); - }, - onUsageMetrics: (usage) => { - updateTokens(usage); - }, - onFinish: () => { - console.info('\nβœ… File manipulation example completed'); - displayTokenUsage('File Manipulation Example'); - console.info(''); - }, - onError: (error) => console.error('❌ Error:', error), - }, - provider - ); - } catch (error) { - if ( - error instanceof Error && - error.message.includes('MCP tools endpoint is not exposed') - ) { - console.error( - '❌ MCP tools are not exposed. Please ensure the Inference Gateway is started with EXPOSE_MCP=true' - ); - console.info('\nπŸ’‘ To fix this, restart the gateway with:'); - console.info(' docker-compose up --build'); - } else { - console.error('❌ Error:', error); - } - } finally { - // Display final token summary - console.info('\n' + '='.repeat(60)); - console.info('πŸ“Š FINAL TOKEN USAGE SUMMARY'); - console.info('='.repeat(60)); - console.info(`πŸ”’ Total Requests: ${tokenTracker.requestCount}`); - console.info( - `πŸ“Š Total Prompt Tokens: ${tokenTracker.totalPromptTokens.toLocaleString()}` - ); - console.info( - `✍️ Total Completion Tokens: ${tokenTracker.totalCompletionTokens.toLocaleString()}` - ); - console.info( - `🎯 Total Tokens Used: ${tokenTracker.totalTokens.toLocaleString()}` - ); - - if (tokenTracker.requestCount > 0) { - console.info( - `πŸ“ˆ Average Tokens per Request: ${Math.round( - tokenTracker.totalTokens / tokenTracker.requestCount - ).toLocaleString()}` - ); - } - - // Calculate cost estimate (example rates - adjust based on actual provider pricing) - const estimatedCost = tokenTracker.totalTokens * 0.000001; // Example: $0.000001 per token - console.info( - `πŸ’° Estimated Cost: $${estimatedCost.toFixed(6)} (example rate)` - ); - console.info('='.repeat(60)); - } -})(); diff --git a/examples/mcp/mcp-servers/README.md b/examples/mcp/mcp-servers/README.md deleted file mode 100644 index 20e4941..0000000 --- a/examples/mcp/mcp-servers/README.md +++ /dev/null @@ -1,241 +0,0 @@ -# MCP Servers - -This directory contains Model Context Protocol (MCP) servers that demonstrate how to build and integrate custom tools with the Inference Gateway. - -## Available Servers - -### 🌐 Web Search Server (`web-search/`) -- **Port**: 3001 -- **Tools**: `fetch_url`, `search_web`, `get_page_title` -- **Purpose**: Provides web content fetching and search capabilities -- **Features**: HTTP requests, simulated search, HTML parsing - -### πŸ“ Filesystem Server (`filesystem/`) -- **Port**: 3000 -- **Tools**: `read_file`, `write_file`, `list_directory`, `create_directory`, `delete_file`, `file_info` -- **Purpose**: Safe filesystem operations within allowed directories -- **Features**: File I/O, directory management, security restrictions - -## Quick Start - -### Individual Server Development - -Each server can be run independently for development: - -```bash -# Web Search Server -cd web-search -npm install -npm run dev - -# Filesystem Server -cd filesystem -npm install -npm run dev -``` - -### Docker Compose Integration - -All servers are configured to work together with the Inference Gateway: - -```bash -# From the main MCP example directory -docker-compose up -d -``` - -This will start: -- Inference Gateway (port 8080) -- MCP Filesystem Server (port 3000) -- MCP Web Search Server (port 3001) -- Optional: Ollama (port 11434) - -## Server Architecture - -Each MCP server follows a consistent structure: - -``` -server-name/ -β”œβ”€β”€ package.json # Dependencies and scripts -β”œβ”€β”€ index.js # Main server implementation -└── README.md # Server-specific documentation -``` - -### Core Endpoints - -All MCP servers implement these standard endpoints: - -- `GET /mcp` - Server information and capabilities -- `POST /mcp/tools/list` - List available tools -- `POST /mcp/tools/call` - Execute tools -- `GET /health` - Health check - -## Tool Development - -### Adding New Tools - -To add a new tool to an existing server: - -1. **Define the tool** in the `/mcp/tools/list` endpoint: -```javascript -{ - name: 'my_new_tool', - description: 'Description of what the tool does', - inputSchema: { - type: 'object', - properties: { - param1: { - type: 'string', - description: 'Parameter description' - } - }, - required: ['param1'] - } -} -``` - -2. **Implement the handler** function: -```javascript -async function handleMyNewTool(args, res) { - const { param1 } = args; - - try { - // Tool logic here - const result = await doSomething(param1); - - res.json({ - content: [ - { - type: 'text', - text: `Result: ${result}` - } - ] - }); - } catch (error) { - res.json({ - content: [ - { - type: 'text', - text: `Error: ${error.message}` - } - ] - }); - } -} -``` - -3. **Add the case** to the tool execution switch: -```javascript -case 'my_new_tool': - await handleMyNewTool(args, res); - break; -``` - -### Creating New Servers - -To create a new MCP server: - -1. **Create directory structure**: -```bash -mkdir mcp-servers/my-server -cd mcp-servers/my-server -``` - -2. **Create package.json**: -```json -{ - "name": "mcp-my-server", - "version": "1.0.0", - "main": "index.js", - "scripts": { - "start": "node index.js", - "dev": "node --watch index.js" - }, - "dependencies": { - "express": "^4.18.2", - "cors": "^2.8.5" - } -} -``` - -3. **Implement the server** following the existing patterns -4. **Add to docker-compose.yml** for integration -5. **Update Inference Gateway** MCP_SERVERS configuration - -## Integration with Inference Gateway - -The servers are configured to work with the Inference Gateway through environment variables: - -```yaml -# In docker-compose.yml -environment: - MCP_ENABLE: 'true' - MCP_EXPOSE: 'true' - MCP_SERVERS: 'filesystem=http://mcp-filesystem:3000/mcp,web-search=http://mcp-web-search:3001/mcp' -``` - -## Security Considerations - -### Filesystem Server -- Restricts operations to allowed directories -- Validates all file paths -- Prevents directory traversal attacks -- Implements proper error handling - -### Web Search Server -- Includes request timeouts -- Limits response sizes -- Handles various content types safely -- Provides safe error messages - -### General Security -- All servers run with minimal privileges -- Docker containers are isolated -- Health checks monitor server status -- Graceful shutdown handling - -## Testing - -Each server includes health check endpoints and can be tested independently: - -```bash -# Test server health -curl http://localhost:3000/health -curl http://localhost:3001/health - -# Test MCP endpoints -curl http://localhost:3000/mcp -curl -X POST http://localhost:3000/mcp/tools/list -``` - -## Monitoring - -Monitor server logs during development: - -```bash -# Follow logs for all services -docker-compose logs -f - -# Follow logs for specific service -docker-compose logs -f mcp-filesystem -docker-compose logs -f mcp-web-search -``` - -## Examples - -See the main MCP example (`../index.ts`) for complete usage examples showing how to: -- Discover available MCP tools -- Execute filesystem operations -- Perform web searches and content fetching -- Handle tool responses and errors - -## Contributing - -When contributing new servers or tools: - -1. Follow the established patterns and conventions -2. Include comprehensive error handling -3. Add proper validation for all inputs -4. Document all tools and parameters -5. Include health check endpoints -6. Test thoroughly with the Inference Gateway -7. Update this README with new server information diff --git a/examples/mcp/mcp-servers/brave-search/README.md b/examples/mcp/mcp-servers/brave-search/README.md new file mode 100644 index 0000000..ebc2331 --- /dev/null +++ b/examples/mcp/mcp-servers/brave-search/README.md @@ -0,0 +1,161 @@ +# Brave Search MCP Server + +A Model Context Protocol (MCP) server that provides search capabilities using the Brave Search API with HTTP transport support. + +## Features + +- **Web Search**: Comprehensive web search with SafeSearch filtering +- **News Search**: Recent news articles with content summaries +- **Marketing Research**: Product and brand research with market insights +- **HTTP Transport**: StreamableHTTP transport for web-based integrations +- **Rate Limiting**: Built-in rate limiting with exponential backoff +- **Session Management**: Proper session handling for MCP connections + +## Prerequisites + +- Node.js 18 or higher +- Brave Search API key from [Brave Search API](https://api.search.brave.com/) + +## Installation + +1. Install dependencies: + +```bash +npm install +``` + +2. Set up environment variables: + +```bash +export BRAVE_API_KEY="your-brave-search-api-key" +export PORT=3000 # Optional, defaults to 3000 +export LOG_LEVEL=info # Optional, defaults to info +``` + +## Usage + +### Start the Server + +```bash +npm start +``` + +The server will start on `http://localhost:3000` with the MCP endpoint available at `/mcp`. + +### Health Check + +Check if the server is running: + +```bash +curl http://localhost:3000/health +``` + +### MCP Tools Available + +1. **brave_web_search** + + - Searches the web using Brave Search + - Parameters: `query` (string), `count` (number, optional), `safesearch` (string, optional) + +2. **brave_news_search** + + - Searches for recent news articles + - Parameters: `query` (string), `count` (number, optional), `freshness` (string, optional) + +3. **marketing_research** + - Performs market research and competitive analysis + - Parameters: `query` (string), `focus_area` (string, optional) + +## Configuration + +### Environment Variables + +- `BRAVE_API_KEY`: Your Brave Search API key (required) +- `PORT`: Server port (default: 3000) +- `LOG_LEVEL`: Logging level - debug, info, warn, error (default: info) + +### Rate Limiting + +The server implements rate limiting to respect Brave Search API limits: + +- Maximum 10 requests per second +- Exponential backoff on rate limit errors +- Automatic retry with increasing delays + +## Development + +### Build + +```bash +npm run build +``` + +### Development Mode + +```bash +npm run dev +``` + +### Linting + +```bash +npm run lint +``` + +## API Reference + +### MCP Endpoint + +**POST /mcp** + +- Establishes MCP connection using StreamableHTTP transport +- Handles tool calls and resource requests +- Returns JSON responses with MCP protocol format + +**GET /mcp** + +- Returns server information and available tools +- Used for capability discovery + +**DELETE /mcp** + +- Closes MCP session +- Cleans up resources + +### Example MCP Tool Call + +```json +{ + "jsonrpc": "2.0", + "id": "1", + "method": "tools/call", + "params": { + "name": "brave_web_search", + "arguments": { + "query": "artificial intelligence trends 2024", + "count": 5, + "safesearch": "moderate" + } + } +} +``` + +## Error Handling + +The server includes comprehensive error handling: + +- API rate limiting with retry logic +- Input validation using Zod schemas +- Structured logging for debugging +- Graceful error responses + +## Security + +- Input validation on all parameters +- SafeSearch filtering for web searches +- Rate limiting to prevent abuse +- Structured logging without sensitive data exposure + +## License + +MIT License - see LICENSE file for details. diff --git a/examples/mcp/mcp-servers/brave-search/index.ts b/examples/mcp/mcp-servers/brave-search/index.ts new file mode 100644 index 0000000..8909e6d --- /dev/null +++ b/examples/mcp/mcp-servers/brave-search/index.ts @@ -0,0 +1,736 @@ +/** + * MCP Brave Search Server + * + * This is a Model Context Protocol (MCP) server that provides Brave Search API + * capabilities. It uses the official MCP TypeScript SDK and implements the + * proper MCP protocol with Streamable HTTP transport to bridge stdio-only + * Brave Search containers to HTTP endpoints. + */ + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import axios from 'axios'; +import cors from 'cors'; +import express from 'express'; +import { randomUUID } from 'node:crypto'; +import { setTimeout } from 'node:timers/promises'; +import { z } from 'zod'; +import { createMcpLogger } from './logger.js'; + +const logger = createMcpLogger('mcp-brave-search', '1.0.0'); + +let lastSearchTime = 0; + +// Rate limiting configuration based on plan +const RATE_LIMITS = { + free: { interval: 1000, maxRetries: 5 }, // 1 query/second + base: { interval: 50, maxRetries: 3 }, // 20 queries/second + pro: { interval: 20, maxRetries: 3 }, // 50 queries/second +}; + +const getCurrentRateLimit = () => { + const planType = (process.env.BRAVE_API_PLAN || 'free').toLowerCase(); + return RATE_LIMITS[planType as keyof typeof RATE_LIMITS] || RATE_LIMITS.free; +}; + +/** + * Rate-limited search function to avoid hitting API limits + */ +const rateLimitedBraveSearch = async ( + endpoint: string, + params: Record, + retries?: number +) => { + const rateLimit = getCurrentRateLimit(); + const maxRetries = retries || rateLimit.maxRetries; + + const now = Date.now(); + const timeSinceLastSearch = now - lastSearchTime; + + if (timeSinceLastSearch < rateLimit.interval) { + const delay = rateLimit.interval - timeSinceLastSearch; + logger.info('Rate limiting: delaying search', { + delay, + endpoint, + interval: rateLimit.interval, + plan: process.env.BRAVE_API_PLAN || 'free', + }); + await setTimeout(delay); + } + + lastSearchTime = Date.now(); + + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const apiKey = process.env.BRAVE_API_KEY; + if (!apiKey) { + throw new Error('BRAVE_API_KEY environment variable is required'); + } + + const response = await axios.get( + `https://api.search.brave.com/res/v1/${endpoint}`, + { + params, + headers: { + Accept: 'application/json', + 'Accept-Encoding': 'gzip', + 'X-Subscription-Token': apiKey, + }, + timeout: 30000, + } + ); + + logger.info('Brave Search API request successful', { + endpoint, + statusCode: response.status, + attempt, + plan: process.env.BRAVE_API_PLAN || 'free', + }); + + return response.data; + } catch (error: any) { + const is429Error = error.response?.status === 429; + const isRateLimitError = + error.message.includes('rate limit') || + error.message.includes('Too Many Requests'); + + if (is429Error || isRateLimitError) { + if (attempt < maxRetries) { + const baseDelay = rateLimit.interval * Math.pow(2, attempt - 1); + const jitter = Math.random() * 1000; + const backoffDelay = baseDelay + jitter; + + logger.warn('Brave Search rate limit hit, retrying', { + attempt, + retries: maxRetries, + delay: backoffDelay, + endpoint, + error: error.message, + statusCode: error.response?.status, + plan: process.env.BRAVE_API_PLAN || 'free', + }); + + await setTimeout(backoffDelay); + continue; + } else { + logger.error('Brave Search rate limit exceeded after all retries', { + attempts: maxRetries, + endpoint, + error: error.message, + statusCode: error.response?.status, + plan: process.env.BRAVE_API_PLAN || 'free', + }); + } + } + + if (!is429Error && !isRateLimitError) { + logger.error('Brave Search API error (non-rate-limit)', { + endpoint, + error: error.message, + statusCode: error.response?.status, + attempt, + }); + } + + throw error; + } + } +}; + +const app = express(); +app.use(cors()); +app.use(express.json({ limit: '10mb' })); + +app.use((req, res, next) => { + const startTime = Date.now(); + const requestId = randomUUID(); + + (req as any).requestId = requestId; + + logger.info('Incoming request', { + requestId, + method: req.method, + url: req.url, + userAgent: req.get('User-Agent'), + contentType: req.get('Content-Type'), + sessionId: req.headers['mcp-session-id'], + }); + + res.on('finish', () => { + const duration = Date.now() - startTime; + logger.info('Request completed', { + requestId, + method: req.method, + url: req.url, + statusCode: res.statusCode, + duration, + sessionId: req.headers['mcp-session-id'], + }); + }); + + next(); +}); + +const transports = new Map(); + +/** + * Enhanced error handling for tools + */ +function createToolError(error: Error, context: Record = {}) { + logger.error('Tool execution error', { + error: error.message, + stack: error.stack, + context, + }); + + return { + content: [ + { + type: 'text' as const, + text: `Error: ${error.message}`, + }, + ], + isError: true, + }; +} + +/** + * Create and configure the MCP server + */ +function createMcpServer() { + logger.info('Creating MCP server instance'); + + const mcpServer = new McpServer({ + name: 'brave-search', + version: '1.0.0', + }); + + mcpServer.tool( + 'brave_web_search', + 'Search the web using Brave Search API with SafeSearch filtering', + { + query: z.string().min(1).max(500).describe('The search query to execute'), + count: z + .number() + .min(1) + .max(20) + .default(10) + .describe('Maximum number of results to return'), + country: z + .string() + .length(2) + .optional() + .describe('Country code for localized results (e.g., US, GB, DE)'), + safesearch: z + .enum(['strict', 'moderate', 'off']) + .default('moderate') + .describe('Safe search setting'), + freshness: z + .enum(['pd', 'pw', 'pm', 'py']) + .optional() + .describe( + 'Freshness filter: pd=past day, pw=past week, pm=past month, py=past year' + ), + text_decorations: z + .boolean() + .default(false) + .describe('Include text decorations in results'), + }, + async ({ + query, + count = 10, + country, + safesearch = 'moderate', + freshness, + text_decorations = false, + }) => { + const operationId = randomUUID(); + + try { + logger.info('Performing Brave web search', { + operationId, + query, + count, + country, + safesearch, + freshness, + }); + + const params: Record = { + q: query, + count: Math.min(count, 20), + safesearch, + text_decorations, + }; + + if (country) params.country = country; + if (freshness) params.freshness = freshness; + + const searchResults = await rateLimitedBraveSearch( + 'web/search', + params + ); + + if ( + !searchResults?.web?.results || + searchResults.web.results.length === 0 + ) { + logger.warn('No search results found', { operationId, query }); + return { + content: [ + { + type: 'text' as const, + text: `No web search results found for "${query}".`, + }, + ], + }; + } + + const results = searchResults.web.results.slice(0, count); + const formattedResults = results + .map((result: any, index: number) => { + return `${index + 1}. ${result.title}\n ${result.url}\n ${result.description || 'No description available'}\n`; + }) + .join('\n'); + + logger.info('Brave web search completed successfully', { + operationId, + query, + resultCount: results.length, + }); + + return { + content: [ + { + type: 'text' as const, + text: `Brave Web Search Results for "${query}":\n\n${formattedResults}`, + }, + ], + }; + } catch (error: any) { + logger.error('Failed to perform Brave web search', { + operationId, + query, + error: error.message, + stack: error.stack, + }); + + return createToolError( + new Error(`Failed to search Brave: ${error.message}`), + { query, operationId } + ); + } + } + ); + + mcpServer.tool( + 'brave_news_search', + 'Search for recent news articles using Brave Search API', + { + query: z + .string() + .min(1) + .max(500) + .describe('The news search query to execute'), + count: z + .number() + .min(1) + .max(20) + .default(10) + .describe('Maximum number of news results to return'), + country: z + .string() + .length(2) + .optional() + .describe('Country code for localized news (e.g., US, GB, DE)'), + freshness: z + .enum(['pd', 'pw', 'pm', 'py']) + .optional() + .describe( + 'Freshness filter: pd=past day, pw=past week, pm=past month, py=past year' + ), + }, + async ({ query, count = 10, country, freshness }) => { + const operationId = randomUUID(); + + try { + logger.info('Performing Brave news search', { + operationId, + query, + count, + country, + freshness, + }); + + const params: Record = { + q: query, + count: Math.min(count, 20), + }; + + if (country) params.country = country; + if (freshness) params.freshness = freshness; + + const searchResults = await rateLimitedBraveSearch( + 'news/search', + params + ); + + if (!searchResults?.results || searchResults.results.length === 0) { + logger.warn('No news results found', { operationId, query }); + return { + content: [ + { + type: 'text' as const, + text: `No news results found for "${query}".`, + }, + ], + }; + } + + const results = searchResults.results.slice(0, count); + const formattedResults = results + .map((result: any, index: number) => { + const publishedDate = result.age ? ` (${result.age})` : ''; + return `${index + 1}. ${result.title}${publishedDate}\n ${result.url}\n ${result.description || 'No description available'}\n`; + }) + .join('\n'); + + logger.info('Brave news search completed successfully', { + operationId, + query, + resultCount: results.length, + }); + + return { + content: [ + { + type: 'text' as const, + text: `Brave News Search Results for "${query}":\n\n${formattedResults}`, + }, + ], + }; + } catch (error: any) { + logger.error('Failed to perform Brave news search', { + operationId, + query, + error: error.message, + stack: error.stack, + }); + + return createToolError( + new Error(`Failed to search Brave News: ${error.message}`), + { query, operationId } + ); + } + } + ); + + mcpServer.tool( + 'marketing_research', + 'Perform automated competitive market research using Brave Search', + { + brand: z + .string() + .min(1) + .max(100) + .describe('The brand or company to research'), + competitors: z + .array(z.string()) + .optional() + .describe('List of competitor brands to include in research'), + topics: z + .array(z.string()) + .default(['reviews', 'pricing', 'features']) + .describe('Research topics to focus on'), + country: z + .string() + .length(2) + .default('US') + .describe('Country code for localized research'), + }, + async ({ + brand, + competitors = [], + topics = ['reviews', 'pricing', 'features'], + country = 'US', + }) => { + const operationId = randomUUID(); + + try { + logger.info('Performing marketing research', { + operationId, + brand, + competitors, + topics, + country, + }); + + const allBrands = [brand, ...competitors]; + const researchResults = []; + + for (const currentBrand of allBrands) { + for (const topic of topics) { + const query = `${currentBrand} ${topic}`; + + try { + const params = { + q: query, + count: 5, + country, + freshness: 'pm', + }; + + const searchResults = await rateLimitedBraveSearch( + 'web/search', + params + ); + + if (searchResults?.web?.results) { + researchResults.push({ + brand: currentBrand, + topic, + results: searchResults.web.results.slice(0, 3), + }); + } + + await setTimeout(500); + } catch (error: any) { + logger.warn('Failed to search for brand topic', { + brand: currentBrand, + topic, + error: error.message, + }); + } + } + } + + if (researchResults.length === 0) { + return { + content: [ + { + type: 'text' as const, + text: `No marketing research data found for "${brand}".`, + }, + ], + }; + } + + let formattedOutput = `Marketing Research Report for "${brand}"\n`; + formattedOutput += `=`.repeat(50) + '\n\n'; + + const brandResults = researchResults.reduce((acc: any, result) => { + if (!acc[result.brand]) acc[result.brand] = {}; + if (!acc[result.brand][result.topic]) + acc[result.brand][result.topic] = []; + acc[result.brand][result.topic].push(...result.results); + return acc; + }, {}); + + for (const [brandName, brandTopics] of Object.entries(brandResults)) { + formattedOutput += `## ${brandName}\n\n`; + + for (const [topicName, topicResults] of Object.entries( + brandTopics as any + )) { + formattedOutput += `### ${topicName.charAt(0).toUpperCase() + topicName.slice(1)}\n`; + + (topicResults as any[]).forEach((result, index) => { + formattedOutput += `${index + 1}. ${result.title}\n`; + formattedOutput += ` ${result.url}\n`; + formattedOutput += ` ${result.description || 'No description available'}\n\n`; + }); + } + formattedOutput += '\n'; + } + + logger.info('Marketing research completed successfully', { + operationId, + brand, + totalResults: researchResults.length, + }); + + return { + content: [ + { + type: 'text' as const, + text: formattedOutput, + }, + ], + }; + } catch (error: any) { + logger.error('Failed to perform marketing research', { + operationId, + brand, + error: error.message, + stack: error.stack, + }); + + return createToolError( + new Error(`Failed to perform marketing research: ${error.message}`), + { brand, operationId } + ); + } + } + ); + + return mcpServer; +} + +app.post('/mcp', async (req, res) => { + try { + logger.info('MCP POST request received', { + headers: req.headers, + bodyKeys: Object.keys(req.body || {}), + method: req.body?.method, + }); + + const accept = req.headers.accept || req.headers.Accept; + if ( + !accept || + !accept.includes('application/json') || + !accept.includes('text/event-stream') + ) { + logger.info('Adding missing Accept headers for MCP compatibility'); + req.headers.accept = 'application/json, text/event-stream'; + } + + const sessionId = req.headers['mcp-session-id'] as string; + let transport: any; + + if (sessionId && transports.has(sessionId)) { + transport = transports.get(sessionId); + logger.info('Using existing session', { sessionId }); + } else { + const newSessionId = randomUUID(); + logger.info('Creating new MCP session', { sessionId: newSessionId }); + + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => newSessionId, + onsessioninitialized: (initSessionId: string) => { + logger.info('MCP session initialized', { sessionId: initSessionId }); + transports.set(initSessionId, transport); + }, + }); + + transport.onclose = () => { + if ((transport as any).sessionId) { + logger.info('MCP session closed', { + sessionId: (transport as any).sessionId, + }); + transports.delete((transport as any).sessionId); + } + }; + + const server = createMcpServer(); + await server.connect(transport); + + transports.set(newSessionId, transport); + + res.setHeader('mcp-session-id', newSessionId); + } + + await transport.handleRequest(req, res, req.body); + } catch (error: any) { + logger.error('Error handling MCP request', { + error: error.message, + stack: error.stack, + method: req.body?.method, + sessionId: req.headers['mcp-session-id'], + }); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error', + }, + id: null, + }); + } + } +}); + +app.get('/mcp', async (req, res) => { + const sessionId = req.headers['mcp-session-id'] as string; + if (!sessionId || !transports.has(sessionId)) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + const transport = transports.get(sessionId); + await transport!.handleRequest(req, res); +}); + +app.delete('/mcp', async (req, res) => { + const sessionId = req.headers['mcp-session-id'] as string; + if (!sessionId || !transports.has(sessionId)) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + const transport = transports.get(sessionId); + await transport!.handleRequest(req, res); +}); + +app.get('/health', (req, res) => { + const healthStatus = { + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + service: 'mcp-brave-search', + version: '1.0.0', + protocol: 'Model Context Protocol', + transport: 'Streamable HTTP', + api_key_configured: !!process.env.BRAVE_API_KEY, + }; + + logger.info('Health check requested', healthStatus); + + res.json(healthStatus); +}); + +const port = parseInt(process.env.PORT || '3002'); +const host = process.env.HOST || '0.0.0.0'; + +app.listen(port, host, () => { + logger.info(`MCP Brave Search server running on http://${host}:${port}`); + logger.info('Protocol: Model Context Protocol (MCP)'); + logger.info('Transport: Streamable HTTP'); + logger.info('Available endpoints:'); + logger.info(' POST /mcp - MCP protocol endpoint'); + logger.info( + ' GET /mcp - SSE notifications (with session-id header)' + ); + logger.info( + ' DELETE /mcp - Session termination (with session-id header)' + ); + logger.info(' GET /health - Health check'); + logger.info('Available tools:'); + logger.info(' - brave_web_search - Web search using Brave Search API'); + logger.info(' - brave_news_search - News search using Brave Search API'); + logger.info( + ' - marketing_research - Automated competitive market research' + ); + + if (!process.env.BRAVE_API_KEY) { + logger.warn( + 'BRAVE_API_KEY environment variable not set - search functionality will be limited' + ); + } +}); + +process.on('SIGTERM', () => { + logger.info('Received SIGTERM, shutting down gracefully'); + Array.from(transports.values()).forEach((transport: any) => { + if (transport.close) transport.close(); + }); + process.exit(0); +}); + +process.on('SIGINT', () => { + logger.info('Received SIGINT, shutting down gracefully'); + Array.from(transports.values()).forEach((transport: any) => { + if (transport.close) transport.close(); + }); + process.exit(0); +}); diff --git a/examples/mcp/mcp-servers/brave-search/logger.ts b/examples/mcp/mcp-servers/brave-search/logger.ts new file mode 100644 index 0000000..a4788a8 --- /dev/null +++ b/examples/mcp/mcp-servers/brave-search/logger.ts @@ -0,0 +1,114 @@ +/** + * Standardized Winston Logger for MCP Servers + * + * Provides consistent, single-line logging across all MCP servers + * with structured metadata and unified formatting. + */ + +import winston from 'winston'; + +/** + * Create a standardized logger for MCP servers + * @param serviceName - The name of the MCP service + * @param version - The version of the service + * @param logLevel - Override log level (defaults to LOG_LEVEL env var or 'info') + * @returns Configured Winston logger + */ +export function createMcpLogger( + serviceName: string, + version = '1.0.0', + logLevel?: string +): winston.Logger { + const level = logLevel || process.env.LOG_LEVEL || 'info'; + + return winston.createLogger({ + level, + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { + service: serviceName, + version, + protocol: 'Model Context Protocol', + transport: 'Streamable HTTP', + }, + transports: [ + new winston.transports.Console({ + handleExceptions: true, + handleRejections: true, + }), + ], + }); +} + +/** + * Log MCP request received + */ +export function logMcpRequest( + logger: winston.Logger, + req: any, + additionalMeta: Record = {} +): void { + const sessionId = req.headers['mcp-session-id']; + const method = req.body?.method; + const id = req.body?.id; + + logger.info('MCP request received', { + sessionId, + method, + requestId: id, + userAgent: req.headers['user-agent'], + contentLength: req.headers['content-length'], + ...additionalMeta, + }); +} + +/** + * Log MCP session events + */ +export function logMcpSession( + logger: winston.Logger, + event: string, + sessionId: string, + additionalMeta: Record = {} +): void { + logger.info(`MCP session ${event}`, { + sessionId, + ...additionalMeta, + }); +} + +/** + * Log MCP tool calls + */ +export function logMcpToolCall( + logger: winston.Logger, + toolName: string, + sessionId: string, + args: Record = {}, + additionalMeta: Record = {} +): void { + logger.info(`MCP tool called: ${toolName}`, { + sessionId, + tool: toolName, + args: Object.keys(args), + ...additionalMeta, + }); +} + +/** + * Log MCP errors with context + */ +export function logMcpError( + logger: winston.Logger, + error: Error, + context: Record = {} +): void { + logger.error('MCP error occurred', { + error: error.message, + stack: error.stack, + ...context, + }); +} diff --git a/examples/mcp/mcp-servers/brave-search/package-lock.json b/examples/mcp/mcp-servers/brave-search/package-lock.json new file mode 100644 index 0000000..0bc3ee1 --- /dev/null +++ b/examples/mcp/mcp-servers/brave-search/package-lock.json @@ -0,0 +1,2472 @@ +{ + "name": "mcp-brave-search-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-brave-search-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "axios": "^1.6.0", + "cors": "^2.8.5", + "express": "^4.18.2", + "winston": "^3.17.0", + "zod": "^3.22.0" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^20.10.0", + "tsx": "^4.19.4", + "typescript": "^5.3.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", + "integrity": "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.18", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.18.tgz", + "integrity": "sha512-nX3d0sxJW41CqQvfOzVG1NCTXfFDrDWIghCZncpHeWlVFd81zxB/DLhg7avFg6eHLCRX7ckBmoIIcqa++upvJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.22.tgz", + "integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.17.57", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.57.tgz", + "integrity": "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.5", + "@esbuild/android-arm": "0.25.5", + "@esbuild/android-arm64": "0.25.5", + "@esbuild/android-x64": "0.25.5", + "@esbuild/darwin-arm64": "0.25.5", + "@esbuild/darwin-x64": "0.25.5", + "@esbuild/freebsd-arm64": "0.25.5", + "@esbuild/freebsd-x64": "0.25.5", + "@esbuild/linux-arm": "0.25.5", + "@esbuild/linux-arm64": "0.25.5", + "@esbuild/linux-ia32": "0.25.5", + "@esbuild/linux-loong64": "0.25.5", + "@esbuild/linux-mips64el": "0.25.5", + "@esbuild/linux-ppc64": "0.25.5", + "@esbuild/linux-riscv64": "0.25.5", + "@esbuild/linux-s390x": "0.25.5", + "@esbuild/linux-x64": "0.25.5", + "@esbuild/netbsd-arm64": "0.25.5", + "@esbuild/netbsd-x64": "0.25.5", + "@esbuild/openbsd-arm64": "0.25.5", + "@esbuild/openbsd-x64": "0.25.5", + "@esbuild/sunos-x64": "0.25.5", + "@esbuild/win32-arm64": "0.25.5", + "@esbuild/win32-ia32": "0.25.5", + "@esbuild/win32-x64": "0.25.5" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/tsx": { + "version": "4.19.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.4.tgz", + "integrity": "sha512-gK5GVzDkJK1SI1zwHf32Mqxf2tSJkNx+eYcNly5+nHvWqXUJYUkWBQtKauoESz3ymezAI++ZwT855x5p5eop+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.25.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.48", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.48.tgz", + "integrity": "sha512-0X1mz8FtgEIvaxGjdIImYpZEaZMrund9pGXm3M6vM7Reba0e2eI71KPjSCGXBfwKDPwPoywf6waUKc3/tFvX2Q==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/examples/mcp/mcp-servers/brave-search/package.json b/examples/mcp/mcp-servers/brave-search/package.json new file mode 100644 index 0000000..007d619 --- /dev/null +++ b/examples/mcp/mcp-servers/brave-search/package.json @@ -0,0 +1,34 @@ +{ + "name": "mcp-brave-search-server", + "version": "1.0.0", + "description": "MCP Brave Search Server with Streamable HTTP transport bridge", + "type": "module", + "main": "index.ts", + "scripts": { + "start": "tsx index.ts", + "dev": "tsx --watch index.ts", + "build": "tsc", + "lint": "eslint .", + "test": "jest" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "axios": "^1.6.0", + "cors": "^2.8.5", + "express": "^4.18.2", + "winston": "^3.17.0", + "zod": "^3.22.0" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/node": "^20.10.0", + "tsx": "^4.19.4", + "typescript": "^5.3.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "author": "Inference Gateway Team", + "license": "MIT" +} diff --git a/examples/mcp/mcp-servers/brave-search/tsconfig.json b/examples/mcp/mcp-servers/brave-search/tsconfig.json new file mode 100644 index 0000000..34147ee --- /dev/null +++ b/examples/mcp/mcp-servers/brave-search/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "node", + "strict": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "outDir": "./dist", + "rootDir": "./", + "resolveJsonModule": true, + "types": ["node"] + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/examples/mcp/mcp-servers/context7/README.md b/examples/mcp/mcp-servers/context7/README.md new file mode 100644 index 0000000..4e4ab73 --- /dev/null +++ b/examples/mcp/mcp-servers/context7/README.md @@ -0,0 +1,30 @@ +# Context7 HTTP Bridge + +This service provides an HTTP interface for the stdio-based Context7 MCP server from Upstash, allowing it to work with the Inference Gateway. + +## Architecture + +``` +Inference Gateway β†’ HTTP Bridge β†’ stdio β†’ Real Context7 MCP Server +``` + +## Features + +- βœ… HTTP-to-stdio protocol bridge +- βœ… Real Context7 integration with Upstash +- βœ… Automatic Context7 server spawning +- βœ… Proper error handling and timeouts +- βœ… Health check endpoint + +## Environment Variables + +- `PORT` - Server port (default: 3002) + +## Available Tools + +1. **c41_resolve-library-id** - Resolve library names to Context7 IDs +2. **c41_get-library-docs** - Fetch up-to-date library documentation + +## Usage + +The bridge automatically spawns the real Context7 MCP server for each tool call and handles the stdio communication protocol. diff --git a/examples/mcp/mcp-servers/context7/index.js b/examples/mcp/mcp-servers/context7/index.js new file mode 100644 index 0000000..53603f7 --- /dev/null +++ b/examples/mcp/mcp-servers/context7/index.js @@ -0,0 +1,843 @@ +/** + * Context7 HTTP Bridge + * + * This service provides an HTTP interface for the stdio-based Context7 MCP server from Upstash, + * allowing it to work with the Inference Gateway. It spawns the real Context7 MCP server + * as a child process and communicates via stdio using the MCP protocol. + */ + +import express from 'express'; +import cors from 'cors'; +import { spawn } from 'node:child_process'; +import { randomUUID } from 'node:crypto'; +import { + createMcpLogger, + logMcpRequest, + logMcpSession, + logMcpToolCall, + logMcpError, +} from './logger.js'; + +const app = express(); +app.use(express.json()); +app.use(cors()); + +const logger = createMcpLogger('mcp-context7', '1.0.0'); + +const mcpSessions = new Map(); +const context7Processes = new Map(); + +/** + * Create a Context7 process and manage its lifecycle + */ +class Context7Process { + constructor() { + this.process = null; + this.messageId = 0; + this.pendingRequests = new Map(); + this.isReady = false; + this.readyPromise = null; + } + + async start() { + if (this.readyPromise) { + return this.readyPromise; + } + + this.readyPromise = new Promise((resolve, reject) => { + logger.info('Spawning Context7 MCP server', { + service: 'context7-process', + }); + + this.process = spawn('npx', ['-y', '@upstash/context7-mcp@latest'], { + stdio: ['pipe', 'pipe', 'pipe'], + env: { + ...process.env, + NODE_ENV: 'production', + }, + }); + + let buffer = ''; + + this.process.stdout.on('data', (data) => { + buffer += data.toString(); + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; // Keep incomplete line in buffer + + for (const line of lines) { + if (line.trim()) { + try { + const message = JSON.parse(line.trim()); + this.handleMessage(message); + } catch { + logger.warn('Non-JSON output from Context7', { + service: 'context7-process', + output: line.trim(), + }); + } + } + } + }); + + this.process.stderr.on('data', (data) => { + const message = data.toString().trim(); + if (message.includes('ready') || message.includes('listening')) { + logger.info('Context7 MCP server ready', { + service: 'context7-process', + }); + this.isReady = true; + resolve(); + } else { + logger.info('Context7 log', { + service: 'context7-process', + message, + }); + } + }); + + this.process.on('exit', (code) => { + logger.info('Context7 process exited', { + service: 'context7-process', + exitCode: code, + }); + this.isReady = false; + this.process = null; + + for (const [, { reject }] of this.pendingRequests) { + reject(new Error('Context7 process terminated')); + } + this.pendingRequests.clear(); + }); + + this.process.on('error', (error) => { + logMcpError(logger, 'Context7 process error', error, { + service: 'context7-process', + }); + reject(error); + }); + globalThis.setTimeout(() => { + this.sendInitialize(); + }, 2000); + }); + + return this.readyPromise; + } + + sendInitialize() { + logger.info('Sending initialize to Context7', { + service: 'context7-process', + }); + const initMessage = { + jsonrpc: '2.0', + id: this.nextMessageId(), + method: 'initialize', + params: { + protocolVersion: '0.1.0', + capabilities: { + tools: {}, + }, + clientInfo: { + name: 'context7-bridge', + version: '1.0.0', + }, + }, + }; + + const messageId = initMessage.id; + const initPromise = new Promise((resolve, reject) => { + this.pendingRequests.set(messageId, { resolve, reject }); + + globalThis.setTimeout(() => { + if (this.pendingRequests.has(messageId)) { + this.pendingRequests.delete(messageId); + reject(new Error('Context7 initialization timeout')); + } + }, 10000); + }); + + this.sendMessage(initMessage); + + initPromise + .then(() => { + logger.info('Context7 initialized successfully', { + service: 'context7-process', + }); + this.isReady = true; + globalThis.setTimeout(() => { + this.sendMessage({ + jsonrpc: '2.0', + method: 'notifications/initialized', + }); + }, 100); + }) + .catch((error) => { + logMcpError(logger, error, { + service: 'context7-process', + operation: 'initialization', + }); + }); + } + + nextMessageId() { + return ++this.messageId; + } + + sendMessage(message) { + if (!this.process || !this.process.stdin.writable) { + throw new Error('Context7 process not available'); + } + + const jsonMessage = JSON.stringify(message) + '\n'; + logger.debug('Sending message to Context7', { + service: 'context7-process', + method: message.method, + id: message.id, + message: jsonMessage.trim(), + }); + this.process.stdin.write(jsonMessage); + } + + handleMessage(message) { + logger.debug('Received message from Context7', { + service: 'context7-process', + method: message.method, + id: message.id, + hasError: !!message.error, + }); + + if (message.id && this.pendingRequests.has(message.id)) { + const { resolve, reject } = this.pendingRequests.get(message.id); + this.pendingRequests.delete(message.id); + + if (message.error) { + logMcpError(logger, message.error, { + service: 'context7-process', + requestId: message.id, + }); + reject(new Error(message.error.message || 'Context7 error')); + return; + } + + logger.debug('Context7 success response', { + service: 'context7-process', + requestId: message.id, + }); + resolve(message.result || message); + return; + } + + if ( + message.result && + message.result.serverInfo && + message.result.serverInfo.name === 'Context7' + ) { + logger.info('Context7 initialized successfully from serverInfo', { + service: 'context7-process', + serverInfo: message.result.serverInfo, + }); + this.isReady = true; + return; + } + + switch (message.method) { + case 'notifications/initialized': + logger.info('Context7 initialized notification received', { + service: 'context7-process', + }); + this.isReady = true; + break; + default: + if (message.method) { + logger.debug('Context7 notification received', { + service: 'context7-process', + method: message.method, + }); + } else if (message.id) { + logger.warn('Received response for unknown request ID', { + service: 'context7-process', + requestId: message.id, + }); + } else { + logger.debug('Context7 message with no ID or method', { + service: 'context7-process', + }); + } + } + } + + async callTool(name, args) { + if (!this.isReady) { + await this.start(); + } + + const messageId = this.nextMessageId(); + const message = { + jsonrpc: '2.0', + id: messageId, + method: 'tools/call', + params: { + name, + arguments: args, + }, + }; + + return new Promise((resolve, reject) => { + this.pendingRequests.set(messageId, { resolve, reject }); + + globalThis.setTimeout(() => { + if (this.pendingRequests.has(messageId)) { + this.pendingRequests.delete(messageId); + reject(new Error('Context7 request timeout')); + } + }, 30000); + + this.sendMessage(message); + }); + } + + requestToolsList() { + logger.info('Requesting tools list from Context7', { + service: 'context7-process', + }); + const toolsListMessage = { + jsonrpc: '2.0', + id: this.nextMessageId(), + method: 'tools/list', + params: {}, + }; + + const messageId = toolsListMessage.id; + const toolsPromise = new Promise((resolve, reject) => { + this.pendingRequests.set(messageId, { resolve, reject }); + + globalThis.setTimeout(() => { + if (this.pendingRequests.has(messageId)) { + this.pendingRequests.delete(messageId); + reject(new Error('Context7 tools list timeout')); + } + }, 5000); + }); + + this.sendMessage(toolsListMessage); + + toolsPromise + .then((result) => { + logger.info('Context7 tools list received', { + service: 'context7-process', + toolsCount: result?.tools?.length || 0, + }); + }) + .catch((error) => { + logMcpError(logger, error, { + service: 'context7-process', + operation: 'tools-list', + }); + }); + } + + async listTools() { + if (!this.isReady) { + await this.start(); + } + + const messageId = this.nextMessageId(); + const message = { + jsonrpc: '2.0', + id: messageId, + method: 'tools/list', + }; + + return new Promise((resolve, reject) => { + this.pendingRequests.set(messageId, { resolve, reject }); + + globalThis.setTimeout(() => { + if (this.pendingRequests.has(messageId)) { + this.pendingRequests.delete(messageId); + reject(new Error('Context7 list tools timeout')); + } + }, 10000); + + this.sendMessage(message); + }); + } + + terminate() { + if (this.process) { + logger.info('Terminating Context7 process', { + service: 'context7-process', + }); + this.process.kill('SIGTERM'); + + globalThis.setTimeout(() => { + if (this.process) { + logger.warn('Force killing Context7 process', { + service: 'context7-process', + }); + this.process.kill('SIGKILL'); + } + }, 5000); + } + } +} + +/** + * Get or create a Context7 process instance + */ +function getContext7Process(sessionId = 'default') { + if (!context7Processes.has(sessionId)) { + const process = new Context7Process(); + context7Processes.set(sessionId, process); + + globalThis.setTimeout( + () => { + if (context7Processes.has(sessionId)) { + const proc = context7Processes.get(sessionId); + proc.terminate(); + context7Processes.delete(sessionId); + } + }, + 10 * 60 * 1000 + ); + } + + return context7Processes.get(sessionId); +} + +/** + * Handle MCP request directly without SDK Server + */ +async function handleMcpRequest(request) { + if (!request || !request.method) { + throw new Error('Invalid request: missing method'); + } + + switch (request.method) { + case 'initialize': { + logger.info('MCP initialize request received', { + protocolVersion: request.params?.protocolVersion, + clientInfo: request.params?.clientInfo, + }); + + return { + jsonrpc: '2.0', + id: request.id, + result: { + protocolVersion: '0.1.0', + capabilities: { + tools: {}, + }, + serverInfo: { + name: 'context7-bridge', + version: '1.0.0', + }, + }, + }; + } + + case 'tools/list': { + logger.info('MCP tools/list request received'); + + return { + jsonrpc: '2.0', + id: request.id, + result: { + tools: [ + { + name: 'c41_resolve-library-id', + description: + 'Resolve library names to Context7-compatible library IDs', + inputSchema: { + type: 'object', + properties: { + libraryName: { + type: 'string', + description: + 'Library name to search for and retrieve a Context7-compatible library ID', + }, + }, + required: ['libraryName'], + }, + }, + { + name: 'c41_get-library-docs', + description: 'Fetch up-to-date library documentation', + inputSchema: { + type: 'object', + properties: { + context7CompatibleLibraryID: { + type: 'string', + description: + 'Exact Context7-compatible library ID (e.g., "/mongodb/docs", "/vercel/next.js")', + }, + tokens: { + type: 'number', + description: + 'Maximum number of tokens of documentation to retrieve', + default: 10000, + }, + topic: { + type: 'string', + description: + 'Topic to focus documentation on (e.g., "hooks", "routing")', + }, + }, + required: ['context7CompatibleLibraryID'], + }, + }, + ], + }, + }; + } + + case 'tools/call': { + logMcpToolCall( + logger, + request.params?.name, + 'unknown', + request.params?.arguments || {} + ); + + if (!request?.params?.name) { + throw new Error('Missing tool name in request'); + } + + const { name, arguments: args } = request.params; + + switch (name) { + case 'c41_resolve-library-id': { + logger.info('Resolving library ID', { + libraryName: args.libraryName, + }); + + try { + const context7 = getContext7Process(); + logger.debug('Calling Context7 resolve-library-id tool'); + + const result = await context7.callTool('resolve-library-id', { + libraryName: args.libraryName, + }); + + logger.info('Context7 resolve-library-id completed', { + libraryName: args.libraryName, + hasContent: !!result.content, + contentLength: result.content?.[0]?.text?.length || 0, + }); + + const responseText = + result.content?.[0]?.text || JSON.stringify(result, null, 2); + + return { + jsonrpc: '2.0', + id: request.id, + result: { + content: [ + { + type: 'text', + text: responseText, + }, + ], + }, + }; + } catch (error) { + logMcpError(logger, error, { + libraryName: args.libraryName, + tool: 'resolve-library-id', + }); + + const errorText = `Error resolving library ID for "${args.libraryName}": ${error.message}`; + + return { + jsonrpc: '2.0', + id: request.id, + result: { + content: [ + { + type: 'text', + text: errorText, + }, + ], + }, + }; + } + } + + case 'c41_get-library-docs': { + logger.info('Getting library documentation', { + context7CompatibleLibraryID: args.context7CompatibleLibraryID, + tokens: args.tokens || 10000, + topic: args.topic, + }); + + try { + const context7 = getContext7Process(); + logger.debug('Calling Context7 get-library-docs tool'); + + const callArgs = { + context7CompatibleLibraryID: args.context7CompatibleLibraryID, + tokens: args.tokens || 10000, + }; + + if (args.topic) { + callArgs.topic = args.topic; + } + + const result = await context7.callTool( + 'get-library-docs', + callArgs + ); + + logger.info('Context7 get-library-docs completed', { + context7CompatibleLibraryID: args.context7CompatibleLibraryID, + hasContent: !!result.content, + contentLength: result.content?.[0]?.text?.length || 0, + }); + + const responseText = + result.content?.[0]?.text || JSON.stringify(result, null, 2); + + return { + jsonrpc: '2.0', + id: request.id, + result: { + content: [ + { + type: 'text', + text: responseText, + }, + ], + }, + }; + } catch (error) { + logMcpError(logger, error, { + context7CompatibleLibraryID: args.context7CompatibleLibraryID, + tool: 'get-library-docs', + }); + + const errorText = `Error getting documentation for "${args.context7CompatibleLibraryID}": ${error.message}`; + + return { + jsonrpc: '2.0', + id: request.id, + result: { + content: [ + { + type: 'text', + text: errorText, + }, + ], + }, + }; + } + } + + default: + throw new Error(`Unknown tool: ${name}`); + } + } + + default: + throw new Error(`Unknown method: ${request.method}`); + } +} + +/** + * Setup MCP endpoints for proper Model Context Protocol communication + */ +function setupSessionRoutes() { + app.post('/mcp', async (req, res) => { + try { + logMcpRequest(logger, req); + + if (!req.body || typeof req.body !== 'object') { + return res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32600, + message: 'Invalid Request - missing or invalid request body', + }, + id: null, + }); + } + + if (!req.body.jsonrpc || !req.body.method) { + return res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32600, + message: 'Invalid Request - missing jsonrpc or method field', + }, + id: req.body.id || null, + }); + } + + const sessionId = req.headers['mcp-session-id'] || randomUUID(); + + if (!mcpSessions.has(sessionId)) { + mcpSessions.set(sessionId, { + createdAt: Date.now(), + }); + logMcpSession(logger, 'created', sessionId); + + globalThis.setTimeout( + () => { + if (mcpSessions.has(sessionId)) { + mcpSessions.delete(sessionId); + logMcpSession(logger, 'cleaned up', sessionId); + } + }, + 10 * 60 * 1000 + ); + } + + const response = await handleMcpRequest(req.body); + + res.setHeader('mcp-session-id', sessionId); + res.json(response); + } catch (error) { + logMcpError(logger, error, { + endpoint: '/mcp', + method: 'POST', + }); + + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error', + data: error.message, + }, + id: req.body?.id || null, + }); + } + } + }); + + app.get('/mcp', async (req, res) => { + const sessionId = req.headers['mcp-session-id']; + + if (!sessionId || !mcpSessions.has(sessionId)) { + return res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32600, + message: 'Invalid or missing session ID', + }, + }); + } + + res.writeHead(200, { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache', + Connection: 'keep-alive', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'mcp-session-id', + }); + + res.write('data: {"type":"connected"}\n\n'); + + const keepAlive = globalThis.setInterval(() => { + res.write('data: {"type":"ping"}\n\n'); + }, 30000); + + req.on('close', () => { + globalThis.clearInterval(keepAlive); + }); + }); + + app.delete('/mcp', async (req, res) => { + const sessionId = req.headers['mcp-session-id']; + + if (!sessionId || !mcpSessions.has(sessionId)) { + return res.status(400).json({ + jsonrpc: '2.0', + error: { + code: -32600, + message: 'Invalid or missing session ID', + }, + }); + } + + mcpSessions.delete(sessionId); + + logMcpSession(logger, 'terminated', sessionId); + res.status(200).json({ + jsonrpc: '2.0', + result: { status: 'terminated' }, + }); + }); +} + +app.get('/health', (_req, res) => { + const healthStatus = { + status: 'healthy', + server: 'context7-bridge', + version: '1.0.0', + activeSessions: mcpSessions.size, + activeProcesses: context7Processes.size, + timestamp: new Date().toISOString(), + }; + + logger.info('Health check requested', healthStatus); + res.json(healthStatus); +}); + +/** + * Start the server + */ +async function startServer() { + const PORT = process.env.PORT || 3002; + const host = process.env.HOST || '0.0.0.0'; + + setupSessionRoutes(); + + app.listen(PORT, host, () => { + logger.info('Context7 HTTP Bridge started', { + host, + port: PORT, + protocol: 'Model Context Protocol (MCP) HTTP Bridge', + target: 'Context7 MCP Server (stdio)', + endpoints: { + mcp: 'POST /mcp - MCP protocol endpoint', + mcpSse: 'GET /mcp - SSE notifications (with session-id header)', + mcpDelete: 'DELETE /mcp - Session termination (with session-id header)', + health: 'GET /health - Health check', + }, + tools: [ + 'c41_resolve-library-id - Resolve library names to Context7 IDs', + 'c41_get-library-docs - Fetch up-to-date library documentation', + ], + }); + }); +} + +process.on('SIGTERM', () => { + logger.info('Received SIGTERM, shutting down gracefully'); + + mcpSessions.clear(); + + context7Processes.forEach((proc) => { + proc.terminate(); + }); + + process.exit(0); +}); + +process.on('SIGINT', () => { + logger.info('Received SIGINT, shutting down gracefully'); + + mcpSessions.clear(); + + context7Processes.forEach((proc) => { + proc.terminate(); + }); + + process.exit(0); +}); + +startServer().catch((error) => { + logMcpError(logger, error, { + operation: 'server-startup', + }); + process.exit(1); +}); diff --git a/examples/mcp/mcp-servers/context7/logger.js b/examples/mcp/mcp-servers/context7/logger.js new file mode 100644 index 0000000..db86d11 --- /dev/null +++ b/examples/mcp/mcp-servers/context7/logger.js @@ -0,0 +1,97 @@ +/** + * Standardized Winston Logger for MCP Servers + * + * Provides consistent, single-line logging across all MCP servers + * with structured metadata and unified formatting. + */ + +import winston from 'winston'; + +/** + * Create a standardized logger for MCP servers + * @param {string} serviceName - The name of the MCP service + * @param {string} [version='1.0.0'] - The version of the service + * @param {string} [logLevel] - Override log level (defaults to LOG_LEVEL env var or 'info') + * @returns {winston.Logger} Configured Winston logger + */ +export function createMcpLogger(serviceName, version = '1.0.0', logLevel) { + const level = logLevel || process.env.LOG_LEVEL || 'info'; + + return winston.createLogger({ + level, + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { + service: serviceName, + version, + protocol: 'Model Context Protocol', + transport: 'Streamable HTTP', + }, + transports: [ + new winston.transports.Console({ + handleExceptions: true, + handleRejections: true, + }), + ], + }); +} + +/** + * Log MCP request received + */ +export function logMcpRequest(logger, req, additionalMeta = {}) { + const sessionId = req.headers['mcp-session-id']; + const method = req.body?.method; + const id = req.body?.id; + + logger.info('MCP request received', { + sessionId, + method, + requestId: id, + userAgent: req.headers['user-agent'], + contentLength: req.headers['content-length'], + ...additionalMeta, + }); +} + +/** + * Log MCP session events + */ +export function logMcpSession(logger, event, sessionId, additionalMeta = {}) { + logger.info(`MCP session ${event}`, { + sessionId, + ...additionalMeta, + }); +} + +/** + * Log MCP tool calls + */ +export function logMcpToolCall( + logger, + toolName, + sessionId, + args = {}, + additionalMeta = {} +) { + logger.info(`MCP tool called: ${toolName}`, { + sessionId, + tool: toolName, + args: Object.keys(args), + ...additionalMeta, + }); +} + +/** + * Log MCP errors with context + */ +export function logMcpError(logger, error, context = {}) { + logger.error('MCP error occurred', { + error: error.message, + stack: error.stack, + ...context, + }); +} diff --git a/examples/mcp/mcp-servers/context7/package-lock.json b/examples/mcp/mcp-servers/context7/package-lock.json new file mode 100644 index 0000000..caeafb4 --- /dev/null +++ b/examples/mcp/mcp-servers/context7/package-lock.json @@ -0,0 +1,2102 @@ +{ + "name": "context7-http-bridge", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "context7-http-bridge", + "version": "1.0.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^0.5.0", + "@upstash/context7-mcp": "latest", + "cors": "^2.8.5", + "express": "^4.18.2", + "winston": "^3.17.0", + "zod": "^3.22.4" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.5.0.tgz", + "integrity": "sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "raw-body": "^3.0.0", + "zod": "^3.23.8" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/@upstash/context7-mcp": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@upstash/context7-mcp/-/context7-mcp-1.0.12.tgz", + "integrity": "sha512-l1l0iSnpHJMgEnsLcf7OEqD3MmirXUb13R4f6hLUD026KaUT54aYXPFS7XgYZ/Rfl2q3ArjX/7N5l87amzyP+g==", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.8.0", + "dotenv": "^16.5.0", + "zod": "^3.24.2" + }, + "bin": { + "context7-mcp": "dist/index.js" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/@modelcontextprotocol/sdk": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", + "integrity": "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@upstash/context7-mcp/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@upstash/context7-mcp/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@upstash/context7-mcp/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dotenv": { + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.43", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.43.tgz", + "integrity": "sha512-KrQFEkfox9WLfYCOksqEWjf97QdgSC1YJSqxJxnWz0l+BW8mN2PQ+yjshL8RC7RC5p5YOlUCLyTDNaf++QbPtA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/examples/mcp/mcp-servers/context7/package.json b/examples/mcp/mcp-servers/context7/package.json new file mode 100644 index 0000000..0815dbf --- /dev/null +++ b/examples/mcp/mcp-servers/context7/package.json @@ -0,0 +1,22 @@ +{ + "name": "context7-http-bridge", + "version": "1.0.0", + "description": "HTTP bridge for stdio-based Context7 MCP server using official MCP SDK", + "main": "index.js", + "type": "module", + "scripts": { + "start": "node index.js", + "dev": "nodemon index.js" + }, + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "@upstash/context7-mcp": "latest", + "@modelcontextprotocol/sdk": "^0.5.0", + "winston": "^3.17.0", + "zod": "^3.22.4" + }, + "devDependencies": { + "nodemon": "^3.0.1" + } +} diff --git a/examples/mcp/mcp-servers/filesystem/index.js b/examples/mcp/mcp-servers/filesystem/index.js index 8f597e8..7c3720a 100644 --- a/examples/mcp/mcp-servers/filesystem/index.js +++ b/examples/mcp/mcp-servers/filesystem/index.js @@ -13,20 +13,28 @@ import { randomUUID } from 'node:crypto'; import { z } from 'zod'; import { promises as fs } from 'node:fs'; import path from 'node:path'; +import { + createMcpLogger, + logMcpRequest, + logMcpSession, + logMcpToolCall, + logMcpError, +} from './logger.js'; -// Express app for HTTP transport const app = express(); app.use(express.json()); -// Map to store transports by session ID +const logger = createMcpLogger('mcp-filesystem', '1.0.0'); + const transports = {}; -// Allowed directories (configurable via environment) -const allowedDirectories = ( - process.env.ALLOWED_DIRECTORIES || '/shared,/tmp' -).split(','); +const allowedDirectories = (process.env.ALLOWED_DIRECTORIES || '/tmp').split( + ',' +); -console.info('Allowed directories:', allowedDirectories); +logger.info('Filesystem server starting', { + allowedDirectories, +}); /** * Check if a path is within allowed directories @@ -48,7 +56,6 @@ function createMcpServer() { version: '1.0.0', }); - // Tool: Read file content mcpServer.tool( 'read_file', { @@ -62,7 +69,7 @@ function createMcpServer() { } try { - console.info(`Reading file: ${filePath}`); + logMcpToolCall(logger, 'read_file', { filePath }); const content = await fs.readFile(filePath, 'utf8'); return { @@ -74,7 +81,7 @@ function createMcpServer() { ], }; } catch (error) { - console.error(`Failed to read file ${filePath}:`, error.message); + logMcpError(logger, error, { filePath, operation: 'read_file' }); let errorMessage = `Failed to read file: ${filePath}\n`; if (error.code === 'ENOENT') { @@ -99,7 +106,6 @@ function createMcpServer() { } ); - // Tool: Write file content mcpServer.tool( 'write_file', { @@ -114,9 +120,11 @@ function createMcpServer() { } try { - console.info(`Writing to file: ${filePath}`); + logMcpToolCall(logger, 'write_file', { + filePath, + contentLength: content.length, + }); - // Ensure directory exists const dir = path.dirname(filePath); await fs.mkdir(dir, { recursive: true }); @@ -131,7 +139,7 @@ function createMcpServer() { ], }; } catch (error) { - console.error(`Failed to write file ${filePath}:`, error.message); + logMcpError(logger, error, { filePath, operation: 'write_file' }); return { content: [ @@ -145,7 +153,6 @@ function createMcpServer() { } ); - // Tool: List directory contents mcpServer.tool( 'list_directory', { @@ -159,7 +166,7 @@ function createMcpServer() { } try { - console.info(`Listing directory: ${dirPath}`); + logMcpToolCall(logger, 'list_directory', { dirPath }); const entries = await fs.readdir(dirPath, { withFileTypes: true }); @@ -175,10 +182,11 @@ function createMcpServer() { modified: stats.mtime.toISOString(), }; } catch (error) { - console.warn( - `Could not get stats for ${entry.name}:`, - error.message - ); + logger.warn('Could not get stats for entry', { + entryName: entry.name, + dirPath, + error: error.message, + }); return { name: entry.name, type: entry.isDirectory() ? 'directory' : 'file', @@ -207,7 +215,7 @@ function createMcpServer() { ], }; } catch (error) { - console.error(`Failed to list directory ${dirPath}:`, error.message); + logMcpError(logger, error, { dirPath, operation: 'list_directory' }); let errorMessage = `Failed to list directory: ${dirPath}\n`; if (error.code === 'ENOENT') { @@ -232,7 +240,6 @@ function createMcpServer() { } ); - // Tool: Create directory mcpServer.tool( 'create_directory', { @@ -246,7 +253,7 @@ function createMcpServer() { } try { - console.info(`Creating directory: ${dirPath}`); + logMcpToolCall(logger, 'create_directory', { dirPath }); await fs.mkdir(dirPath, { recursive: true }); @@ -259,7 +266,7 @@ function createMcpServer() { ], }; } catch (error) { - console.error(`Failed to create directory ${dirPath}:`, error.message); + logMcpError(logger, error, { dirPath, operation: 'create_directory' }); return { content: [ @@ -273,7 +280,6 @@ function createMcpServer() { } ); - // Tool: Delete file mcpServer.tool( 'delete_file', { @@ -287,7 +293,7 @@ function createMcpServer() { } try { - console.info(`Deleting file: ${filePath}`); + logMcpToolCall(logger, 'delete_file', { filePath }); await fs.unlink(filePath); @@ -300,7 +306,7 @@ function createMcpServer() { ], }; } catch (error) { - console.error(`Failed to delete file ${filePath}:`, error.message); + logMcpError(logger, error, { filePath, operation: 'delete_file' }); let errorMessage = `Failed to delete file: ${filePath}\n`; if (error.code === 'ENOENT') { @@ -326,7 +332,75 @@ function createMcpServer() { } ); - // Tool: Get file info + mcpServer.tool( + 'delete_directory', + { + path: z.string().describe('The directory path to delete'), + recursive: z + .boolean() + .optional() + .describe('Whether to delete recursively (default: false)'), + }, + async ({ path: dirPath, recursive = false }) => { + if (!isPathAllowed(dirPath)) { + throw new Error( + `Access denied: ${dirPath} is not in allowed directories` + ); + } + + try { + logMcpToolCall(logger, 'delete_directory', { + dirPath, + recursive, + }); + + if (recursive) { + await fs.rm(dirPath, { recursive: true, force: true }); + } else { + await fs.rmdir(dirPath); + } + + return { + content: [ + { + type: 'text', + text: `Successfully deleted directory: ${dirPath}`, + }, + ], + }; + } catch (error) { + logMcpError(logger, error, { + dirPath, + recursive, + operation: 'delete_directory', + }); + + let errorMessage = `Failed to delete directory: ${dirPath}\n`; + if (error.code === 'ENOENT') { + errorMessage += 'Directory does not exist'; + } else if (error.code === 'EACCES') { + errorMessage += 'Permission denied'; + } else if (error.code === 'ENOTDIR') { + errorMessage += 'Path is not a directory'; + } else if (error.code === 'ENOTEMPTY') { + errorMessage += + 'Directory is not empty (use recursive option to delete non-empty directories)'; + } else { + errorMessage += error.message; + } + + return { + content: [ + { + type: 'text', + text: errorMessage, + }, + ], + }; + } + } + ); + mcpServer.tool( 'file_info', { @@ -340,7 +414,7 @@ function createMcpServer() { } try { - console.info(`Getting info for: ${filePath}`); + logMcpToolCall(logger, 'file_info', { filePath }); const stats = await fs.stat(filePath); @@ -379,7 +453,7 @@ function createMcpServer() { ], }; } catch (error) { - console.error(`Failed to get info for ${filePath}:`, error.message); + logMcpError(logger, error, { filePath, operation: 'file_info' }); let errorMessage = `Failed to get file info: ${filePath}\n`; if (error.code === 'ENOENT') { @@ -409,60 +483,48 @@ function createMcpServer() { * Setup MCP endpoints for proper Model Context Protocol communication */ function setupSessionRoutes() { - // Handle POST requests for MCP communication app.post('/mcp', async (req, res) => { try { - console.info('MCP POST request received:'); - console.info(' Headers: %s', JSON.stringify(req.headers, null, 2)); - console.info(' Body: %s', JSON.stringify(req.body, null, 2)); + logMcpRequest(logger, req, 'MCP POST request received'); - // Fix missing Accept headers for compatibility with Go MCP clients - // The StreamableHTTPServerTransport requires both application/json and text/event-stream const accept = req.headers.accept || req.headers.Accept; if ( !accept || !accept.includes('application/json') || !accept.includes('text/event-stream') ) { - console.info('Adding missing Accept headers for MCP compatibility'); + logger.debug('Adding missing Accept headers for MCP compatibility'); req.headers.accept = 'application/json, text/event-stream'; } - // Check for existing session ID const sessionId = req.headers['mcp-session-id']; let transport; if (sessionId && transports[sessionId]) { - // Reuse existing transport transport = transports[sessionId]; } else { - // Create new transport for new session transport = new StreamableHTTPServerTransport({ sessionIdGenerator: () => randomUUID(), onsessioninitialized: (newSessionId) => { - console.info(`MCP session initialized: ${newSessionId}`); - // Store the transport by session ID + logMcpSession(logger, 'initialized', { sessionId: newSessionId }); transports[newSessionId] = transport; }, }); - // Clean up transport when closed transport.onclose = () => { if (transport.sessionId) { - console.info(`MCP session closed: ${transport.sessionId}`); + logMcpSession(logger, 'closed', { sessionId: transport.sessionId }); delete transports[transport.sessionId]; } }; - // Create and connect MCP server const server = createMcpServer(); await server.connect(transport); } - // Handle the MCP request await transport.handleRequest(req, res, req.body); } catch (error) { - console.error('Error handling MCP request:', error); + logMcpError(logger, error, { operation: 'mcp_request_handling' }); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', @@ -475,67 +537,6 @@ function setupSessionRoutes() { } } }); - - // Handle GET requests for SSE (server-to-client notifications) - app.get('/mcp', async (req, res) => { - const sessionId = req.headers['mcp-session-id']; - if (!sessionId || !transports[sessionId]) { - res.status(400).send('Invalid or missing session ID'); - return; - } - - const transport = transports[sessionId]; - await transport.handleRequest(req, res); - }); - - // Handle DELETE requests for session termination - app.delete('/mcp', async (req, res) => { - const sessionId = req.headers['mcp-session-id']; - if (!sessionId || !transports[sessionId]) { - res.status(400).send('Invalid or missing session ID'); - return; - } - - const transport = transports[sessionId]; - await transport.handleRequest(req, res); - }); -} - -/** - * Initialize sample files - */ -async function initializeSampleFiles() { - try { - // Create sample files in allowed directories - for (const dir of allowedDirectories) { - try { - await fs.access(dir); - - const sampleFile = path.join(dir, 'mcp-filesystem-example.txt'); - const sampleContent = `Hello from MCP Filesystem Server! - -This is a sample file created by the MCP Filesystem Server. -Created at: ${new Date().toISOString()} - -You can use the following MCP tools to interact with this file: -- read_file: Read this file's content -- write_file: Modify this file -- file_info: Get detailed information about this file -- list_directory: List all files in this directory -- delete_file: Delete this file - -Available directories: ${allowedDirectories.join(', ')} -`; - - await fs.writeFile(sampleFile, sampleContent); - console.info(`Created sample file: ${sampleFile}`); - } catch (error) { - console.info(`Could not create sample file in ${dir}:`, error.message); - } - } - } catch (error) { - console.error('Error initializing sample files:', error); - } } /** @@ -552,7 +553,7 @@ app.get('/health', (req, res) => { activeSessions: Object.keys(transports).length, }; - console.info('Health check requested: %j', healthStatus); + logger.info('Health check requested', healthStatus); res.json(healthStatus); }); @@ -563,42 +564,39 @@ async function startServer() { const port = process.env.PORT || 3000; const host = process.env.HOST || '0.0.0.0'; - // Set up session routes setupSessionRoutes(); app.listen(port, host, async () => { - console.info(`MCP Filesystem server running on http://${host}:${port}`); - console.info('Protocol: Model Context Protocol (MCP)'); - console.info('Transport: Streamable HTTP'); - console.info('Available endpoints:'); - console.info(' POST /mcp - MCP protocol endpoint'); - console.info( - ' GET /mcp - SSE notifications (with session-id header)' - ); - console.info( - ' DELETE /mcp - Session termination (with session-id header)' - ); - console.info(' GET /health - Health check'); - console.info('Available tools:'); - console.info(' - read_file - Read content from a file'); - console.info(' - write_file - Write content to a file'); - console.info(' - list_directory - List directory contents'); - console.info(' - create_directory - Create a new directory'); - console.info(' - delete_file - Delete a file'); - console.info(' - move_file - Move/rename a file'); - console.info('Allowed directories:', allowedDirectories); - - // Initialize sample files - await initializeSampleFiles(); - - console.info('MCP Filesystem server ready for connections'); + logger.info('MCP Filesystem server started', { + host, + port, + protocol: 'Model Context Protocol (MCP)', + transport: 'Streamable HTTP', + endpoints: { + mcp: 'POST /mcp - MCP protocol endpoint', + mcpSSE: 'GET /mcp - SSE notifications (with session-id header)', + mcpTerminate: + 'DELETE /mcp - Session termination (with session-id header)', + health: 'GET /health - Health check', + }, + tools: [ + 'read_file - Read content from a file', + 'write_file - Write content to a file', + 'list_directory - List directory contents', + 'create_directory - Create a new directory', + 'delete_file - Delete a file', + 'delete_directory - Delete a directory', + 'file_info - Get file or directory information', + ], + allowedDirectories, + }); + + logger.info('MCP Filesystem server ready for connections'); }); } -// Graceful shutdown process.on('SIGTERM', () => { - console.info('Received SIGTERM, shutting down gracefully'); - // Close all transports + logger.info('Received SIGTERM, shutting down gracefully'); Object.values(transports).forEach((transport) => { if (transport.close) transport.close(); }); @@ -606,16 +604,14 @@ process.on('SIGTERM', () => { }); process.on('SIGINT', () => { - console.info('Received SIGINT, shutting down gracefully'); - // Close all transports + logger.info('Received SIGINT, shutting down gracefully'); Object.values(transports).forEach((transport) => { if (transport.close) transport.close(); }); process.exit(0); }); -// Start the server startServer().catch((error) => { - console.error('Failed to start server:', error); + logMcpError(logger, error, { operation: 'server_startup' }); process.exit(1); }); diff --git a/examples/mcp/mcp-servers/filesystem/logger.js b/examples/mcp/mcp-servers/filesystem/logger.js new file mode 100644 index 0000000..db86d11 --- /dev/null +++ b/examples/mcp/mcp-servers/filesystem/logger.js @@ -0,0 +1,97 @@ +/** + * Standardized Winston Logger for MCP Servers + * + * Provides consistent, single-line logging across all MCP servers + * with structured metadata and unified formatting. + */ + +import winston from 'winston'; + +/** + * Create a standardized logger for MCP servers + * @param {string} serviceName - The name of the MCP service + * @param {string} [version='1.0.0'] - The version of the service + * @param {string} [logLevel] - Override log level (defaults to LOG_LEVEL env var or 'info') + * @returns {winston.Logger} Configured Winston logger + */ +export function createMcpLogger(serviceName, version = '1.0.0', logLevel) { + const level = logLevel || process.env.LOG_LEVEL || 'info'; + + return winston.createLogger({ + level, + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { + service: serviceName, + version, + protocol: 'Model Context Protocol', + transport: 'Streamable HTTP', + }, + transports: [ + new winston.transports.Console({ + handleExceptions: true, + handleRejections: true, + }), + ], + }); +} + +/** + * Log MCP request received + */ +export function logMcpRequest(logger, req, additionalMeta = {}) { + const sessionId = req.headers['mcp-session-id']; + const method = req.body?.method; + const id = req.body?.id; + + logger.info('MCP request received', { + sessionId, + method, + requestId: id, + userAgent: req.headers['user-agent'], + contentLength: req.headers['content-length'], + ...additionalMeta, + }); +} + +/** + * Log MCP session events + */ +export function logMcpSession(logger, event, sessionId, additionalMeta = {}) { + logger.info(`MCP session ${event}`, { + sessionId, + ...additionalMeta, + }); +} + +/** + * Log MCP tool calls + */ +export function logMcpToolCall( + logger, + toolName, + sessionId, + args = {}, + additionalMeta = {} +) { + logger.info(`MCP tool called: ${toolName}`, { + sessionId, + tool: toolName, + args: Object.keys(args), + ...additionalMeta, + }); +} + +/** + * Log MCP errors with context + */ +export function logMcpError(logger, error, context = {}) { + logger.error('MCP error occurred', { + error: error.message, + stack: error.stack, + ...context, + }); +} diff --git a/examples/mcp/mcp-servers/filesystem/package-lock.json b/examples/mcp/mcp-servers/filesystem/package-lock.json index eb070a4..2a9002f 100644 --- a/examples/mcp/mcp-servers/filesystem/package-lock.json +++ b/examples/mcp/mcp-servers/filesystem/package-lock.json @@ -12,12 +12,33 @@ "@modelcontextprotocol/sdk": "^1.12.0", "cors": "^2.8.5", "express": "^4.18.2", + "winston": "^3.17.0", "zod": "^3.22.0" }, "engines": { "node": ">=18.0.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.0.tgz", @@ -314,6 +335,12 @@ "node": ">= 0.6" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -349,6 +376,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "1.20.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", @@ -426,6 +459,51 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -537,6 +615,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -685,6 +769,12 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -703,6 +793,12 @@ "node": ">= 0.8" } }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -846,12 +942,30 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -864,6 +978,35 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -990,6 +1133,15 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1096,6 +1248,20 @@ "node": ">=0.10.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -1164,6 +1330,15 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1323,6 +1498,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1332,6 +1525,21 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1341,6 +1549,15 @@ "node": ">=0.6" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1372,6 +1589,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1405,6 +1628,42 @@ "node": ">= 8" } }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/examples/mcp/mcp-servers/filesystem/package.json b/examples/mcp/mcp-servers/filesystem/package.json index 93e0406..63db973 100644 --- a/examples/mcp/mcp-servers/filesystem/package.json +++ b/examples/mcp/mcp-servers/filesystem/package.json @@ -12,6 +12,7 @@ "@modelcontextprotocol/sdk": "^1.12.0", "cors": "^2.8.5", "express": "^4.18.2", + "winston": "^3.17.0", "zod": "^3.22.0" }, "engines": { diff --git a/examples/mcp/mcp-servers/memory/README.md b/examples/mcp/mcp-servers/memory/README.md new file mode 100644 index 0000000..47ae5e4 --- /dev/null +++ b/examples/mcp/mcp-servers/memory/README.md @@ -0,0 +1,97 @@ +# MCP Memory Server + +A Model Context Protocol (MCP) server that provides memory persistence capabilities for AI agents. This server helps agents save their state when HTTP errors occur, allowing them to continue from where they left off in subsequent iterations. + +## Features + +- **State Persistence**: Save arbitrary state objects with session IDs +- **Error State Recovery**: Specially handle saving state when HTTP errors occur +- **Session Management**: List, restore, and clear saved sessions +- **File-based Storage**: Persistent storage using JSON files + +## Tools + +### `save-state` + +Saves the current state for a session. + +**Parameters:** + +- `sessionId` (string): Unique session identifier +- `state` (object): State object to persist +- `context` (string, optional): Context description + +### `save-error-state` + +Saves state along with error information when an HTTP error occurs. + +**Parameters:** + +- `sessionId` (string): Unique session identifier +- `state` (object): State object to persist +- `error` (object): Error information (message, code, status, url) +- `context` (string, optional): Context description + +### `restore-state` + +Restores the saved state for a session. + +**Parameters:** + +- `sessionId` (string): Unique session identifier + +### `list-sessions` + +Lists all saved sessions with their metadata. + +### `clear-session` + +Removes a saved session. + +**Parameters:** + +- `sessionId` (string): Unique session identifier + +## Environment Variables + +- `MEMORY_DIR`: Directory for storing memory files (default: `/tmp/memory`) +- `PORT`: Server port (default: `3004`) + +## Usage Example + +```javascript +// Save state before making an HTTP request +await saveState({ + sessionId: 'agent-task-123', + state: { + currentStep: 'api-call', + url: 'https://api.example.com/data', + payload: { param: 'value' }, + retryCount: 0, + }, + context: 'About to make API call to fetch user data', +}); + +// If HTTP error occurs, save error state +await saveErrorState({ + sessionId: 'agent-task-123', + state: { + currentStep: 'api-call-failed', + url: 'https://api.example.com/data', + payload: { param: 'value' }, + retryCount: 1, + }, + error: { + message: 'HTTP 500 Internal Server Error', + status: 500, + url: 'https://api.example.com/data', + }, + context: 'API call failed, need to retry', +}); + +// In next iteration, restore state +const restoredData = await restoreState({ + sessionId: 'agent-task-123', +}); +// Continue from where we left off... +``` diff --git a/examples/mcp/mcp-servers/memory/index.js b/examples/mcp/mcp-servers/memory/index.js new file mode 100644 index 0000000..6d72042 --- /dev/null +++ b/examples/mcp/mcp-servers/memory/index.js @@ -0,0 +1,1027 @@ +/** + * MCP Memory Server + * + * This is a Model Context Protocol (MCP) server that provides memory + * operations for persisting state when HTTP errors occur. It allows + * agents to save their progress and continue from where they left off + * after encountering errors. + */ + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import express from 'express'; +import { randomUUID } from 'node:crypto'; +import { z } from 'zod'; +import MemoryManager from './memory-manager.js'; +import { + createMcpLogger, + logMcpRequest, + logMcpSession, + logMcpToolCall, + logMcpError, +} from './logger.js'; + +const app = express(); +app.use(express.json()); + +const logger = createMcpLogger('mcp-memory', '1.0.0'); + +const transports = {}; + +const memoryDir = process.env.MEMORY_DIR || '/tmp/memory'; + +// Initialize the memory manager +const memoryManager = new MemoryManager(memoryDir); + +logger.info('MCP Memory Server initializing', { + memoryDir, + nodeVersion: process.version, + platform: process.platform, + environment: process.env.NODE_ENV || 'development', +}); + +/** + * Create and configure the MCP server + */ +function createMcpServer() { + logger.debug('Creating new MCP server instance'); + + const mcpServer = new McpServer({ + name: 'memory', + version: '1.0.0', + }); + + logger.debug('MCP server instance created'); + + mcpServer.tool( + 'save-state', + { + sessionId: z.string().describe('Unique session identifier'), + state: z.object({}).passthrough().describe('State object to persist'), + context: z.string().optional().describe('Optional context description'), + }, + async ({ sessionId, state, context }) => { + const startTime = Date.now(); + + logger.debug('save-state tool called', { + sessionId, + stateSize: JSON.stringify(state).length, + }); + + try { + // Use fast in-memory operation + await memoryManager.saveState(sessionId, state, context); + + const duration = Date.now() - startTime; + + logger.info('State saved successfully', { + sessionId, + duration, + dataSize: JSON.stringify(state).length, + }); + + logMcpToolCall(logger, 'save-state', sessionId, { sessionId }); + + return { + content: [ + { + type: 'text', + text: `State saved successfully for session: ${sessionId}`, + }, + ], + }; + } catch (error) { + const duration = Date.now() - startTime; + + logger.error('Failed to save state', { + sessionId, + error: error.message, + stack: error.stack, + duration, + }); + + logMcpError(logger, error, { sessionId }); + logMcpToolCall(logger, 'save-state', sessionId, { sessionId }); + + return { + content: [ + { + type: 'text', + text: `Failed to save state: ${error.message}`, + }, + ], + isError: true, + }; + } + } + ); + + mcpServer.tool( + 'save-error-state', + { + sessionId: z.string().describe('Unique session identifier'), + state: z.object({}).passthrough().describe('State object to persist'), + error: z + .object({ + message: z.string(), + code: z.number().optional(), + status: z.number().optional(), + url: z.string().optional(), + }) + .describe('Error information'), + context: z.string().optional().describe('Optional context description'), + }, + async ({ sessionId, state, error, context }) => { + const startTime = Date.now(); + + logger.info('save-error-state tool called', { + sessionId, + errorMessage: error.message, + errorCode: error.code, + errorStatus: error.status, + errorUrl: error.url, + hasContext: !!context, + stateKeys: Object.keys(state || {}), + }); + + try { + // Use fast in-memory operation with error tracking + await memoryManager.saveErrorState(sessionId, state, error, context); + + const duration = Date.now() - startTime; + + logger.info('Error state saved successfully', { + sessionId, + duration, + errorMessage: error.message, + }); + + logMcpToolCall(logger, 'save-error-state', sessionId, { + sessionId, + error: error.message, + }); + + return { + content: [ + { + type: 'text', + text: `Error state saved successfully for session: ${sessionId}. Error: ${error.message}`, + }, + ], + }; + } catch (saveError) { + const duration = Date.now() - startTime; + + logger.error('Failed to save error state', { + sessionId, + originalError: error.message, + saveError: saveError.message, + stack: saveError.stack, + duration, + }); + + logMcpError(logger, saveError, { + sessionId, + originalError: error.message, + }); + logMcpToolCall(logger, 'save-error-state', sessionId, { sessionId }); + + return { + content: [ + { + type: 'text', + text: `Failed to save error state: ${saveError.message}`, + }, + ], + isError: true, + }; + } + } + ); + + mcpServer.tool( + 'restore-state', + { + sessionId: z.string().describe('Unique session identifier'), + }, + async ({ sessionId }) => { + const startTime = Date.now(); + + logger.debug('restore-state tool called', { sessionId }); + + try { + // Use fast in-memory operation + const memoryData = await memoryManager.restoreState(sessionId); + + const duration = Date.now() - startTime; + + if (!memoryData) { + logger.debug('No saved state found for session', { + sessionId, + duration, + }); + + return { + content: [ + { + type: 'text', + text: `No saved state found for session: ${sessionId}`, + }, + ], + }; + } + + logger.debug('State restored successfully', { + sessionId, + hasError: !!memoryData.lastError, + duration, + }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + sessionId: memoryData.sessionId, + state: memoryData.state, + context: memoryData.context, + timestamp: memoryData.timestamp, + lastError: memoryData.lastError, + hasError: !!memoryData.lastError, + }, + null, + 2 + ), + }, + ], + }; + } catch (error) { + const duration = Date.now() - startTime; + + logger.error('Failed to restore state', { + sessionId, + error: error.message, + duration, + }); + + return { + content: [ + { + type: 'text', + text: `Failed to restore state: ${error.message}`, + }, + ], + isError: true, + }; + } + } + ); + + mcpServer.tool('list-sessions', {}, async () => { + const startTime = Date.now(); + + logger.info('list-sessions tool called'); + + try { + // Use fast in-memory operation + const sessions = memoryManager.listSessions(); + const duration = Date.now() - startTime; + + logger.info('Sessions listed successfully', { + totalSessions: sessions.length, + duration, + }); + + logMcpToolCall('list-sessions', {}, true, duration); + + return { + content: [ + { + type: 'text', + text: JSON.stringify({ sessions }, null, 2), + }, + ], + }; + } catch (error) { + const duration = Date.now() - startTime; + + logger.error('Failed to list sessions', { + error: error.message, + stack: error.stack, + duration, + }); + + logMcpError('list-sessions', error, {}); + logMcpToolCall('list-sessions', {}, false, duration); + + return { + content: [ + { + type: 'text', + text: `Failed to list sessions: ${error.message}`, + }, + ], + isError: true, + }; + } + }); + + mcpServer.tool( + 'save-conversation', + { + sessionId: z.string().describe('Unique session identifier'), + messages: z + .array( + z.object({ + role: z + .enum(['user', 'assistant', 'system']) + .describe('Message role'), + content: z.string().describe('Message content'), + timestamp: z + .string() + .optional() + .describe('Message timestamp (ISO string)'), + }) + ) + .describe('Array of conversation messages'), + context: z.string().optional().describe('Optional context description'), + }, + async ({ sessionId, messages, context }) => { + const startTime = Date.now(); + + logger.info('save-conversation tool called', { + sessionId, + messageCount: messages.length, + hasContext: !!context, + roles: messages.map((m) => m.role), + }); + + try { + const timestampedMessages = messages.map((msg) => ({ + ...msg, + timestamp: msg.timestamp || new Date().toISOString(), + })); + + // Use fast in-memory operation + await memoryManager.saveConversation( + sessionId, + timestampedMessages, + context + ); + + const duration = Date.now() - startTime; + + logger.info('Conversation saved successfully', { + sessionId, + duration, + messageCount: timestampedMessages.length, + }); + + logMcpToolCall(logger, 'save-conversation', sessionId, { sessionId }); + + return { + content: [ + { + type: 'text', + text: `Conversation saved successfully for session: ${sessionId}. Saved ${timestampedMessages.length} messages.`, + }, + ], + }; + } catch (error) { + const duration = Date.now() - startTime; + + logger.error('Failed to save conversation', { + sessionId, + error: error.message, + stack: error.stack, + duration, + }); + + logMcpError(logger, error, { sessionId }); + logMcpToolCall(logger, 'save-conversation', sessionId, { sessionId }); + + return { + content: [ + { + type: 'text', + text: `Failed to save conversation: ${error.message}`, + }, + ], + isError: true, + }; + } + } + ); + + mcpServer.tool( + 'add-message', + { + sessionId: z.string().describe('Unique session identifier'), + role: z.enum(['user', 'assistant', 'system']).describe('Message role'), + content: z.string().describe('Message content'), + timestamp: z + .string() + .optional() + .describe('Message timestamp (ISO string)'), + }, + async ({ sessionId, role, content, timestamp }) => { + const startTime = Date.now(); + + logger.info('add-message tool called', { + sessionId, + role, + contentLength: content.length, + hasTimestamp: !!timestamp, + }); + + try { + const messageTimestamp = timestamp || new Date().toISOString(); + const newMessage = { + role, + content, + timestamp: messageTimestamp, + }; + + // Use fast in-memory operation + const totalMessages = await memoryManager.addMessage( + sessionId, + newMessage + ); + + const duration = Date.now() - startTime; + + logger.info('Message added successfully', { + sessionId, + role, + duration, + totalMessages, + }); + + logMcpToolCall(logger, 'add-message', sessionId, { sessionId, role }); + + return { + content: [ + { + type: 'text', + text: `Message added successfully for session: ${sessionId}. Role: ${role}. Total messages: ${totalMessages}`, + }, + ], + }; + } catch (error) { + const duration = Date.now() - startTime; + + logger.error('Failed to add message', { + sessionId, + role, + error: error.message, + stack: error.stack, + duration, + }); + + logMcpError(logger, error, { sessionId, role }); + logMcpToolCall(logger, 'add-message', sessionId, { sessionId, role }); + + return { + content: [ + { + type: 'text', + text: `Failed to add message: ${error.message}`, + }, + ], + isError: true, + }; + } + } + ); + + mcpServer.tool( + 'get-conversation', + { + sessionId: z.string().describe('Unique session identifier'), + filterRole: z + .enum(['user', 'assistant', 'system']) + .optional() + .describe('Optional: filter messages by role'), + limit: z + .number() + .optional() + .describe( + 'Optional: limit number of messages returned (most recent first)' + ), + }, + async ({ sessionId, filterRole, limit }) => { + const startTime = Date.now(); + + logger.info('get-conversation tool called', { + sessionId, + filterRole, + limit, + }); + + try { + // Use fast in-memory operation + const conversationData = await memoryManager.getConversation(sessionId); + + const duration = Date.now() - startTime; + + if (!conversationData || !conversationData.messages) { + logger.info('No conversation found for session', { + sessionId, + duration, + }); + + logMcpToolCall(logger, 'get-conversation', sessionId, { + sessionId, + }); + + return { + content: [ + { + type: 'text', + text: `No conversation found for session: ${sessionId}`, + }, + ], + }; + } + + let messages = [...conversationData.messages]; + + // Apply role filter if specified + if (filterRole) { + messages = messages.filter((msg) => msg.role === filterRole); + } + + // Apply limit if specified (get most recent messages) + if (limit && limit > 0) { + messages = messages.slice(-limit); + } + + logger.info('Conversation retrieved successfully', { + sessionId, + totalMessages: conversationData.messages.length, + filteredMessages: messages.length, + filterRole, + limit, + duration, + }); + + logMcpToolCall(logger, 'get-conversation', sessionId, { sessionId }); + + return { + content: [ + { + type: 'text', + text: JSON.stringify( + { + sessionId, + conversation: { + messages, + context: conversationData.context, + lastUpdated: conversationData.lastUpdated, + totalMessages: conversationData.messages.length, + filteredMessages: messages.length, + }, + timestamp: conversationData.timestamp, + }, + null, + 2 + ), + }, + ], + }; + } catch (error) { + const duration = Date.now() - startTime; + + logger.error('Failed to get conversation', { + sessionId, + error: error.message, + stack: error.stack, + duration, + }); + + logMcpError(logger, error, { sessionId }); + logMcpToolCall(logger, 'get-conversation', sessionId, { sessionId }); + + return { + content: [ + { + type: 'text', + text: `Failed to get conversation: ${error.message}`, + }, + ], + isError: true, + }; + } + } + ); + + mcpServer.tool( + 'clear-conversation', + { + sessionId: z.string().describe('Unique session identifier'), + keepOtherData: z + .boolean() + .optional() + .default(true) + .describe( + 'Whether to keep other session data (state, errors) and only clear conversation' + ), + }, + async ({ sessionId, keepOtherData = true }) => { + const startTime = Date.now(); + + logger.info('clear-conversation tool called', { + sessionId, + keepOtherData, + }); + + try { + // Use fast in-memory operation + const cleared = await memoryManager.clearConversation( + sessionId, + keepOtherData + ); + + const duration = Date.now() - startTime; + + if (!cleared) { + logger.info('No conversation found to clear', { + sessionId, + duration, + }); + logMcpToolCall(logger, 'clear-conversation', sessionId, { + sessionId, + }); + + return { + content: [ + { + type: 'text', + text: `No conversation found to clear for session: ${sessionId}`, + }, + ], + }; + } + + const message = keepOtherData + ? `Conversation cleared successfully for session: ${sessionId} (other data preserved)` + : `Entire session cleared successfully: ${sessionId}`; + + logger.info( + keepOtherData + ? 'Conversation cleared successfully (keeping other data)' + : 'Entire session cleared successfully', + { + sessionId, + duration, + } + ); + + logMcpToolCall(logger, 'clear-conversation', sessionId, { + sessionId, + }); + + return { + content: [ + { + type: 'text', + text: message, + }, + ], + }; + } catch (error) { + const duration = Date.now() - startTime; + + logger.error('Failed to clear conversation', { + sessionId, + keepOtherData, + error: error.message, + stack: error.stack, + duration, + }); + + logMcpError(logger, error, { sessionId }); + logMcpToolCall(logger, 'clear-conversation', sessionId, { sessionId }); + + return { + content: [ + { + type: 'text', + text: `Failed to clear conversation: ${error.message}`, + }, + ], + isError: true, + }; + } + } + ); + + mcpServer.tool( + 'clear-session', + { + sessionId: z.string().describe('Unique session identifier'), + }, + async ({ sessionId }) => { + const startTime = Date.now(); + + logger.info('clear-session tool called', { sessionId }); + + try { + // Use fast in-memory operation + const cleared = await memoryManager.clearSession(sessionId); + + const duration = Date.now() - startTime; + + if (!cleared) { + logger.info('No session found to clear', { sessionId, duration }); + logMcpToolCall('clear-session', { sessionId }, true, duration); + + return { + content: [ + { + type: 'text', + text: `No session found to clear: ${sessionId}`, + }, + ], + }; + } + + logger.info('Session cleared successfully', { sessionId, duration }); + logMcpToolCall('clear-session', { sessionId }, true, duration); + + return { + content: [ + { + type: 'text', + text: `Session cleared successfully: ${sessionId}`, + }, + ], + }; + } catch (error) { + const duration = Date.now() - startTime; + + logger.error('Failed to clear session', { + sessionId, + error: error.message, + stack: error.stack, + duration, + }); + + logMcpError('clear-session', error, { sessionId }); + logMcpToolCall('clear-session', { sessionId }, false, duration); + + return { + content: [ + { + type: 'text', + text: `Failed to clear session: ${error.message}`, + }, + ], + isError: true, + }; + } + } + ); + + logger.debug('All MCP tools registered'); + return mcpServer; +} + +/** + * Handle MCP requests + */ +app.post('/mcp', async (req, res) => { + const requestId = randomUUID(); + const sessionId = req.headers['mcp-session-id'] || randomUUID(); + const startTime = Date.now(); + + logger.info('MCP request received', { + requestId, + sessionId, + method: req.body?.method, + hasParams: !!req.body?.params, + userAgent: req.headers['user-agent'], + contentType: req.headers['content-type'], + contentLength: req.headers['content-length'], + }); + + logMcpRequest(logger, req, { + sessionId, + requestId, + params: req.body?.params, + }); + + try { + let transport = transports[sessionId]; + + if (!transport) { + logger.debug('Creating new transport for session', { + sessionId, + requestId, + }); + + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => sessionId, + }); + + transports[sessionId] = transport; + + transport.onclose = () => { + logger.debug('Transport closed for session', { sessionId }); + delete transports[sessionId]; + }; + + const mcpServer = createMcpServer(); + await mcpServer.connect(transport); + + logger.debug('MCP server connected to transport', { + sessionId, + requestId, + }); + logMcpSession(logger, 'connected', sessionId); + } else { + logger.debug('Reusing existing transport for session', { + sessionId, + requestId, + }); + } + + await transport.handleRequest(req, res, req.body); + + const duration = Date.now() - startTime; + + logger.info('MCP request handled successfully', { + requestId, + sessionId, + duration, + method: req.body?.method, + }); + } catch (error) { + const duration = Date.now() - startTime; + + logger.error('Error handling MCP request', { + requestId, + sessionId, + error: error.message, + stack: error.stack, + method: req.body?.method, + duration, + }); + + logMcpError(logger, error, { + sessionId, + requestId, + method: req.body?.method, + }); + + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error', + }, + id: null, + }); + } + } +}); + +app.get('/health', (req, res) => { + const requestId = randomUUID(); + + logger.debug('Health check requested', { + requestId, + userAgent: req.headers['user-agent'], + }); + + const healthData = { + status: 'healthy', + timestamp: new Date().toISOString(), + activeSessions: Object.keys(transports).length, + memoryDir, + }; + + logger.debug('Health check response', { requestId, healthData }); + + res.status(200).json(healthData); +}); + +app.get('/mcp', (req, res) => { + const requestId = randomUUID(); + + logger.warn('Method not allowed: GET /mcp', { + requestId, + userAgent: req.headers['user-agent'], + }); + + res.status(405).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not allowed', + }, + id: null, + }); +}); + +app.delete('/mcp', (req, res) => { + const requestId = randomUUID(); + + logger.warn('Method not allowed: DELETE /mcp', { + requestId, + userAgent: req.headers['user-agent'], + }); + + res.status(405).json({ + jsonrpc: '2.0', + error: { + code: -32000, + message: 'Method not allowed', + }, + id: null, + }); +}); + +const PORT = process.env.PORT || 3004; + +logger.info('Starting MCP Memory Server', { + port: PORT, + memoryDir, + nodeVersion: process.version, + environment: process.env.NODE_ENV || 'development', +}); + +async function startServer() { + try { + await memoryManager.initialize(); + logger.info('MemoryManager initialized successfully'); + + app.listen(PORT, () => { + logger.info('MCP Memory Server started successfully', { + port: PORT, + memoryDir, + endpoints: ['/mcp', '/health'], + sessionsLoaded: memoryManager.getStats().sessionsInMemory, + }); + }); + } catch (error) { + logger.error('Failed to start server', { + error: error.message, + stack: error.stack, + }); + process.exit(1); + } +} + +startServer(); + +process.on('SIGTERM', async () => { + logger.info('SIGTERM received, shutting down gracefully'); + + try { + await memoryManager.shutdown(); + logger.info('MemoryManager shutdown completed'); + } catch (error) { + logger.error('Error during MemoryManager shutdown', { + error: error.message, + }); + } + + Object.keys(transports).forEach((sessionId) => { + logger.debug('Closing transport for session', { sessionId }); + const transport = transports[sessionId]; + if (transport && transport.close) { + transport.close(); + } + }); + + logger.info('MCP Memory Server shutdown complete'); + process.exit(0); +}); + +process.on('SIGINT', async () => { + logger.info('SIGINT received, shutting down gracefully'); + + try { + // Shutdown MemoryManager first to persist data + await memoryManager.shutdown(); + logger.info('MemoryManager shutdown completed'); + } catch (error) { + logger.error('Error during MemoryManager shutdown', { + error: error.message, + }); + } + + Object.keys(transports).forEach((sessionId) => { + logger.debug('Closing transport for session', { sessionId }); + const transport = transports[sessionId]; + if (transport && transport.close) { + transport.close(); + } + }); + + logger.info('MCP Memory Server shutdown complete'); + process.exit(0); +}); diff --git a/examples/mcp/mcp-servers/memory/logger.js b/examples/mcp/mcp-servers/memory/logger.js new file mode 100644 index 0000000..db86d11 --- /dev/null +++ b/examples/mcp/mcp-servers/memory/logger.js @@ -0,0 +1,97 @@ +/** + * Standardized Winston Logger for MCP Servers + * + * Provides consistent, single-line logging across all MCP servers + * with structured metadata and unified formatting. + */ + +import winston from 'winston'; + +/** + * Create a standardized logger for MCP servers + * @param {string} serviceName - The name of the MCP service + * @param {string} [version='1.0.0'] - The version of the service + * @param {string} [logLevel] - Override log level (defaults to LOG_LEVEL env var or 'info') + * @returns {winston.Logger} Configured Winston logger + */ +export function createMcpLogger(serviceName, version = '1.0.0', logLevel) { + const level = logLevel || process.env.LOG_LEVEL || 'info'; + + return winston.createLogger({ + level, + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { + service: serviceName, + version, + protocol: 'Model Context Protocol', + transport: 'Streamable HTTP', + }, + transports: [ + new winston.transports.Console({ + handleExceptions: true, + handleRejections: true, + }), + ], + }); +} + +/** + * Log MCP request received + */ +export function logMcpRequest(logger, req, additionalMeta = {}) { + const sessionId = req.headers['mcp-session-id']; + const method = req.body?.method; + const id = req.body?.id; + + logger.info('MCP request received', { + sessionId, + method, + requestId: id, + userAgent: req.headers['user-agent'], + contentLength: req.headers['content-length'], + ...additionalMeta, + }); +} + +/** + * Log MCP session events + */ +export function logMcpSession(logger, event, sessionId, additionalMeta = {}) { + logger.info(`MCP session ${event}`, { + sessionId, + ...additionalMeta, + }); +} + +/** + * Log MCP tool calls + */ +export function logMcpToolCall( + logger, + toolName, + sessionId, + args = {}, + additionalMeta = {} +) { + logger.info(`MCP tool called: ${toolName}`, { + sessionId, + tool: toolName, + args: Object.keys(args), + ...additionalMeta, + }); +} + +/** + * Log MCP errors with context + */ +export function logMcpError(logger, error, context = {}) { + logger.error('MCP error occurred', { + error: error.message, + stack: error.stack, + ...context, + }); +} diff --git a/examples/mcp/mcp-servers/memory/memory-manager.js b/examples/mcp/mcp-servers/memory/memory-manager.js new file mode 100644 index 0000000..9a5e71c --- /dev/null +++ b/examples/mcp/mcp-servers/memory/memory-manager.js @@ -0,0 +1,529 @@ +/** + * In-Memory Storage Manager with Background Persistence + * + * This module provides fast in-memory operations with automatic background + * disk persistence to avoid blocking the main thread on file I/O. + */ + +import { promises as fs } from 'node:fs'; +import path from 'node:path'; +import { createMcpLogger } from './logger.js'; + +const logger = createMcpLogger('memory-manager', '1.0.0'); + +class MemoryManager { + constructor(memoryDir = '/tmp/memory', persistIntervalMs = 5000) { + this.memoryDir = memoryDir; + this.persistIntervalMs = persistIntervalMs; + + this.memoryStore = new Map(); + + this.dirtySet = new Set(); + + this.persistenceInterval = null; + this.isShuttingDown = false; + + logger.info('MemoryManager initialized', { + memoryDir: this.memoryDir, + persistIntervalMs: this.persistIntervalMs, + }); + } + + /** + * Initialize the memory manager + */ + async initialize() { + try { + await fs.mkdir(this.memoryDir, { recursive: true }); + + await this.loadExistingSessions(); + + this.startBackgroundPersistence(); + + logger.info('MemoryManager started successfully', { + loadedSessions: this.memoryStore.size, + }); + } catch (error) { + logger.error('Failed to initialize MemoryManager', { + error: error.message, + stack: error.stack, + }); + throw error; + } + } + + /** + * Load existing sessions from disk into memory on startup + */ + async loadExistingSessions() { + try { + const files = await fs.readdir(this.memoryDir); + const jsonFiles = files.filter((file) => file.endsWith('.json')); + + logger.debug('Loading existing sessions', { + totalFiles: files.length, + jsonFiles: jsonFiles.length, + }); + + let loadedCount = 0; + let errorCount = 0; + + for (const file of jsonFiles) { + try { + const sessionId = path.basename(file, '.json'); + const filePath = path.join(this.memoryDir, file); + const fileContent = await fs.readFile(filePath, 'utf8'); + const memoryData = JSON.parse(fileContent); + + this.memoryStore.set(sessionId, memoryData); + loadedCount++; + + logger.debug('Session loaded successfully', { sessionId }); + } catch (readError) { + errorCount++; + logger.warn('Failed to load session file', { + file, + error: readError.message, + }); + } + } + + logger.info('Session loading completed', { + loadedCount, + errorCount, + totalSessions: this.memoryStore.size, + }); + } catch (error) { + logger.error('Failed to load existing sessions', { + error: error.message, + }); + // Don't throw - we can continue with empty memory + } + } + + /** + * Start background persistence process + */ + startBackgroundPersistence() { + if (this.persistenceInterval) { + globalThis.clearInterval(this.persistenceInterval); + } + + this.persistenceInterval = globalThis.setInterval(async () => { + if (this.isShuttingDown) return; + + logger.debug('Background persistence tick', { + dirtySessions: this.dirtySet.size, + totalSessions: this.memoryStore.size, + }); + + try { + await this.persistDirtySessions(); + } catch (error) { + logger.error('Background persistence failed', { + error: error.message, + }); + } + }, this.persistIntervalMs); + + logger.info('Background persistence started', { + intervalMs: this.persistIntervalMs, + intervalId: this.persistenceInterval, + }); + } + + /** + * Persist dirty sessions to disk + */ + async persistDirtySessions() { + if (this.dirtySet.size === 0) return; + + const sessionsToPersist = Array.from(this.dirtySet); + this.dirtySet.clear(); + + logger.debug('Persisting dirty sessions', { + sessionCount: sessionsToPersist.length, + }); + + const persistPromises = sessionsToPersist.map(async (sessionId) => { + try { + const memoryData = this.memoryStore.get(sessionId); + if (!memoryData) { + logger.warn('Session not found in memory during persistence', { + sessionId, + }); + return; + } + + const filePath = path.join(this.memoryDir, `${sessionId}.json`); + await fs.writeFile(filePath, JSON.stringify(memoryData, null, 2)); + + logger.debug('Session persisted successfully', { sessionId }); + } catch (error) { + logger.error('Failed to persist session', { + sessionId, + error: error.message, + }); + this.dirtySet.add(sessionId); + } + }); + + await Promise.allSettled(persistPromises); + } + + /** + * Save state to memory (fast, synchronous operation) + */ + saveState(sessionId, state, context = null) { + const memoryData = { + sessionId, + state, + context, + timestamp: new Date().toISOString(), + lastError: this.memoryStore.get(sessionId)?.lastError || null, + }; + + this.memoryStore.set(sessionId, memoryData); + this.dirtySet.add(sessionId); + + logger.debug('State saved to memory', { + sessionId, + stateSize: JSON.stringify(state).length, + totalDirtySessions: this.dirtySet.size, + }); + + return memoryData; + } + + /** + * Save error state for a session (fast in-memory operation) + */ + async saveErrorState(sessionId, state, error, context) { + const memoryData = { + sessionId, + state, + context, + timestamp: new Date().toISOString(), + lastError: { + ...error, + timestamp: new Date().toISOString(), + }, + }; + + this.memoryStore.set(sessionId, memoryData); + + this.dirtySet.add(sessionId); + + logger.debug('Error state saved to memory', { + sessionId, + errorMessage: error.message, + memorySize: this.memoryStore.size, + }); + } + + /** + * Restore state from memory (fast, synchronous operation) + */ + restoreState(sessionId) { + const memoryData = this.memoryStore.get(sessionId); + + if (!memoryData) { + logger.debug('No state found in memory', { sessionId }); + return null; + } + + logger.debug('State restored from memory', { + sessionId, + hasError: !!memoryData.lastError, + }); + + return memoryData; + } + + /** + * List all sessions in memory (fast, synchronous operation) + */ + listSessions() { + const sessions = Array.from(this.memoryStore.entries()).map( + ([sessionId, data]) => ({ + sessionId, + context: data.context, + timestamp: data.timestamp, + hasError: !!data.lastError, + lastError: data.lastError?.message, + }) + ); + + logger.debug('Sessions listed from memory', { + sessionCount: sessions.length, + }); + + return sessions; + } + + /** + * Clear session from memory and mark for deletion + */ + clearSession(sessionId) { + const existed = this.memoryStore.has(sessionId); + this.memoryStore.delete(sessionId); + this.dirtySet.delete(sessionId); + + if (existed) { + this.scheduleFileDeletion(sessionId); + logger.debug('Session cleared from memory', { sessionId }); + } + + return existed; + } + + /** + * Schedule file deletion for the next persistence cycle + */ + scheduleFileDeletion(sessionId) { + this.dirtySet.add(`__DELETE__${sessionId}`); + } + + /** + * Enhanced persistence that handles deletions + */ + async persistDirtySessionsWithDeletions() { + if (this.dirtySet.size === 0) return; + + const operations = Array.from(this.dirtySet); + this.dirtySet.clear(); + + const saveOperations = operations.filter( + (op) => !op.startsWith('__DELETE__') + ); + const deleteOperations = operations + .filter((op) => op.startsWith('__DELETE__')) + .map((op) => op.replace('__DELETE__', '')); + + logger.debug('Persisting sessions with operations', { + saveCount: saveOperations.length, + deleteCount: deleteOperations.length, + }); + + const savePromises = saveOperations.map(async (sessionId) => { + try { + const memoryData = this.memoryStore.get(sessionId); + if (!memoryData) return; + + const filePath = path.join(this.memoryDir, `${sessionId}.json`); + await fs.writeFile(filePath, JSON.stringify(memoryData, null, 2)); + } catch (error) { + logger.error('Failed to persist session', { + sessionId, + error: error.message, + }); + this.dirtySet.add(sessionId); + } + }); + + const deletePromises = deleteOperations.map(async (sessionId) => { + try { + const filePath = path.join(this.memoryDir, `${sessionId}.json`); + await fs.unlink(filePath); + logger.debug('Session file deleted', { sessionId }); + } catch (error) { + if (error.code !== 'ENOENT') { + logger.error('Failed to delete session file', { + sessionId, + error: error.message, + }); + } + } + }); + + await Promise.allSettled([...savePromises, ...deletePromises]); + } + + /** + * Graceful shutdown with immediate persistence + */ + async shutdown() { + logger.info('MemoryManager shutting down...'); + + this.isShuttingDown = true; + + if (this.persistenceInterval) { + globalThis.clearInterval(this.persistenceInterval); + this.persistenceInterval = null; + } + + try { + await this.persistDirtySessionsWithDeletions(); + logger.info('Final persistence completed successfully'); + } catch (error) { + logger.error('Failed to complete final persistence', { + error: error.message, + }); + } + + logger.info('MemoryManager shutdown completed'); + } + + /** + * Get memory statistics + */ + getStats() { + return { + sessionsInMemory: this.memoryStore.size, + dirtySessionsCount: this.dirtySet.size, + isShuttingDown: this.isShuttingDown, + persistIntervalMs: this.persistIntervalMs, + }; + } + + /** + * Save conversation to memory (fast in-memory operation) + */ + saveConversation(sessionId, messages, context = null) { + const existingData = this.memoryStore.get(sessionId) || {}; + + const timestampedMessages = messages.map((msg) => ({ + ...msg, + timestamp: msg.timestamp || new Date().toISOString(), + })); + + const memoryData = { + ...existingData, + sessionId, + conversation: { + messages: timestampedMessages, + context, + lastUpdated: new Date().toISOString(), + }, + timestamp: new Date().toISOString(), + }; + + this.memoryStore.set(sessionId, memoryData); + this.dirtySet.add(sessionId); + + logger.debug('Conversation saved to memory', { + sessionId, + messageCount: timestampedMessages.length, + memorySize: this.memoryStore.size, + }); + + return memoryData; + } + + /** + * Add message to conversation (fast in-memory operation) + */ + addMessage(sessionId, role, content, timestamp = null) { + const messageTimestamp = timestamp || new Date().toISOString(); + const newMessage = { + role, + content, + timestamp: messageTimestamp, + }; + + let memoryData = this.memoryStore.get(sessionId) || { + sessionId, + timestamp: new Date().toISOString(), + }; + + if (!memoryData.conversation) { + memoryData.conversation = { + messages: [], + context: null, + lastUpdated: new Date().toISOString(), + }; + } + + memoryData.conversation.messages.push(newMessage); + memoryData.conversation.lastUpdated = new Date().toISOString(); + memoryData.timestamp = new Date().toISOString(); + + this.memoryStore.set(sessionId, memoryData); + this.dirtySet.add(sessionId); + + logger.debug('Message added to memory', { + sessionId, + role, + totalMessages: memoryData.conversation.messages.length, + }); + + return memoryData; + } + + /** + * Get conversation from memory (fast, synchronous operation) + */ + getConversation(sessionId, filterRole = null, limit = null) { + const memoryData = this.memoryStore.get(sessionId); + + if ( + !memoryData || + !memoryData.conversation || + !memoryData.conversation.messages + ) { + logger.debug('No conversation found in memory', { sessionId }); + return null; + } + + let messages = [...memoryData.conversation.messages]; + + if (filterRole) { + messages = messages.filter((msg) => msg.role === filterRole); + } + + if (limit && limit > 0) { + messages = messages.slice(-limit); + } + + logger.debug('Conversation retrieved from memory', { + sessionId, + totalMessages: memoryData.conversation.messages.length, + filteredMessages: messages.length, + filterRole, + limit, + }); + + return { + sessionId: memoryData.sessionId, + conversation: { + messages, + context: memoryData.conversation.context, + lastUpdated: memoryData.conversation.lastUpdated, + totalMessages: memoryData.conversation.messages.length, + filteredMessages: messages.length, + }, + timestamp: memoryData.timestamp, + }; + } + + /** + * Clear conversation from memory while keeping other data + */ + clearConversation(sessionId, keepOtherData = true) { + if (keepOtherData) { + const memoryData = this.memoryStore.get(sessionId); + if (!memoryData) { + logger.debug('No session found to clear conversation', { sessionId }); + return false; + } + + delete memoryData.conversation; + memoryData.timestamp = new Date().toISOString(); + + this.memoryStore.set(sessionId, memoryData); + this.dirtySet.add(sessionId); + + logger.debug('Conversation cleared from memory (keeping other data)', { + sessionId, + }); + return true; + } else { + return this.clearSession(sessionId); + } + } +} + +MemoryManager.prototype.persistDirtySessions = + MemoryManager.prototype.persistDirtySessionsWithDeletions; + +export default MemoryManager; diff --git a/examples/mcp/mcp-servers/memory/package-lock.json b/examples/mcp/mcp-servers/memory/package-lock.json new file mode 100644 index 0000000..49cf410 --- /dev/null +++ b/examples/mcp/mcp-servers/memory/package-lock.json @@ -0,0 +1,1692 @@ +{ + "name": "mcp-memory-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-memory-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "cors": "^2.8.5", + "express": "^4.18.2", + "winston": "^3.17.0", + "zod": "^3.22.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", + "integrity": "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.46", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.46.tgz", + "integrity": "sha512-IqRxcHEIjqLd4LNS/zKffB3Jzg3NwqJxQQ0Ns7pdrvgGkwQsEBdEQcOHaBVqvvZArShRzI39+aMST3FBGmTrLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/examples/mcp/mcp-servers/memory/package.json b/examples/mcp/mcp-servers/memory/package.json new file mode 100644 index 0000000..3b6ded0 --- /dev/null +++ b/examples/mcp/mcp-servers/memory/package.json @@ -0,0 +1,23 @@ +{ + "name": "mcp-memory-server", + "version": "1.0.0", + "description": "MCP Memory Server for persisting state on HTTP errors", + "type": "module", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "node --watch index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "cors": "^2.8.5", + "express": "^4.18.2", + "winston": "^3.17.0", + "zod": "^3.22.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "author": "Inference Gateway Team", + "license": "MIT" +} diff --git a/examples/mcp/mcp-servers/npm/README.md b/examples/mcp/mcp-servers/npm/README.md new file mode 100644 index 0000000..ce847a9 --- /dev/null +++ b/examples/mcp/mcp-servers/npm/README.md @@ -0,0 +1,101 @@ +# MCP NPM Server + +A Model Context Protocol (MCP) server that provides safe npm command execution capabilities. + +## Features + +- **Whitelisted Commands**: Only allows execution of pre-approved npm commands for security +- **Directory Restrictions**: Commands can only be executed in allowed directories +- **Timeout Protection**: Commands are automatically terminated after 5 minutes +- **Process Isolation**: Each command runs in its own process with controlled environment + +## Whitelisted NPM Commands + +For security, only the following npm commands are allowed: + +- `install` - Install packages +- `ci` - Clean install from package-lock.json +- `run` - Run scripts defined in package.json +- `start` - Run the start script +- `build` - Run the build script +- `test` - Run tests +- `lint` - Run linting +- `init` - Initialize a new package.json +- `list` - List installed packages +- `outdated` - Check for outdated packages +- `audit` - Run security audit +- `version` - Show version information +- `view` - View package information +- `search` - Search for packages +- `info` - Show package information + +## Available Tools + +### npm_run + +Execute whitelisted npm commands safely. + +**Parameters:** + +- `command` (required): The npm command to run +- `args` (optional): Additional arguments for the command +- `workingDir` (optional): Working directory (default: /tmp) + +### npm_init_project + +Initialize a new npm project in a specified directory. + +**Parameters:** + +- `projectName` (required): Name of the project to initialize +- `workingDir` (optional): Directory where to create the project (default: /tmp) +- `packageManager` (optional): Package manager to use (default: npm) + +### npm_install_package + +Install npm packages in a project. + +**Parameters:** + +- `packages` (required): Array of package names to install +- `workingDir` (optional): Working directory (default: /tmp) +- `saveDev` (optional): Install as dev dependency +- `global` (optional): Install globally (not recommended in containers) + +## Environment Variables + +- `ALLOWED_DIRECTORIES`: Comma-separated list of allowed directories (default: `/tmp`) +- `PORT`: Server port (default: 3003) +- `NODE_ENV`: Node environment (default: production) + +## Example Usage + +```bash +# Health check +curl http://localhost:3003/health + +# Example MCP request to run npm install +curl -X POST http://localhost:3003/mcp \ + -H "Content-Type: application/json" \ + -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "npm_run", + "arguments": { + "command": "install", + "args": ["express"], + "workingDir": "/tmp/my-project" + } + } + }' +``` + +## Security Considerations + +- Only whitelisted npm commands can be executed +- Commands are restricted to allowed directories +- Process timeout prevents hanging operations +- No shell injection vulnerabilities due to controlled argument passing +- Environment variables are controlled and sanitized diff --git a/examples/mcp/mcp-servers/npm/index.js b/examples/mcp/mcp-servers/npm/index.js new file mode 100644 index 0000000..035f0cf --- /dev/null +++ b/examples/mcp/mcp-servers/npm/index.js @@ -0,0 +1,671 @@ +/** + * MCP NPM Server + * + * This is a Model Context Protocol (MCP) server that provides npm + * operations. It uses the official MCP TypeScript SDK and implements + * the proper MCP protocol with Streamable HTTP transport. + * + * Security: Only whitelisted npm commands are allowed for security. + */ + +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; +import express from 'express'; +import { randomUUID } from 'node:crypto'; +import { z } from 'zod'; +import { exec } from 'node:child_process'; +import { promisify } from 'node:util'; +import path from 'node:path'; +import { + createMcpLogger, + logMcpRequest, + logMcpSession, + logMcpToolCall, + logMcpError, +} from './logger.js'; + +const execAsync = promisify(exec); + +const logger = createMcpLogger('mcp-npm', '1.0.0'); + +const app = express(); +app.use(express.json()); + +const transports = {}; + +const workingDirectory = process.env.WORKING_DIRECTORY || '/tmp'; + +const ALLOWED_NPM_COMMANDS = [ + 'init', + 'install', + 'uninstall', + 'update', + 'list', + 'info', + 'search', + 'view', + 'outdated', + 'audit', + 'test', + 'run', + 'start', + 'build', + 'version', + 'npx', +]; + +logger.info('NPM MCP Server starting', { + workingDirectory, + allowedCommands: ALLOWED_NPM_COMMANDS, +}); + +/** + * Validate npm command for security + */ +function validateNpmCommand(command) { + const parts = command.trim().split(/\s+/); + if (parts.length === 0) { + throw new Error('Empty command'); + } + + if (parts[0] === 'npm') { + parts.shift(); + } + + if (parts.length === 0) { + throw new Error('No npm command specified'); + } + + const npmCommand = parts[0]; + if (!ALLOWED_NPM_COMMANDS.includes(npmCommand)) { + throw new Error( + `Command '${npmCommand}' is not allowed. Allowed commands: ${ALLOWED_NPM_COMMANDS.join(', ')}` + ); + } + + return parts.join(' '); +} + +/** + * Execute npm command safely + */ +async function executeNpmCommand(command, cwd = workingDirectory) { + const validatedCommand = validateNpmCommand(command); + const fullCommand = `npm ${validatedCommand}`; + + logger.info('Executing npm command', { + command: fullCommand, + workingDirectory: cwd, + }); + + try { + const { stdout, stderr } = await execAsync(fullCommand, { + cwd, + timeout: 300000, // 5 minute timeout (increased from 30 seconds) + maxBuffer: 1024 * 1024, // 1MB buffer + }); + + return { + success: true, + stdout: stdout || '', + stderr: stderr || '', + command: fullCommand, + cwd, + }; + } catch (error) { + logMcpError(logger, error, { + command: fullCommand, + workingDirectory: cwd, + }); + + return { + success: false, + stdout: error.stdout || '', + stderr: error.stderr || error.message, + command: fullCommand, + cwd, + error: error.message, + }; + } +} + +/** + * Create and configure the MCP server + */ +function createMcpServer() { + const mcpServer = new McpServer({ + name: 'npm', + version: '1.0.0', + }); + + mcpServer.tool( + 'npm_run', + { + command: z + .string() + .describe('The npm command to run (without "npm" prefix)'), + cwd: z + .string() + .optional() + .describe('Working directory (defaults to /tmp)'), + }, + async ({ command, cwd }) => { + const workDir = cwd || workingDirectory; + + logMcpToolCall(logger, 'npm_run', 'unknown', { command, cwd: workDir }); + + try { + const result = await executeNpmCommand(command, workDir); + + let responseText = `NPM Command: npm ${command}\n`; + responseText += `Working Directory: ${workDir}\n`; + responseText += `Status: ${result.success ? 'SUCCESS' : 'FAILED'}\n\n`; + + if (result.stdout) { + responseText += `STDOUT:\n${result.stdout}\n\n`; + } + + if (result.stderr) { + responseText += `STDERR:\n${result.stderr}\n\n`; + } + + if (!result.success && result.error) { + responseText += `ERROR: ${result.error}\n`; + } + + return { + content: [ + { + type: 'text', + text: responseText, + }, + ], + }; + } catch (error) { + logMcpError(logger, error, { + tool: 'npm_run', + command, + workingDirectory: workDir, + }); + + return { + content: [ + { + type: 'text', + text: `Failed to execute npm command: ${command}\nError: ${error.message}`, + }, + ], + }; + } + } + ); + + mcpServer.tool( + 'npm_init', + { + name: z.string().describe('Project name'), + cwd: z + .string() + .optional() + .describe('Working directory (defaults to /tmp)'), + yes: z.boolean().optional().describe('Skip prompts and use defaults'), + }, + async ({ name, cwd, yes = true }) => { + const workDir = cwd || workingDirectory; + const projectDir = path.join(workDir, name); + + logMcpToolCall(logger, 'npm_init', 'unknown', { + name, + cwd: workDir, + yes, + }); + + try { + await execAsync(`mkdir -p "${projectDir}"`); + + const initCommand = yes ? 'init -y' : 'init'; + const result = await executeNpmCommand(initCommand, projectDir); + + let responseText = `NPM Project Initialization\n`; + responseText += `Project Name: ${name}\n`; + responseText += `Directory: ${projectDir}\n`; + responseText += `Status: ${result.success ? 'SUCCESS' : 'FAILED'}\n\n`; + + if (result.stdout) { + responseText += `STDOUT:\n${result.stdout}\n\n`; + } + + if (result.stderr) { + responseText += `STDERR:\n${result.stderr}\n\n`; + } + + return { + content: [ + { + type: 'text', + text: responseText, + }, + ], + }; + } catch (error) { + logMcpError(logger, error, { + tool: 'npm_init', + projectName: name, + workingDirectory: projectDir, + }); + + return { + content: [ + { + type: 'text', + text: `Failed to initialize npm project: ${name}\nError: ${error.message}`, + }, + ], + }; + } + } + ); + + mcpServer.tool( + 'npm_install', + { + packages: z + .array(z.string()) + .optional() + .describe('Packages to install (empty for package.json install)'), + cwd: z + .string() + .optional() + .describe('Working directory (defaults to /tmp)'), + dev: z.boolean().optional().describe('Install as dev dependencies'), + global: z.boolean().optional().describe('Install globally'), + }, + async ({ packages = [], cwd, dev = false, global = false }) => { + const workDir = cwd || workingDirectory; + + logMcpToolCall(logger, 'npm_install', 'unknown', { + packages, + cwd: workDir, + dev, + global, + }); + + try { + let command = 'install'; + + if (global) { + command += ' -g'; + } + + if (dev) { + command += ' --save-dev'; + } + + if (packages.length > 0) { + command += ' ' + packages.join(' '); + } + + const result = await executeNpmCommand(command, workDir); + + let responseText = `NPM Install\n`; + responseText += `Working Directory: ${workDir}\n`; + responseText += `Packages: ${packages.length > 0 ? packages.join(', ') : 'package.json dependencies'}\n`; + responseText += `Dev Dependencies: ${dev}\n`; + responseText += `Global: ${global}\n`; + responseText += `Status: ${result.success ? 'SUCCESS' : 'FAILED'}\n\n`; + + if (result.stdout) { + responseText += `STDOUT:\n${result.stdout}\n\n`; + } + + if (result.stderr) { + responseText += `STDERR:\n${result.stderr}\n\n`; + } + + return { + content: [ + { + type: 'text', + text: responseText, + }, + ], + }; + } catch (error) { + logMcpError(logger, error, { + tool: 'npm_install', + packages, + workingDirectory: workDir, + dev, + global, + }); + + return { + content: [ + { + type: 'text', + text: `Failed to install npm packages\nError: ${error.message}`, + }, + ], + }; + } + } + ); + + mcpServer.tool( + 'create_nextjs_project', + { + name: z.string().describe('Project name for the Next.js application'), + cwd: z + .string() + .optional() + .describe('Working directory (defaults to /tmp)'), + typescript: z + .boolean() + .optional() + .describe('Use TypeScript (default: true)'), + tailwind: z + .boolean() + .optional() + .describe('Use Tailwind CSS (default: true)'), + eslint: z.boolean().optional().describe('Use ESLint (default: true)'), + appRouter: z + .boolean() + .optional() + .describe('Use App Router (default: true)'), + srcDir: z + .boolean() + .optional() + .describe('Use src/ directory (default: false)'), + importAlias: z + .string() + .optional() + .describe('Import alias (default: @/*)'), + }, + async ({ + name, + cwd, + typescript = true, + tailwind = true, + eslint = true, + appRouter = true, + srcDir = false, + importAlias = '@/*', + }) => { + const workDir = cwd || workingDirectory; + + logMcpToolCall(logger, 'create_nextjs_project', 'unknown', { + name, + cwd: workDir, + typescript, + tailwind, + eslint, + appRouter, + srcDir, + importAlias, + }); + + try { + let command = `npx create-next-app@latest "${name}" --yes`; + + if (typescript) { + command += ' --typescript'; + } else { + command += ' --javascript'; + } + + if (tailwind) { + command += ' --tailwind'; + } else { + command += ' --no-tailwind'; + } + + if (eslint) { + command += ' --eslint'; + } else { + command += ' --no-eslint'; + } + + if (appRouter) { + command += ' --app'; + } else { + command += ' --no-app'; + } + + if (srcDir) { + command += ' --src-dir'; + } else { + command += ' --no-src-dir'; + } + + if (importAlias && importAlias !== '@/*') { + command += ` --import-alias "${importAlias}"`; + } + + logger.info('Creating Next.js project', { + command, + workingDirectory: workDir, + projectName: name, + options: { + typescript, + tailwind, + eslint, + appRouter, + srcDir, + importAlias, + }, + }); + + const { stdout, stderr } = await execAsync(command, { + cwd: workDir, + timeout: 600000, // 10 minute timeout for project creation (increased from 3 minutes) + maxBuffer: 1024 * 1024 * 5, // 5MB buffer + }); + + let responseText = `Next.js Project Creation\n`; + responseText += `Project Name: ${name}\n`; + responseText += `Directory: ${path.join(workDir, name)}\n`; + responseText += `TypeScript: ${typescript}\n`; + responseText += `Tailwind CSS: ${tailwind}\n`; + responseText += `ESLint: ${eslint}\n`; + responseText += `App Router: ${appRouter}\n`; + responseText += `Source Directory: ${srcDir}\n`; + responseText += `Import Alias: ${importAlias}\n`; + responseText += `Status: SUCCESS\n\n`; + + if (stdout) { + responseText += `STDOUT:\n${stdout}\n\n`; + } + + if (stderr) { + responseText += `STDERR:\n${stderr}\n\n`; + } + + responseText += `Next.js project '${name}' created successfully!\n`; + responseText += `To get started:\n`; + responseText += ` cd ${name}\n`; + responseText += ` npm run dev\n`; + + return { + content: [ + { + type: 'text', + text: responseText, + }, + ], + }; + } catch (error) { + logMcpError(logger, error, { + tool: 'create_nextjs_project', + projectName: name, + workingDirectory: workDir, + }); + + return { + content: [ + { + type: 'text', + text: `Failed to create Next.js project: ${name}\nError: ${error.message}\nSTDOUT: ${error.stdout || 'N/A'}\nSTDERR: ${error.stderr || 'N/A'}`, + }, + ], + }; + } + } + ); + + return mcpServer; +} + +/** + * Setup MCP endpoints for proper Model Context Protocol communication + */ +function setupSessionRoutes() { + app.post('/mcp', async (req, res) => { + try { + logMcpRequest(logger, req); + + const accept = req.headers.accept || req.headers.Accept; + if ( + !accept || + !accept.includes('application/json') || + !accept.includes('text/event-stream') + ) { + logger.debug('Adding missing Accept headers for MCP compatibility'); + req.headers.accept = 'application/json, text/event-stream'; + } + + const sessionId = req.headers['mcp-session-id']; + let transport; + + if (sessionId && transports[sessionId]) { + transport = transports[sessionId]; + } else { + transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + onsessioninitialized: (newSessionId) => { + logMcpSession(logger, 'initialized', newSessionId); + transports[newSessionId] = transport; + }, + }); + + transport.onclose = () => { + if (transport.sessionId) { + logMcpSession(logger, 'closed', transport.sessionId); + delete transports[transport.sessionId]; + } + }; + + const server = createMcpServer(); + await server.connect(transport); + } + + await transport.handleRequest(req, res, req.body); + } catch (error) { + logMcpError(logger, error, { + endpoint: '/mcp', + method: 'POST', + }); + if (!res.headersSent) { + res.status(500).json({ + jsonrpc: '2.0', + error: { + code: -32603, + message: 'Internal server error', + }, + id: null, + }); + } + } + }); + + app.get('/mcp', async (req, res) => { + const sessionId = req.headers['mcp-session-id']; + if (!sessionId || !transports[sessionId]) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + const transport = transports[sessionId]; + await transport.handleRequest(req, res); + }); + + app.delete('/mcp', async (req, res) => { + const sessionId = req.headers['mcp-session-id']; + if (!sessionId || !transports[sessionId]) { + res.status(400).send('Invalid or missing session ID'); + return; + } + + const transport = transports[sessionId]; + await transport.handleRequest(req, res); + }); +} + +/** + * Health check endpoint + */ +function setupHealthCheck() { + app.get('/health', (req, res) => { + res.json({ + status: 'healthy', + server: 'mcp-npm', + version: '1.0.0', + timestamp: new Date().toISOString(), + workingDirectory, + allowedCommands: ALLOWED_NPM_COMMANDS, + }); + }); +} + +/** + * Start the server + */ +async function startServer() { + const port = process.env.PORT || 3003; + + setupSessionRoutes(); + setupHealthCheck(); + + app.listen(port, '0.0.0.0', () => { + logger.info('MCP NPM Server started successfully', { + host: '0.0.0.0', + port, + endpoints: { + mcp: 'POST /mcp - MCP protocol communication', + mcpSse: 'GET /mcp - MCP SSE notifications', + mcpDelete: 'DELETE /mcp - MCP session termination', + health: 'GET /health - Health check', + }, + tools: [ + 'npm_run - Run any whitelisted npm command', + 'npm_init - Initialize a new npm project', + 'npm_install - Install npm packages', + 'create_nextjs_project - Create a new Next.js project', + ], + workingDirectory, + allowedCommands: ALLOWED_NPM_COMMANDS, + }); + }); +} + +process.on('SIGTERM', () => { + logger.info('Received SIGTERM, shutting down gracefully'); + Object.values(transports).forEach((transport) => { + if (transport.close) transport.close(); + }); + process.exit(0); +}); + +process.on('SIGINT', () => { + logger.info('Received SIGINT, shutting down gracefully'); + Object.values(transports).forEach((transport) => { + if (transport.close) transport.close(); + }); + process.exit(0); +}); + +startServer().catch((error) => { + logMcpError(logger, error, { + operation: 'server-startup', + }); + process.exit(1); +}); diff --git a/examples/mcp/mcp-servers/npm/logger.js b/examples/mcp/mcp-servers/npm/logger.js new file mode 100644 index 0000000..db86d11 --- /dev/null +++ b/examples/mcp/mcp-servers/npm/logger.js @@ -0,0 +1,97 @@ +/** + * Standardized Winston Logger for MCP Servers + * + * Provides consistent, single-line logging across all MCP servers + * with structured metadata and unified formatting. + */ + +import winston from 'winston'; + +/** + * Create a standardized logger for MCP servers + * @param {string} serviceName - The name of the MCP service + * @param {string} [version='1.0.0'] - The version of the service + * @param {string} [logLevel] - Override log level (defaults to LOG_LEVEL env var or 'info') + * @returns {winston.Logger} Configured Winston logger + */ +export function createMcpLogger(serviceName, version = '1.0.0', logLevel) { + const level = logLevel || process.env.LOG_LEVEL || 'info'; + + return winston.createLogger({ + level, + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { + service: serviceName, + version, + protocol: 'Model Context Protocol', + transport: 'Streamable HTTP', + }, + transports: [ + new winston.transports.Console({ + handleExceptions: true, + handleRejections: true, + }), + ], + }); +} + +/** + * Log MCP request received + */ +export function logMcpRequest(logger, req, additionalMeta = {}) { + const sessionId = req.headers['mcp-session-id']; + const method = req.body?.method; + const id = req.body?.id; + + logger.info('MCP request received', { + sessionId, + method, + requestId: id, + userAgent: req.headers['user-agent'], + contentLength: req.headers['content-length'], + ...additionalMeta, + }); +} + +/** + * Log MCP session events + */ +export function logMcpSession(logger, event, sessionId, additionalMeta = {}) { + logger.info(`MCP session ${event}`, { + sessionId, + ...additionalMeta, + }); +} + +/** + * Log MCP tool calls + */ +export function logMcpToolCall( + logger, + toolName, + sessionId, + args = {}, + additionalMeta = {} +) { + logger.info(`MCP tool called: ${toolName}`, { + sessionId, + tool: toolName, + args: Object.keys(args), + ...additionalMeta, + }); +} + +/** + * Log MCP errors with context + */ +export function logMcpError(logger, error, context = {}) { + logger.error('MCP error occurred', { + error: error.message, + stack: error.stack, + ...context, + }); +} diff --git a/examples/mcp/mcp-servers/npm/package-lock.json b/examples/mcp/mcp-servers/npm/package-lock.json new file mode 100644 index 0000000..3bbf2c7 --- /dev/null +++ b/examples/mcp/mcp-servers/npm/package-lock.json @@ -0,0 +1,1692 @@ +{ + "name": "mcp-npm-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mcp-npm-server", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "cors": "^2.8.5", + "express": "^4.18.2", + "winston": "^3.17.0", + "zod": "^3.22.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.1.tgz", + "integrity": "sha512-KG1CZhZfWg+u8pxeM/mByJDScJSrjjxLc8fwQqbsS8xCjBmQfMNEBTotYdNanKekepnfRI85GtgQlctLFpcYPw==", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.6", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/router/node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/router/node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "3.25.46", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.46.tgz", + "integrity": "sha512-IqRxcHEIjqLd4LNS/zKffB3Jzg3NwqJxQQ0Ns7pdrvgGkwQsEBdEQcOHaBVqvvZArShRzI39+aMST3FBGmTrLQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } + } + } +} diff --git a/examples/mcp/mcp-servers/npm/package.json b/examples/mcp/mcp-servers/npm/package.json new file mode 100644 index 0000000..d7a68e6 --- /dev/null +++ b/examples/mcp/mcp-servers/npm/package.json @@ -0,0 +1,23 @@ +{ + "name": "mcp-npm-server", + "version": "1.0.0", + "description": "MCP NPM Server for running whitelisted npm commands", + "type": "module", + "main": "index.js", + "scripts": { + "start": "node index.js", + "dev": "node --watch index.js" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.0", + "cors": "^2.8.5", + "express": "^4.18.2", + "winston": "^3.17.0", + "zod": "^3.22.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "author": "Inference Gateway Team", + "license": "MIT" +} diff --git a/examples/mcp/mcp-servers/web-search/README.md b/examples/mcp/mcp-servers/web-search/README.md index 35cc9b9..a63113a 100644 --- a/examples/mcp/mcp-servers/web-search/README.md +++ b/examples/mcp/mcp-servers/web-search/README.md @@ -1,11 +1,11 @@ # MCP Web Search Server -A Model Context Protocol (MCP) server that provides web search and URL fetching capabilities. +A Model Context Protocol (MCP) server that provides web search and URL fetching capabilities using DuckDuckGo. ## Features - **fetch_url**: Fetch content from any URL with error handling and timeout support -- **search_web**: Simulated web search functionality (can be replaced with real search APIs) +- **search_web**: Real web search functionality using DuckDuckGo with safe search options - **get_page_title**: Extract page titles from web pages using HTML parsing ## Installation @@ -85,11 +85,18 @@ Returns server health status. "name": "search_web", "arguments": { "query": "machine learning tutorials", - "limit": 5 + "limit": 5, + "safe_search": "moderate" } } ``` +#### Safe Search Options + +- `strict`: Strict safe search filtering +- `moderate`: Moderate safe search filtering (default) +- `off`: No safe search filtering + ### Get Page Title ```json diff --git a/examples/mcp/mcp-servers/web-search/index-http.js b/examples/mcp/mcp-servers/web-search/index-http.js deleted file mode 100644 index 513d933..0000000 --- a/examples/mcp/mcp-servers/web-search/index-http.js +++ /dev/null @@ -1,331 +0,0 @@ -/** - * HTTP-based Web Search Server using MCP SDK - * - * This server uses the official MCP TypeScript SDK with StreamableHTTPServerTransport - * in stateless mode, which responds with plain HTTP JSON-RPC instead of SSE. - * This is compatible with Go MCP clients that expect standard HTTP responses. - */ - -import express from 'express'; -import axios from 'axios'; -import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; -import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; -import { z } from 'zod'; - -const app = express(); -app.use(express.json()); - -/** - * Generate simulated search results - */ -function generateSearchResults(query, limit) { - const results = []; - const domains = [ - 'example.com', - 'wikipedia.org', - 'github.com', - 'stackoverflow.com', - 'medium.com', - ]; - - for (let i = 0; i < Math.min(limit, 10); i++) { - const domain = domains[i % domains.length]; - const title = `${query} - Result ${i + 1}`; - const url = `https://${domain}/${query - .toLowerCase() - .replace(/\s+/g, '-')}-${i + 1}`; - const description = `This is a simulated search result for "${query}". It would normally contain relevant information about your search query.`; - - results.push(`${i + 1}. ${title}\n ${url}\n ${description}\n`); - } - - return results.join('\n'); -} - -/** - * Create and configure the MCP server - */ -function createServer() { - const server = new McpServer( - { - name: 'web-search-server', - version: '1.0.0', - }, - { - capabilities: { - tools: {}, - }, - } - ); - - // Add fetch_url tool - server.tool( - 'fetch_url', - { - description: 'Fetch content from a URL', - inputSchema: { - type: 'object', - properties: { - url: z.string().url(), - timeout: z.number().min(1000).max(30000).optional().default(10000), - }, - required: ['url'], - }, - }, - async ({ url, timeout = 10000 }) => { - try { - console.info(`Fetching URL: ${url}`); - const response = await axios.get(url, { - timeout, - headers: { - 'User-Agent': 'HTTP-Web-Search-Server/1.0.0', - }, - maxRedirects: 5, - validateStatus: (status) => status < 500, - }); - - const contentType = response.headers['content-type'] || ''; - let content; - if (contentType.includes('application/json')) { - content = JSON.stringify(response.data, null, 2); - } else if (contentType.includes('text/')) { - content = response.data.toString(); - } else { - content = `Binary content (${contentType}), size: ${ - JSON.stringify(response.data).length - } bytes`; - } - - // Truncate very large responses - if (content.length > 10000) { - content = content.substring(0, 10000) + '\n\n... (content truncated)'; - } - - return { - content: [ - { - type: 'text', - text: `URL: ${url}\nStatus: ${response.status} ${response.statusText}\nContent-Type: ${contentType}\n\nContent:\n${content}`, - }, - ], - }; - } catch (error) { - console.error(`Failed to fetch URL ${url}:`, error.message); - let errorMessage = `Failed to fetch URL: ${url}\n`; - if (error.code === 'ENOTFOUND') { - errorMessage += 'Domain not found'; - } else if (error.code === 'ECONNREFUSED') { - errorMessage += 'Connection refused'; - } else if (error.code === 'ETIMEDOUT') { - errorMessage += 'Request timed out'; - } else if (error.response) { - errorMessage += `HTTP ${error.response.status}: ${error.response.statusText}`; - } else { - errorMessage += error.message; - } - - return { - content: [ - { - type: 'text', - text: errorMessage, - }, - ], - }; - } - } - ); - - // Add search_web tool - server.tool( - 'search_web', - { - description: 'Perform a web search', - inputSchema: { - type: 'object', - properties: { - query: z.string().min(1).max(500), - limit: z.number().min(1).max(20).optional().default(5), - }, - required: ['query'], - }, - }, - async ({ query, limit = 5 }) => { - console.info(`Searching for: "${query}" (limit: ${limit})`); - const searchResults = generateSearchResults(query, limit); - - return { - content: [ - { - type: 'text', - text: `Search Results for "${query}":\n\n${searchResults}`, - }, - ], - }; - } - ); - - // Add get_page_title tool - server.tool( - 'get_page_title', - { - description: 'Extract title from a web page', - inputSchema: { - type: 'object', - properties: { - url: z.string().url(), - }, - required: ['url'], - }, - }, - async ({ url }) => { - try { - console.info(`Extracting title from: ${url}`); - const response = await axios.get(url, { - timeout: 10000, - headers: { - 'User-Agent': 'HTTP-Web-Search-Server/1.0.0', - }, - }); - - const titleMatch = response.data.match(/]*>([^<]+)<\/title>/i); - const title = titleMatch ? titleMatch[1].trim() : 'No title found'; - - return { - content: [ - { - type: 'text', - text: `Title: ${title}\nURL: ${url}`, - }, - ], - }; - } catch (error) { - console.error(`Failed to extract title from ${url}:`, error.message); - - return { - content: [ - { - type: 'text', - text: `Failed to extract title from: ${url}\nError: ${error.message}`, - }, - ], - }; - } - } - ); - - return server; -} - -/** - * Handle MCP requests using stateless mode - */ -app.post('/mcp', async (req, res) => { - try { - console.info('HTTP JSON-RPC request received:'); - console.info(' Body: %s', JSON.stringify(req.body, null, 2)); - - // Create new server and transport instances for each request (stateless mode) - const server = createServer(); - const transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: undefined, // Stateless mode - }); - - // Clean up on request close - res.on('close', () => { - console.info('Request closed'); - transport.close(); - server.close(); - }); - - // Connect server to transport - await server.connect(transport); - - // Handle the request - await transport.handleRequest(req, res, req.body); - } catch (error) { - console.error('Error handling MCP request:', error); - if (!res.headersSent) { - res.status(500).json({ - jsonrpc: '2.0', - error: { - code: -32603, - message: 'Internal server error', - data: error.message, - }, - id: req.body?.id || null, - }); - } - } -}); - -// Handle unsupported methods for stateless mode -app.get('/mcp', async (req, res) => { - console.info('Received GET MCP request'); - res.status(405).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: 'Method not allowed in stateless mode.', - }, - id: null, - }); -}); - -app.delete('/mcp', async (req, res) => { - console.info('Received DELETE MCP request'); - res.status(405).json({ - jsonrpc: '2.0', - error: { - code: -32000, - message: 'Method not allowed in stateless mode.', - }, - id: null, - }); -}); - -// Health check endpoint -app.get('/health', (req, res) => { - const healthStatus = { - status: 'healthy', - timestamp: new Date().toISOString(), - uptime: process.uptime(), - service: 'http-web-search', - version: '1.0.0', - protocol: 'HTTP JSON-RPC', - }; - - console.info('Health check requested: %j', healthStatus); - res.json(healthStatus); -}); - -// Start the server -const port = process.env.PORT || 3001; -const host = process.env.HOST || '0.0.0.0'; - -app.listen(port, host, () => { - console.info(`HTTP Web Search server running on http://${host}:${port}`); - console.info('Protocol: HTTP JSON-RPC 2.0'); - console.info('Available endpoints:'); - console.info(' POST /mcp - JSON-RPC endpoint'); - console.info(' GET /health - Health check'); - console.info('Available methods:'); - console.info(' - initialize - Initialize the server'); - console.info(' - tools/list - List available tools'); - console.info(' - tools/call - Call a tool'); - console.info('Available tools:'); - console.info(' - fetch_url - Fetch content from a URL'); - console.info(' - search_web - Perform web search (simulated)'); - console.info(' - get_page_title - Extract title from a web page'); -}); - -// Graceful shutdown -process.on('SIGTERM', () => { - console.info('Received SIGTERM, shutting down gracefully'); - process.exit(0); -}); - -process.on('SIGINT', () => { - console.info('Received SIGINT, shutting down gracefully'); - process.exit(0); -}); diff --git a/examples/mcp/mcp-servers/web-search/index.js b/examples/mcp/mcp-servers/web-search/index.js index eadea5b..9fa8451 100644 --- a/examples/mcp/mcp-servers/web-search/index.js +++ b/examples/mcp/mcp-servers/web-search/index.js @@ -10,26 +10,125 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'; import express from 'express'; import { randomUUID } from 'node:crypto'; +import { setTimeout } from 'node:timers/promises'; import { z } from 'zod'; import axios from 'axios'; +import * as cheerio from 'cheerio'; +import { search, SafeSearchType } from 'duck-duck-scrape'; +import { createMcpLogger } from './logger.js'; +import cors from 'cors'; + +const logger = createMcpLogger('mcp-web-search', '1.0.0'); + +let lastSearchTime = 0; +const MIN_SEARCH_INTERVAL = 2000; + +const rateLimitedSearch = async (query, options, retries = 3) => { + const now = Date.now(); + const timeSinceLastSearch = now - lastSearchTime; + + if (timeSinceLastSearch < MIN_SEARCH_INTERVAL) { + const delay = MIN_SEARCH_INTERVAL - timeSinceLastSearch; + logger.info('Rate limiting: delaying search', { delay, query }); + await setTimeout(delay); + } + + lastSearchTime = Date.now(); + + for (let attempt = 1; attempt <= retries; attempt++) { + try { + return await search(query, options); + } catch (error) { + if ( + error.message.includes('anomaly') || + error.message.includes('too quickly') + ) { + if (attempt < retries) { + const backoffDelay = MIN_SEARCH_INTERVAL * attempt; + logger.warn('DuckDuckGo rate limit hit, retrying', { + attempt, + retries, + delay: backoffDelay, + query, + error: error.message, + }); + await setTimeout(backoffDelay); + continue; + } + } + throw error; + } + } +}; -// Express app for HTTP transport const app = express(); -app.use(express.json()); +app.use(cors()); +app.use(express.json({ limit: '10mb' })); + +app.use((req, res, next) => { + const startTime = Date.now(); + const requestId = randomUUID(); + + req.requestId = requestId; + + logger.info('Incoming request', { + requestId, + method: req.method, + url: req.url, + userAgent: req.get('User-Agent'), + contentType: req.get('Content-Type'), + sessionId: req.headers['mcp-session-id'], + }); + + res.on('finish', () => { + const duration = Date.now() - startTime; + logger.info('Request completed', { + requestId, + method: req.method, + url: req.url, + statusCode: res.statusCode, + duration, + sessionId: req.headers['mcp-session-id'], + }); + }); + + next(); +}); + +const transports = new Map(); + +/** + * Enhanced error handling for tools + */ +function createToolError(error, context = {}) { + logger.error('Tool execution error', { + error: error.message, + stack: error.stack, + context, + }); -// Map to store transports by session ID -const transports = {}; + return { + content: [ + { + type: 'text', + text: `Error: ${error.message}`, + }, + ], + isError: true, + }; +} /** * Create and configure the MCP server */ function createMcpServer() { + logger.info('Creating MCP server instance'); + const mcpServer = new McpServer({ name: 'web-search', version: '1.0.0', }); - // Tool: Fetch URL content mcpServer.tool( 'fetch_url', { @@ -40,25 +139,79 @@ function createMcpServer() { .max(30000) .default(10000) .describe('Request timeout in milliseconds'), + extract_text: z + .boolean() + .default(true) + .describe('Extract plain text from HTML content'), }, - async ({ url, timeout = 10000 }) => { + async ({ url, timeout = 10000, extract_text = true }) => { + const operationId = randomUUID(); + try { - console.info(`Fetching URL: ${url}`); + logger.info('Fetching URL', { + operationId, + url, + timeout, + extract_text, + }); const response = await axios.get(url, { timeout, headers: { - 'User-Agent': 'MCP-Web-Search-Server/1.0.0', + 'User-Agent': 'MCP-Web-Search-Server/1.0.0 (Compatible)', + Accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', + 'Accept-Language': 'en-US,en;q=0.5', + 'Accept-Encoding': 'gzip, deflate', + Connection: 'keep-alive', }, maxRedirects: 5, - validateStatus: (status) => status < 500, // Accept 4xx but not 5xx + validateStatus: (status) => status < 500, }); const contentType = response.headers['content-type'] || ''; let content; + logger.info('URL fetch successful', { + operationId, + url, + statusCode: response.status, + contentType, + contentLength: response.data?.length || 0, + }); + if (contentType.includes('application/json')) { content = JSON.stringify(response.data, null, 2); + } else if (contentType.includes('text/html') && extract_text) { + const rawContent = response.data.toString(); + + try { + const $ = cheerio.load(rawContent); + + $('script, style, nav, footer, aside').remove(); + + const title = $('title').text().trim(); + + const mainContent = $( + 'main, article, .content, #content, .main' + ).first(); + const extractedText = + mainContent.length > 0 ? mainContent.text() : $('body').text(); + + const cleanText = extractedText + .replace(/\s+/g, ' ') + .replace(/\n\s*\n/g, '\n') + .trim(); + + content = `Title: ${title}\n\nContent:\n${cleanText}`; + } catch (parseError) { + logger.warn('Failed to parse HTML, returning raw content', { + operationId, + url, + error: parseError.message, + }); + content = rawContent; + } } else if (contentType.includes('text/')) { content = response.data.toString(); } else { @@ -67,49 +220,63 @@ function createMcpServer() { } bytes`; } - // Truncate very large responses - if (content.length > 10000) { - content = content.substring(0, 10000) + '\n\n... (content truncated)'; + const maxLength = 15000; + if (content.length > maxLength) { + content = + content.substring(0, maxLength) + + '\n\n... (content truncated due to length)'; + logger.info('Content truncated', { + operationId, + originalLength: content.length + (content.length - maxLength), + truncatedLength: content.length, + }); } return { content: [ { type: 'text', - text: `URL: ${url}\nStatus: ${response.status} ${response.statusText}\nContent-Type: ${contentType}\n\nContent:\n${content}`, + text: `URL: ${url}\nStatus: ${response.status} ${response.statusText}\nContent-Type: ${contentType}\nExtracted Text: ${extract_text}\n\n${content}`, }, ], }; } catch (error) { - console.error(`Failed to fetch URL ${url}:`, error.message); + logger.error('Failed to fetch URL', { + operationId, + url, + error: error.message, + code: error.code, + status: error.response?.status, + }); let errorMessage = `Failed to fetch URL: ${url}\n`; - if (error.code === 'ENOTFOUND') { - errorMessage += 'Domain not found'; - } else if (error.code === 'ECONNREFUSED') { - errorMessage += 'Connection refused'; - } else if (error.code === 'ETIMEDOUT') { - errorMessage += 'Request timed out'; - } else if (error.response) { - errorMessage += `HTTP ${error.response.status}: ${error.response.statusText}`; - } else { - errorMessage += error.message; + switch (error.code) { + case 'ENOTFOUND': + errorMessage += 'Domain not found or DNS resolution failed'; + break; + case 'ECONNREFUSED': + errorMessage += 'Connection refused by the server'; + break; + case 'ETIMEDOUT': + errorMessage += 'Request timed out'; + break; + case 'ECONNRESET': + errorMessage += 'Connection was reset by the server'; + break; + default: + if (error.response) { + errorMessage += `HTTP ${error.response.status}: ${error.response.statusText}`; + } else { + errorMessage += error.message; + } } - return { - content: [ - { - type: 'text', - text: errorMessage, - }, - ], - }; + return createToolError(new Error(errorMessage), { url, operationId }); } } ); - // Tool: Web search (simulated) mcpServer.tool( 'search_web', { @@ -120,25 +287,93 @@ function createMcpServer() { .max(20) .default(5) .describe('Maximum number of results to return'), + safe_search: z + .enum(['strict', 'moderate', 'off']) + .default('moderate') + .describe('Safe search setting'), }, - async ({ query, limit = 5 }) => { - console.info(`Searching for: "${query}" (limit: ${limit})`); + async ({ query, limit = 5, safe_search = 'moderate' }) => { + const operationId = randomUUID(); - // Generate simulated search results - const searchResults = generateSearchResults(query, limit); + try { + logger.info('Performing DuckDuckGo web search', { + operationId, + query, + limit, + safe_search, + }); - return { - content: [ - { - type: 'text', - text: `Search Results for "${query}":\n\n${searchResults}`, - }, - ], - }; + // Map safe_search string to SafeSearchType enum + const safeSearchMap = { + strict: SafeSearchType.STRICT, + moderate: SafeSearchType.MODERATE, + off: SafeSearchType.OFF, + }; + + const searchOptions = { + safeSearch: safeSearchMap[safe_search], + time: null, + locale: 'en-us', + count: Math.min(limit, 20), + }; + + const searchResults = await rateLimitedSearch(query, searchOptions); + + if ( + !searchResults || + !searchResults.results || + searchResults.results.length === 0 + ) { + logger.warn('No search results found', { operationId, query }); + return { + content: [ + { + type: 'text', + text: `No search results found for "${query}".`, + }, + ], + }; + } + + const results = searchResults.results.slice(0, limit); + const formattedResults = results + .map((result, index) => { + return `${index + 1}. ${result.title}\n ${result.url}\n ${ + result.description + }\n`; + }) + .join('\n'); + + logger.info('DuckDuckGo search completed successfully', { + operationId, + query, + resultCount: results.length, + }); + + return { + content: [ + { + type: 'text', + text: `DuckDuckGo Search Results for "${query}":\n\n${formattedResults}`, + }, + ], + }; + } catch (error) { + logger.error('Failed to perform DuckDuckGo search', { + operationId, + query, + error: error.message, + stack: error.stack, + }); + + return createToolError( + new Error(`Failed to search DuckDuckGo: ${error.message}`), + { query, operationId } + ); + } } ); - // Tool: Get page title mcpServer.tool( 'get_page_title', { @@ -146,7 +381,7 @@ function createMcpServer() { }, async ({ url }) => { try { - console.info(`Extracting title from: ${url}`); + logger.info('Extracting page title', { url }); const response = await axios.get(url, { timeout: 10000, @@ -155,7 +390,6 @@ function createMcpServer() { }, }); - // Simple title extraction using regex (cheerio would require additional dependency) const titleMatch = response.data.match(/]*>([^<]+)<\/title>/i); const title = titleMatch ? titleMatch[1].trim() : 'No title found'; @@ -168,7 +402,10 @@ function createMcpServer() { ], }; } catch (error) { - console.error(`Failed to extract title from ${url}:`, error.message); + logger.error('Failed to extract page title', { + url, + error: error.message, + }); return { content: [ @@ -185,87 +422,65 @@ function createMcpServer() { return mcpServer; } -/** - * Generate simulated search results - */ -function generateSearchResults(query, limit) { - const results = []; - const domains = [ - 'example.com', - 'wikipedia.org', - 'github.com', - 'stackoverflow.com', - 'medium.com', - ]; - - for (let i = 0; i < Math.min(limit, 10); i++) { - const domain = domains[i % domains.length]; - const title = `${query} - Result ${i + 1}`; - const url = `https://${domain}/${query - .toLowerCase() - .replace(/\s+/g, '-')}-${i + 1}`; - const description = `This is a simulated search result for "${query}". It would normally contain relevant information about your search query.`; - - results.push(`${i + 1}. ${title}\n ${url}\n ${description}\n`); - } - - return results.join('\n'); -} - -// Handle POST requests for MCP communication app.post('/mcp', async (req, res) => { try { - console.info('MCP POST request received:'); - console.info(' Headers: %s', JSON.stringify(req.headers, null, 2)); - console.info(' Body: %s', JSON.stringify(req.body, null, 2)); + logger.info('MCP POST request received', { + headers: req.headers, + bodyKeys: Object.keys(req.body || {}), + method: req.body?.method, + }); - // Fix missing Accept headers for compatibility with Go MCP clients - // The StreamableHTTPServerTransport requires both application/json and text/event-stream const accept = req.headers.accept || req.headers.Accept; if ( !accept || !accept.includes('application/json') || !accept.includes('text/event-stream') ) { - console.info('Adding missing Accept headers for MCP compatibility'); + logger.info('Adding missing Accept headers for MCP compatibility'); req.headers.accept = 'application/json, text/event-stream'; } - // Check for existing session ID const sessionId = req.headers['mcp-session-id']; let transport; if (sessionId && transports[sessionId]) { - // Reuse existing transport transport = transports[sessionId]; + logger.info('Using existing session', { sessionId }); } else { - // Create new transport for new session + const newSessionId = randomUUID(); + logger.info('Creating new MCP session', { sessionId: newSessionId }); + transport = new StreamableHTTPServerTransport({ - sessionIdGenerator: () => randomUUID(), - onsessioninitialized: (newSessionId) => { - console.info(`MCP session initialized: ${newSessionId}`); - // Store the transport by session ID - transports[newSessionId] = transport; + sessionIdGenerator: () => newSessionId, + onsessioninitialized: (initSessionId) => { + logger.info('MCP session initialized', { sessionId: initSessionId }); + transports[initSessionId] = transport; }, }); - // Clean up transport when closed transport.onclose = () => { if (transport.sessionId) { - console.info(`MCP session closed: ${transport.sessionId}`); + logger.info('MCP session closed', { sessionId: transport.sessionId }); delete transports[transport.sessionId]; } }; - // Create and connect MCP server const server = createMcpServer(); await server.connect(transport); + + transports[newSessionId] = transport; + + res.setHeader('mcp-session-id', newSessionId); } - // Handle the MCP request await transport.handleRequest(req, res, req.body); } catch (error) { - console.error('Error handling MCP request:', error); + logger.error('Error handling MCP request', { + error: error.message, + stack: error.stack, + method: req.body?.method, + sessionId: req.headers['mcp-session-id'], + }); if (!res.headersSent) { res.status(500).json({ jsonrpc: '2.0', @@ -279,7 +494,6 @@ app.post('/mcp', async (req, res) => { } }); -// Handle GET requests for SSE (server-to-client notifications) app.get('/mcp', async (req, res) => { const sessionId = req.headers['mcp-session-id']; if (!sessionId || !transports[sessionId]) { @@ -291,7 +505,6 @@ app.get('/mcp', async (req, res) => { await transport.handleRequest(req, res); }); -// Handle DELETE requests for session termination app.delete('/mcp', async (req, res) => { const sessionId = req.headers['mcp-session-id']; if (!sessionId || !transports[sessionId]) { @@ -303,7 +516,6 @@ app.delete('/mcp', async (req, res) => { await transport.handleRequest(req, res); }); -// Health check endpoint app.get('/health', (req, res) => { const healthStatus = { status: 'healthy', @@ -315,38 +527,35 @@ app.get('/health', (req, res) => { transport: 'Streamable HTTP', }; - console.info('Health check requested: %j', healthStatus); + logger.info('Health check requested', healthStatus); res.json(healthStatus); }); -// Start the server const port = process.env.PORT || 3001; const host = process.env.HOST || '0.0.0.0'; app.listen(port, host, () => { - console.info(`MCP Web Search server running on http://${host}:${port}`); - console.info('Protocol: Model Context Protocol (MCP)'); - console.info('Transport: Streamable HTTP'); - console.info('Available endpoints:'); - console.info(' POST /mcp - MCP protocol endpoint'); - console.info( + logger.info(`MCP Web Search server running on http://${host}:${port}`); + logger.info('Protocol: Model Context Protocol (MCP)'); + logger.info('Transport: Streamable HTTP'); + logger.info('Available endpoints:'); + logger.info(' POST /mcp - MCP protocol endpoint'); + logger.info( ' GET /mcp - SSE notifications (with session-id header)' ); - console.info( + logger.info( ' DELETE /mcp - Session termination (with session-id header)' ); - console.info(' GET /health - Health check'); - console.info('Available tools:'); - console.info(' - fetch_url - Fetch content from a URL'); - console.info(' - search_web - Perform web search (simulated)'); - console.info(' - get_page_title - Extract title from a web page'); + logger.info(' GET /health - Health check'); + logger.info('Available tools:'); + logger.info(' - fetch_url - Fetch content from a URL'); + logger.info(' - search_web - Perform web search using DuckDuckGo'); + logger.info(' - get_page_title - Extract title from a web page'); }); -// Graceful shutdown process.on('SIGTERM', () => { - console.info('Received SIGTERM, shutting down gracefully'); - // Close all transports + logger.info('Received SIGTERM, shutting down gracefully'); Object.values(transports).forEach((transport) => { if (transport.close) transport.close(); }); @@ -354,8 +563,7 @@ process.on('SIGTERM', () => { }); process.on('SIGINT', () => { - console.info('Received SIGINT, shutting down gracefully'); - // Close all transports + logger.info('Received SIGINT, shutting down gracefully'); Object.values(transports).forEach((transport) => { if (transport.close) transport.close(); }); diff --git a/examples/mcp/mcp-servers/web-search/logger.js b/examples/mcp/mcp-servers/web-search/logger.js new file mode 100644 index 0000000..db86d11 --- /dev/null +++ b/examples/mcp/mcp-servers/web-search/logger.js @@ -0,0 +1,97 @@ +/** + * Standardized Winston Logger for MCP Servers + * + * Provides consistent, single-line logging across all MCP servers + * with structured metadata and unified formatting. + */ + +import winston from 'winston'; + +/** + * Create a standardized logger for MCP servers + * @param {string} serviceName - The name of the MCP service + * @param {string} [version='1.0.0'] - The version of the service + * @param {string} [logLevel] - Override log level (defaults to LOG_LEVEL env var or 'info') + * @returns {winston.Logger} Configured Winston logger + */ +export function createMcpLogger(serviceName, version = '1.0.0', logLevel) { + const level = logLevel || process.env.LOG_LEVEL || 'info'; + + return winston.createLogger({ + level, + format: winston.format.combine( + winston.format.timestamp({ format: 'YYYY-MM-DDTHH:mm:ss.SSSZ' }), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { + service: serviceName, + version, + protocol: 'Model Context Protocol', + transport: 'Streamable HTTP', + }, + transports: [ + new winston.transports.Console({ + handleExceptions: true, + handleRejections: true, + }), + ], + }); +} + +/** + * Log MCP request received + */ +export function logMcpRequest(logger, req, additionalMeta = {}) { + const sessionId = req.headers['mcp-session-id']; + const method = req.body?.method; + const id = req.body?.id; + + logger.info('MCP request received', { + sessionId, + method, + requestId: id, + userAgent: req.headers['user-agent'], + contentLength: req.headers['content-length'], + ...additionalMeta, + }); +} + +/** + * Log MCP session events + */ +export function logMcpSession(logger, event, sessionId, additionalMeta = {}) { + logger.info(`MCP session ${event}`, { + sessionId, + ...additionalMeta, + }); +} + +/** + * Log MCP tool calls + */ +export function logMcpToolCall( + logger, + toolName, + sessionId, + args = {}, + additionalMeta = {} +) { + logger.info(`MCP tool called: ${toolName}`, { + sessionId, + tool: toolName, + args: Object.keys(args), + ...additionalMeta, + }); +} + +/** + * Log MCP errors with context + */ +export function logMcpError(logger, error, context = {}) { + logger.error('MCP error occurred', { + error: error.message, + stack: error.stack, + ...context, + }); +} diff --git a/examples/mcp/mcp-servers/web-search/package-lock.json b/examples/mcp/mcp-servers/web-search/package-lock.json index c795960..bc62746 100644 --- a/examples/mcp/mcp-servers/web-search/package-lock.json +++ b/examples/mcp/mcp-servers/web-search/package-lock.json @@ -13,13 +13,35 @@ "axios": "^1.6.0", "cheerio": "^1.0.0-rc.12", "cors": "^2.8.5", + "duck-duck-scrape": "^2.2.7", "express": "^4.18.2", + "winston": "^3.17.0", "zod": "^3.22.0" }, "engines": { "node": ">=18.0.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "license": "MIT", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.12.0.tgz", @@ -304,6 +326,12 @@ "node": ">= 0.6" } }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", + "license": "MIT" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -339,6 +367,12 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -493,6 +527,51 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "license": "MIT", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -688,6 +767,19 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/duck-duck-scrape": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/duck-duck-scrape/-/duck-duck-scrape-2.2.7.tgz", + "integrity": "sha512-BEcglwnfx5puJl90KQfX+Q2q5vCguqyMpZcSRPBWk8OY55qWwV93+E+7DbIkrGDW4qkqPfUvtOUdi0lXz6lEMQ==", + "license": "MIT", + "dependencies": { + "html-entities": "^2.3.3", + "needle": "^3.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/Snazzah" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -708,6 +800,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==", + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -896,6 +994,12 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -914,6 +1018,12 @@ "node": ">= 0.8" } }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==", + "license": "MIT" + }, "node_modules/follow-redirects": { "version": "1.15.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", @@ -1064,6 +1174,22 @@ "node": ">= 0.4" } }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, "node_modules/htmlparser2": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", @@ -1126,12 +1252,30 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1144,6 +1288,35 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "license": "MIT" }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==", + "license": "MIT" + }, + "node_modules/logform": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz", + "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==", + "license": "MIT", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1219,6 +1392,22 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -1282,6 +1471,15 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "license": "MIT", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -1431,6 +1629,20 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -1499,12 +1711,27 @@ ], "license": "MIT" }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, "node_modules/send": { "version": "0.19.0", "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", @@ -1658,6 +1885,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1667,6 +1912,21 @@ "node": ">= 0.8" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==", + "license": "MIT" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1676,6 +1936,15 @@ "node": ">=0.6" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1716,6 +1985,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1770,6 +2045,42 @@ "node": ">= 8" } }, + "node_modules/winston": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.17.0.tgz", + "integrity": "sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==", + "license": "MIT", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.7.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.9.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz", + "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==", + "license": "MIT", + "dependencies": { + "logform": "^2.7.0", + "readable-stream": "^3.6.2", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/examples/mcp/mcp-servers/web-search/package.json b/examples/mcp/mcp-servers/web-search/package.json index db7c2f3..818919a 100644 --- a/examples/mcp/mcp-servers/web-search/package.json +++ b/examples/mcp/mcp-servers/web-search/package.json @@ -13,7 +13,9 @@ "axios": "^1.6.0", "cheerio": "^1.0.0-rc.12", "cors": "^2.8.5", + "duck-duck-scrape": "^2.2.7", "express": "^4.18.2", + "winston": "^3.17.0", "zod": "^3.22.0" }, "engines": { diff --git a/examples/mcp/shared/.gitignore b/examples/mcp/shared/.gitignore index 0c42a13..d6b7ef3 100644 --- a/examples/mcp/shared/.gitignore +++ b/examples/mcp/shared/.gitignore @@ -1,3 +1,2 @@ * !.gitignore -!sample_sales_data.csv diff --git a/examples/mcp/shared/sample_sales_data.csv b/examples/mcp/shared/sample_sales_data.csv deleted file mode 100644 index 628aa2d..0000000 --- a/examples/mcp/shared/sample_sales_data.csv +++ /dev/null @@ -1,16 +0,0 @@ -Date,Product,Quantity,Revenue,Region -2025-01-01,Laptop,10,15000,North -2025-01-02,Mouse,25,500,South -2025-01-03,Keyboard,15,750,East -2025-01-04,Monitor,8,2400,West -2025-01-05,Laptop,12,18000,North -2025-01-06,Mouse,30,600,South -2025-01-07,Keyboard,20,1000,East -2025-01-08,Monitor,6,1800,West -2025-01-09,Laptop,8,12000,Central -2025-01-10,Mouse,35,700,North -2025-01-11,Keyboard,25,1250,South -2025-01-12,Monitor,10,3000,East -2025-01-13,Laptop,15,22500,West -2025-01-14,Mouse,40,800,Central -2025-01-15,Keyboard,18,900,North diff --git a/examples/mcp/tsconfig.json b/examples/mcp/tsconfig.json index 3a2170b..585016f 100644 --- a/examples/mcp/tsconfig.json +++ b/examples/mcp/tsconfig.json @@ -7,6 +7,6 @@ "strict": true, "skipLibCheck": true }, - "include": ["index.ts"], + "include": ["**/*.ts"], "exclude": ["node_modules"] } diff --git a/src/client.ts b/src/client.ts index 26f113d..0ad12aa 100644 --- a/src/client.ts +++ b/src/client.ts @@ -280,7 +280,7 @@ export class InferenceGatewayClient { this.apiKey = options.apiKey; this.defaultHeaders = options.defaultHeaders || {}; this.defaultQuery = options.defaultQuery || {}; - this.timeout = options.timeout || 30000; + this.timeout = options.timeout || 60000; // Increased default timeout to 60 seconds this.fetchFn = options.fetch || globalThis.fetch; }