# 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:
--------------------------------------------------------------------------------
```
1 | # Vapi API Keys
2 | VAPI_ORG_ID=000c3516-6c06-4462-bd9d-2f15d109478e
3 | VAPI_PRIVATE_KEY=8300521f-7421-4088-8a13-d0df6ea29962
4 | VAPI_KNOWLEDGE_ID=f2a554b0-fe9a-456e-a7ab-3294d3689534
5 | VAPI_JWT_PRIVATE=da163ef8-ac5b-43d1-9117-a002aaba0926
6 | 
7 | # Environment
8 | NODE_ENV=development 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/.env:
--------------------------------------------------------------------------------
```
1 | # Vapi API Keys
2 | VAPI_ORG_ID=000c3516-6c06-4462-bd9d-2f15d109478e
3 | VAPI_PRIVATE_KEY=8300521f-7421-4088-8a13-d0df6ea29962
4 | VAPI_KNOWLEDGE_ID=f2a554b0-fe9a-456e-a7ab-3294d3689534
5 | VAPI_JWT_PRIVATE=da163ef8-ac5b-43d1-9117-a002aaba0926
6 | 
7 | # Server Configuration
8 | PORT=3000
9 | NODE_ENV=development 
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
 1 | # Node.js dependencies
 2 | node_modules/
 3 | npm-debug.log
 4 | yarn-debug.log
 5 | yarn-error.log
 6 | .pnp
 7 | .pnp.js
 8 | 
 9 | # Build output
10 | dist/
11 | build/
12 | out/
13 | 
14 | # Environment variables
15 | .env
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 | 
21 | # IDE files
22 | .idea/
23 | .vscode/
24 | *.swp
25 | *.swo
26 | .DS_Store
27 | 
28 | # Logs
29 | logs
30 | *.log
31 | 
32 | # Cache
33 | .npm
34 | .eslintcache
35 | 
36 | # Package files
37 | package-lock.json
38 | yarn.lock
39 | 
40 | # Examples and test directories from other repos
41 | client-sdk-python/
42 | client-sdk-web/
43 | server-example-javascript-node/
44 | vapi-express-starter/ 
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
  1 | # Vapi MCP for Cursor
  2 | 
  3 | This project implements a Model Context Protocol (MCP) server for integrating Vapi's voice AI capabilities with Cursor.
  4 | 
  5 | ## Setup Instructions
  6 | 
  7 | ### 1. Project Structure
  8 | 
  9 | The Vapi MCP server is structured as follows:
 10 | - `vapi-mcp-server/` - Main server code
 11 |   - `src/` - TypeScript source files
 12 |   - `dist/` - Compiled JavaScript output
 13 |   - `.env` - Environment variables for API keys
 14 | 
 15 | ### 2. Environment Configuration
 16 | 
 17 | Create a `.env` file in the `vapi-mcp-server` directory with the following variables:
 18 | 
 19 | ```
 20 | # Vapi API Keys
 21 | VAPI_ORG_ID=your-org-id
 22 | VAPI_PRIVATE_KEY=your-private-key
 23 | VAPI_KNOWLEDGE_ID=your-knowledge-id
 24 | VAPI_JWT_PRIVATE=your-jwt-private
 25 | 
 26 | # Environment
 27 | NODE_ENV=development
 28 | ```
 29 | 
 30 | ### 3. Building the Server
 31 | 
 32 | To build the server:
 33 | 
 34 | ```bash
 35 | cd vapi-mcp/vapi-mcp-server
 36 | npm install
 37 | npm run build
 38 | ```
 39 | 
 40 | ### 4. Configuration in Cursor
 41 | 
 42 | #### Important: Avoiding "Client Closed" Errors
 43 | 
 44 | When configuring the Vapi MCP server in Cursor's MCP settings, pay attention to the following crucial details:
 45 | 
 46 | 1. **Working Directory**: The `cwd` parameter is required to ensure the server runs in the correct directory and can access the `.env` file properly.
 47 | 
 48 | 2. **Environment Variables**: Must be explicitly provided in the configuration, even if they exist in the `.env` file.
 49 | 
 50 | 3. **Module Type**: The server uses ES modules, so the `package.json` must include `"type": "module"`.
 51 | 
 52 | Here's the correct configuration for `.cursor/mcp.json`:
 53 | 
 54 | ```json
 55 | "Vapi Voice AI Tools": {
 56 |   "command": "node",
 57 |   "type": "stdio",
 58 |   "args": [
 59 |     "/Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server/dist/index.js"
 60 |   ],
 61 |   "cwd": "/Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server",
 62 |   "env": {
 63 |     "VAPI_ORG_ID": "your-org-id",
 64 |     "VAPI_PRIVATE_KEY": "your-private-key",
 65 |     "VAPI_KNOWLEDGE_ID": "your-knowledge-id",
 66 |     "VAPI_JWT_PRIVATE": "your-jwt-private",
 67 |     "NODE_ENV": "development"
 68 |   }
 69 | }
 70 | ```
 71 | 
 72 | ## Troubleshooting
 73 | 
 74 | ### "Client Closed" Error in Cursor
 75 | 
 76 | If you see "Client Closed" in the Cursor MCP Tools panel:
 77 | 
 78 | 1. **Check Working Directory**: Ensure the `cwd` parameter is set correctly in your mcp.json
 79 | 2. **Verify Environment Variables**: Make sure all required environment variables are passed in the configuration
 80 | 3. **Check Module Type**: Ensure `package.json` has `"type": "module"`
 81 | 4. **Inspect Permissions**: Make sure the dist/index.js file is executable (`chmod +x dist/index.js`)
 82 | 5. **Test Server Directly**: Run the server manually to check for errors:
 83 |    ```bash
 84 |    cd vapi-mcp/vapi-mcp-server
 85 |    node --trace-warnings dist/index.js
 86 |    ```
 87 | 
 88 | ### Module Not Found Errors
 89 | 
 90 | If you get "Error: Cannot find module" when running:
 91 | 
 92 | 1. **Check Working Directory**: Are you running from the correct directory?
 93 | 2. **Rebuild**: Try rebuilding the project with `npm run build`
 94 | 3. **Dependencies**: Ensure all dependencies are installed with `npm install`
 95 | 
 96 | ## Available Tools
 97 | 
 98 | The Vapi MCP server provides the following tools:
 99 | 
100 | 1. **vapi_call** - Make outbound calls using Vapi's voice AI
101 | 2. **vapi_assistant** - Manage voice assistants (create, get, list, update, delete)
102 | 3. **vapi_conversation** - Retrieve conversation details from calls
103 | 
104 | ## Lessons Learned
105 | 
106 | 1. When integrating with Cursor's MCP:
107 |    - Always specify the `cwd` parameter to ensure the server runs in the correct directory
108 |    - Pass all required environment variables directly in the MCP configuration
109 |    - For ES modules, ensure package.json has `"type": "module"` and tsconfig.json uses appropriate module settings
110 |    - Test the server directly before configuring in Cursor
111 | 
112 | 2. The server command path must be absolute and correctly formed in the Cursor MCP config
113 | 
114 | 3. Using stdio transport type is required for proper integration with Cursor 
```
--------------------------------------------------------------------------------
/vapi-mcp-server/README.md:
--------------------------------------------------------------------------------
```markdown
  1 | # Vapi MCP Server
  2 | 
  3 | This is the server component for the Vapi Voice AI Tools integration with Cursor via the Model Context Protocol (MCP).
  4 | 
  5 | ## Setup Guide
  6 | 
  7 | ### 1. Environment Configuration
  8 | 
  9 | Create a `.env` file in this directory with your Vapi API credentials:
 10 | 
 11 | ```
 12 | # Vapi API Keys
 13 | VAPI_ORG_ID=your-org-id
 14 | VAPI_PRIVATE_KEY=your-private-key
 15 | VAPI_KNOWLEDGE_ID=your-knowledge-id
 16 | VAPI_JWT_PRIVATE=your-jwt-private
 17 | 
 18 | # Environment
 19 | NODE_ENV=development
 20 | ```
 21 | 
 22 | ### 2. Installation
 23 | 
 24 | Install the dependencies:
 25 | 
 26 | ```bash
 27 | npm install
 28 | ```
 29 | 
 30 | ### 3. Build
 31 | 
 32 | Build the TypeScript code:
 33 | 
 34 | ```bash
 35 | npm run build
 36 | ```
 37 | 
 38 | ### 4. Run
 39 | 
 40 | Start the server:
 41 | 
 42 | ```bash
 43 | npm start
 44 | ```
 45 | 
 46 | ## Cursor MCP Integration
 47 | 
 48 | ### Proper Configuration (Important!)
 49 | 
 50 | To avoid the "Client Closed" error in Cursor, ensure your `.cursor/mcp.json` configuration includes:
 51 | 
 52 | 1. **The correct working directory (`cwd`)** 
 53 | 2. **All environment variables**
 54 | 3. **Correct file path to the server**
 55 | 
 56 | Example configuration:
 57 | 
 58 | ```json
 59 | "Vapi Voice AI Tools": {
 60 |   "command": "node",
 61 |   "type": "stdio",
 62 |   "args": [
 63 |     "/Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server/dist/index.js"
 64 |   ],
 65 |   "cwd": "/Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server",
 66 |   "env": {
 67 |     "VAPI_ORG_ID": "000c3516-6c06-4462-bd9d-2f15d109478e",
 68 |     "VAPI_PRIVATE_KEY": "8300521f-7421-4088-8a13-d0df6ea29962",
 69 |     "VAPI_KNOWLEDGE_ID": "f2a554b0-fe9a-456e-a7ab-3294d3689534",
 70 |     "VAPI_JWT_PRIVATE": "da163ef8-ac5b-43d1-9117-a002aaba0926",
 71 |     "NODE_ENV": "development"
 72 |   }
 73 | }
 74 | ```
 75 | 
 76 | ## Critical Configuration Files
 77 | 
 78 | ### 1. package.json
 79 | 
 80 | Ensure your `package.json` has the following settings:
 81 | 
 82 | ```json
 83 | {
 84 |   "name": "vapi-mcp-server",
 85 |   "version": "1.0.0",
 86 |   "description": "MCP Server for Vapi API integration",
 87 |   "main": "dist/index.js",
 88 |   "type": "module",
 89 |   "scripts": {
 90 |     "build": "tsc",
 91 |     "start": "node dist/index.js",
 92 |     "dev": "tsc -w & node --watch dist/index.js"
 93 |   }
 94 | }
 95 | ```
 96 | 
 97 | The **"type": "module"** line is critical for ES modules to work correctly.
 98 | 
 99 | ### 2. tsconfig.json
