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

```
├── .gitignore
├── docs
│   ├── deployment-guide.md
│   └── integration-guide.md
├── examples
│   ├── cursor-configuration.md
│   └── usage-examples.md
├── README.md
├── vapi-mcp-docker.md
├── vapi-mcp-http-server
│   ├── .env
│   ├── package.json
│   ├── README.md
│   ├── src
│   │   ├── controllers
│   │   │   ├── assistants.ts
│   │   │   ├── calls.ts
│   │   │   └── conversations.ts
│   │   ├── index.ts
│   │   └── routes
│   │       ├── assistants.ts
│   │       ├── calls.ts
│   │       └── conversations.ts
│   └── tsconfig.json
├── vapi-mcp-integration.md
└── vapi-mcp-server
    ├── .env
    ├── package.json
    ├── README.md
    ├── src
    │   ├── index.ts
    │   └── types.ts
    └── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/vapi-mcp-server/.env:
--------------------------------------------------------------------------------

```
# Vapi API Keys
VAPI_ORG_ID=000c3516-6c06-4462-bd9d-2f15d109478e
VAPI_PRIVATE_KEY=8300521f-7421-4088-8a13-d0df6ea29962
VAPI_KNOWLEDGE_ID=f2a554b0-fe9a-456e-a7ab-3294d3689534
VAPI_JWT_PRIVATE=da163ef8-ac5b-43d1-9117-a002aaba0926

# Environment
NODE_ENV=development 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/.env:
--------------------------------------------------------------------------------

```
# Vapi API Keys
VAPI_ORG_ID=000c3516-6c06-4462-bd9d-2f15d109478e
VAPI_PRIVATE_KEY=8300521f-7421-4088-8a13-d0df6ea29962
VAPI_KNOWLEDGE_ID=f2a554b0-fe9a-456e-a7ab-3294d3689534
VAPI_JWT_PRIVATE=da163ef8-ac5b-43d1-9117-a002aaba0926

# Server Configuration
PORT=3000
NODE_ENV=development 
```

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

```
# Node.js dependencies
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
.pnp
.pnp.js

# Build output
dist/
build/
out/

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# IDE files
.idea/
.vscode/
*.swp
*.swo
.DS_Store

# Logs
logs
*.log

# Cache
.npm
.eslintcache

# Package files
package-lock.json
yarn.lock

# Examples and test directories from other repos
client-sdk-python/
client-sdk-web/
server-example-javascript-node/
vapi-express-starter/ 
```

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

```markdown
# Vapi MCP for Cursor

This project implements a Model Context Protocol (MCP) server for integrating Vapi's voice AI capabilities with Cursor.

## Setup Instructions

### 1. Project Structure

The Vapi MCP server is structured as follows:
- `vapi-mcp-server/` - Main server code
  - `src/` - TypeScript source files
  - `dist/` - Compiled JavaScript output
  - `.env` - Environment variables for API keys

### 2. Environment Configuration

Create a `.env` file in the `vapi-mcp-server` directory with the following variables:

```
# Vapi API Keys
VAPI_ORG_ID=your-org-id
VAPI_PRIVATE_KEY=your-private-key
VAPI_KNOWLEDGE_ID=your-knowledge-id
VAPI_JWT_PRIVATE=your-jwt-private

# Environment
NODE_ENV=development
```

### 3. Building the Server

To build the server:

```bash
cd vapi-mcp/vapi-mcp-server
npm install
npm run build
```

### 4. Configuration in Cursor

#### Important: Avoiding "Client Closed" Errors

When configuring the Vapi MCP server in Cursor's MCP settings, pay attention to the following crucial details:

1. **Working Directory**: The `cwd` parameter is required to ensure the server runs in the correct directory and can access the `.env` file properly.

2. **Environment Variables**: Must be explicitly provided in the configuration, even if they exist in the `.env` file.

3. **Module Type**: The server uses ES modules, so the `package.json` must include `"type": "module"`.

Here's the correct configuration for `.cursor/mcp.json`:

```json
"Vapi Voice AI Tools": {
  "command": "node",
  "type": "stdio",
  "args": [
    "/Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server/dist/index.js"
  ],
  "cwd": "/Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server",
  "env": {
    "VAPI_ORG_ID": "your-org-id",
    "VAPI_PRIVATE_KEY": "your-private-key",
    "VAPI_KNOWLEDGE_ID": "your-knowledge-id",
    "VAPI_JWT_PRIVATE": "your-jwt-private",
    "NODE_ENV": "development"
  }
}
```

## Troubleshooting

### "Client Closed" Error in Cursor

If you see "Client Closed" in the Cursor MCP Tools panel:

1. **Check Working Directory**: Ensure the `cwd` parameter is set correctly in your mcp.json
2. **Verify Environment Variables**: Make sure all required environment variables are passed in the configuration
3. **Check Module Type**: Ensure `package.json` has `"type": "module"`
4. **Inspect Permissions**: Make sure the dist/index.js file is executable (`chmod +x dist/index.js`)
5. **Test Server Directly**: Run the server manually to check for errors:
   ```bash
   cd vapi-mcp/vapi-mcp-server
   node --trace-warnings dist/index.js
   ```

### Module Not Found Errors

If you get "Error: Cannot find module" when running:

1. **Check Working Directory**: Are you running from the correct directory?
2. **Rebuild**: Try rebuilding the project with `npm run build`
3. **Dependencies**: Ensure all dependencies are installed with `npm install`

## Available Tools

The Vapi MCP server provides the following tools:

1. **vapi_call** - Make outbound calls using Vapi's voice AI
2. **vapi_assistant** - Manage voice assistants (create, get, list, update, delete)
3. **vapi_conversation** - Retrieve conversation details from calls

## Lessons Learned

1. When integrating with Cursor's MCP:
   - Always specify the `cwd` parameter to ensure the server runs in the correct directory
   - Pass all required environment variables directly in the MCP configuration
   - For ES modules, ensure package.json has `"type": "module"` and tsconfig.json uses appropriate module settings
   - Test the server directly before configuring in Cursor

2. The server command path must be absolute and correctly formed in the Cursor MCP config

3. Using stdio transport type is required for proper integration with Cursor 
```

--------------------------------------------------------------------------------
/vapi-mcp-server/README.md:
--------------------------------------------------------------------------------

```markdown
# Vapi MCP Server

This is the server component for the Vapi Voice AI Tools integration with Cursor via the Model Context Protocol (MCP).

## Setup Guide

### 1. Environment Configuration

Create a `.env` file in this directory with your Vapi API credentials:

```
# Vapi API Keys
VAPI_ORG_ID=your-org-id
VAPI_PRIVATE_KEY=your-private-key
VAPI_KNOWLEDGE_ID=your-knowledge-id
VAPI_JWT_PRIVATE=your-jwt-private

# Environment
NODE_ENV=development
```

### 2. Installation

Install the dependencies:

```bash
npm install
```

### 3. Build

Build the TypeScript code:

```bash
npm run build
```

### 4. Run

Start the server:

```bash
npm start
```

## Cursor MCP Integration

### Proper Configuration (Important!)

To avoid the "Client Closed" error in Cursor, ensure your `.cursor/mcp.json` configuration includes:

1. **The correct working directory (`cwd`)** 
2. **All environment variables**
3. **Correct file path to the server**

Example configuration:

```json
"Vapi Voice AI Tools": {
  "command": "node",
  "type": "stdio",
  "args": [
    "/Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server/dist/index.js"
  ],
  "cwd": "/Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server",
  "env": {
    "VAPI_ORG_ID": "000c3516-6c06-4462-bd9d-2f15d109478e",
    "VAPI_PRIVATE_KEY": "8300521f-7421-4088-8a13-d0df6ea29962",
    "VAPI_KNOWLEDGE_ID": "f2a554b0-fe9a-456e-a7ab-3294d3689534",
    "VAPI_JWT_PRIVATE": "da163ef8-ac5b-43d1-9117-a002aaba0926",
    "NODE_ENV": "development"
  }
}
```

## Critical Configuration Files

### 1. package.json

Ensure your `package.json` has the following settings:

```json
{
  "name": "vapi-mcp-server",
  "version": "1.0.0",
  "description": "MCP Server for Vapi API integration",
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc -w & node --watch dist/index.js"
  }
}
```

The **"type": "module"** line is critical for ES modules to work correctly.

### 2. tsconfig.json

Ensure your `tsconfig.json` has the correct module settings:

```json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src"
  }
}
```

## Common Issues and Solutions

### 1. "Client Closed" Error in Cursor

**Problem**: The tools panel in Cursor shows "Client Closed" even though the server appears to be properly configured.

**Solutions**:
- Ensure the `cwd` parameter is set in your mcp.json configuration
- Make sure all environment variables are explicitly passed in the configuration
- Verify the server can run standalone with `node dist/index.js`
- Check for any errors in the console when starting the server
- Make sure the `package.json` has `"type": "module"` set

### 2. ES Module Errors

**Problem**: Errors about imports not being recognized or "Cannot use import outside a module".

**Solutions**:
- Add `"type": "module"` to your `package.json`
- Update `tsconfig.json` to use `"module": "NodeNext"` and `"moduleResolution": "NodeNext"`
- Use `.js` extensions in your imports even in TypeScript files
- Rebuild the project with `npm run build`

### 3. "Cannot find module" Error

**Problem**: Node.js cannot find a module or the main server file.

**Solutions**:
- Ensure you're running from the correct working directory
- Check that all dependencies are installed with `npm install`
- Rebuild the project with `npm run build`
- Verify the file paths in your mcp.json configuration are absolute and correct
- Use `node --trace-warnings dist/index.js` to see detailed error messages

## Debugging

To run the server with additional debugging information:

```bash
node --trace-warnings dist/index.js
```

Or with Node.js inspector:

```bash
node --inspect dist/index.js
```

## MCP Server Structure

The server implements three main Vapi tools:

1. **vapi_call** - Make outbound calls
2. **vapi_assistant** - Manage voice assistants
3. **vapi_conversation** - Retrieve conversation details

Refer to the source code documentation for details on how each tool works. 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/README.md:
--------------------------------------------------------------------------------

```markdown
# Vapi MCP HTTP Server

An HTTP API server for Vapi voice AI platform, providing REST endpoints that match the functionality of the Vapi MCP integration.

## Features

- RESTful API for Vapi voice AI platform
- Make outbound calls with AI assistants
- Manage voice assistants (create, list, get, update, delete)
- Access conversation details from calls
- Compatible with n8n, Postman, and other HTTP-based tools

## Installation

1. Clone the repository:
```bash
git clone https://github.com/yourusername/vapi-mcp-http-server.git
```

2. Install dependencies:
```bash
cd vapi-mcp-http-server
npm install
```

3. Build the project:
```bash
npm run build
```

## Configuration

Create a `.env` file in the root directory with your Vapi API credentials:

```
# Vapi API Keys
VAPI_ORG_ID=your_vapi_org_id
VAPI_PRIVATE_KEY=your_vapi_private_key
VAPI_KNOWLEDGE_ID=your_vapi_knowledge_id
VAPI_JWT_PRIVATE=your_vapi_jwt_private

# Server Configuration
PORT=3000
NODE_ENV=development
```

## Running the Server

To start the server:

```bash
npm start
```

For development with automatic reloading:

```bash
npm run dev
```

The server will start on port 3000 by default (or the port specified in your `.env` file).

## API Endpoints

### Calls

- `POST /api/calls` - Make an outbound call
- `GET /api/calls` - List all calls
- `GET /api/calls/:id` - Get details of a specific call

### Assistants

- `POST /api/assistants` - Create a new assistant
- `GET /api/assistants` - List all assistants
- `GET /api/assistants/:id` - Get details of a specific assistant
- `PUT /api/assistants/:id` - Update an assistant
- `DELETE /api/assistants/:id` - Delete an assistant

### Conversations

- `GET /api/conversations` - List all conversations (based on calls)
- `GET /api/conversations/:callId` - Get conversation details for a specific call

## Example Requests

### Making a Call

```bash
curl -X POST http://localhost:3000/api/calls \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+1234567890",
    "assistantId": "asst_123456"
  }'
```

Or with a custom assistant:

```bash
curl -X POST http://localhost:3000/api/calls \
  -H "Content-Type: application/json" \
  -d '{
    "phoneNumber": "+1234567890",
    "assistantConfig": {
      "name": "Sales Assistant",
      "model": "gpt-4",
      "voice": "alloy",
      "firstMessage": "Hello, I am calling from Example Company."
    }
  }'
```

### Creating an Assistant

```bash
curl -X POST http://localhost:3000/api/assistants \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Sales Assistant",
    "model": "gpt-4",
    "voice": "alloy",
    "firstMessage": "Hello, I am calling from Example Company.",
    "instructions": "You are a friendly sales representative."
  }'
```

## Using with n8n

To use this API with n8n:

1. Add an HTTP Request node
2. Set the method to the appropriate HTTP method (GET, POST, PUT, DELETE)
3. Set the URL to `http://your-server:3000/api/[endpoint]`
4. For POST/PUT requests, set the Body Content Type to JSON and provide the required parameters
5. Connect to subsequent nodes for processing the response

## Project Structure

```
vapi-mcp-http-server/
├── dist/               # Compiled JavaScript output
├── src/                # TypeScript source files
│   ├── index.ts        # Main server entry point
│   └── routes/         # API route handlers
│       ├── calls.ts    # Call-related endpoints
│       ├── assistants.ts # Assistant-related endpoints
│       └── conversations.ts # Conversation-related endpoints
├── .env                # Environment variables (create this file)
├── package.json        # Dependencies and scripts
└── tsconfig.json       # TypeScript configuration
```

## Development

### Adding New Routes

To add a new API endpoint:

1. Add a new route handler in the appropriate file in the `src/routes` directory
2. Update the `src/index.ts` file to include the new route if necessary

## Troubleshooting

If you encounter issues:

1. Check that your Vapi API credentials are correct in the `.env` file
2. Ensure the build completed successfully with `npm run build`
3. Check the server logs for error messages
4. Verify that your requests include all required parameters

## License

MIT 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "declaration": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
} 
```

--------------------------------------------------------------------------------
/vapi-mcp-server/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "declaration": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": [
    "src/**/*"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

```

--------------------------------------------------------------------------------
/vapi-mcp-server/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "vapi-mcp-server",
  "version": "1.0.0",
  "description": "MCP Server for Vapi API integration",
  "main": "dist/index.js",
  "type": "module",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc -w & node --watch dist/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "mcp",
    "vapi",
    "ai",
    "voice",
    "cursor"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.4.0",
    "@types/node": "^20.11.19",
    "@vapi-ai/server-sdk": "^0.5.0",
    "dotenv": "^16.4.7",
    "typescript": "^5.3.3",
    "zod": "^3.22.4"
  }
} 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/package.json:
--------------------------------------------------------------------------------

```json
{
  "name": "vapi-mcp-http-server",
  "version": "1.0.0",
  "description": "HTTP Server for Vapi API integration via MCP",
  "main": "dist/index.js",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsc -w & node --watch dist/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "vapi",
    "api",
    "mcp",
    "http",
    "server"
  ],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@types/cors": "^2.8.17",
    "@types/express": "^4.17.21",
    "@types/node": "^20.11.13",
    "@vapi-ai/server-sdk": "^0.5.0",
    "cors": "^2.8.5",
    "dotenv": "^16.4.7",
    "express": "^4.18.2",
    "node-fetch": "^2.6.9",
    "typescript": "^5.3.3"
  }
}

```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/routes/conversations.ts:
--------------------------------------------------------------------------------

```typescript
import express, { Request, Response, NextFunction } from 'express';
import { listConversations, getConversation } from '../controllers/conversations';

const router = express.Router();

// GET /api/conversations - List conversations
router.get('/', function(req: Request, res: Response, next: NextFunction) {
  listConversations(req.query)
    .then(result => {
      res.json(result);
    })
    .catch(next);
});

// GET /api/conversations/:callId - Get conversation by call ID
router.get('/:callId', function(req: Request, res: Response, next: NextFunction) {
  getConversation(req.params.callId)
    .then(result => {
      if (!result) {
        return res.status(404).json({ error: 'Conversation not found' });
      }
      res.json(result);
    })
    .catch(next);
});

export default router; 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/routes/calls.ts:
--------------------------------------------------------------------------------

```typescript
import express, { Request, Response, NextFunction } from 'express';
import { createCall, listCalls, getCall } from '../controllers/calls';

const router = express.Router();

// POST /api/calls - Create a new call
router.post('/', function(req: Request, res: Response, next: NextFunction) {
  createCall(req.body)
    .then(result => {
      res.status(201).json(result);
    })
    .catch(next);
});

// GET /api/calls - List calls
router.get('/', function(req: Request, res: Response, next: NextFunction) {
  listCalls(req.query)
    .then(result => {
      res.json(result);
    })
    .catch(next);
});

// GET /api/calls/:id - Get a call by ID
router.get('/:id', function(req: Request, res: Response, next: NextFunction) {
  getCall(req.params.id)
    .then(result => {
      if (!result) {
        return res.status(404).json({ error: 'Call not found' });
      }
      res.json(result);
    })
    .catch(next);
});

export default router; 
```

--------------------------------------------------------------------------------
/examples/cursor-configuration.md:
--------------------------------------------------------------------------------

```markdown
# Cursor Configuration for Vapi MCP

This guide shows how to configure Cursor to use the Vapi MCP server.

## Add the Server to Cursor Settings