100 | 
101 | Ensure your `tsconfig.json` has the correct module settings:
102 | 
103 | ```json
104 | {
105 |   "compilerOptions": {
106 |     "target": "es2020",
107 |     "module": "NodeNext",
108 |     "moduleResolution": "NodeNext",
109 |     "esModuleInterop": true,
110 |     "outDir": "./dist",
111 |     "rootDir": "./src"
112 |   }
113 | }
114 | ```
115 | 
116 | ## Common Issues and Solutions
117 | 
118 | ### 1. "Client Closed" Error in Cursor
119 | 
120 | **Problem**: The tools panel in Cursor shows "Client Closed" even though the server appears to be properly configured.
121 | 
122 | **Solutions**:
123 | - Ensure the `cwd` parameter is set in your mcp.json configuration
124 | - Make sure all environment variables are explicitly passed in the configuration
125 | - Verify the server can run standalone with `node dist/index.js`
126 | - Check for any errors in the console when starting the server
127 | - Make sure the `package.json` has `"type": "module"` set
128 | 
129 | ### 2. ES Module Errors
130 | 
131 | **Problem**: Errors about imports not being recognized or "Cannot use import outside a module".
132 | 
133 | **Solutions**:
134 | - Add `"type": "module"` to your `package.json`
135 | - Update `tsconfig.json` to use `"module": "NodeNext"` and `"moduleResolution": "NodeNext"`
136 | - Use `.js` extensions in your imports even in TypeScript files
137 | - Rebuild the project with `npm run build`
138 | 
139 | ### 3. "Cannot find module" Error
140 | 
141 | **Problem**: Node.js cannot find a module or the main server file.
142 | 
143 | **Solutions**:
144 | - Ensure you're running from the correct working directory
145 | - Check that all dependencies are installed with `npm install`
146 | - Rebuild the project with `npm run build`
147 | - Verify the file paths in your mcp.json configuration are absolute and correct
148 | - Use `node --trace-warnings dist/index.js` to see detailed error messages
149 | 
150 | ## Debugging
151 | 
152 | To run the server with additional debugging information:
153 | 
154 | ```bash
155 | node --trace-warnings dist/index.js
156 | ```
157 | 
158 | Or with Node.js inspector:
159 | 
160 | ```bash
161 | node --inspect dist/index.js
162 | ```
163 | 
164 | ## MCP Server Structure
165 | 
166 | The server implements three main Vapi tools:
167 | 
168 | 1. **vapi_call** - Make outbound calls
169 | 2. **vapi_assistant** - Manage voice assistants
170 | 3. **vapi_conversation** - Retrieve conversation details
171 | 
172 | Refer to the source code documentation for details on how each tool works. 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/README.md:
--------------------------------------------------------------------------------
```markdown
  1 | # Vapi MCP HTTP Server
  2 | 
  3 | An HTTP API server for Vapi voice AI platform, providing REST endpoints that match the functionality of the Vapi MCP integration.
  4 | 
  5 | ## Features
  6 | 
  7 | - RESTful API for Vapi voice AI platform
  8 | - Make outbound calls with AI assistants
  9 | - Manage voice assistants (create, list, get, update, delete)
 10 | - Access conversation details from calls
 11 | - Compatible with n8n, Postman, and other HTTP-based tools
 12 | 
 13 | ## Installation
 14 | 
 15 | 1. Clone the repository:
 16 | ```bash
 17 | git clone https://github.com/yourusername/vapi-mcp-http-server.git
 18 | ```
 19 | 
 20 | 2. Install dependencies:
 21 | ```bash
 22 | cd vapi-mcp-http-server
 23 | npm install
 24 | ```
 25 | 
 26 | 3. Build the project:
 27 | ```bash
 28 | npm run build
 29 | ```
 30 | 
 31 | ## Configuration
 32 | 
 33 | Create a `.env` file in the root directory with your Vapi API credentials:
 34 | 
 35 | ```
 36 | # Vapi API Keys
 37 | VAPI_ORG_ID=your_vapi_org_id
 38 | VAPI_PRIVATE_KEY=your_vapi_private_key
 39 | VAPI_KNOWLEDGE_ID=your_vapi_knowledge_id
 40 | VAPI_JWT_PRIVATE=your_vapi_jwt_private
 41 | 
 42 | # Server Configuration
 43 | PORT=3000
 44 | NODE_ENV=development
 45 | ```
 46 | 
 47 | ## Running the Server
 48 | 
 49 | To start the server:
 50 | 
 51 | ```bash
 52 | npm start
 53 | ```
 54 | 
 55 | For development with automatic reloading:
 56 | 
 57 | ```bash
 58 | npm run dev
 59 | ```
 60 | 
 61 | The server will start on port 3000 by default (or the port specified in your `.env` file).
 62 | 
 63 | ## API Endpoints
 64 | 
 65 | ### Calls
 66 | 
 67 | - `POST /api/calls` - Make an outbound call
 68 | - `GET /api/calls` - List all calls
 69 | - `GET /api/calls/:id` - Get details of a specific call
 70 | 
 71 | ### Assistants
 72 | 
 73 | - `POST /api/assistants` - Create a new assistant
 74 | - `GET /api/assistants` - List all assistants
 75 | - `GET /api/assistants/:id` - Get details of a specific assistant
 76 | - `PUT /api/assistants/:id` - Update an assistant
 77 | - `DELETE /api/assistants/:id` - Delete an assistant
 78 | 
 79 | ### Conversations
 80 | 
 81 | - `GET /api/conversations` - List all conversations (based on calls)
 82 | - `GET /api/conversations/:callId` - Get conversation details for a specific call
 83 | 
 84 | ## Example Requests
 85 | 
 86 | ### Making a Call
 87 | 
 88 | ```bash
 89 | curl -X POST http://localhost:3000/api/calls \
 90 |   -H "Content-Type: application/json" \
 91 |   -d '{
 92 |     "phoneNumber": "+1234567890",
 93 |     "assistantId": "asst_123456"
 94 |   }'
 95 | ```
 96 | 
 97 | Or with a custom assistant:
 98 | 
 99 | ```bash
100 | curl -X POST http://localhost:3000/api/calls \
101 |   -H "Content-Type: application/json" \
102 |   -d '{
103 |     "phoneNumber": "+1234567890",
104 |     "assistantConfig": {
105 |       "name": "Sales Assistant",
106 |       "model": "gpt-4",
107 |       "voice": "alloy",
108 |       "firstMessage": "Hello, I am calling from Example Company."
109 |     }
110 |   }'
111 | ```
112 | 
113 | ### Creating an Assistant
114 | 
115 | ```bash
116 | curl -X POST http://localhost:3000/api/assistants \
117 |   -H "Content-Type: application/json" \
118 |   -d '{
119 |     "name": "Sales Assistant",
120 |     "model": "gpt-4",
121 |     "voice": "alloy",
122 |     "firstMessage": "Hello, I am calling from Example Company.",
123 |     "instructions": "You are a friendly sales representative."
124 |   }'
125 | ```
126 | 
127 | ## Using with n8n
128 | 
129 | To use this API with n8n:
130 | 
131 | 1. Add an HTTP Request node
132 | 2. Set the method to the appropriate HTTP method (GET, POST, PUT, DELETE)
133 | 3. Set the URL to `http://your-server:3000/api/[endpoint]`
134 | 4. For POST/PUT requests, set the Body Content Type to JSON and provide the required parameters
135 | 5. Connect to subsequent nodes for processing the response
136 | 
137 | ## Project Structure
138 | 
139 | ```
140 | vapi-mcp-http-server/
141 | ├── dist/               # Compiled JavaScript output
142 | ├── src/                # TypeScript source files
143 | │   ├── index.ts        # Main server entry point
144 | │   └── routes/         # API route handlers
145 | │       ├── calls.ts    # Call-related endpoints
146 | │       ├── assistants.ts # Assistant-related endpoints
147 | │       └── conversations.ts # Conversation-related endpoints
148 | ├── .env                # Environment variables (create this file)
149 | ├── package.json        # Dependencies and scripts
150 | └── tsconfig.json       # TypeScript configuration
151 | ```
152 | 
153 | ## Development
154 | 
155 | ### Adding New Routes
156 | 
157 | To add a new API endpoint:
158 | 
159 | 1. Add a new route handler in the appropriate file in the `src/routes` directory
160 | 2. Update the `src/index.ts` file to include the new route if necessary
161 | 
162 | ## Troubleshooting
163 | 
164 | If you encounter issues:
165 | 
166 | 1. Check that your Vapi API credentials are correct in the `.env` file
167 | 2. Ensure the build completed successfully with `npm run build`
168 | 3. Check the server logs for error messages
169 | 4. Verify that your requests include all required parameters
170 | 
171 | ## License
172 | 
173 | MIT 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/tsconfig.json:
--------------------------------------------------------------------------------
```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "es2020",
 4 |     "module": "commonjs",
 5 |     "moduleResolution": "node",
 6 |     "esModuleInterop": true,
 7 |     "outDir": "./dist",
 8 |     "rootDir": "./src",
 9 |     "strict": true,
10 |     "declaration": true,
11 |     "skipLibCheck": true,
12 |     "forceConsistentCasingInFileNames": true
13 |   },
14 |   "include": [
15 |     "src/**/*"
16 |   ],
17 |   "exclude": [
18 |     "node_modules",
19 |     "dist"
20 |   ]
21 | } 
```
--------------------------------------------------------------------------------
/vapi-mcp-server/tsconfig.json:
--------------------------------------------------------------------------------
```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "es2020",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "outDir": "./dist",
 8 |     "rootDir": "./src",
 9 |     "strict": true,
10 |     "declaration": true,
11 |     "skipLibCheck": true,
12 |     "forceConsistentCasingInFileNames": true
13 |   },
14 |   "include": [
15 |     "src/**/*"
16 |   ],
17 |   "exclude": [
18 |     "node_modules",
19 |     "dist"
20 |   ]
21 | }
22 | 
```
--------------------------------------------------------------------------------
/vapi-mcp-server/package.json:
--------------------------------------------------------------------------------
```json
 1 | {
 2 |   "name": "vapi-mcp-server",
 3 |   "version": "1.0.0",
 4 |   "description": "MCP Server for Vapi API integration",
 5 |   "main": "dist/index.js",
 6 |   "type": "module",
 7 |   "scripts": {
 8 |     "build": "tsc",
 9 |     "start": "node dist/index.js",
10 |     "dev": "tsc -w & node --watch dist/index.js",
11 |     "test": "echo \"Error: no test specified\" && exit 1"
12 |   },
13 |   "keywords": [
14 |     "mcp",
15 |     "vapi",
16 |     "ai",
17 |     "voice",
18 |     "cursor"
19 |   ],
20 |   "author": "",
21 |   "license": "MIT",
22 |   "dependencies": {
23 |     "@modelcontextprotocol/sdk": "^0.4.0",
24 |     "@types/node": "^20.11.19",
25 |     "@vapi-ai/server-sdk": "^0.5.0",
26 |     "dotenv": "^16.4.7",
27 |     "typescript": "^5.3.3",
28 |     "zod": "^3.22.4"
29 |   }
30 | } 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/package.json:
--------------------------------------------------------------------------------
```json
 1 | {
 2 |   "name": "vapi-mcp-http-server",
 3 |   "version": "1.0.0",
 4 |   "description": "HTTP Server for Vapi API integration via MCP",
 5 |   "main": "dist/index.js",
 6 |   "scripts": {
 7 |     "build": "tsc",
 8 |     "start": "node dist/index.js",
 9 |     "dev": "tsc -w & node --watch dist/index.js",
10 |     "test": "echo \"Error: no test specified\" && exit 1"
11 |   },
12 |   "keywords": [
13 |     "vapi",
14 |     "api",
15 |     "mcp",
16 |     "http",
17 |     "server"
18 |   ],
19 |   "author": "",
20 |   "license": "MIT",
21 |   "dependencies": {
22 |     "@types/cors": "^2.8.17",
23 |     "@types/express": "^4.17.21",
24 |     "@types/node": "^20.11.13",
25 |     "@vapi-ai/server-sdk": "^0.5.0",
26 |     "cors": "^2.8.5",
27 |     "dotenv": "^16.4.7",
28 |     "express": "^4.18.2",
29 |     "node-fetch": "^2.6.9",
30 |     "typescript": "^5.3.3"
31 |   }
32 | }
33 | 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/routes/conversations.ts:
--------------------------------------------------------------------------------
```typescript
 1 | import express, { Request, Response, NextFunction } from 'express';
 2 | import { listConversations, getConversation } from '../controllers/conversations';
 3 | 
 4 | const router = express.Router();
 5 | 
 6 | // GET /api/conversations - List conversations
 7 | router.get('/', function(req: Request, res: Response, next: NextFunction) {
 8 |   listConversations(req.query)
 9 |     .then(result => {
10 |       res.json(result);
11 |     })
12 |     .catch(next);
13 | });
14 | 
15 | // GET /api/conversations/:callId - Get conversation by call ID
16 | router.get('/:callId', function(req: Request, res: Response, next: NextFunction) {
17 |   getConversation(req.params.callId)
18 |     .then(result => {
19 |       if (!result) {
20 |         return res.status(404).json({ error: 'Conversation not found' });
21 |       }
22 |       res.json(result);
23 |     })
24 |     .catch(next);
25 | });
26 | 
27 | export default router; 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/routes/calls.ts:
--------------------------------------------------------------------------------
```typescript
 1 | import express, { Request, Response, NextFunction } from 'express';
 2 | import { createCall, listCalls, getCall } from '../controllers/calls';
 3 | 
 4 | const router = express.Router();
 5 | 
 6 | // POST /api/calls - Create a new call
 7 | router.post('/', function(req: Request, res: Response, next: NextFunction) {
 8 |   createCall(req.body)
 9 |     .then(result => {
10 |       res.status(201).json(result);
11 |     })
12 |     .catch(next);
13 | });
14 | 
15 | // GET /api/calls - List calls
16 | router.get('/', function(req: Request, res: Response, next: NextFunction) {
17 |   listCalls(req.query)
18 |     .then(result => {
19 |       res.json(result);
20 |     })
21 |     .catch(next);
22 | });
23 | 
24 | // GET /api/calls/:id - Get a call by ID
25 | router.get('/:id', function(req: Request, res: Response, next: NextFunction) {
26 |   getCall(req.params.id)
27 |     .then(result => {
28 |       if (!result) {
29 |         return res.status(404).json({ error: 'Call not found' });
30 |       }
31 |       res.json(result);
32 |     })
33 |     .catch(next);
34 | });
35 | 
36 | export default router; 
```
--------------------------------------------------------------------------------
/examples/cursor-configuration.md:
--------------------------------------------------------------------------------
```markdown
 1 | # Cursor Configuration for Vapi MCP
 2 | 
 3 | This guide shows how to configure Cursor to use the Vapi MCP server.
 4 | 
 5 | ## Add the Server to Cursor Settings
 6 | 
 7 | 1. Open Cursor
 8 | 2. Go to Settings (⌘+, on Mac or Ctrl+, on Windows)
 9 | 3. Click on "Edit Settings (JSON)"