1. Open Cursor
2. Go to Settings (⌘+, on Mac or Ctrl+, on Windows)
3. Click on "Edit Settings (JSON)"
4. Add the following configuration:

```json
{
  "mcp.config.servers": [
    {
      "name": "Vapi MCP Server",
      "command": "node /Users/yourusername/path/to/vapi-mcp-server/dist/index.js"
    }
  ]
}
```

Replace `/Users/yourusername/path/to/` with the absolute path to your vapi-mcp-server directory.

## Example Complete Path

On macOS, your configuration might look like:

```json
{
  "mcp.config.servers": [
    {
      "name": "Vapi MCP Server",
      "command": "node /Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server/dist/index.js"
    }
  ]
}
```

## Testing the Configuration

1. Save the settings
2. Restart Cursor
3. Open a new file
4. Try using one of the Vapi tools:

```
You can now make calls using the Vapi voice AI platform by using the vapi_call tool.
Try listing available assistants with the vapi_assistant tool, action "list".
```

## Troubleshooting

If the tools don't appear:

1. Make sure the server is running (start it with `npm start` in the vapi-mcp-server directory)
2. Check that the path to the server is correct in your Cursor settings
3. Restart Cursor after making changes
4. Check the Cursor console for any errors (Help > Toggle Developer Tools) 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/controllers/conversations.ts:
--------------------------------------------------------------------------------

```typescript
import { vapiClient } from '../index';

// List conversations (via calls)
export const listConversations = async (query: any) => {
  const { limit = 10, offset = 0 } = query;
  
  const filters = {
    limit: Number(limit),
    offset: Number(offset)
  };
  
  const calls = await vapiClient.calls.list(filters);
  const callsArray = Array.isArray(calls) ? calls : [];
  
  // Map calls to conversation summaries
  const conversations = callsArray.map(call => ({
    callId: call.id,
    assistantId: call.assistantId,
    phoneNumber: "unknown", // We don't have direct access to the phone number
    startTime: call.createdAt,
    endTime: call.updatedAt,
    status: call.status,
    messageCount: 0 // We don't have direct access to message count
  }));
  
  return {
    success: true,
    conversations,
    pagination: {
      total: conversations.length,
      limit: filters.limit,
      offset: filters.offset
    }
  };
};

// Get conversation details for a call
export const getConversation = async (callId: string) => {
  // Get call details
  const call = await vapiClient.calls.get(callId);
  
  // Create a conversation object
  const conversation = {
    callId: call.id,
    assistantId: call.assistantId,
    phoneNumber: "unknown", // We don't have direct access to the phone number
    startTime: call.createdAt,
    endTime: call.updatedAt,
    status: call.status,
    messages: [] // SDK doesn't provide direct access to messages
  };
  
  return {
    success: true,
    conversation
  };
}; 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/routes/assistants.ts:
--------------------------------------------------------------------------------

```typescript
import express, { Request, Response, NextFunction } from 'express';
import { createAssistant, listAssistants, getAssistant, updateAssistant, deleteAssistant } from '../controllers/assistants';

const router = express.Router();

// POST /api/assistants - Create a new assistant
router.post('/', function(req: Request, res: Response, next: NextFunction) {
  createAssistant(req.body)
    .then(result => {
      res.status(201).json(result);
    })
    .catch(next);
});

// GET /api/assistants - List assistants
router.get('/', function(req: Request, res: Response, next: NextFunction) {
  listAssistants(req.query)
    .then(result => {
      res.json(result);
    })
    .catch(next);
});

// GET /api/assistants/:id - Get an assistant by ID
router.get('/:id', function(req: Request, res: Response, next: NextFunction) {
  getAssistant(req.params.id)
    .then(result => {
      if (!result) {
        return res.status(404).json({ error: 'Assistant not found' });
      }
      res.json(result);
    })
    .catch(next);
});

// PUT /api/assistants/:id - Update an assistant
router.put('/:id', function(req: Request, res: Response, next: NextFunction) {
  updateAssistant(req.params.id, req.body)
    .then(result => {
      res.json(result);
    })
    .catch(next);
});

// DELETE /api/assistants/:id - Delete an assistant
router.delete('/:id', function(req: Request, res: Response, next: NextFunction) {
  deleteAssistant(req.params.id)
    .then(result => {
      res.json(result);
    })
    .catch(next);
});

export default router; 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/controllers/calls.ts:
--------------------------------------------------------------------------------

```typescript
import { vapiClient } from '../index';

// Create a new call
export const createCall = async (data: any) => {
  const { phoneNumber, assistantId, assistantConfig, metadata } = data;
  
  // Validate required fields
  if (!phoneNumber) {
    throw new Error('Phone number is required');
  }
  
  // Initialize call parameters
  const callParams: any = {
    phoneNumber: phoneNumber
  };
  
  // Set assistant details
  if (assistantId) {
    callParams.assistantId = assistantId;
  } else if (assistantConfig) {
    callParams.assistant = {
      name: assistantConfig.name || "Default Assistant",
      model: assistantConfig.model === "gpt-4" ? "gpt_4" : "gpt_3_5_turbo",
      voice: {
        provider: "eleven_labs",
        voiceId: assistantConfig.voice || "alloy"
      },
      firstMessage: assistantConfig.firstMessage,
      maxDuration: assistantConfig.maxDuration
    };
  } else {
    throw new Error('Either assistantId or assistantConfig is required');
  }
  
  // Add metadata if provided
  if (metadata) {
    callParams.metadata = metadata;
  }
  
  // Make the call
  const result = await vapiClient.calls.create(callParams);
  
  return {
    success: true,
    callId: result.id,
    status: result.status
  };
};

// List all calls
export const listCalls = async (query: any) => {
  const { limit = 10, offset = 0 } = query;
  
  const filters = {
    limit: Number(limit),
    offset: Number(offset)
  };
  
  const calls = await vapiClient.calls.list(filters);
  const callsArray = Array.isArray(calls) ? calls : [];
  
  return {
    success: true,
    calls: callsArray,
    pagination: {
      total: callsArray.length,
      limit: filters.limit,
      offset: filters.offset
    }
  };
};

// Get a call by ID
export const getCall = async (id: string) => {
  const call = await vapiClient.calls.get(id);
  
  return {
    success: true,
    call
  };
}; 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/controllers/assistants.ts:
--------------------------------------------------------------------------------

```typescript
import { vapiClient } from '../index';

// Create a new assistant
export const createAssistant = async (data: any) => {
  const {
    name,
    model,
    voice,
    firstMessage,
    instructions,
    maxDuration
  } = data;
  
  // Validate required fields
  if (!name) {
    throw new Error('Name is required');
  }
  
  // Set up assistant parameters
  const createParams: any = {
    name: name,
    model: model === "gpt-4" ? "gpt_4" : "gpt_3_5_turbo",
    voice: {
      provider: "eleven_labs",
      voiceId: voice || "alloy"
    }
  };
  
  // Add optional parameters
  if (firstMessage) createParams.firstMessage = firstMessage;
  if (instructions) createParams.instructions = instructions;
  if (maxDuration) createParams.maxDuration = maxDuration;
  
  // Create the assistant
  const assistant = await vapiClient.assistants.create(createParams);
  
  return {
    success: true,
    assistant
  };
};

// List all assistants
export const listAssistants = async (query: any) => {
  const assistants = await vapiClient.assistants.list();
  
  return {
    success: true,
    assistants: Array.isArray(assistants) ? assistants : []
  };
};

// Get an assistant by ID
export const getAssistant = async (id: string) => {
  const assistant = await vapiClient.assistants.get(id);
  
  return {
    success: true,
    assistant
  };
};

// Update an assistant
export const updateAssistant = async (id: string, data: any) => {
  const {
    name,
    voice,
    firstMessage,
    instructions,
    maxDuration
  } = data;
  
  // Set up update parameters
  const updateParams: any = {};
  
  // Add parameters only if they're provided
  if (name) updateParams.name = name;
  if (voice) {
    updateParams.voice = {
      provider: "eleven_labs",
      voiceId: voice
    };
  }
  if (firstMessage) updateParams.firstMessage = firstMessage;
  if (instructions) updateParams.instructions = instructions;
  if (maxDuration) updateParams.maxDuration = maxDuration;
  
  // Update the assistant
  const assistant = await vapiClient.assistants.update(id, updateParams);
  
  return {
    success: true,
    assistant
  };
};

// Delete an assistant
export const deleteAssistant = async (id: string) => {
  await vapiClient.assistants.delete(id);
  
  return {
    success: true,
    message: `Assistant ${id} deleted successfully`
  };
}; 
```

--------------------------------------------------------------------------------
/vapi-mcp-server/src/types.ts:
--------------------------------------------------------------------------------

```typescript
// Vapi API Interfaces

export interface VapiVoice {
  provider: string;
  voiceId: string;
}

export interface VapiAssistant {
  id: string;
  name: string;
  model: string;
  voice: VapiVoice;
  firstMessage?: string;
  instructions?: string;
  maxDuration?: number;
  createdAt: string;
  updatedAt: string;
}

export interface VapiMessage {
  id: string;
  role: "assistant" | "user";
  content: string;
  timestamp: string;
}

export interface VapiCall {
  id: string;
  assistantId: string;
  to: string;
  from: string;
  status: string;
  startTime?: string;
  endTime?: string;
  duration?: number;
  messageCount?: number;
  metadata?: Record<string, string>;
}

export interface VapiListResponse<T> {
  data: T[];
  total: number;
  limit: number;
  offset: number;
}

// Request Types

export interface CreateCallRequest {
  phoneNumber: string;
  assistantId?: string;
  assistant?: {
    name?: string;
    model?: string;
    voice?: VapiVoice;
    firstMessage?: string;
    maxDuration?: number;
  };
  metadata?: Record<string, string>;
}

export interface CreateAssistantRequest {
  name: string;
  model: string;
  voice: VapiVoice;
  firstMessage?: string;
  instructions?: string;
  maxDuration?: number;
}

export interface UpdateAssistantRequest {
  name?: string;
  voice?: VapiVoice;
  firstMessage?: string;
  instructions?: string;
  maxDuration?: number;
}

export interface ListCallsParams {
  limit?: number;
  offset?: number;
  startDate?: string;
  endDate?: string;
}

// Response Types for MCP

export interface CallToolResponse {
  callId: string;
  status: string;
}

export interface AssistantToolResponse {
  success: boolean;
  assistant?: any;
  assistants?: any[];
  message?: string;
  error?: string;
}

export interface ConversationGetResponse {
  success: boolean;
  conversation?: {
    callId: string;
    assistantId: string;
    phoneNumber: string;
    startTime?: string;
    endTime?: string;
    duration?: number;
    status: string;
    messages: any[];
  };
  error?: string;
}

export interface ConversationListResponse {
  success: boolean;
  conversations?: Array<{
    callId: string;
    assistantId: string;
    phoneNumber: string;
    startTime?: string;
    endTime?: string;
    duration?: number;
    status: string;
    messageCount: number;
  }>;
  pagination?: {
    total: number;
    limit: number;
    offset: number;
  };
  error?: string;
} 
```

--------------------------------------------------------------------------------
/docs/deployment-guide.md:
--------------------------------------------------------------------------------

```markdown
# Vapi MCP Integration

This document explains how the Vapi MCP (Model Context Protocol) service is integrated with the n8n platform.

## Overview

The Vapi MCP service provides Voice AI capabilities for the n8n platform. It enables:
- Making outbound voice calls with AI assistants
- Creating and managing voice assistants with different voices
- Retrieving and analyzing conversation transcripts

## Architecture

The Vapi MCP integration consists of:

1. **Docker Container**: A containerized version of the Vapi MCP HTTP server
2. **Caddy Routing**: Reverse proxy setup to route `/vapi/*` URLs to the Vapi service
3. **Environment Variables**: Configuration for connecting to the Vapi API
4. **n8n Custom Node**: A custom node in n8n that connects to the Vapi service

## Access

The service is accessible via:

- **External URL**: https://staging-n8n.ai-advantage.au/vapi/
- **Internal URL (from other containers)**: http://vapi-mcp:3000
- **Local URL (from n8n container)**: http://vapi-mcp:3000

## API Endpoints

The service exposes the following API endpoints:

- `GET /api/assistants` - List voice assistants
- `POST /api/assistants` - Create a new voice assistant
- `GET /api/assistants/:id` - Get a specific voice assistant
- `POST /api/calls` - Make an outbound call
- `GET /api/calls` - List calls
- `GET /api/calls/:id` - Get details about a specific call
- `GET /api/conversations` - List conversations
- `GET /api/conversations/:id` - Get a specific conversation transcript

## Configuration

The service uses the following environment variables:

- `VAPI_ORG_ID` - Vapi organization ID
- `VAPI_PRIVATE_KEY` - Vapi private API key
- `VAPI_KNOWLEDGE_ID` - Vapi knowledge base ID
- `VAPI_JWT_PRIVATE` - JWT private key for Vapi authentication

## Usage in n8n

To use the Vapi service in n8n:

1. Access via the custom n8n node
2. API requests can be made to `{{$env.VAPI_API_URL}}/api/...` using the HTTP Request node

## Maintenance

### Updating the Service

1. Pull the latest code:
   ```bash
   cd /home/mcage/vapi-mcp
   git pull
   ```

2. Rebuild and restart the container:
   ```bash
   cd /home/mcage/.n8n-setup
   sudo docker compose up -d --build vapi-mcp
   ```

### Monitoring

Check the service status:
```bash
sudo docker logs vapi-mcp
```

Check the health endpoint:
```bash
curl http://localhost:3000/health
```

## Troubleshooting

If the service is not responding:

1. Check container status:
   ```bash
   sudo docker ps | grep vapi-mcp
   ```

2. Check logs:
   ```bash
   sudo docker logs vapi-mcp
   ```

3. Verify Caddy configuration:
   ```bash
   sudo docker logs n8n-setup-caddy-1
   ```

4. Restart the service:
   ```bash
   sudo docker restart vapi-mcp
   ``` 
```

--------------------------------------------------------------------------------
/vapi-mcp-integration.md:
--------------------------------------------------------------------------------

```markdown
# Vapi MCP Integration

This document explains how the Vapi MCP (Model Context Protocol) service is integrated with the n8n platform.

## Overview

The Vapi MCP service provides Voice AI capabilities for the n8n platform. It enables:
- Making outbound voice calls with AI assistants
- Creating and managing voice assistants with different voices
- Retrieving and analyzing conversation transcripts

## Architecture

The Vapi MCP integration consists of:

1. **Docker Container**: A containerized version of the Vapi MCP HTTP server
2. **Caddy Routing**: Reverse proxy setup to route `/vapi/*` URLs to the Vapi service
3. **Environment Variables**: Configuration for connecting to the Vapi API
4. **n8n Custom Node**: A custom node in n8n that connects to the Vapi service

## Access

The service is accessible via:

- **External URL**: https://staging-n8n.ai-advantage.au/vapi/
- **Internal URL (from other containers)**: http://vapi-mcp:3000
- **Local URL (from n8n container)**: http://vapi-mcp:3000

## API Endpoints

The service exposes the following API endpoints:

- `GET /api/assistants` - List voice assistants
- `POST /api/assistants` - Create a new voice assistant
- `GET /api/assistants/:id` - Get a specific voice assistant
- `POST /api/calls` - Make an outbound call
- `GET /api/calls` - List calls
- `GET /api/calls/:id` - Get details about a specific call
- `GET /api/conversations` - List conversations
- `GET /api/conversations/:id` - Get a specific conversation transcript

## Configuration

The service uses the following environment variables:

- `VAPI_ORG_ID` - Vapi organization ID
- `VAPI_PRIVATE_KEY` - Vapi private API key
- `VAPI_KNOWLEDGE_ID` - Vapi knowledge base ID
- `VAPI_JWT_PRIVATE` - JWT private key for Vapi authentication

## Usage in n8n

To use the Vapi service in n8n:

1. Access via the custom n8n node
2. API requests can be made to `{{$env.VAPI_API_URL}}/api/...` using the HTTP Request node

## Maintenance

### Updating the Service

1. Pull the latest code:
   ```bash
   cd /home/mcage/vapi-mcp
   git pull
   ```

2. Rebuild and restart the container:
   ```bash
   cd /home/mcage/.n8n-setup
   sudo docker compose up -d --build vapi-mcp
   ```

### Monitoring

Check the service status:
```bash
sudo docker logs vapi-mcp
```

Check the health endpoint:
```bash
curl http://localhost:3000/health
```

## Troubleshooting

If the service is not responding:

1. Check container status:
   ```bash
   sudo docker ps | grep vapi-mcp
   ```

2. Check logs:
   ```bash
   sudo docker logs vapi-mcp
   ```

3. Verify Caddy configuration:
   ```bash
   sudo docker logs n8n-setup-caddy-1
   ```

4. Restart the service:
   ```bash
   sudo docker restart vapi-mcp
   ``` 
```

--------------------------------------------------------------------------------
/examples/usage-examples.md:
--------------------------------------------------------------------------------

```markdown
# Vapi MCP Usage Examples

This document provides examples of how to use the Vapi MCP tools from Cursor.

## Outbound Calls

### Basic Call

```
Use the Vapi Voice AI platform to call my colleague at +1234567890.
```

Cursor will use the `vapi_call` tool with:

```json
{
  "phoneNumber": "+1234567890"
}
```

### Call with Existing Assistant