10 | 4. Add the following configuration:
11 | 
12 | ```json
13 | {
14 |   "mcp.config.servers": [
15 |     {
16 |       "name": "Vapi MCP Server",
17 |       "command": "node /Users/yourusername/path/to/vapi-mcp-server/dist/index.js"
18 |     }
19 |   ]
20 | }
21 | ```
22 | 
23 | Replace `/Users/yourusername/path/to/` with the absolute path to your vapi-mcp-server directory.
24 | 
25 | ## Example Complete Path
26 | 
27 | On macOS, your configuration might look like:
28 | 
29 | ```json
30 | {
31 |   "mcp.config.servers": [
32 |     {
33 |       "name": "Vapi MCP Server",
34 |       "command": "node /Users/matthewcage/Documents/AA-GitHub/MCP/vapi-mcp/vapi-mcp-server/dist/index.js"
35 |     }
36 |   ]
37 | }
38 | ```
39 | 
40 | ## Testing the Configuration
41 | 
42 | 1. Save the settings
43 | 2. Restart Cursor
44 | 3. Open a new file
45 | 4. Try using one of the Vapi tools:
46 | 
47 | ```
48 | You can now make calls using the Vapi voice AI platform by using the vapi_call tool.
49 | Try listing available assistants with the vapi_assistant tool, action "list".
50 | ```
51 | 
52 | ## Troubleshooting
53 | 
54 | If the tools don't appear:
55 | 
56 | 1. Make sure the server is running (start it with `npm start` in the vapi-mcp-server directory)
57 | 2. Check that the path to the server is correct in your Cursor settings
58 | 3. Restart Cursor after making changes
59 | 4. Check the Cursor console for any errors (Help > Toggle Developer Tools) 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/controllers/conversations.ts:
--------------------------------------------------------------------------------
```typescript
 1 | import { vapiClient } from '../index';
 2 | 
 3 | // List conversations (via calls)
 4 | export const listConversations = async (query: any) => {
 5 |   const { limit = 10, offset = 0 } = query;
 6 |   
 7 |   const filters = {
 8 |     limit: Number(limit),
 9 |     offset: Number(offset)
10 |   };
11 |   
12 |   const calls = await vapiClient.calls.list(filters);
13 |   const callsArray = Array.isArray(calls) ? calls : [];
14 |   
15 |   // Map calls to conversation summaries
16 |   const conversations = callsArray.map(call => ({
17 |     callId: call.id,
18 |     assistantId: call.assistantId,
19 |     phoneNumber: "unknown", // We don't have direct access to the phone number
20 |     startTime: call.createdAt,
21 |     endTime: call.updatedAt,
22 |     status: call.status,
23 |     messageCount: 0 // We don't have direct access to message count
24 |   }));
25 |   
26 |   return {
27 |     success: true,
28 |     conversations,
29 |     pagination: {
30 |       total: conversations.length,
31 |       limit: filters.limit,
32 |       offset: filters.offset
33 |     }
34 |   };
35 | };
36 | 
37 | // Get conversation details for a call
38 | export const getConversation = async (callId: string) => {
39 |   // Get call details
40 |   const call = await vapiClient.calls.get(callId);
41 |   
42 |   // Create a conversation object
43 |   const conversation = {
44 |     callId: call.id,
45 |     assistantId: call.assistantId,
46 |     phoneNumber: "unknown", // We don't have direct access to the phone number
47 |     startTime: call.createdAt,
48 |     endTime: call.updatedAt,
49 |     status: call.status,
50 |     messages: [] // SDK doesn't provide direct access to messages
51 |   };
52 |   
53 |   return {
54 |     success: true,
55 |     conversation
56 |   };
57 | }; 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/routes/assistants.ts:
--------------------------------------------------------------------------------
```typescript
 1 | import express, { Request, Response, NextFunction } from 'express';
 2 | import { createAssistant, listAssistants, getAssistant, updateAssistant, deleteAssistant } from '../controllers/assistants';
 3 | 
 4 | const router = express.Router();
 5 | 
 6 | // POST /api/assistants - Create a new assistant
 7 | router.post('/', function(req: Request, res: Response, next: NextFunction) {
 8 |   createAssistant(req.body)
 9 |     .then(result => {
10 |       res.status(201).json(result);
11 |     })
12 |     .catch(next);
13 | });
14 | 
15 | // GET /api/assistants - List assistants
16 | router.get('/', function(req: Request, res: Response, next: NextFunction) {
17 |   listAssistants(req.query)
18 |     .then(result => {
19 |       res.json(result);
20 |     })
21 |     .catch(next);
22 | });
23 | 
24 | // GET /api/assistants/:id - Get an assistant by ID
25 | router.get('/:id', function(req: Request, res: Response, next: NextFunction) {
26 |   getAssistant(req.params.id)
27 |     .then(result => {
28 |       if (!result) {
29 |         return res.status(404).json({ error: 'Assistant not found' });
30 |       }
31 |       res.json(result);
32 |     })
33 |     .catch(next);
34 | });
35 | 
36 | // PUT /api/assistants/:id - Update an assistant
37 | router.put('/:id', function(req: Request, res: Response, next: NextFunction) {
38 |   updateAssistant(req.params.id, req.body)
39 |     .then(result => {
40 |       res.json(result);
41 |     })
42 |     .catch(next);
43 | });
44 | 
45 | // DELETE /api/assistants/:id - Delete an assistant
46 | router.delete('/:id', function(req: Request, res: Response, next: NextFunction) {
47 |   deleteAssistant(req.params.id)
48 |     .then(result => {
49 |       res.json(result);
50 |     })
51 |     .catch(next);
52 | });
53 | 
54 | export default router; 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/controllers/calls.ts:
--------------------------------------------------------------------------------
```typescript
 1 | import { vapiClient } from '../index';
 2 | 
 3 | // Create a new call
 4 | export const createCall = async (data: any) => {
 5 |   const { phoneNumber, assistantId, assistantConfig, metadata } = data;
 6 |   
 7 |   // Validate required fields
 8 |   if (!phoneNumber) {
 9 |     throw new Error('Phone number is required');
10 |   }
11 |   
12 |   // Initialize call parameters
13 |   const callParams: any = {
14 |     phoneNumber: phoneNumber
15 |   };
16 |   
17 |   // Set assistant details
18 |   if (assistantId) {
19 |     callParams.assistantId = assistantId;
20 |   } else if (assistantConfig) {
21 |     callParams.assistant = {
22 |       name: assistantConfig.name || "Default Assistant",
23 |       model: assistantConfig.model === "gpt-4" ? "gpt_4" : "gpt_3_5_turbo",
24 |       voice: {
25 |         provider: "eleven_labs",
26 |         voiceId: assistantConfig.voice || "alloy"
27 |       },
28 |       firstMessage: assistantConfig.firstMessage,
29 |       maxDuration: assistantConfig.maxDuration
30 |     };
31 |   } else {
32 |     throw new Error('Either assistantId or assistantConfig is required');
33 |   }
34 |   
35 |   // Add metadata if provided
36 |   if (metadata) {
37 |     callParams.metadata = metadata;
38 |   }
39 |   
40 |   // Make the call
41 |   const result = await vapiClient.calls.create(callParams);
42 |   
43 |   return {
44 |     success: true,
45 |     callId: result.id,
46 |     status: result.status
47 |   };
48 | };
49 | 
50 | // List all calls
51 | export const listCalls = async (query: any) => {
52 |   const { limit = 10, offset = 0 } = query;
53 |   
54 |   const filters = {
55 |     limit: Number(limit),
56 |     offset: Number(offset)
57 |   };
58 |   
59 |   const calls = await vapiClient.calls.list(filters);
60 |   const callsArray = Array.isArray(calls) ? calls : [];
61 |   
62 |   return {
63 |     success: true,
64 |     calls: callsArray,
65 |     pagination: {
66 |       total: callsArray.length,
67 |       limit: filters.limit,
68 |       offset: filters.offset
69 |     }
70 |   };
71 | };
72 | 
73 | // Get a call by ID
74 | export const getCall = async (id: string) => {
75 |   const call = await vapiClient.calls.get(id);
76 |   
77 |   return {
78 |     success: true,
79 |     call
80 |   };
81 | }; 
```
--------------------------------------------------------------------------------
/vapi-mcp-http-server/src/controllers/assistants.ts:
--------------------------------------------------------------------------------
```typescript
  1 | import { vapiClient } from '../index';
  2 | 
  3 | // Create a new assistant
  4 | export const createAssistant = async (data: any) => {
  5 |   const {
  6 |     name,
  7 |     model,
  8 |     voice,
  9 |     firstMessage,
 10 |     instructions,
 11 |     maxDuration
 12 |   } = data;
 13 |   
 14 |   // Validate required fields
 15 |   if (!name) {
 16 |     throw new Error('Name is required');
 17 |   }
 18 |   
 19 |   // Set up assistant parameters
 20 |   const createParams: any = {
 21 |     name: name,
 22 |     model: model === "gpt-4" ? "gpt_4" : "gpt_3_5_turbo",
 23 |     voice: {
 24 |       provider: "eleven_labs",
 25 |       voiceId: voice || "alloy"
 26 |     }
 27 |   };
 28 |   
 29 |   // Add optional parameters
 30 |   if (firstMessage) createParams.firstMessage = firstMessage;
 31 |   if (instructions) createParams.instructions = instructions;
 32 |   if (maxDuration) createParams.maxDuration = maxDuration;
 33 |   
 34 |   // Create the assistant
 35 |   const assistant = await vapiClient.assistants.create(createParams);
 36 |   
 37 |   return {
 38 |     success: true,
 39 |     assistant
 40 |   };
 41 | };
 42 | 
 43 | // List all assistants
 44 | export const listAssistants = async (query: any) => {
 45 |   const assistants = await vapiClient.assistants.list();
 46 |   
 47 |   return {
 48 |     success: true,
 49 |     assistants: Array.isArray(assistants) ? assistants : []
 50 |   };
 51 | };
 52 | 
 53 | // Get an assistant by ID
 54 | export const getAssistant = async (id: string) => {
 55 |   const assistant = await vapiClient.assistants.get(id);
 56 |   
 57 |   return {
 58 |     success: true,
 59 |     assistant
 60 |   };
 61 | };
 62 | 
 63 | // Update an assistant
 64 | export const updateAssistant = async (id: string, data: any) => {
 65 |   const {
 66 |     name,
 67 |     voice,
 68 |     firstMessage,
 69 |     instructions,
 70 |     maxDuration
 71 |   } = data;
 72 |   
 73 |   // Set up update parameters
 74 |   const updateParams: any = {};
 75 |   
 76 |   // Add parameters only if they're provided
 77 |   if (name) updateParams.name = name;
 78 |   if (voice) {
 79 |     updateParams.voice = {
 80 |       provider: "eleven_labs",
 81 |       voiceId: voice
 82 |     };
 83 |   }
 84 |   if (firstMessage) updateParams.firstMessage = firstMessage;
 85 |   if (instructions) updateParams.instructions = instructions;
 86 |   if (maxDuration) updateParams.maxDuration = maxDuration;
 87 |   
 88 |   // Update the assistant
 89 |   const assistant = await vapiClient.assistants.update(id, updateParams);
 90 |   
 91 |   return {
 92 |     success: true,
 93 |     assistant
 94 |   };
 95 | };
 96 | 
 97 | // Delete an assistant
 98 | export const deleteAssistant = async (id: string) => {
 99 |   await vapiClient.assistants.delete(id);
100 |   
101 |   return {
102 |     success: true,
103 |     message: `Assistant ${id} deleted successfully`
104 |   };
105 | }; 
```
--------------------------------------------------------------------------------
/vapi-mcp-server/src/types.ts:
--------------------------------------------------------------------------------
```typescript
  1 | // Vapi API Interfaces
  2 | 
  3 | export interface VapiVoice {
  4 |   provider: string;
  5 |   voiceId: string;
  6 | }
  7 | 
  8 | export interface VapiAssistant {
  9 |   id: string;
 10 |   name: string;
 11 |   model: string;
 12 |   voice: VapiVoice;
 13 |   firstMessage?: string;
 14 |   instructions?: string;
 15 |   maxDuration?: number;
 16 |   createdAt: string;
 17 |   updatedAt: string;
 18 | }
 19 | 
 20 | export interface VapiMessage {
 21 |   id: string;
 22 |   role: "assistant" | "user";
 23 |   content: string;
 24 |   timestamp: string;
 25 | }
 26 | 
 27 | export interface VapiCall {
 28 |   id: string;
 29 |   assistantId: string;
 30 |   to: string;
 31 |   from: string;
 32 |   status: string;
 33 |   startTime?: string;
 34 |   endTime?: string;
 35 |   duration?: number;
 36 |   messageCount?: number;
 37 |   metadata?: Record<string, string>;
 38 | }
 39 | 
 40 | export interface VapiListResponse<T> {
 41 |   data: T[];
 42 |   total: number;
 43 |   limit: number;
 44 |   offset: number;
 45 | }
 46 | 
 47 | // Request Types
 48 | 
 49 | export interface CreateCallRequest {
 50 |   phoneNumber: string;
 51 |   assistantId?: string;
 52 |   assistant?: {
 53 |     name?: string;
 54 |     model?: string;
 55 |     voice?: VapiVoice;
 56 |     firstMessage?: string;
 57 |     maxDuration?: number;
 58 |   };
 59 |   metadata?: Record<string, string>;
 60 | }
 61 | 
 62 | export interface CreateAssistantRequest {
 63 |   name: string;
 64 |   model: string;
 65 |   voice: VapiVoice;
 66 |   firstMessage?: string;
 67 |   instructions?: string;
 68 |   maxDuration?: number;
 69 | }
 70 | 
 71 | export interface UpdateAssistantRequest {
 72 |   name?: string;
 73 |   voice?: VapiVoice;
 74 |   firstMessage?: string;
 75 |   instructions?: string;
 76 |   maxDuration?: number;
 77 | }
 78 | 
 79 | export interface ListCallsParams {
 80 |   limit?: number;
 81 |   offset?: number;
 82 |   startDate?: string;
 83 |   endDate?: string;
 84 | }
 85 | 
 86 | // Response Types for MCP
 87 | 
 88 | export interface CallToolResponse {
 89 |   callId: string;
 90 |   status: string;
 91 | }
 92 | 
 93 | export interface AssistantToolResponse {
 94 |   success: boolean;
 95 |   assistant?: any;
 96 |   assistants?: any[];
 97 |   message?: string;
 98 |   error?: string;
 99 | }
100 | 
101 | export interface ConversationGetResponse {
102 |   success: boolean;
103 |   conversation?: {
104 |     callId: string;
105 |     assistantId: string;
106 |     phoneNumber: string;
107 |     startTime?: string;
108 |     endTime?: string;
109 |     duration?: number;
110 |     status: string;
111 |     messages: any[];
112 |   };
113 |   error?: string;
114 | }
115 | 
116 | export interface ConversationListResponse {
117 |   success: boolean;
118 |   conversations?: Array<{
119 |     callId: string;
120 |     assistantId: string;
121 |     phoneNumber: string;
122 |     startTime?: string;
123 |     endTime?: string;
124 |     duration?: number;
125 |     status: string;
126 |     messageCount: number;
127 |   }>;
128 |   pagination?: {
129 |     total: number;
130 |     limit: number;
131 |     offset: number;
132 |   };
133 |   error?: string;
134 | } 
```
--------------------------------------------------------------------------------
/docs/deployment-guide.md:
--------------------------------------------------------------------------------
```markdown
  1 | # Vapi MCP Integration
  2 | 
  3 | This document explains how the Vapi MCP (Model Context Protocol) service is integrated with the n8n platform.
  4 | 
  5 | ## Overview
  6 | 
  7 | The Vapi MCP service provides Voice AI capabilities for the n8n platform. It enables:
  8 | - Making outbound voice calls with AI assistants
  9 | - Creating and managing voice assistants with different voices
 10 | - Retrieving and analyzing conversation transcripts
 11 | 
 12 | ## Architecture
 13 | 
 14 | The Vapi MCP integration consists of:
 15 | 
 16 | 1. **Docker Container**: A containerized version of the Vapi MCP HTTP server
 17 | 2. **Caddy Routing**: Reverse proxy setup to route `/vapi/*` URLs to the Vapi service
 18 | 3. **Environment Variables**: Configuration for connecting to the Vapi API
 19 | 4. **n8n Custom Node**: A custom node in n8n that connects to the Vapi service
 20 | 
 21 | ## Access
 22 | 
 23 | The service is accessible via:
 24 | 
 25 | - **External URL**: https://staging-n8n.ai-advantage.au/vapi/
 26 | - **Internal URL (from other containers)**: http://vapi-mcp:3000
 27 | - **Local URL (from n8n container)**: http://vapi-mcp:3000
 28 | 
 29 | ## API Endpoints
 30 | 
 31 | The service exposes the following API endpoints:
 32 | 
 33 | - `GET /api/assistants` - List voice assistants
 34 | - `POST /api/assistants` - Create a new voice assistant
 35 | - `GET /api/assistants/:id` - Get a specific voice assistant
 36 | - `POST /api/calls` - Make an outbound call
 37 | - `GET /api/calls` - List calls
 38 | - `GET /api/calls/:id` - Get details about a specific call
 39 | - `GET /api/conversations` - List conversations
 40 | - `GET /api/conversations/:id` - Get a specific conversation transcript
 41 | 
 42 | ## Configuration
 43 | 
 44 | The service uses the following environment variables:
 45 | 
 46 | - `VAPI_ORG_ID` - Vapi organization ID
 47 | - `VAPI_PRIVATE_KEY` - Vapi private API key
 48 | - `VAPI_KNOWLEDGE_ID` - Vapi knowledge base ID
 49 | - `VAPI_JWT_PRIVATE` - JWT private key for Vapi authentication
 50 | 
 51 | ## Usage in n8n
 52 | 
 53 | To use the Vapi service in n8n:
 54 | 
 55 | 1. Access via the custom n8n node
 56 | 2. API requests can be made to `{{$env.VAPI_API_URL}}/api/...` using the HTTP Request node
 57 | 
 58 | ## Maintenance
 59 | 
 60 | ### Updating the Service
 61 | 
 62 | 1. Pull the latest code:
 63 |    ```bash
 64 |    cd /home/mcage/vapi-mcp
 65 |    git pull
 66 |    ```
 67 | 
 68 | 2. Rebuild and restart the container:
 69 |    ```bash
 70 |    cd /home/mcage/.n8n-setup
 71 |    sudo docker compose up -d --build vapi-mcp
 72 |    ```
 73 | 
 74 | ### Monitoring
 75 | 
 76 | Check the service status:
 77 | ```bash
 78 | sudo docker logs vapi-mcp
 79 | ```
 80 | 
 81 | Check the health endpoint:
 82 | ```bash
 83 | curl http://localhost:3000/health
 84 | ```
 85 | 
 86 | ## Troubleshooting
 87 | 
 88 | If the service is not responding:
 89 | 
 90 | 1. Check container status:
 91 |    ```bash
 92 |    sudo docker ps | grep vapi-mcp
 93 |    ```
 94 | 
 95 | 2. Check logs:
 96 |    ```bash
 97 |    sudo docker logs vapi-mcp
 98 |    ```
 99 | 
100 | 3. Verify Caddy configuration:
101 |    ```bash
102 |    sudo docker logs n8n-setup-caddy-1
103 |    ```
104 | 
105 | 4. Restart the service:
106 |    ```bash
107 |    sudo docker restart vapi-mcp
108 |    ``` 
```
--------------------------------------------------------------------------------
/vapi-mcp-integration.md:
--------------------------------------------------------------------------------
```markdown
  1 | # Vapi MCP Integration
  2 | 
  3 | This document explains how the Vapi MCP (Model Context Protocol) service is integrated with the n8n platform.
  4 | 
  5 | ## Overview
  6 | 
  7 | The Vapi MCP service provides Voice AI capabilities for the n8n platform. It enables:
  8 | - Making outbound voice calls with AI assistants
  9 | - Creating and managing voice assistants with different voices
 10 | - Retrieving and analyzing conversation transcripts
 11 | 
 12 | ## Architecture
 13 | 
 14 | The Vapi MCP integration consists of:
 15 | 
 16 | 1. **Docker Container**: A containerized version of the Vapi MCP HTTP server
 17 | 2. **Caddy Routing**: Reverse proxy setup to route `/vapi/*` URLs to the Vapi service
 18 | 3. **Environment Variables**: Configuration for connecting to the Vapi API
 19 | 4. **n8n Custom Node**: A custom node in n8n that connects to the Vapi service
 20 | 
 21 | ## Access
 22 | 
 23 | The service is accessible via:
 24 | 
 25 | - **External URL**: https://staging-n8n.ai-advantage.au/vapi/
 26 | - **Internal URL (from other containers)**: http://vapi-mcp:3000
 27 | - **Local URL (from n8n container)**: http://vapi-mcp:3000
 28 | 
 29 | ## API Endpoints
 30 | 
 31 | The service exposes the following API endpoints:
 32 | 
 33 | - `GET /api/assistants` - List voice assistants
 34 | - `POST /api/assistants` - Create a new voice assistant
 35 | - `GET /api/assistants/:id` - Get a specific voice assistant
 36 | - `POST /api/calls` - Make an outbound call
 37 | - `GET /api/calls` - List calls
 38 | - `GET /api/calls/:id` - Get details about a specific call
 39 | - `GET /api/conversations` - List conversations
 40 | - `GET /api/conversations/:id` - Get a specific conversation transcript
 41 | 
 42 | ## Configuration
 43 | 
 44 | The service uses the following environment variables:
 45 | 
 46 | - `VAPI_ORG_ID` - Vapi organization ID
 47 | - `VAPI_PRIVATE_KEY` - Vapi private API key
 48 | - `VAPI_KNOWLEDGE_ID` - Vapi knowledge base ID
 49 | - `VAPI_JWT_PRIVATE` - JWT private key for Vapi authentication
 50 | 
 51 | ## Usage in n8n
 52 | 
 53 | To use the Vapi service in n8n:
 54 | 
 55 | 1. Access via the custom n8n node
 56 | 2. API requests can be made to `{{$env.VAPI_API_URL}}/api/...` using the HTTP Request node
 57 | 
 58 | ## Maintenance
 59 | 
 60 | ### Updating the Service
 61 | 
 62 | 1. Pull the latest code:
 63 |    ```bash
 64 |    cd /home/mcage/vapi-mcp
 65 |    git pull
 66 |    ```
 67 | 
 68 | 2. Rebuild and restart the container:
 69 |    ```bash
 70 |    cd /home/mcage/.n8n-setup
 71 |    sudo docker compose up -d --build vapi-mcp
 72 |    ```
 73 | 
 74 | ### Monitoring
 75 | 
 76 | Check the service status:
 77 | ```bash
 78 | sudo docker logs vapi-mcp
 79 | ```
 80 | 
 81 | Check the health endpoint:
 82 | ```bash
 83 | curl http://localhost:3000/health
 84 | ```
 85 | 
 86 | ## Troubleshooting
 87 | 
 88 | If the service is not responding:
 89 | 
 90 | 1. Check container status:
 91 |    ```bash
 92 |    sudo docker ps | grep vapi-mcp
 93 |    ```
 94 | 
 95 | 2. Check logs:
 96 |    ```bash
 97 |    sudo docker logs vapi-mcp
 98 |    ```
 99 | 
100 | 3. Verify Caddy configuration:
101 |    ```bash
102 |    sudo docker logs n8n-setup-caddy-1
103 |    ```
104 | 
105 | 4. Restart the service:
106 |    ```bash
107 |    sudo docker restart vapi-mcp
108 |    ``` 
```
--------------------------------------------------------------------------------
/examples/usage-examples.md:
--------------------------------------------------------------------------------
```markdown
  1 | # Vapi MCP Usage Examples
  2 | 
  3 | This document provides examples of how to use the Vapi MCP tools from Cursor.
  4 | 
  5 | ## Outbound Calls
  6 | 
  7 | ### Basic Call
  8 | 
  9 | ```
 10 | Use the Vapi Voice AI platform to call my colleague at +1234567890.
 11 | ```
 12 | 
 13 | Cursor will use the `vapi_call` tool with:
 14 | 
 15 | ```json
 16 | {
 17 |   "phoneNumber": "+1234567890"
 18 | }
 19 | ```
 20 | 
 21 | ### Call with Existing Assistant
 22 | 
 23 | ```
 24 | Call +1234567890 using the Sales Assistant with ID assistant_abc123.
 25 | ```
 26 | 
 27 | Cursor will use the `vapi_call` tool with:
 28 | 
 29 | ```json
 30 | {
 31 |   "phoneNumber": "+1234567890",
 32 |   "assistantId": "assistant_abc123"
 33 | }
 34 | ```
 35 | 
 36 | ### Call with Custom Assistant Configuration
 37 | 
 38 | ```
 39 | Call +1234567890 with a new assistant using the Alloy voice that introduces itself as a support agent.
 40 | ```
 41 | 
 42 | Cursor will use the `vapi_call` tool with:
 43 | 
 44 | ```json
 45 | {
 46 |   "phoneNumber": "+1234567890",
 47 |   "assistantConfig": {
 48 |     "voice": "alloy",
 49 |     "firstMessage": "Hello, this is the support team calling. How may I help you today?"
 50 |   }
 51 | }
 52 | ```
 53 | 
 54 | ## Assistant Management
 55 | 
 56 | ### Creating an Assistant
 57 | 
 58 | ```
 59 | Create a new voice assistant for sales calls with the Nova voice that introduces itself as Sarah from the sales team.
 60 | ```
 61 | 
 62 | Cursor will use the `vapi_assistant` tool with:
 63 | 
 64 | ```json
 65 | {
 66 |   "action": "create",
 67 |   "params": {
 68 |     "name": "Sales Assistant",
 69 |     "voice": "nova",
 70 |     "firstMessage": "Hello, this is Sarah from the sales team. Do you have a moment to talk about our new offerings?",
 71 |     "model": "gpt-4"
 72 |   }
 73 | }
 74 | ```
 75 | 
 76 | ### Listing All Assistants
 77 | 
 78 | ```
 79 | Show me a list of all my voice assistants.
 80 | ```
 81 | 
 82 | Cursor will use the `vapi_assistant` tool with:
 83 | 
 84 | ```json
 85 | {
 86 |   "action": "list"
 87 | }
 88 | ```
 89 | 
 90 | ### Getting Assistant Details
 91 | 
 92 | ```
 93 | Show me the details of assistant_abc123.
 94 | ```
 95 | 
 96 | Cursor will use the `vapi_assistant` tool with:
 97 | 
 98 | ```json
 99 | {
100 |   "action": "get",
101 |   "assistantId": "assistant_abc123"
102 | }
103 | ```
104 | 
105 | ### Updating an Assistant
106 | 
107 | ```
108 | Update the Sales Assistant with ID assistant_abc123 to use the Echo voice instead.
109 | ```
110 | 
111 | Cursor will use the `vapi_assistant` tool with:
112 | 
113 | ```json
114 | {
115 |   "action": "update",
116 |   "assistantId": "assistant_abc123",
117 |   "params": {
118 |     "voice": "echo"
119 |   }
120 | }
121 | ```
122 | 
123 | ### Deleting an Assistant
124 | 
125 | ```
126 | Delete the assistant with ID assistant_abc123.
127 | ```
128 | 
129 | Cursor will use the `vapi_assistant` tool with:
130 | 
131 | ```json
132 | {
133 |   "action": "delete",
134 |   "assistantId": "assistant_abc123"
135 | }
136 | ```
137 | 
138 | ## Conversation Management
139 | 
140 | ### Retrieving a Conversation
141 | 
142 | ```
143 | Show me the transcript of call call_xyz789.
144 | ```
145 | 
146 | Cursor will use the `vapi_conversation` tool with:
147 | 
148 | ```json
149 | {
150 |   "action": "get",
151 |   "callId": "call_xyz789"
152 | }
153 | ```
154 | 
155 | ### Listing Recent Conversations
156 | 
157 | ```
158 | Show me a list of my 5 most recent calls.
159 | ```
160 | 
161 | Cursor will use the `vapi_conversation` tool with:
162 | 
163 | ```json
164 | {
165 |   "action": "list",
166 |   "filters": {
167 |     "limit": 5
168 |   }
169 | }
170 | ```
171 | 
172 | ### Filtering Conversations by Date
173 | 
174 | ```
175 | Show me calls made between January 1st and January 15th, 2023.
176 | ```
177 | 
178 | Cursor will use the `vapi_conversation` tool with:
179 | 
180 | ```json
181 | {
182 |   "action": "list",
183 |   "filters": {
184 |     "startDate": "2023-01-01T00:00:00Z",
185 |     "endDate": "2023-01-15T23:59:59Z"
186 |   }
187 | }
188 | ```
189 | 
190 | ## Common Workflows
191 | 
192 | ### Complete Sales Workflow
193 | 
194 | ```
195 | 1. Create a new assistant for product demos with the Alloy voice
196 | 2. Use this assistant to call +1234567890
197 | 3. After the call finishes, retrieve the conversation transcript
198 | ```
199 | 
200 | This will involve three sequential tool calls:
201 | 
202 | 1. `vapi_assistant` with action "create"
203 | 2. `vapi_call` with the newly created assistant ID
204 | 3. `vapi_conversation` with action "get" and the call ID returned from step 2
205 | 
206 | ### Customer Support Workflow
207 | 
208 | ```
209 | 1. List my existing assistants
210 | 2. Use the Support Assistant to call customer +1234567890
211 | 3. Retrieve the conversation transcript
212 | ```
213 | 
214 | This will involve:
215 | 
216 | 1. `vapi_assistant` with action "list"
217 | 2. `vapi_call` with the Support Assistant ID
218 | 3. `vapi_conversation` with action "get" and the resulting call ID 
```
--------------------------------------------------------------------------------
/docs/integration-guide.md:
--------------------------------------------------------------------------------
```markdown
  1 | # Vapi MCP Integration - Service Connection Guide
  2 | 
  3 | This guide explains how other services in the docker-compose stack can connect to the Vapi MCP service.
  4 | 
  5 | ## Connection Methods
  6 | 
  7 | The Vapi MCP service is available through the following endpoints:
  8 | 
  9 | 1. **HTTP API**: `http://vapi-mcp:3000`
 10 | 2. **External URL**: `https://staging-n8n.ai-advantage.au/vapi/`
 11 | 
 12 | ## N8N Integration
 13 | 
 14 | ### Using HTTP Request Node
 15 | 
 16 | 1. Create a new HTTP Request node in your n8n workflow
 17 | 2. Use the environment variable for dynamic configuration:
 18 |    ```
 19 |    {{$env.VAPI_API_URL}}/api/calls
 20 |    ```
 21 | 3. Example configuration:
 22 |    - **Method**: POST
 23 |    - **URL**: `{{$env.VAPI_API_URL}}/api/calls`
 24 |    - **Headers**: `Content-Type: application/json`
 25 |    - **Body**:
 26 |      ```json
 27 |      {
 28 |        "phoneNumber": "+61412345678",
 29 |        "assistantId": "your-assistant-id",
 30 |        "metadata": {
 31 |          "campaign": "welcome_call"
 32 |        }
 33 |      }
 34 |      ```
 35 | 
 36 | ### Available Endpoints
 37 | 
 38 | - **Assistants**: `{{$env.VAPI_API_URL}}/api/assistants`
 39 | - **Calls**: `{{$env.VAPI_API_URL}}/api/calls`
 40 | - **Conversations**: `{{$env.VAPI_API_URL}}/api/conversations`
 41 | 
 42 | ## Other Docker Services
 43 | 
 44 | Other containers can reach the Vapi MCP service using the service name:
 45 | 
 46 | ```
 47 | http://vapi-mcp:3000
 48 | ```
 49 | 
 50 | ## Connection Examples
 51 | 
 52 | ### Python Example
 53 | 
 54 | ```python
 55 | import requests
 56 | 
 57 | # Base URL for internal docker network
 58 | base_url = "http://vapi-mcp:3000"
 59 | 
 60 | # Create a new assistant
 61 | response = requests.post(
 62 |     f"{base_url}/api/assistants",
 63 |     json={
 64 |         "name": "Customer Support",
 65 |         "model": "gpt-4",
 66 |         "voice": "alloy",
 67 |         "firstMessage": "Hello, this is customer support. How can I help you today?"
 68 |     }
 69 | )
 70 | 
 71 | print(response.json())
 72 | ```
 73 | 
 74 | ### JavaScript Example
 75 | 
 76 | ```javascript
 77 | const axios = require('axios');
 78 | 
 79 | // Base URL for internal docker network
 80 | const baseUrl = 'http://vapi-mcp:3000';
 81 | 
 82 | // Make a call
 83 | async function makeCall() {
 84 |   try {
 85 |     const response = await axios.post(`${baseUrl}/api/calls`, {
 86 |       phoneNumber: '+61412345678',
 87 |       assistantId: 'assistant_abc123',
 88 |       metadata: {
 89 |         campaign: 'support_followup'
 90 |       }
 91 |     });
 92 |     
 93 |     console.log('Call initiated:', response.data);
 94 |   } catch (error) {
 95 |     console.error('Error making call:', error.response?.data || error.message);
 96 |   }
 97 | }
 98 | 
 99 | makeCall();
100 | ```
101 | 
102 | ## Health Check
103 | 
104 | All services can verify the Vapi MCP service health using:
105 | 
106 | ```
107 | http://vapi-mcp:3000/health
108 | ```
109 | 
110 | Expected response:
111 | ```json
112 | {"status":"ok"}
113 | ```
114 | 
115 | ## Environment Variables
116 | 
117 | The following environment variables are available for service configuration:
118 | 
119 | | Variable | Purpose | Where Set |
120 | |----------|---------|-----------|
121 | | `VAPI_API_URL` | Base URL for Vapi API | In n8n container as `http://vapi-mcp:3000` |
122 | | `VAPI_ORG_ID` | Vapi organization identifier | `.env` file |
123 | | `VAPI_PRIVATE_KEY` | Authentication key | `.env` file |
124 | | `VAPI_KNOWLEDGE_ID` | Knowledge base identifier | `.env` file |
125 | | `VAPI_JWT_PRIVATE` | JWT authentication key | `.env` file |
126 | 
127 | ## Troubleshooting
128 | 
129 | If services cannot connect to Vapi MCP:
130 | 
131 | 1. Verify the container is running:
132 |    ```bash
133 |    sudo docker ps | grep vapi-mcp
134 |    ```
135 | 
136 | 2. Check the service is healthy:
137 |    ```bash
138 |    sudo docker exec -it vapi-mcp wget -q -O - http://localhost:3000/health
139 |    ```
140 | 
141 | 3. Verify network connectivity (from another container):
142 |    ```bash
143 |    sudo docker exec -it n8n wget -q -O - http://vapi-mcp:3000/health
144 |    ```
145 | 
146 | 4. Review logs for errors:
147 |    ```bash
148 |    sudo docker logs vapi-mcp
149 |    ```
150 | 
151 | ## Notes for N8N Custom Nodes
152 | 
153 | When creating custom n8n nodes that connect to Vapi MCP:
154 | 
155 | 1. Use the environment variable for flexibility:
156 |    ```javascript
157 |    const baseUrl = process.env.VAPI_API_URL || 'http://vapi-mcp:3000';
158 |    ```
159 | 
160 | 2. Include proper error handling and timeouts
161 | 3. Consider implementing retry logic for critical calls
162 | 
163 | ## Additional Resources
164 | 
165 | 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
  1 | # Vapi MCP Integration - Service Connection Guide
  2 | 
  3 | This guide explains how other services in the docker-compose stack can connect to the Vapi MCP service.
  4 | 
  5 | ## Connection Methods
  6 | 
  7 | The Vapi MCP service is available through the following endpoints:
  8 | 
  9 | 1. **HTTP API**: `http://vapi-mcp:3000`
 10 | 2. **External URL**: `https://staging-n8n.ai-advantage.au/vapi/`
 11 | 
 12 | ## N8N Integration
 13 | 
 14 | ### Using HTTP Request Node
 15 | 
 16 | 1. Create a new HTTP Request node in your n8n workflow
 17 | 2. Use the environment variable for dynamic configuration:
 18 |    ```
 19 |    {{$env.VAPI_API_URL}}/api/calls
 20 |    ```
 21 | 3. Example configuration:
 22 |    - **Method**: POST
 23 |    - **URL**: `{{$env.VAPI_API_URL}}/api/calls`
 24 |    - **Headers**: `Content-Type: application/json`
 25 |    - **Body**:
 26 |      ```json
 27 |      {
 28 |        "phoneNumber": "+61412345678",
 29 |        "assistantId": "your-assistant-id",
 30 |        "metadata": {
 31 |          "campaign": "welcome_call"
 32 |        }
 33 |      }
 34 |      ```
 35 | 
 36 | ### Available Endpoints
 37 | 
 38 | - **Assistants**: `{{$env.VAPI_API_URL}}/api/assistants`
 39 | - **Calls**: `{{$env.VAPI_API_URL}}/api/calls`
 40 | - **Conversations**: `{{$env.VAPI_API_URL}}/api/conversations`
 41 | 
 42 | ## Other Docker Services
 43 | 
 44 | Other containers can reach the Vapi MCP service using the service name:
 45 | 
 46 | ```
 47 | http://vapi-mcp:3000
 48 | ```
 49 | 
 50 | ## Connection Examples
 51 | 
 52 | ### Python Example
 53 | 
 54 | ```python
 55 | import requests
 56 | 
 57 | # Base URL for internal docker network
 58 | base_url = "http://vapi-mcp:3000"
 59 | 
 60 | # Create a new assistant
 61 | response = requests.post(
 62 |     f"{base_url}/api/assistants",
 63 |     json={
 64 |         "name": "Customer Support",
 65 |         "model": "gpt-4",
 66 |         "voice": "alloy",
 67 |         "firstMessage": "Hello, this is customer support. How can I help you today?"
 68 |     }
 69 | )
 70 | 
 71 | print(response.json())
 72 | ```
 73 | 
 74 | ### JavaScript Example
 75 | 
 76 | ```javascript
 77 | const axios = require('axios');
 78 | 
 79 | // Base URL for internal docker network
 80 | const baseUrl = 'http://vapi-mcp:3000';
 81 | 
 82 | // Make a call
 83 | async function makeCall() {
 84 |   try {
 85 |     const response = await axios.post(`${baseUrl}/api/calls`, {
 86 |       phoneNumber: '+61412345678',
 87 |       assistantId: 'assistant_abc123',
 88 |       metadata: {
 89 |         campaign: 'support_followup'
 90 |       }
 91 |     });
 92 |     
 93 |     console.log('Call initiated:', response.data);
 94 |   } catch (error) {
 95 |     console.error('Error making call:', error.response?.data || error.message);
 96 |   }
 97 | }
 98 | 
 99 | makeCall();
100 | ```
101 | 
102 | ## Health Check
103 | 
104 | All services can verify the Vapi MCP service health using:
105 | 
106 | ```
107 | http://vapi-mcp:3000/health
108 | ```
109 | 
110 | Expected response:
111 | ```json
112 | {"status":"ok"}
113 | ```
114 | 
115 | ## Environment Variables
116 | 
117 | The following environment variables are available for service configuration:
118 | 
119 | | Variable | Purpose | Where Set |
120 | |----------|---------|-----------|
121 | | `VAPI_API_URL` | Base URL for Vapi API | In n8n container as `http://vapi-mcp:3000` |
122 | | `VAPI_ORG_ID` | Vapi organization identifier | `.env` file |
123 | | `VAPI_PRIVATE_KEY` | Authentication key | `.env` file |
124 | | `VAPI_KNOWLEDGE_ID` | Knowledge base identifier | `.env` file |
125 | | `VAPI_JWT_PRIVATE` | JWT authentication key | `.env` file |
126 | 
127 | ## Troubleshooting
128 | 
129 | If services cannot connect to Vapi MCP:
130 | 
131 | 1. Verify the container is running:
132 |    ```bash
133 |    sudo docker ps | grep vapi-mcp
134 |    ```
135 | 
136 | 2. Check the service is healthy:
137 |    ```bash
138 |    sudo docker exec -it vapi-mcp wget -q -O - http://localhost:3000/health
139 |    ```
140 | 
141 | 3. Verify network connectivity (from another container):
142 |    ```bash
143 |    sudo docker exec -it n8n wget -q -O - http://vapi-mcp:3000/health
144 |    ```
145 | 
146 | 4. Review logs for errors:
147 |    ```bash
148 |    sudo docker logs vapi-mcp
149 |    ```
150 | 
151 | ## Notes for N8N Custom Nodes
152 | 
153 | When creating custom n8n nodes that connect to Vapi MCP:
154 | 
155 | 1. Use the environment variable for flexibility:
156 |    ```javascript
157 |    const baseUrl = process.env.VAPI_API_URL || 'http://vapi-mcp:3000';
158 |    ```
159 | 
160 | 2. Include proper error handling and timeouts
161 | 3. Consider implementing retry logic for critical calls
162 | 
163 | ## Additional Resources
164 | 
165 | 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
  1 | import express from 'express';
  2 | import cors from 'cors';
  3 | import dotenv from 'dotenv';
  4 | import { VapiClient } from '@vapi-ai/server-sdk';
  5 | 
  6 | // Import routes
  7 | import callsRouter from './routes/calls';
  8 | import assistantsRouter from './routes/assistants';
  9 | import conversationsRouter from './routes/conversations';
 10 | 
 11 | // Load environment variables
 12 | dotenv.config();
 13 | 
 14 | // Initialize the vapi client
 15 | export const vapiClient = new VapiClient({
 16 |   token: () => process.env.VAPI_PRIVATE_KEY || ""
 17 | });
 18 | 
 19 | // Initialize Express app
 20 | const app = express();
 21 | const port = process.env.PORT || 3000;
 22 | 
 23 | // Middleware
 24 | app.use(cors());
 25 | app.use(express.json());
 26 | app.use(express.urlencoded({ extended: true }));
 27 | 
 28 | // Request logging middleware
 29 | app.use((req, res, next) => {
 30 |   console.log(`${new Date().toISOString()} - ${req.method} ${req.path}`);
 31 |   next();
 32 | });
 33 | 
 34 | // MCP Server-Sent Events endpoint
 35 | app.get('/sse', (req, res) => {
 36 |   // Set headers for SSE
 37 |   res.setHeader('Content-Type', 'text/event-stream');
 38 |   res.setHeader('Cache-Control', 'no-cache');
 39 |   res.setHeader('Connection', 'keep-alive');
 40 |   
 41 |   // Send the initial endpoint event as required by MCP protocol
 42 |   res.write(`event: endpoint\ndata: /mcp-messages\n\n`);
 43 |   
 44 |   // Keep the connection alive
 45 |   const keepAliveInterval = setInterval(() => {
 46 |     res.write(': keepalive\n\n');
 47 |   }, 30000);
 48 |   
 49 |   // Handle client disconnect
 50 |   req.on('close', () => {
 51 |     clearInterval(keepAliveInterval);
 52 |     console.log('SSE connection closed');
 53 |   });
 54 | });
 55 | 
 56 | // MCP messages endpoint for client to post messages
 57 | app.post('/mcp-messages', express.json(), async (req: express.Request, res: express.Response) => {
 58 |   try {
 59 |     const message = req.body;
 60 |     console.log('Received MCP message:', message);
 61 |     
 62 |     // Check if it's a valid JSON-RPC 2.0 message
 63 |     if (message.jsonrpc !== '2.0') {
 64 |       return res.status(400).json({
 65 |         jsonrpc: '2.0',
 66 |         id: message.id,
 67 |         error: {
 68 |           code: -32600,
 69 |           message: 'Invalid Request: Not a valid JSON-RPC 2.0 message'
 70 |         }
 71 |       });
 72 |     }
 73 |     
 74 |     // Handle different MCP protocol methods
 75 |     if (message.method === 'initialize') {
 76 |       // Handle initialization request
 77 |       return res.json({
 78 |         jsonrpc: '2.0',
 79 |         id: message.id,
 80 |         result: {
 81 |           capabilities: {
 82 |             tools: true,
 83 |             resources: true,
 84 |             prompts: true,
 85 |             roots: false
 86 |           },
 87 |           protocolVersion: '2024-11-05'
 88 |         }
 89 |       });
 90 |     } else if (message.method === 'mcp/list_capabilities') {
 91 |       // Return MCP capabilities
 92 |       return res.json({
 93 |         jsonrpc: '2.0',
 94 |         id: message.id,
 95 |         result: {
 96 |           capabilities: {
 97 |             tools: true,
 98 |             resources: true,
 99 |             prompts: true,
100 |             roots: false
101 |           },
102 |           schema_version: '2024-11-05'
103 |         }
104 |       });
105 |     } else if (message.method === 'tools/list') {
106 |       // Return available tools that map to our API endpoints
107 |       return res.json({
108 |         jsonrpc: '2.0',
109 |         id: message.id,
110 |         result: {
111 |           tools: [
112 |             {
113 |               name: 'createAssistant',
114 |               description: 'Create a new voice assistant',
115 |               schema: {
116 |                 type: 'object',
117 |                 properties: {
118 |                   name: {
119 |                     type: 'string',
120 |                     description: 'Name of the assistant'
121 |                   },
122 |                   model: {
123 |                     type: 'string',
124 |                     description: 'LLM model to use'
125 |                   },
126 |                   voice: {
127 |                     type: 'string',
128 |                     description: 'Voice to use'
129 |                   },
130 |                   firstMessage: {
131 |                     type: 'string',
132 |                     description: 'First message to say'
133 |                   }
134 |                 },
135 |                 required: ['name', 'model', 'voice']
136 |               }
137 |             },
138 |             {
139 |               name: 'makeCall',
140 |               description: 'Make an outbound call',
141 |               schema: {
142 |                 type: 'object',
143 |                 properties: {
144 |                   phoneNumber: {
145 |                     type: 'string',
146 |                     description: 'Phone number to call'
147 |                   },
148 |                   assistantId: {
149 |                     type: 'string',
150 |                     description: 'ID of the assistant to use'
151 |                   },
152 |                   metadata: {
153 |                     type: 'object',
154 |                     description: 'Additional metadata for the call'
155 |                   }
156 |                 },
157 |                 required: ['phoneNumber', 'assistantId']
158 |               }
159 |             },
160 |             {
161 |               name: 'getAssistants',
162 |               description: 'List all assistants',
163 |               schema: {
164 |                 type: 'object',
165 |                 properties: {}
166 |               }
167 |             },
168 |             {
169 |               name: 'getAssistant',
170 |               description: 'Get a specific assistant',
171 |               schema: {
172 |                 type: 'object',
173 |                 properties: {
174 |                   id: {
175 |                     type: 'string',
176 |                     description: 'ID of the assistant'
177 |                   }
178 |                 },
179 |                 required: ['id']
180 |               }
181 |             },
182 |             {
183 |               name: 'getCalls',
184 |               description: 'List all calls',
185 |               schema: {
186 |                 type: 'object',
187 |                 properties: {}
188 |               }
189 |             },
190 |             {
191 |               name: 'getCall',
192 |               description: 'Get a specific call',
193 |               schema: {
194 |                 type: 'object',
195 |                 properties: {
196 |                   id: {
197 |                     type: 'string',
198 |                     description: 'ID of the call'
199 |                   }
200 |                 },
201 |                 required: ['id']
202 |               }
203 |             }
204 |           ]
205 |         }
206 |       });
207 |     } else if (message.method === 'prompts/list') {
208 |       // Return available prompts
209 |       return res.json({
210 |         jsonrpc: '2.0',
211 |         id: message.id,
212 |         result: {
213 |           prompts: [
214 |             {
215 |               id: "vapi-assistant-prompt",
216 |               name: "Vapi Assistant Prompt",
217 |               description: "A prompt for creating a Vapi voice assistant"
218 |             }
219 |           ]
220 |         }
221 |       });
222 |     } else if (message.method === 'prompts/get') {
223 |       // Return a specific prompt
224 |       const promptId = message.params.id;
225 |       if (promptId === "vapi-assistant-prompt") {
226 |         return res.json({
227 |           jsonrpc: '2.0',
228 |           id: message.id,
229 |           result: {
230 |             prompt: {
231 |               id: "vapi-assistant-prompt",
232 |               name: "Vapi Assistant Prompt",
233 |               description: "A prompt for creating a Vapi voice assistant",
234 |               content: "You are a helpful voice assistant powered by Vapi."
235 |             }
236 |           }
237 |         });
238 |       } else {
239 |         return res.json({
240 |           jsonrpc: '2.0',
241 |           id: message.id,
242 |           error: {
243 |             code: -32602,
244 |             message: `Prompt not found: ${promptId}`
245 |           }
246 |         });
247 |       }
248 |     } else if (message.method === 'resources/list') {
249 |       // Return available resources
250 |       return res.json({
251 |         jsonrpc: '2.0',
252 |         id: message.id,
253 |         result: {
254 |           resources: [
255 |             {
256 |               uri: "vapi-docs",
257 |               name: "Vapi Documentation",
258 |               description: "Documentation for the Vapi API"
259 |             }
260 |           ]
261 |         }
262 |       });
263 |     } else if (message.method === 'resources/get') {
264 |       // Return a specific resource
265 |       const resourceUri = message.params.uri;
266 |       if (resourceUri === "vapi-docs") {
267 |         return res.json({
268 |           jsonrpc: '2.0',
269 |           id: message.id,
270 |           result: {
271 |             content: "# Vapi Documentation\n\nThis is the documentation for the Vapi voice assistant API."
272 |           }
273 |         });
274 |       } else {
275 |         return res.json({
276 |           jsonrpc: '2.0',
277 |           id: message.id,
278 |           error: {
279 |             code: -32602,
280 |             message: `Resource not found: ${resourceUri}`
281 |           }
282 |         });
283 |       }
284 |     } else if (message.method === 'tools/call') {
285 |       // Handle tool calls by mapping to our API
286 |       const { name, arguments: args } = message.params;
287 |       
288 |       let result;
289 |       try {
290 |         if (name === 'createAssistant') {
291 |           // Map to POST /api/assistants
292 |           const response = await fetch(`http://localhost:${port}/api/assistants`, {
293 |             method: 'POST',
294 |             headers: { 'Content-Type': 'application/json' },
295 |             body: JSON.stringify(args)
296 |           });
297 |           result = await response.json();
298 |         } else if (name === 'makeCall') {
299 |           // Map to POST /api/calls
300 |           const response = await fetch(`http://localhost:${port}/api/calls`, {
301 |             method: 'POST',
302 |             headers: { 'Content-Type': 'application/json' },
303 |             body: JSON.stringify(args)
304 |           });
305 |           result = await response.json();
306 |         } else if (name === 'getAssistants') {
307 |           // Map to GET /api/assistants
308 |           const response = await fetch(`http://localhost:${port}/api/assistants`);
309 |           result = await response.json();
310 |         } else if (name === 'getAssistant') {
311 |           // Map to GET /api/assistants/:id
312 |           const response = await fetch(`http://localhost:${port}/api/assistants/${args.id}`);
313 |           result = await response.json();
314 |         } else if (name === 'getCalls') {
315 |           // Map to GET /api/calls
316 |           const response = await fetch(`http://localhost:${port}/api/calls`);
317 |           result = await response.json();
318 |         } else if (name === 'getCall') {
319 |           // Map to GET /api/calls/:id
320 |           const response = await fetch(`http://localhost:${port}/api/calls/${args.id}`);
321 |           result = await response.json();
322 |         } else {
323 |           throw new Error(`Unknown tool: ${name}`);
324 |         }
325 |         
326 |         return res.json({
327 |           jsonrpc: '2.0',
328 |           id: message.id,
329 |           result: {
330 |             content: [
331 |               {
332 |                 type: 'text',
333 |                 text: JSON.stringify(result)
334 |               }
335 |             ]
336 |           }
337 |         });
338 |       } catch (error: any) {
339 |         console.error('Error handling tool call:', error);
340 |         return res.json({
341 |           jsonrpc: '2.0',
342 |           id: message.id,
343 |           error: {
344 |             code: -32603,
345 |             message: `Internal error: ${error.message}`
346 |           }
347 |         });
348 |       }
349 |     } else {
350 |       // Unsupported method
351 |       return res.json({
352 |         jsonrpc: '2.0',
353 |         id: message.id,
354 |         error: {
355 |           code: -32601,
356 |           message: `Method not found: ${message.method}`
357 |         }
358 |       });
359 |     }
360 |   } catch (error: any) {
361 |     console.error('Error processing MCP message:', error);
362 |     return res.status(500).json({
363 |       jsonrpc: '2.0',
364 |       id: req.body?.id || null,
365 |       error: {
366 |         code: -32603,
367 |         message: `Internal error: ${error.message}`
368 |       }
369 |     });
370 |   }
371 | });
372 | 
373 | // Original API routes
374 | app.use('/api/calls', callsRouter);
375 | app.use('/api/assistants', assistantsRouter);
376 | app.use('/api/conversations', conversationsRouter);
377 | 
378 | // Health check endpoint
379 | app.get('/health', (req, res) => {
380 |   res.status(200).json({ status: 'ok' });
381 | });
382 | 
383 | // Root route
384 | app.get('/', (req, res) => {
385 |   res.json({
386 |     name: 'Vapi MCP HTTP Server',
387 |     version: '1.0.0',
388 |     description: 'HTTP server for Vapi voice AI integration with MCP'
389 |   });
390 | });
391 | 
392 | // Error handling middleware
393 | app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
394 |   console.error(err.stack);
395 |   res.status(500).json({
396 |     success: false,
397 |     error: err.message || 'An unexpected error occurred'
398 |   });
399 | });
400 | 
401 | // Start the server
402 | app.listen(port, () => {
403 |   console.log(`Vapi MCP HTTP Server running on port ${port}`);
404 | }); 
```
--------------------------------------------------------------------------------
/vapi-mcp-server/src/index.ts:
--------------------------------------------------------------------------------
```typescript
  1 | #!/usr/bin/env node
  2 | 
  3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  5 | import {
  6 |     CallToolRequestSchema,
  7 |     ListToolsRequestSchema,
  8 | } from "@modelcontextprotocol/sdk/types.js";
  9 | import { z } from "zod";
 10 | import { zodToJsonSchema } from "zod-to-json-schema";
 11 | import { VapiClient } from "@vapi-ai/server-sdk";
 12 | import dotenv from "dotenv";
 13 | 
 14 | // Load environment variables
 15 | dotenv.config();
 16 | 
 17 | // Log environment variables (without exposing sensitive data)
 18 | console.error("Vapi Environment:", {
 19 |     VAPI_ORG_ID: process.env.VAPI_ORG_ID ? "Set" : "Not set",
 20 |     VAPI_PRIVATE_KEY: process.env.VAPI_PRIVATE_KEY ? "Set" : "Not set",
 21 |     VAPI_KNOWLEDGE_ID: process.env.VAPI_KNOWLEDGE_ID ? "Set" : "Not set",
 22 |     VAPI_JWT_PRIVATE: process.env.VAPI_JWT_PRIVATE ? "Set" : "Not set",
 23 |     NODE_ENV: process.env.NODE_ENV
 24 | });
 25 | 
 26 | // Initialize Vapi client
 27 | const vapiClient = new VapiClient({
 28 |     token: () => {
 29 |         const key = process.env.VAPI_PRIVATE_KEY;
 30 |         if (!key) {
 31 |             throw new Error("VAPI_PRIVATE_KEY environment variable is not set");
 32 |         }
 33 |         console.error("Initializing Vapi client with auth token");
 34 |         return key;
 35 |     },
 36 | });
 37 | 
 38 | // Zod schemas for our tools
 39 | const CallToolSchema = z.object({
 40 |     phoneNumber: z.string().describe("The phone number to call"),
 41 |     assistantId: z.string().optional().describe("The ID of the Vapi assistant to use"),
 42 |     assistantConfig: z.object({
 43 |         name: z.string().optional().describe("Name of the assistant"),
 44 |         model: z.string().optional().describe("The LLM model to use"),
 45 |         voice: z.string().optional().describe("The voice to use"),
 46 |         firstMessage: z.string().optional().describe("First message to say when the call is answered"),
 47 |         maxDuration: z.number().optional().describe("Maximum call duration in seconds"),
 48 |     }).optional().describe("Assistant configuration, if no assistantId is provided"),
 49 |     metadata: z.record(z.string()).optional().describe("Additional metadata for the call"),
 50 | });
 51 | 
 52 | const AssistantActionSchema = z.enum(["create", "get", "list", "update", "delete"]);
 53 | 
 54 | const AssistantToolSchema = z.object({
 55 |     action: AssistantActionSchema.describe("Action to perform on the assistant"),
 56 |     assistantId: z.string().optional().describe("The ID of the assistant (required for get, update, delete)"),
 57 |     params: z.object({
 58 |         name: z.string().optional().describe("Name of the assistant"),
 59 |         model: z.string().optional().describe("The LLM model to use"),
 60 |         voice: z.string().optional().describe("The voice to use for the assistant"),
 61 |         firstMessage: z.string().optional().describe("First message the assistant will say"),
 62 |         instructions: z.string().optional().describe("Instructions for the assistant's behavior"),
 63 |         maxDuration: z.number().optional().describe("Maximum call duration in seconds"),
 64 |     }).optional().describe("Parameters for creating or updating an assistant"),
 65 | });
 66 | 
 67 | const ConversationActionSchema = z.enum(["get", "list"]);
 68 | 
 69 | const ConversationToolSchema = z.object({
 70 |     action: ConversationActionSchema.describe("Action to perform (get or list conversations)"),
 71 |     callId: z.string().optional().describe("The ID of the call to retrieve conversation for"),
 72 |     filters: z.object({
 73 |         limit: z.number().optional().describe("Maximum number of conversations to return"),
 74 |         offset: z.number().optional().describe("Offset for pagination"),
 75 |         startDate: z.string().optional().describe("Start date for filtering conversations"),
 76 |         endDate: z.string().optional().describe("End date for filtering conversations"),
 77 |     }).optional().describe("Filters for listing conversations"),
 78 | });
 79 | 
 80 | async function main() {
 81 |     // Initialize the MCP server
 82 |     const server = new Server(
 83 |         {
 84 |             name: "vapi-mcp-server",
 85 |             version: "1.0.0",
 86 |         },
 87 |         {
 88 |             capabilities: {
 89 |                 tools: {
 90 |                     list: true,
 91 |                     call: true,
 92 |                 },
 93 |             },
 94 |         }
 95 |     );
 96 | 
 97 |     // Verify connection to Vapi API
 98 |     try {
 99 |         console.error("Verifying connection to Vapi API...");
100 |         // Attempt to list assistants to verify API connection
101 |         await vapiClient.assistants.list();
102 |         console.error("✓ Successfully connected to Vapi API");
103 |     } catch (error) {
104 |         console.error("✗ Failed to connect to Vapi API:", error);
105 |         console.error("The server will continue to run but API calls may fail.");
106 |         console.error("Check your API key and internet connection.");
107 |     }
108 | 
109 |     // Set up tool handlers
110 |     server.setRequestHandler(ListToolsRequestSchema, async () => ({
111 |         tools: [
112 |             {
113 |                 name: "vapi_call",
114 |                 description: "Make an outbound call using the Vapi voice AI platform",
115 |                 inputSchema: zodToJsonSchema(CallToolSchema),
116 |             },
117 |             {
118 |                 name: "vapi_assistant",
119 |                 description: "Manage Vapi voice assistants (create, get, list, update, delete)",
120 |                 inputSchema: zodToJsonSchema(AssistantToolSchema),
121 |             },
122 |             {
123 |                 name: "vapi_conversation",
124 |                 description: "Retrieve conversation details from Vapi calls",
125 |                 inputSchema: zodToJsonSchema(ConversationToolSchema),
126 |             },
127 |         ],
128 |     }));
129 | 
130 |     server.setRequestHandler(CallToolRequestSchema, async (request) => {
131 |         const { name, arguments: args } = request.params;
132 |         
133 |         try {
134 |             switch (name) {
135 |                 case "vapi_call": {
136 |                     const validatedArgs = CallToolSchema.parse(args);
137 |                     console.error(`Making call to ${validatedArgs.phoneNumber}`);
138 |                     
139 |                     try {
140 |                         // Prepare call parameters based on the validated arguments
141 |                         const callParams: any = {
142 |                             phoneNumber: validatedArgs.phoneNumber
143 |                         };
144 |                         
145 |                         // Add assistantId or assistant details
146 |                         if (validatedArgs.assistantId) {
147 |                             callParams.assistantId = validatedArgs.assistantId;
148 |                         } else if (validatedArgs.assistantConfig) {
149 |                             // Use a simpler approach with any type to avoid TypeScript errors
150 |                             callParams.assistant = {
151 |                                 name: validatedArgs.assistantConfig.name || "Default Assistant",
152 |                                 model: validatedArgs.assistantConfig.model === "gpt-4" ? "gpt_4" : "gpt_3_5_turbo",
153 |                                 voice: {
154 |                                     provider: "eleven_labs",
155 |                                     voiceId: validatedArgs.assistantConfig.voice || "alloy"
156 |                                 },
157 |                                 firstMessage: validatedArgs.assistantConfig.firstMessage,
158 |                                 maxDuration: validatedArgs.assistantConfig.maxDuration
159 |                             };
160 |                         } else {
161 |                             throw new Error("Either assistantId or assistantConfig is required");
162 |                         }
163 |                         
164 |                         // Make the actual API call with type assertion
165 |                         const result = await vapiClient.calls.create(callParams);
166 |                         
167 |                         return {
168 |                             content: [
169 |                                 {
170 |                                     type: "text",
171 |                                     text: JSON.stringify({
172 |                                         callId: result.id,
173 |                                         status: result.status,
174 |                                     }, null, 2),
175 |                                 },
176 |                             ],
177 |                         };
178 |                     } catch (callError) {
179 |                         console.error("Error making call:", callError);
180 |                         throw callError;
181 |                     }
182 |                 }
183 |                 
184 |                 case "vapi_assistant": {
185 |                     const validatedArgs = AssistantToolSchema.parse(args);
186 |                     console.error(`Performing ${validatedArgs.action} operation on assistant`);
187 |                     
188 |                     switch (validatedArgs.action) {
189 |                         case "create": {
190 |                             if (!validatedArgs.params) {
191 |                                 throw new Error("params is required for create operation");
192 |                             }
193 |                             
194 |                             try {
195 |                                 // Use any type to bypass strict type checking
196 |                                 const createParams: any = {
197 |                                     name: validatedArgs.params.name || "New Assistant",
198 |                                     model: validatedArgs.params.model === "gpt-4" ? "gpt_4" : "gpt_3_5_turbo",
199 |                                     voice: {
200 |                                         provider: "eleven_labs",
201 |                                         voiceId: validatedArgs.params.voice || "alloy"
202 |                                     }
203 |                                 };
204 |                                 
205 |                                 // Add optional parameters if provided
206 |                                 if (validatedArgs.params.firstMessage) {
207 |                                     createParams.firstMessage = validatedArgs.params.firstMessage;
208 |                                 }
209 |                                 
210 |                                 if (validatedArgs.params.instructions) {
211 |                                     createParams.instructions = validatedArgs.params.instructions;
212 |                                 }
213 |                                 
214 |                                 if (validatedArgs.params.maxDuration) {
215 |                                     createParams.maxDuration = validatedArgs.params.maxDuration;
216 |                                 }
217 |                                 
218 |                                 const assistant = await vapiClient.assistants.create(createParams);
219 |                                 
220 |                                 return {
221 |                                     content: [
222 |                                         {
223 |                                             type: "text",
224 |                                             text: JSON.stringify({
225 |                                                 success: true,
226 |                                                 assistant: assistant,
227 |                                             }, null, 2),
228 |                                         },
229 |                                     ],
230 |                                 };
231 |                             } catch (assistantError) {
232 |                                 console.error("Error creating assistant:", assistantError);
233 |                                 throw assistantError;
234 |                             }
235 |                         }
236 |                         
237 |                         case "get": {
238 |                             if (!validatedArgs.assistantId) {
239 |                                 throw new Error("assistantId is required for get operation");
240 |                             }
241 |                             
242 |                             const assistant = await vapiClient.assistants.get(validatedArgs.assistantId);
243 |                             
244 |                             return {
245 |                                 content: [
246 |                                     {
247 |                                         type: "text",
248 |                                         text: JSON.stringify({
249 |                                             success: true,
250 |                                             assistant: assistant,
251 |                                         }, null, 2),
252 |                                     },
253 |                                 ],
254 |                             };
255 |                         }
256 |                         
257 |                         case "list": {
258 |                             const assistants = await vapiClient.assistants.list();
259 |                             
260 |                             return {
261 |                                 content: [
262 |                                     {
263 |                                         type: "text",
264 |                                         text: JSON.stringify({
265 |                                             success: true,
266 |                                             assistants: assistants,
267 |                                         }, null, 2),
268 |                                     },
269 |                                 ],
270 |                             };
271 |                         }
272 |                         
273 |                         case "update": {
274 |                             if (!validatedArgs.assistantId) {
275 |                                 throw new Error("assistantId is required for update operation");
276 |                             }
277 |                             
278 |                             if (!validatedArgs.params) {
279 |                                 throw new Error("params is required for update operation");
280 |                             }
281 |                             
282 |                             try {
283 |                                 // Use any type to bypass strict type checking
284 |                                 const updateParams: any = {};
285 |                                 
286 |                                 // Only add parameters that are provided
287 |                                 if (validatedArgs.params.name) {
288 |                                     updateParams.name = validatedArgs.params.name;
289 |                                 }
290 |                                 
291 |                                 if (validatedArgs.params.voice) {
292 |                                     updateParams.voice = {
293 |                                         provider: "eleven_labs",
294 |                                         voiceId: validatedArgs.params.voice
295 |                                     };
296 |                                 }
297 |                                 
298 |                                 if (validatedArgs.params.firstMessage) {
299 |                                     updateParams.firstMessage = validatedArgs.params.firstMessage;
300 |                                 }
301 |                                 
302 |                                 if (validatedArgs.params.instructions) {
303 |                                     updateParams.instructions = validatedArgs.params.instructions;
304 |                                 }
305 |                                 
306 |                                 if (validatedArgs.params.maxDuration) {
307 |                                     updateParams.maxDuration = validatedArgs.params.maxDuration;
308 |                                 }
309 |                                 
310 |                                 const assistant = await vapiClient.assistants.update(
311 |                                     validatedArgs.assistantId,
312 |                                     updateParams
313 |                                 );
314 |                                 
315 |                                 return {
316 |                                     content: [
317 |                                         {
318 |                                             type: "text",
319 |                                             text: JSON.stringify({
320 |                                                 success: true,
321 |                                                 assistant: assistant,
322 |                                             }, null, 2),
323 |                                         },
324 |                                     ],
325 |                                 };
326 |                             } catch (updateError) {
327 |                                 console.error("Error updating assistant:", updateError);
328 |                                 throw updateError;
329 |                             }
330 |                         }
331 |                         
332 |                         case "delete": {
333 |                             if (!validatedArgs.assistantId) {
334 |                                 throw new Error("assistantId is required for delete operation");
335 |                             }
336 |                             
337 |                             await vapiClient.assistants.delete(validatedArgs.assistantId);
338 |                             
339 |                             return {
340 |                                 content: [
341 |                                     {
342 |                                         type: "text",
343 |                                         text: JSON.stringify({
344 |                                             success: true,
345 |                                             message: `Assistant ${validatedArgs.assistantId} deleted successfully`,
346 |                                         }, null, 2),
347 |                                     },
348 |                                 ],
349 |                             };
350 |                         }
351 |                     }
352 |                 }
353 |                 
354 |                 case "vapi_conversation": {
355 |                     const validatedArgs = ConversationToolSchema.parse(args);
356 |                     console.error(`Performing ${validatedArgs.action} operation on conversation`);
357 |                     
358 |                     switch (validatedArgs.action) {
359 |                         case "get": {
360 |                             if (!validatedArgs.callId) {
361 |                                 throw new Error("callId is required for get operation");
362 |                             }
363 |                             
364 |                             try {
365 |                                 // Get call details
366 |                                 const call = await vapiClient.calls.get(validatedArgs.callId);
367 |                                 
368 |                                 // Construct conversation response using properties that are known to exist
369 |                                 const conversation = {
370 |                                     callId: call.id,
371 |                                     assistantId: call.assistantId,
372 |                                     phoneNumber: "unknown", // We don't have access to the 'to' property
373 |                                     startTime: call.createdAt, 
374 |                                     endTime: call.updatedAt,
375 |                                     duration: 0, // We don't have access to the duration property
376 |                                     status: call.status,
377 |                                     messages: [], // We don't have access to messages directly
378 |                                 };
379 |                                 
380 |                                 return {
381 |                                     content: [
382 |                                         {
383 |                                             type: "text",
384 |                                             text: JSON.stringify({
385 |                                                 success: true,
386 |                                                 conversation: conversation,
387 |                                             }, null, 2),
388 |                                         },
389 |                                     ],
390 |                                 };
391 |                             } catch (getError) {
392 |                                 console.error("Error getting conversation:", getError);
393 |                                 throw getError;
394 |                             }
395 |                         }
396 |                         
397 |                         case "list": {
398 |                             try {
399 |                                 const filters = {
400 |                                     limit: validatedArgs.filters?.limit || 10,
401 |                                     offset: validatedArgs.filters?.offset || 0,
402 |                                 };
403 |                                 
404 |                                 // List calls with filters
405 |                                 const calls = await vapiClient.calls.list(filters);
406 |                                 
407 |                                 // Ensure calls is treated as an array
408 |                                 const callsArray = Array.isArray(calls) ? calls : [];
409 |                                 
410 |                                 // Map calls to conversation summary format
411 |                                 const conversations = callsArray.map(call => ({
412 |                                     callId: call.id,
413 |                                     assistantId: call.assistantId,
414 |                                     phoneNumber: "unknown", // We don't have access to the 'to' property
415 |                                     startTime: call.createdAt,
416 |                                     endTime: call.updatedAt,
417 |                                     duration: 0, // We don't have access to duration
418 |                                     status: call.status,
419 |                                     messageCount: 0, // We don't have access to message count
420 |                                 }));
421 |                                 
422 |                                 return {
423 |                                     content: [
424 |                                         {
425 |                                             type: "text",
426 |                                             text: JSON.stringify({
427 |                                                 success: true,
428 |                                                 conversations: conversations,
429 |                                                 pagination: {
430 |                                                     total: callsArray.length,
431 |                                                     limit: filters.limit,
432 |                                                     offset: filters.offset,
433 |                                                 },
434 |                                             }, null, 2),
435 |                                         },
436 |                                     ],
437 |                                 };
438 |                             } catch (listError) {
439 |                                 console.error("Error listing conversations:", listError);
440 |                                 throw listError;
441 |                             }
442 |                         }
443 |                     }
444 |                 }
445 |                 
446 |                 default:
447 |                     throw new Error(`Unknown tool: ${name}`);
448 |             }
449 |         } catch (error: unknown) {
450 |             const errorMessage = error instanceof Error ? error.message : String(error);
451 |             console.error(`Error in tool execution: ${errorMessage}`, error);
452 |             return {
453 |                 content: [
454 |                     {
455 |                         type: "text",
456 |                         text: JSON.stringify({
457 |                             error: errorMessage || "An unknown error occurred",
458 |                         }, null, 2),
459 |                     },
460 |                 ],
461 |             };
462 |         }
463 |     });
464 | 
465 |     // Connect to transport
466 |     const transport = new StdioServerTransport();
467 |     await server.connect(transport);
468 |     
469 |     console.error("Vapi MCP Server started successfully");
470 | }
471 | 
472 | main().catch((error: unknown) => {
473 |     const errorMessage = error instanceof Error ? error.message : String(error);
474 |     console.error("Error starting Vapi MCP Server:", errorMessage);
475 |     process.exit(1);
476 | });
477 | 
```