```
Call +1234567890 using the Sales Assistant with ID assistant_abc123.
```

Cursor will use the `vapi_call` tool with:

```json
{
  "phoneNumber": "+1234567890",
  "assistantId": "assistant_abc123"
}
```

### Call with Custom Assistant Configuration

```
Call +1234567890 with a new assistant using the Alloy voice that introduces itself as a support agent.
```

Cursor will use the `vapi_call` tool with:

```json
{
  "phoneNumber": "+1234567890",
  "assistantConfig": {
    "voice": "alloy",
    "firstMessage": "Hello, this is the support team calling. How may I help you today?"
  }
}
```

## Assistant Management

### Creating an Assistant

```
Create a new voice assistant for sales calls with the Nova voice that introduces itself as Sarah from the sales team.
```

Cursor will use the `vapi_assistant` tool with:

```json
{
  "action": "create",
  "params": {
    "name": "Sales Assistant",
    "voice": "nova",
    "firstMessage": "Hello, this is Sarah from the sales team. Do you have a moment to talk about our new offerings?",
    "model": "gpt-4"
  }
}
```

### Listing All Assistants

```
Show me a list of all my voice assistants.
```

Cursor will use the `vapi_assistant` tool with:

```json
{
  "action": "list"
}
```

### Getting Assistant Details

```
Show me the details of assistant_abc123.
```

Cursor will use the `vapi_assistant` tool with:

```json
{
  "action": "get",
  "assistantId": "assistant_abc123"
}
```

### Updating an Assistant

```
Update the Sales Assistant with ID assistant_abc123 to use the Echo voice instead.
```

Cursor will use the `vapi_assistant` tool with:

```json
{
  "action": "update",
  "assistantId": "assistant_abc123",
  "params": {
    "voice": "echo"
  }
}
```

### Deleting an Assistant

```
Delete the assistant with ID assistant_abc123.
```

Cursor will use the `vapi_assistant` tool with:

```json
{
  "action": "delete",
  "assistantId": "assistant_abc123"
}
```

## Conversation Management

### Retrieving a Conversation

```
Show me the transcript of call call_xyz789.
```

Cursor will use the `vapi_conversation` tool with:

```json
{
  "action": "get",
  "callId": "call_xyz789"
}
```

### Listing Recent Conversations

```
Show me a list of my 5 most recent calls.
```

Cursor will use the `vapi_conversation` tool with:

```json
{
  "action": "list",
  "filters": {
    "limit": 5
  }
}
```

### Filtering Conversations by Date

```
Show me calls made between January 1st and January 15th, 2023.
```

Cursor will use the `vapi_conversation` tool with:

```json
{
  "action": "list",
  "filters": {
    "startDate": "2023-01-01T00:00:00Z",
    "endDate": "2023-01-15T23:59:59Z"
  }
}
```

## Common Workflows

### Complete Sales Workflow

```
1. Create a new assistant for product demos with the Alloy voice
2. Use this assistant to call +1234567890
3. After the call finishes, retrieve the conversation transcript
```

This will involve three sequential tool calls:

1. `vapi_assistant` with action "create"
2. `vapi_call` with the newly created assistant ID
3. `vapi_conversation` with action "get" and the call ID returned from step 2

### Customer Support Workflow

```
1. List my existing assistants
2. Use the Support Assistant to call customer +1234567890
3. Retrieve the conversation transcript
```

This will involve:

1. `vapi_assistant` with action "list"
2. `vapi_call` with the Support Assistant ID
3. `vapi_conversation` with action "get" and the resulting call ID 
```

--------------------------------------------------------------------------------
/docs/integration-guide.md:
--------------------------------------------------------------------------------

```markdown
# Vapi MCP Integration - Service Connection Guide

This guide explains how other services in the docker-compose stack can connect to the Vapi MCP service.

## Connection Methods

The Vapi MCP service is available through the following endpoints:

1. **HTTP API**: `http://vapi-mcp:3000`
2. **External URL**: `https://staging-n8n.ai-advantage.au/vapi/`

## N8N Integration

### Using HTTP Request Node

1. Create a new HTTP Request node in your n8n workflow
2. Use the environment variable for dynamic configuration:
   ```
   {{$env.VAPI_API_URL}}/api/calls
   ```
3. Example configuration:
   - **Method**: POST
   - **URL**: `{{$env.VAPI_API_URL}}/api/calls`
   - **Headers**: `Content-Type: application/json`
   - **Body**:
     ```json
     {
       "phoneNumber": "+61412345678",
       "assistantId": "your-assistant-id",
       "metadata": {
         "campaign": "welcome_call"
       }
     }
     ```

### Available Endpoints

- **Assistants**: `{{$env.VAPI_API_URL}}/api/assistants`
- **Calls**: `{{$env.VAPI_API_URL}}/api/calls`
- **Conversations**: `{{$env.VAPI_API_URL}}/api/conversations`

## Other Docker Services

Other containers can reach the Vapi MCP service using the service name:

```
http://vapi-mcp:3000
```

## Connection Examples

### Python Example

```python
import requests

# Base URL for internal docker network
base_url = "http://vapi-mcp:3000"

# Create a new assistant
response = requests.post(
    f"{base_url}/api/assistants",
    json={
        "name": "Customer Support",
        "model": "gpt-4",
        "voice": "alloy",
        "firstMessage": "Hello, this is customer support. How can I help you today?"
    }
)

print(response.json())
```

### JavaScript Example

```javascript
const axios = require('axios');

// Base URL for internal docker network
const baseUrl = 'http://vapi-mcp:3000';

// Make a call
async function makeCall() {
  try {
    const response = await axios.post(`${baseUrl}/api/calls`, {
      phoneNumber: '+61412345678',
      assistantId: 'assistant_abc123',
      metadata: {
        campaign: 'support_followup'
      }
    });
    
    console.log('Call initiated:', response.data);
  } catch (error) {
    console.error('Error making call:', error.response?.data || error.message);
  }
}

makeCall();
```

## Health Check

All services can verify the Vapi MCP service health using:

```
http://vapi-mcp:3000/health
```

Expected response:
```json
{"status":"ok"}
```

## Environment Variables

The following environment variables are available for service configuration:

| Variable | Purpose | Where Set |
|----------|---------|-----------|
| `VAPI_API_URL` | Base URL for Vapi API | In n8n container as `http://vapi-mcp:3000` |
| `VAPI_ORG_ID` | Vapi organization identifier | `.env` file |
| `VAPI_PRIVATE_KEY` | Authentication key | `.env` file |
| `VAPI_KNOWLEDGE_ID` | Knowledge base identifier | `.env` file |
| `VAPI_JWT_PRIVATE` | JWT authentication key | `.env` file |

## Troubleshooting

If services cannot connect to Vapi MCP:

1. Verify the container is running:
   ```bash
   sudo docker ps | grep vapi-mcp
   ```

2. Check the service is healthy:
   ```bash
   sudo docker exec -it vapi-mcp wget -q -O - http://localhost:3000/health
   ```

3. Verify network connectivity (from another container):
   ```bash
   sudo docker exec -it n8n wget -q -O - http://vapi-mcp:3000/health
   ```

4. Review logs for errors:
   ```bash
   sudo docker logs vapi-mcp
   ```

## Notes for N8N Custom Nodes

When creating custom n8n nodes that connect to Vapi MCP:

1. Use the environment variable for flexibility:
   ```javascript
   const baseUrl = process.env.VAPI_API_URL || 'http://vapi-mcp:3000';
   ```

2. Include proper error handling and timeouts
3. Consider implementing retry logic for critical calls

## Additional Resources

For detailed API documentation and examples, refer to the original vapi-mcp project documentation or view the service API info at `https://staging-n8n.ai-advantage.au/vapi/`. 
```

--------------------------------------------------------------------------------
/vapi-mcp-docker.md:
--------------------------------------------------------------------------------

```markdown
# Vapi MCP Integration - Service Connection Guide

This guide explains how other services in the docker-compose stack can connect to the Vapi MCP service.

## Connection Methods

The Vapi MCP service is available through the following endpoints:

1. **HTTP API**: `http://vapi-mcp:3000`
2. **External URL**: `https://staging-n8n.ai-advantage.au/vapi/`

## N8N Integration

### Using HTTP Request Node

1. Create a new HTTP Request node in your n8n workflow
2. Use the environment variable for dynamic configuration:
   ```
   {{$env.VAPI_API_URL}}/api/calls
   ```
3. Example configuration:
   - **Method**: POST
   - **URL**: `{{$env.VAPI_API_URL}}/api/calls`
   - **Headers**: `Content-Type: application/json`
   - **Body**:
     ```json
     {
       "phoneNumber": "+61412345678",
       "assistantId": "your-assistant-id",
       "metadata": {
         "campaign": "welcome_call"
       }
     }
     ```

### Available Endpoints

- **Assistants**: `{{$env.VAPI_API_URL}}/api/assistants`
- **Calls**: `{{$env.VAPI_API_URL}}/api/calls`
- **Conversations**: `{{$env.VAPI_API_URL}}/api/conversations`

## Other Docker Services

Other containers can reach the Vapi MCP service using the service name:

```
http://vapi-mcp:3000
```

## Connection Examples

### Python Example

```python
import requests

# Base URL for internal docker network
base_url = "http://vapi-mcp:3000"

# Create a new assistant
response = requests.post(
    f"{base_url}/api/assistants",
    json={
        "name": "Customer Support",
        "model": "gpt-4",
        "voice": "alloy",
        "firstMessage": "Hello, this is customer support. How can I help you today?"
    }
)

print(response.json())
```

### JavaScript Example

```javascript
const axios = require('axios');

// Base URL for internal docker network
const baseUrl = 'http://vapi-mcp:3000';

// Make a call
async function makeCall() {
  try {
    const response = await axios.post(`${baseUrl}/api/calls`, {
      phoneNumber: '+61412345678',
      assistantId: 'assistant_abc123',
      metadata: {
        campaign: 'support_followup'
      }
    });
    
    console.log('Call initiated:', response.data);
  } catch (error) {
    console.error('Error making call:', error.response?.data || error.message);
  }
}

makeCall();
```

## Health Check

All services can verify the Vapi MCP service health using:

```
http://vapi-mcp:3000/health
```

Expected response:
```json
{"status":"ok"}
```

## Environment Variables

The following environment variables are available for service configuration:

| Variable | Purpose | Where Set |
|----------|---------|-----------|
| `VAPI_API_URL` | Base URL for Vapi API | In n8n container as `http://vapi-mcp:3000` |
| `VAPI_ORG_ID` | Vapi organization identifier | `.env` file |
| `VAPI_PRIVATE_KEY` | Authentication key | `.env` file |
| `VAPI_KNOWLEDGE_ID` | Knowledge base identifier | `.env` file |
| `VAPI_JWT_PRIVATE` | JWT authentication key | `.env` file |

## Troubleshooting

If services cannot connect to Vapi MCP:

1. Verify the container is running:
   ```bash
   sudo docker ps | grep vapi-mcp
   ```

2. Check the service is healthy:
   ```bash
   sudo docker exec -it vapi-mcp wget -q -O - http://localhost:3000/health
   ```

3. Verify network connectivity (from another container):
   ```bash
   sudo docker exec -it n8n wget -q -O - http://vapi-mcp:3000/health
   ```

4. Review logs for errors:
   ```bash
   sudo docker logs vapi-mcp
   ```

## Notes for N8N Custom Nodes

When creating custom n8n nodes that connect to Vapi MCP:

1. Use the environment variable for flexibility:
   ```javascript
   const baseUrl = process.env.VAPI_API_URL || 'http://vapi-mcp:3000';
   ```

2. Include proper error handling and timeouts
3. Consider implementing retry logic for critical calls

## Additional Resources

For detailed API documentation and examples, refer to the original vapi-mcp project documentation or view the service API info at `https://staging-n8n.ai-advantage.au/vapi/`. 
```

--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/index.ts:
--------------------------------------------------------------------------------

```typescript
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import { VapiClient } from '@vapi-ai/server-sdk';

// Import routes
import callsRouter from './routes/calls';
import assistantsRouter from './routes/assistants';
import conversationsRouter from './routes/conversations';

// Load environment variables
dotenv.config();

// Initialize the vapi client
export const vapiClient = new VapiClient({
  token: () => process.env.VAPI_PRIVATE_KEY || ""
});

// Initialize Express app
const app = express();
const port = process.env.PORT || 3000;

// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Request logging middleware
app.use((req, res, next) => {
  console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
  next();
});

// MCP Server-Sent Events endpoint
app.get('/sse', (req, res) => {
  // Set headers for SSE
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  // Send the initial endpoint event as required by MCP protocol
  res.write(`event: endpoint\ndata: /mcp-messages\n\n`);
  
  // Keep the connection alive
  const keepAliveInterval = setInterval(() => {
    res.write(': keepalive\n\n');
  }, 30000);
  
  // Handle client disconnect
  req.on('close', () => {
    clearInterval(keepAliveInterval);
    console.log('SSE connection closed');
  });
});

// MCP messages endpoint for client to post messages
app.post('/mcp-messages', express.json(), async (req: express.Request, res: express.Response) => {
  try {
    const message = req.body;
    console.log('Received MCP message:', message);
    
    // Check if it's a valid JSON-RPC 2.0 message
    if (message.jsonrpc !== '2.0') {
      return res.status(400).json({
        jsonrpc: '2.0',
        id: message.id,
        error: {
          code: -32600,
          message: 'Invalid Request: Not a valid JSON-RPC 2.0 message'
        }
      });
    }
    
    // Handle different MCP protocol methods
    if (message.method === 'initialize') {
      // Handle initialization request
      return res.json({
        jsonrpc: '2.0',
        id: message.id,
        result: {
          capabilities: {
            tools: true,
            resources: true,
            prompts: true,
            roots: false
          },
          protocolVersion: '2024-11-05'
        }
      });
    } else if (message.method === 'mcp/list_capabilities') {
      // Return MCP capabilities
      return res.json({
        jsonrpc: '2.0',
        id: message.id,
        result: {
          capabilities: {
            tools: true,
            resources: true,
            prompts: true,
            roots: false
          },
          schema_version: '2024-11-05'
        }
      });
    } else if (message.method === 'tools/list') {
      // Return available tools that map to our API endpoints
      return res.json({
        jsonrpc: '2.0',
        id: message.id,
        result: {
          tools: [
            {
              name: 'createAssistant',
              description: 'Create a new voice assistant',
              schema: {
                type: 'object',
                properties: {
                  name: {
                    type: 'string',
                    description: 'Name of the assistant'
                  },
                  model: {
                    type: 'string',
                    description: 'LLM model to use'
                  },
                  voice: {
                    type: 'string',
                    description: 'Voice to use'
                  },
                  firstMessage: {
                    type: 'string',
                    description: 'First message to say'
                  }
                },
                required: ['name', 'model', 'voice']
              }
            },
            {
              name: 'makeCall',
              description: 'Make an outbound call',
              schema: {
                type: 'object',
                properties: {
                  phoneNumber: {
                    type: 'string',
                    description: 'Phone number to call'
                  },
                  assistantId: {
                    type: 'string',
                    description: 'ID of the assistant to use'
                  },
                  metadata: {
                    type: 'object',
                    description: 'Additional metadata for the call'
                  }
                },
                required: ['phoneNumber', 'assistantId']
              }
            },
            {
              name: 'getAssistants',
              description: 'List all assistants',
              schema: {
                type: 'object',
                properties: {}
              }
            },
            {
              name: 'getAssistant',
              description: 'Get a specific assistant',
              schema: {
                type: 'object',
                properties: {
                  id: {
                    type: 'string',
                    description: 'ID of the assistant'
                  }
                },
                required: ['id']
              }
            },
            {
              name: 'getCalls',
              description: 'List all calls',
              schema: {
                type: 'object',
                properties: {}
              }
            },
            {
              name: 'getCall',
              description: 'Get a specific call',
              schema: {
                type: 'object',
                properties: {
                  id: {
                    type: 'string',
                    description: 'ID of the call'
                  }
                },
                required: ['id']
              }
            }
          ]
        }
      });
    } else if (message.method === 'prompts/list') {
      // Return available prompts
      return res.json({
        jsonrpc: '2.0',
        id: message.id,
        result: {
          prompts: [
            {
              id: "vapi-assistant-prompt",
              name: "Vapi Assistant Prompt",
              description: "A prompt for creating a Vapi voice assistant"
            }
          ]
        }
      });
    } else if (message.method === 'prompts/get') {
      // Return a specific prompt
      const promptId = message.params.id;
      if (promptId === "vapi-assistant-prompt") {
        return res.json({
          jsonrpc: '2.0',
          id: message.id,
          result: {
            prompt: {
              id: "vapi-assistant-prompt",
              name: "Vapi Assistant Prompt",
              description: "A prompt for creating a Vapi voice assistant",
              content: "You are a helpful voice assistant powered by Vapi."
            }
          }
        });
      } else {
        return res.json({
          jsonrpc: '2.0',
          id: message.id,
          error: {
            code: -32602,
            message: `Prompt not found: ${promptId}`
          }
        });
      }
    } else if (message.method === 'resources/list') {
      // Return available resources
      return res.json({
        jsonrpc: '2.0',
        id: message.id,
        result: {
          resources: [
            {
              uri: "vapi-docs",
              name: "Vapi Documentation",
              description: "Documentation for the Vapi API"
            }
          ]
        }
      });
    } else if (message.method === 'resources/get') {
      // Return a specific resource
      const resourceUri = message.params.uri;
      if (resourceUri === "vapi-docs") {
        return res.json({
          jsonrpc: '2.0',
          id: message.id,
          result: {
            content: "# Vapi Documentation\n\nThis is the documentation for the Vapi voice assistant API."
          }
        });
      } else {
        return res.json({
          jsonrpc: '2.0',
          id: message.id,
          error: {
            code: -32602,
            message: `Resource not found: ${resourceUri}`
          }
        });
      }
    } else if (message.method === 'tools/call') {
      // Handle tool calls by mapping to our API
      const { name, arguments: args } = message.params;
      
      let result;
      try {
        if (name === 'createAssistant') {
          // Map to POST /api/assistants
          const response = await fetch(`http://localhost:${port}/api/assistants`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(args)
          });
          result = await response.json();
        } else if (name === 'makeCall') {
          // Map to POST /api/calls
          const response = await fetch(`http://localhost:${port}/api/calls`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(args)
          });
          result = await response.json();
        } else if (name === 'getAssistants') {
          // Map to GET /api/assistants
          const response = await fetch(`http://localhost:${port}/api/assistants`);
          result = await response.json();
        } else if (name === 'getAssistant') {
          // Map to GET /api/assistants/:id
          const response = await fetch(`http://localhost:${port}/api/assistants/${args.id}`);
          result = await response.json();
        } else if (name === 'getCalls') {
          // Map to GET /api/calls
          const response = await fetch(`http://localhost:${port}/api/calls`);
          result = await response.json();
        } else if (name === 'getCall') {
          // Map to GET /api/calls/:id
          const response = await fetch(`http://localhost:${port}/api/calls/${args.id}`);
          result = await response.json();
        } else {
          throw new Error(`Unknown tool: ${name}`);
        }
        
        return res.json({
          jsonrpc: '2.0',
          id: message.id,
          result: {
            content: [
              {
                type: 'text',
                text: JSON.stringify(result)
              }
            ]
          }
        });
      } catch (error: any) {
        console.error('Error handling tool call:', error);
        return res.json({
          jsonrpc: '2.0',
          id: message.id,
          error: {
            code: -32603,
            message: `Internal error: ${error.message}`
          }
        });
      }
    } else {
      // Unsupported method
      return res.json({
        jsonrpc: '2.0',
        id: message.id,
        error: {
          code: -32601,
          message: `Method not found: ${message.method}`
        }
      });
    }
  } catch (error: any) {
    console.error('Error processing MCP message:', error);
    return res.status(500).json({
      jsonrpc: '2.0',
      id: req.body?.id || null,
      error: {
        code: -32603,
        message: `Internal error: ${error.message}`
      }
    });
  }
});

// Original API routes
app.use('/api/calls', callsRouter);
app.use('/api/assistants', assistantsRouter);
app.use('/api/conversations', conversationsRouter);

// Health check endpoint
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'ok' });
});

// Root route
app.get('/', (req, res) => {
  res.json({
    name: 'Vapi MCP HTTP Server',
    version: '1.0.0',
    description: 'HTTP server for Vapi voice AI integration with MCP'
  });
});

// Error handling middleware
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  console.error(err.stack);
  res.status(500).json({
    success: false,
    error: err.message || 'An unexpected error occurred'
  });
});

// Start the server
app.listen(port, () => {
  console.log(`Vapi MCP HTTP Server running on port ${port}`);
}); 
```

--------------------------------------------------------------------------------
/vapi-mcp-server/src/index.ts:
--------------------------------------------------------------------------------

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

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
    CallToolRequestSchema,
    ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { zodToJsonSchema } from "zod-to-json-schema";
import { VapiClient } from "@vapi-ai/server-sdk";
import dotenv from "dotenv";

// Load environment variables
dotenv.config();

// Log environment variables (without exposing sensitive data)
console.error("Vapi Environment:", {
    VAPI_ORG_ID: process.env.VAPI_ORG_ID ? "Set" : "Not set",
    VAPI_PRIVATE_KEY: process.env.VAPI_PRIVATE_KEY ? "Set" : "Not set",
    VAPI_KNOWLEDGE_ID: process.env.VAPI_KNOWLEDGE_ID ? "Set" : "Not set",
    VAPI_JWT_PRIVATE: process.env.VAPI_JWT_PRIVATE ? "Set" : "Not set",
    NODE_ENV: process.env.NODE_ENV
});

// Initialize Vapi client
const vapiClient = new VapiClient({
    token: () => {
        const key = process.env.VAPI_PRIVATE_KEY;
        if (!key) {
            throw new Error("VAPI_PRIVATE_KEY environment variable is not set");
        }
        console.error("Initializing Vapi client with auth token");
        return key;
    },
});

// Zod schemas for our tools
const CallToolSchema = z.object({
    phoneNumber: z.string().describe("The phone number to call"),
    assistantId: z.string().optional().describe("The ID of the Vapi assistant to use"),
    assistantConfig: z.object({
        name: z.string().optional().describe("Name of the assistant"),
        model: z.string().optional().describe("The LLM model to use"),
        voice: z.string().optional().describe("The voice to use"),
        firstMessage: z.string().optional().describe("First message to say when the call is answered"),
        maxDuration: z.number().optional().describe("Maximum call duration in seconds"),
    }).optional().describe("Assistant configuration, if no assistantId is provided"),
    metadata: z.record(z.string()).optional().describe("Additional metadata for the call"),
});

const AssistantActionSchema = z.enum(["create", "get", "list", "update", "delete"]);

const AssistantToolSchema = z.object({
    action: AssistantActionSchema.describe("Action to perform on the assistant"),
    assistantId: z.string().optional().describe("The ID of the assistant (required for get, update, delete)"),
    params: z.object({
        name: z.string().optional().describe("Name of the assistant"),
        model: z.string().optional().describe("The LLM model to use"),
        voice: z.string().optional().describe("The voice to use for the assistant"),
        firstMessage: z.string().optional().describe("First message the assistant will say"),
        instructions: z.string().optional().describe("Instructions for the assistant's behavior"),
        maxDuration: z.number().optional().describe("Maximum call duration in seconds"),
    }).optional().describe("Parameters for creating or updating an assistant"),
});

const ConversationActionSchema = z.enum(["get", "list"]);

const ConversationToolSchema = z.object({
    action: ConversationActionSchema.describe("Action to perform (get or list conversations)"),
    callId: z.string().optional().describe("The ID of the call to retrieve conversation for"),
    filters: z.object({
        limit: z.number().optional().describe("Maximum number of conversations to return"),
        offset: z.number().optional().describe("Offset for pagination"),
        startDate: z.string().optional().describe("Start date for filtering conversations"),
        endDate: z.string().optional().describe("End date for filtering conversations"),
    }).optional().describe("Filters for listing conversations"),
});

async function main() {
    // Initialize the MCP server
    const server = new Server(
        {
            name: "vapi-mcp-server",
            version: "1.0.0",
        },
        {
            capabilities: {
                tools: {
                    list: true,
                    call: true,
                },
            },
        }
    );

    // Verify connection to Vapi API
    try {
        console.error("Verifying connection to Vapi API...");
        // Attempt to list assistants to verify API connection
        await vapiClient.assistants.list();
        console.error("✓ Successfully connected to Vapi API");
    } catch (error) {
        console.error("✗ Failed to connect to Vapi API:", error);
        console.error("The server will continue to run but API calls may fail.");
        console.error("Check your API key and internet connection.");
    }

    // Set up tool handlers
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
        tools: [
            {
                name: "vapi_call",
                description: "Make an outbound call using the Vapi voice AI platform",
                inputSchema: zodToJsonSchema(CallToolSchema),
            },
            {
                name: "vapi_assistant",
                description: "Manage Vapi voice assistants (create, get, list, update, delete)",
                inputSchema: zodToJsonSchema(AssistantToolSchema),
            },
            {
                name: "vapi_conversation",
                description: "Retrieve conversation details from Vapi calls",
                inputSchema: zodToJsonSchema(ConversationToolSchema),
            },
        ],
    }));

    server.setRequestHandler(CallToolRequestSchema, async (request) => {
        const { name, arguments: args } = request.params;
        
        try {
            switch (name) {
                case "vapi_call": {
                    const validatedArgs = CallToolSchema.parse(args);
                    console.error(`Making call to ${validatedArgs.phoneNumber}`);
                    
                    try {
                        // Prepare call parameters based on the validated arguments
                        const callParams: any = {
                            phoneNumber: validatedArgs.phoneNumber
                        };
                        
                        // Add assistantId or assistant details
                        if (validatedArgs.assistantId) {
                            callParams.assistantId = validatedArgs.assistantId;
                        } else if (validatedArgs.assistantConfig) {
                            // Use a simpler approach with any type to avoid TypeScript errors
                            callParams.assistant = {
                                name: validatedArgs.assistantConfig.name || "Default Assistant",
                                model: validatedArgs.assistantConfig.model === "gpt-4" ? "gpt_4" : "gpt_3_5_turbo",
                                voice: {
                                    provider: "eleven_labs",
                                    voiceId: validatedArgs.assistantConfig.voice || "alloy"
                                },
                                firstMessage: validatedArgs.assistantConfig.firstMessage,
                                maxDuration: validatedArgs.assistantConfig.maxDuration
                            };
                        } else {
                            throw new Error("Either assistantId or assistantConfig is required");
                        }
                        
                        // Make the actual API call with type assertion
                        const result = await vapiClient.calls.create(callParams);
                        
                        return {
                            content: [
                                {
                                    type: "text",
                                    text: JSON.stringify({
                                        callId: result.id,
                                        status: result.status,
                                    }, null, 2),
                                },
                            ],
                        };
                    } catch (callError) {
                        console.error("Error making call:", callError);
                        throw callError;
                    }
                }
                
                case "vapi_assistant": {
                    const validatedArgs = AssistantToolSchema.parse(args);
                    console.error(`Performing ${validatedArgs.action} operation on assistant`);
                    
                    switch (validatedArgs.action) {
                        case "create": {
                            if (!validatedArgs.params) {
                                throw new Error("params is required for create operation");
                            }
                            
                            try {
                                // Use any type to bypass strict type checking
                                const createParams: any = {
                                    name: validatedArgs.params.name || "New Assistant",
                                    model: validatedArgs.params.model === "gpt-4" ? "gpt_4" : "gpt_3_5_turbo",
                                    voice: {
                                        provider: "eleven_labs",
                                        voiceId: validatedArgs.params.voice || "alloy"
                                    }
                                };
                                
                                // Add optional parameters if provided
                                if (validatedArgs.params.firstMessage) {
                                    createParams.firstMessage = validatedArgs.params.firstMessage;
                                }
                                
                                if (validatedArgs.params.instructions) {
                                    createParams.instructions = validatedArgs.params.instructions;
                                }
                                
                                if (validatedArgs.params.maxDuration) {
                                    createParams.maxDuration = validatedArgs.params.maxDuration;
                                }
                                
                                const assistant = await vapiClient.assistants.create(createParams);
                                
                                return {
                                    content: [
                                        {
                                            type: "text",
                                            text: JSON.stringify({
                                                success: true,
                                                assistant: assistant,
                                            }, null, 2),
                                        },
                                    ],
                                };
                            } catch (assistantError) {
                                console.error("Error creating assistant:", assistantError);
                                throw assistantError;
                            }
                        }
                        
                        case "get": {
                            if (!validatedArgs.assistantId) {
                                throw new Error("assistantId is required for get operation");
                            }
                            
                            const assistant = await vapiClient.assistants.get(validatedArgs.assistantId);
                            
                            return {
                                content: [
                                    {
                                        type: "text",
                                        text: JSON.stringify({
                                            success: true,
                                            assistant: assistant,
                                        }, null, 2),
                                    },
                                ],
                            };
                        }
                        
                        case "list": {
                            const assistants = await vapiClient.assistants.list();
                            
                            return {
                                content: [
                                    {
                                        type: "text",
                                        text: JSON.stringify({
                                            success: true,
                                            assistants: assistants,
                                        }, null, 2),
                                    },
                                ],
                            };
                        }
                        
                        case "update": {
                            if (!validatedArgs.assistantId) {
                                throw new Error("assistantId is required for update operation");
                            }
                            
                            if (!validatedArgs.params) {
                                throw new Error("params is required for update operation");
                            }
                            
                            try {
                                // Use any type to bypass strict type checking
                                const updateParams: any = {};
                                
                                // Only add parameters that are provided
                                if (validatedArgs.params.name) {
                                    updateParams.name = validatedArgs.params.name;
                                }
                                
                                if (validatedArgs.params.voice) {
                                    updateParams.voice = {
                                        provider: "eleven_labs",
                                        voiceId: validatedArgs.params.voice
                                    };
                                }
                                
                                if (validatedArgs.params.firstMessage) {
                                    updateParams.firstMessage = validatedArgs.params.firstMessage;
                                }
                                
                                if (validatedArgs.params.instructions) {
                                    updateParams.instructions = validatedArgs.params.instructions;
                                }
                                
                                if (validatedArgs.params.maxDuration) {
                                    updateParams.maxDuration = validatedArgs.params.maxDuration;
                                }
                                
                                const assistant = await vapiClient.assistants.update(
                                    validatedArgs.assistantId,
                                    updateParams
                                );
                                
                                return {
                                    content: [
                                        {
                                            type: "text",
                                            text: JSON.stringify({
                                                success: true,
                                                assistant: assistant,
                                            }, null, 2),
                                        },
                                    ],
                                };
                            } catch (updateError) {
                                console.error("Error updating assistant:", updateError);
                                throw updateError;
                            }
                        }
                        
                        case "delete": {
                            if (!validatedArgs.assistantId) {
                                throw new Error("assistantId is required for delete operation");
                            }
                            
                            await vapiClient.assistants.delete(validatedArgs.assistantId);
                            
                            return {
                                content: [
                                    {
                                        type: "text",
                                        text: JSON.stringify({
                                            success: true,
                                            message: `Assistant ${validatedArgs.assistantId} deleted successfully`,
                                        }, null, 2),
                                    },
                                ],
                            };
                        }
                    }
                }
                
                case "vapi_conversation": {
                    const validatedArgs = ConversationToolSchema.parse(args);
                    console.error(`Performing ${validatedArgs.action} operation on conversation`);
                    
                    switch (validatedArgs.action) {
                        case "get": {
                            if (!validatedArgs.callId) {
                                throw new Error("callId is required for get operation");
                            }
                            
                            try {
                                // Get call details
                                const call = await vapiClient.calls.get(validatedArgs.callId);
                                
                                // Construct conversation response using properties that are known to exist
                                const conversation = {
                                    callId: call.id,
                                    assistantId: call.assistantId,
                                    phoneNumber: "unknown", // We don't have access to the 'to' property
                                    startTime: call.createdAt, 
                                    endTime: call.updatedAt,
                                    duration: 0, // We don't have access to the duration property
                                    status: call.status,
                                    messages: [], // We don't have access to messages directly
                                };
                                
                                return {
                                    content: [
                                        {
                                            type: "text",
                                            text: JSON.stringify({
                                                success: true,
                                                conversation: conversation,
                                            }, null, 2),
                                        },
                                    ],
                                };
                            } catch (getError) {
                                console.error("Error getting conversation:", getError);
                                throw getError;
                            }
                        }
                        
                        case "list": {
                            try {
                                const filters = {
                                    limit: validatedArgs.filters?.limit || 10,
                                    offset: validatedArgs.filters?.offset || 0,
                                };
                                
                                // List calls with filters
                                const calls = await vapiClient.calls.list(filters);
                                
                                // Ensure calls is treated as an array
                                const callsArray = Array.isArray(calls) ? calls : [];
                                
                                // Map calls to conversation summary format
                                const conversations = callsArray.map(call => ({
                                    callId: call.id,
                                    assistantId: call.assistantId,
                                    phoneNumber: "unknown", // We don't have access to the 'to' property
                                    startTime: call.createdAt,
                                    endTime: call.updatedAt,
                                    duration: 0, // We don't have access to duration
                                    status: call.status,
                                    messageCount: 0, // We don't have access to message count
                                }));
                                
                                return {
                                    content: [
                                        {
                                            type: "text",
                                            text: JSON.stringify({
                                                success: true,
                                                conversations: conversations,
                                                pagination: {
                                                    total: callsArray.length,
                                                    limit: filters.limit,
                                                    offset: filters.offset,
                                                },
                                            }, null, 2),
                                        },
                                    ],
                                };
                            } catch (listError) {
                                console.error("Error listing conversations:", listError);
                                throw listError;
                            }
                        }
                    }
                }
                
                default:
                    throw new Error(`Unknown tool: ${name}`);
            }
        } catch (error: unknown) {
            const errorMessage = error instanceof Error ? error.message : String(error);
            console.error(`Error in tool execution: ${errorMessage}`, error);
            return {
                content: [
                    {
                        type: "text",
                        text: JSON.stringify({
                            error: errorMessage || "An unknown error occurred",
                        }, null, 2),
                    },
                ],
            };
        }
    });

    // Connect to transport
    const transport = new StdioServerTransport();
    await server.connect(transport);
    
    console.error("Vapi MCP Server started successfully");
}

main().catch((error: unknown) => {
    const errorMessage = error instanceof Error ? error.message : String(error);
    console.error("Error starting Vapi MCP Server:", errorMessage);
    process.exit(1);
});

```