diff --git a/README.md b/README.md
index 3460867..b50a9a4 100644
--- a/README.md
+++ b/README.md
@@ -2,33 +2,71 @@
[](https://smithery.ai/server/@VapiAI/vapi-mcp-server)
-The Vapi [Model Context Protocol](https://modelcontextprotocol.com/) server allows you to integrate with Vapi APIs through function calling.
+Build AI voice assistants and phone agents with [Vapi](https://vapi.ai) using the [Model Context Protocol](https://modelcontextprotocol.com/).
+## Claude Code Setup (Recommended)
+
+The easiest way to get started. No API key needed - authenticate via browser on first use.
+
+### 1. Add MCP Server
+
+Add to your `~/.claude.json`:
+
+```json
+{
+ "mcpServers": {
+ "vapi": {
+ "command": "npx",
+ "args": ["-y", "@vapi-ai/mcp-server"]
+ }
+ }
+}
+```
+
+### 2. Install Skill (Optional)
+
+The Vapi skill helps Claude guide you through building voice assistants:
+
+```bash
+mkdir -p ~/.claude/skills/vapi
+curl -o ~/.claude/skills/vapi/SKILL.md https://raw.githubusercontent.com/VapiAI/mcp-server/main/skill/SKILL.md
+```
+
+### 3. Restart Claude Code
+
+After restarting, use `/vapi` or ask Claude to help build a voice assistant. On first use, you'll be prompted to sign in via browser - no API key copy-paste needed.
+
+---
+
## Claude Desktop Setup
-1. Open `Claude Desktop` and press `CMD + ,` to go to `Settings`.
-2. Click on the `Developer` tab.
-3. Click on the `Edit Config` button.
-4. This will open the `claude_desktop_config.json` file in your file explorer.
-5. Get your Vapi API key from the Vapi dashboard ().
-6. Add the following to your `claude_desktop_config.json` file. See [here](https://modelcontextprotocol.io/quickstart/user) for more details.
-7. Restart the Claude Desktop after editing the config file.
+### With OAuth (No API Key)
+
+```json
+{
+ "mcpServers": {
+ "vapi": {
+ "command": "npx",
+ "args": ["-y", "@vapi-ai/mcp-server"]
+ }
+ }
+}
+```
+
+### With API Key
-### Local Configuration
+If you prefer to use an API key directly, get one from the [Vapi dashboard](https://dashboard.vapi.ai/org/api-keys):
```json
{
"mcpServers": {
- "vapi-mcp-server": {
+ "vapi": {
"command": "npx",
- "args": [
- "-y",
- "@vapi-ai/mcp-server"
- ],
+ "args": ["-y", "@vapi-ai/mcp-server"],
"env": {
"VAPI_TOKEN": ""
}
@@ -39,10 +77,12 @@ The Vapi [Model Context Protocol](https://modelcontextprotocol.com/) server allo
### Remote Configuration
+Connect to Vapi's hosted MCP server:
+
```json
{
"mcpServers": {
- "vapi-mcp": {
+ "vapi": {
"command": "npx",
"args": [
"mcp-remote",
@@ -58,40 +98,39 @@ The Vapi [Model Context Protocol](https://modelcontextprotocol.com/) server allo
}
```
-### Example Usage with Claude Desktop
+---
-1. Create or import a phone number using the Vapi dashboard ().
-2. Create a new assistant using the existing 'Appointment Scheduler' template in the Vapi dashboard ().
-3. Make sure to configure Claude Desktop to use the Vapi MCP server and restart the Claude Desktop app.
-4. Ask Claude to initiate or schedule a call. See examples below:
+## Example Usage
-**Example 1:** Request an immediate call
+### Create a Voice Assistant
-```md
-I'd like to speak with my ShopHelper assistant to talk about my recent order. Can you have it call me at +1234567890?
+Ask Claude:
```
-
-**Example 2:** Schedule a future call
-
-```md
-I need to schedule a call with Mary assistant for next Tuesday at 3:00 PM. My phone number is +1555123456.
+I want to build a voice assistant that can schedule appointments
```
-**Example 3:** Make a call with dynamic variables
+### Make an Outbound Call
-```md
-I want to call +1234567890 with my appointment reminder assistant. Use these details:
+```
+Call +1234567890 using my appointment reminder assistant with these details:
- Customer name: Sarah Johnson
- Appointment date: March 25th
- Appointment time: 2:30 PM
-- Doctor name: Dr. Smith
```
+### Schedule a Future Call
+
+```
+Schedule a call with my support assistant for next Tuesday at 3:00 PM to +1555123456
+```
+
+---
+
## Using Variable Values in Assistant Prompts
-The `create_call` action supports passing dynamic variables through `assistantOverrides.variableValues`. These variables can be used in your assistant's prompts using double curly braces: `{{variableName}}`.
+The `create_call` action supports passing dynamic variables through `assistantOverrides.variableValues`. Use double curly braces in your assistant's prompts: `{{variableName}}`.
-### Example Assistant Prompt with Variables
+### Example Prompt with Variables
```
Hello {{customerName}}, this is a reminder about your appointment on {{appointmentDate}} at {{appointmentTime}} with {{doctorName}}.
@@ -99,7 +138,7 @@ Hello {{customerName}}, this is a reminder about your appointment on {{appointme
### Default Variables
-The following variables are automatically available (no need to pass in variableValues):
+These are automatically available (no need to pass):
- `{{now}}` - Current date and time (UTC)
- `{{date}}` - Current date (UTC)
@@ -109,29 +148,69 @@ The following variables are automatically available (no need to pass in variable
- `{{year}}` - Current year (UTC)
- `{{customer.number}}` - Customer's phone number
-For more details on default variables and advanced date/time formatting, see the [official Vapi documentation](https://docs.vapi.ai/assistants/dynamic-variables#default-variables).
+See [Vapi documentation](https://docs.vapi.ai/assistants/dynamic-variables#default-variables) for advanced date/time formatting.
-## Remote MCP
+---
-To connect to Vapi's MCP server remotely:
+## Remote MCP Server
-### Streamable HTTP (Recommended)
+Connect to Vapi's hosted MCP server from any MCP client:
-The default and recommended way to connect is via Streamable HTTP Transport:
+### Streamable HTTP (Recommended)
-- Connect to `https://mcp.vapi.ai/mcp` from any MCP client using Streamable HTTP Transport
-- Include your Vapi API key as a bearer token in the request headers
-- Example header: `Authorization: Bearer your_vapi_api_key_here`
+- URL: `https://mcp.vapi.ai/mcp`
+- Header: `Authorization: Bearer your_vapi_api_key_here`
### SSE (Deprecated)
-Server-Sent Events (SSE) Transport is still supported but deprecated:
-
-- Connect to `https://mcp.vapi.ai/sse` from any MCP client using SSE Transport
-- Include your Vapi API key as a bearer token in the request headers
-- Example header: `Authorization: Bearer your_vapi_api_key_here`
-
-This connection allows you to access Vapi's functionality remotely without running a local server.
+- URL: `https://mcp.vapi.ai/sse`
+- Header: `Authorization: Bearer your_vapi_api_key_here`
+
+---
+
+## Available Tools
+
+### Assistants
+| Tool | Description |
+|------|-------------|
+| `vapi_list_assistants` | List all assistants |
+| `vapi_get_assistant` | Get assistant by ID |
+| `vapi_create_assistant` | Create new assistant |
+| `vapi_update_assistant` | Update assistant |
+| `vapi_delete_assistant` | Delete assistant |
+
+### Calls
+| Tool | Description |
+|------|-------------|
+| `vapi_list_calls` | List call history |
+| `vapi_get_call` | Get call details |
+| `vapi_create_call` | Start outbound call (immediate or scheduled) |
+
+### Phone Numbers
+| Tool | Description |
+|------|-------------|
+| `vapi_list_phone_numbers` | List phone numbers |
+| `vapi_get_phone_number` | Get phone number details |
+| `vapi_buy_phone_number` | Purchase new number |
+| `vapi_update_phone_number` | Update number settings |
+| `vapi_delete_phone_number` | Release number |
+
+### Tools (Function Calling)
+| Tool | Description |
+|------|-------------|
+| `vapi_list_tools` | List custom tools |
+| `vapi_get_tool` | Get tool details |
+| `vapi_create_tool` | Create tool for API integration |
+| `vapi_update_tool` | Update tool |
+| `vapi_delete_tool` | Delete tool |
+
+### Authentication
+| Tool | Description |
+|------|-------------|
+| `vapi_login` | Start OAuth flow |
+| `vapi_auth_status` | Check auth status |
+
+---
## Development
@@ -139,101 +218,48 @@ This connection allows you to access Vapi's functionality remotely without runni
# Install dependencies
npm install
-# Build the server
+# Build
npm run build
-# Use inspector to test the server
+# Test with MCP inspector
npm run inspector
```
-Update your `claude_desktop_config.json` to use the local server.
+### Local Development Config
```json
{
"mcpServers": {
"vapi-local": {
"command": "node",
- "args": [
- "/dist/index.js"
- ],
+ "args": ["/dist/index.js"],
"env": {
"VAPI_TOKEN": ""
}
- },
+ }
}
}
```
### Testing
-The project has two types of tests:
-
-#### Unit Tests
-
-Unit tests use mocks to test the MCP server without making actual API calls to Vapi.
-
```bash
-# Run unit tests
+# Unit tests (mocked)
npm run test:unit
-```
-
-#### End-to-End Tests
-E2E tests run the full MCP server with actual API calls to Vapi.
-
-```bash
-# Set your Vapi API token
+# E2E tests (requires VAPI_TOKEN)
export VAPI_TOKEN=your_token_here
-
-# Run E2E tests
npm run test:e2e
-```
-
-Note: E2E tests require a valid Vapi API token to be set in the environment.
-
-#### Running All Tests
-
-To run all tests at once:
-```bash
+# All tests
npm test
```
+---
+
## References
-- [VAPI Remote MCP Server](https://mcp.vapi.ai/)
-- [VAPI MCP Tool](https://docs.vapi.ai/tools/mcp)
-- [VAPI MCP Server SDK](https://docs.vapi.ai/sdk/mcp-server)
+- [Vapi Documentation](https://docs.vapi.ai)
+- [Vapi Dashboard](https://dashboard.vapi.ai)
+- [Vapi Remote MCP Server](https://mcp.vapi.ai/)
- [Model Context Protocol](https://modelcontextprotocol.com/)
-- [Claude Desktop](https://modelcontextprotocol.io/quickstart/user)
-
-## Supported Actions
-
-The Vapi MCP Server provides the following tools for integration:
-
-### Assistant Tools
-
-- `list_assistants`: Lists all Vapi assistants
-- `create_assistant`: Creates a new Vapi assistant
-- `update_assistant`: Updates an existing Vapi assistant
-- `get_assistant`: Gets a Vapi assistant by ID
-
-### Call Tools
-
-- `list_calls`: Lists all Vapi calls
-- `create_call`: Creates an outbound call with support for:
- - Immediate or scheduled calls
- - Dynamic variable values through `assistantOverrides`
-- `get_call`: Gets details of a specific call
-
-> **Note:** The `create_call` action supports scheduling calls for immediate execution or for a future time. You can also pass dynamic variables using `assistantOverrides.variableValues` to personalize assistant messages.
-
-### Phone Number Tools
-
-- `list_phone_numbers`: Lists all Vapi phone numbers
-- `get_phone_number`: Gets details of a specific phone number
-
-### Vapi Tools
-
-- `list_tools`: Lists all Vapi tools
-- `get_tool`: Gets details of a specific tool
diff --git a/package.json b/package.json
index cc96a24..b447975 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@vapi-ai/mcp-server",
- "description": "Vapi MCP Server",
- "version": "0.0.9",
+ "description": "Vapi MCP Server - Build AI voice assistants with Claude",
+ "version": "0.0.10",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"type": "module",
diff --git a/skill/PROMPT_GUIDE.md b/skill/PROMPT_GUIDE.md
new file mode 100644
index 0000000..fe27bbb
--- /dev/null
+++ b/skill/PROMPT_GUIDE.md
@@ -0,0 +1,148 @@
+# Voice Assistant Prompt Engineering Guide
+
+Good voice assistant prompts are different from text-based prompts. This guide covers best practices for crafting effective prompts for Vapi voice assistants.
+
+## Prompt Structure
+
+### 1. Identity & Role
+Define who the assistant is clearly and concisely.
+
+```
+You are Amy, a friendly and professional receptionist for VAPI Health Clinic.
+```
+
+### 2. Core Responsibilities
+List the primary functions the assistant should perform.
+
+```
+Your responsibilities:
+- Answer patient questions about the clinic
+- Book, reschedule, and cancel appointments
+- Transfer calls to appropriate staff when needed
+```
+
+### 3. Constraints & Boundaries
+Set clear limits on what the assistant should and shouldn't do.
+
+```
+Important constraints:
+- Operating hours are 9 AM to 5 PM daily
+- Never provide medical advice - always defer to doctors
+- If asked about emergencies, direct them to call 911
+```
+
+### 4. Conversational Style
+Provide voice-specific guidance for natural conversations.
+
+```
+Conversation style:
+- Be warm and professional
+- Ask ONE question at a time, then wait for response
+- Keep responses concise (1-2 sentences when possible)
+- Use natural speech patterns, not robotic responses
+```
+
+### 5. Tool Usage
+Explain when and how to use available tools.
+
+```
+When booking appointments:
+1. First check availability using the calendar tool
+2. Confirm the date and time with the patient
+3. Book only after verbal confirmation
+```
+
+## Voice-Specific Best Practices
+
+- **Keep it concise**: Phone conversations need shorter responses than text
+- **One question at a time**: Don't overwhelm with multiple questions
+- **Confirm understanding**: Repeat back important details (dates, names, numbers)
+- **Handle interruptions**: Users will interrupt - design for it
+- **Graceful fallbacks**: Always have a path to human handoff
+
+## Example Prompts
+
+### Healthcare Receptionist
+
+```
+You are Amy, a warm and professional receptionist for VAPI Health Clinic.
+
+Your role:
+- Answer patient FAQs about clinic hours, location, and services
+- Help patients book, reschedule, or cancel appointments
+- Transfer calls to nurses or doctors when medically necessary
+
+Guidelines:
+- Clinic hours: 9 AM - 5 PM, Monday through Friday
+- Never provide medical advice - say "I'd recommend speaking with one of our nurses about that"
+- For emergencies, immediately say "Please hang up and call 911"
+- Ask one question at a time and wait for the response
+- Keep responses brief and conversational
+
+When booking appointments:
+1. Ask what type of appointment they need
+2. Check available slots using the calendar tool
+3. Offer 2-3 options and let them choose
+4. Confirm the final booking by repeating the details
+```
+
+### Customer Support Agent
+
+```
+You are Alex, a helpful support agent for TechCorp.
+
+Your role:
+- Help customers troubleshoot product issues
+- Process returns and exchanges
+- Answer questions about orders and shipping
+
+Guidelines:
+- Always verify the customer's identity first (order number or email)
+- Be patient and empathetic - customers may be frustrated
+- If you can't resolve an issue, offer to transfer to a specialist
+- Keep technical explanations simple and jargon-free
+
+Escalation triggers - transfer to human agent if:
+- Customer explicitly asks for a human
+- Issue requires account changes you can't make
+- Customer is upset after 2 resolution attempts
+```
+
+### Outbound Appointment Reminder
+
+```
+You are calling on behalf of Dr. Smith's Dental Office to remind {{customerName}} about their upcoming appointment.
+
+Your script:
+1. Introduce yourself: "Hi, this is an automated call from Dr. Smith's Dental Office"
+2. State the purpose: "I'm calling to remind you about your appointment on {{appointmentDate}} at {{appointmentTime}}"
+3. Confirm: "Will you be able to make it?"
+4. If yes: "Great! We'll see you then. Goodbye!"
+5. If no: "I understand. Would you like me to transfer you to reschedule?"
+
+Guidelines:
+- Be brief and respectful of their time
+- If you reach voicemail, leave a short message with callback number
+- Don't call back if they decline or seem annoyed
+```
+
+## Dynamic Variables
+
+Use double curly braces for dynamic content: `{{variableName}}`
+
+### Default Variables (always available)
+- `{{now}}` - Current date and time (UTC)
+- `{{date}}` - Current date (UTC)
+- `{{time}}` - Current time (UTC)
+- `{{customer.number}}` - Customer's phone number
+
+### Custom Variables
+Pass custom variables via `assistantOverrides.variableValues` when creating calls:
+- `{{customerName}}`
+- `{{appointmentDate}}`
+- `{{appointmentTime}}`
+- `{{orderNumber}}`
+
+## More Examples
+
+For complete, production-ready examples with tool integrations, see: https://github.com/VapiAI/examples
diff --git a/skill/SKILL.md b/skill/SKILL.md
new file mode 100644
index 0000000..2596b27
--- /dev/null
+++ b/skill/SKILL.md
@@ -0,0 +1,100 @@
+---
+name: vapi
+description: Build AI voice assistants and phone agents with Vapi. Use this skill when users want to create voice agents, phone bots, IVR systems, outbound calling campaigns, or any voice-based AI application.
+---
+
+# Vapi - AI Voice Assistant Builder
+
+Build AI-powered voice assistants, phone agents, and conversational AI applications with Vapi.
+
+## When This Skill is Activated
+
+When a user wants to build a voice assistant or phone agent, follow these steps:
+
+### Step 1: Check if Vapi MCP is Installed
+
+First, check if the Vapi MCP server is available by looking for `vapi_` tools. If not available:
+
+1. Add the Vapi MCP server to the `mcpServers` section:
+
+```json
+{
+ "mcpServers": {
+ "vapi": {
+ "command": "npx",
+ "args": ["-y", "@vapi-ai/mcp-server"]
+ }
+ }
+}
+```
+
+2. Tell the user to restart Claude Code for the changes to take effect
+3. After restart, continue with Step 2
+
+### Step 2: Authenticate with Vapi
+
+If the user hasn't authenticated yet (tools return auth errors):
+
+1. Call `vapi_login` to start the OAuth flow
+2. Tell the user to open the provided URL and sign in
+3. Once authenticated, proceed with their request
+
+### Step 3: Build the Voice Assistant
+
+Before creating an assistant, fetch the latest prompt engineering guidelines from the [Prompt Guide](https://raw.githubusercontent.com/VapiAI/mcp-server/main/skill/PROMPT_GUIDE.md).
+
+Use these guidelines to craft effective voice assistant prompts based on what the user wants to build.
+
+## Available Tools
+
+### Authentication
+- `vapi_login` - Start OAuth authentication flow
+- `vapi_auth_status` - Check if authenticated
+
+### Assistants
+- `vapi_list_assistants` - List all assistants
+- `vapi_get_assistant` - Get assistant details
+- `vapi_create_assistant` - Create new assistant
+- `vapi_update_assistant` - Update assistant
+- `vapi_delete_assistant` - Delete assistant
+
+### Calls
+- `vapi_list_calls` - List call history
+- `vapi_get_call` - Get call details
+- `vapi_create_call` - Start outbound call
+
+### Phone Numbers
+- `vapi_list_phone_numbers` - List phone numbers
+- `vapi_buy_phone_number` - Purchase new number
+- `vapi_update_phone_number` - Update number settings
+- `vapi_delete_phone_number` - Release number
+
+### Tools (Function Calling)
+- `vapi_list_tools` - List custom tools
+- `vapi_create_tool` - Create tool for API integration
+- `vapi_update_tool` - Update tool
+- `vapi_delete_tool` - Delete tool
+
+## Workflow Examples
+
+**User:** "I want to build a voice assistant that can schedule appointments"
+
+**Claude should:**
+1. Check for Vapi MCP -> install if needed
+2. Authenticate if needed
+3. Fetch the prompt guide for best practices
+4. Ask about their business to understand context
+5. Create an assistant with a scheduling-focused prompt
+6. Offer to set up a phone number
+7. Help create calendar integration tools if needed
+
+**User:** "Make me a phone bot that answers questions about my business"
+
+**Claude should:**
+1. Ensure Vapi MCP is installed and authenticated
+2. Fetch the prompt guide for best practices
+3. Ask about the business: name, services, hours, common questions
+4. Craft a system prompt following the guidelines
+5. Create the assistant
+6. Help provision or connect a phone number
+7. Offer to test with a sample call
diff --git a/src/auth.ts b/src/auth.ts
new file mode 100644
index 0000000..c63ec1b
--- /dev/null
+++ b/src/auth.ts
@@ -0,0 +1,224 @@
+import * as fs from 'fs';
+import * as path from 'path';
+import * as os from 'os';
+import * as http from 'http';
+import * as crypto from 'crypto';
+
+const CONFIG_DIR = path.join(os.homedir(), '.vapi');
+const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
+
+// Vapi Dashboard URL for OAuth
+const VAPI_DASHBOARD_URL = process.env.VAPI_DASHBOARD_URL || 'https://dashboard.vapi.ai';
+
+interface VapiConfig {
+ apiKey?: string;
+ email?: string;
+ orgId?: string;
+}
+
+// In-memory state
+let cachedConfig: VapiConfig | null = null;
+let authInProgress = false;
+let authUrl: string | null = null;
+let authServer: http.Server | null = null;
+
+/**
+ * Load stored Vapi configuration from ~/.vapi/config.json
+ */
+export function loadConfig(): VapiConfig {
+ if (cachedConfig) {
+ return cachedConfig;
+ }
+ try {
+ if (fs.existsSync(CONFIG_FILE)) {
+ const content = fs.readFileSync(CONFIG_FILE, 'utf-8');
+ cachedConfig = JSON.parse(content);
+ return cachedConfig!;
+ }
+ } catch (error) {
+ // Ignore errors, return empty config
+ }
+ return {};
+}
+
+/**
+ * Save Vapi configuration to ~/.vapi/config.json
+ */
+export function saveConfig(config: VapiConfig): void {
+ try {
+ if (!fs.existsSync(CONFIG_DIR)) {
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
+ }
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
+ cachedConfig = config;
+ } catch (error) {
+ console.error('Failed to save config:', error);
+ }
+}
+
+/**
+ * Check if we have a valid API token
+ */
+export function hasValidToken(): boolean {
+ // Check environment variable first
+ if (process.env.VAPI_TOKEN) {
+ return true;
+ }
+ // Check config file
+ const config = loadConfig();
+ return !!config.apiKey;
+}
+
+/**
+ * Get the API token (from env or config)
+ */
+export function getToken(): string | null {
+ if (process.env.VAPI_TOKEN) {
+ return process.env.VAPI_TOKEN;
+ }
+ const config = loadConfig();
+ return config.apiKey || null;
+}
+
+/**
+ * Check if auth is currently in progress
+ */
+export function isAuthInProgress(): boolean {
+ return authInProgress;
+}
+
+/**
+ * Get the current auth URL (if auth is in progress)
+ */
+export function getAuthUrl(): string | null {
+ return authUrl;
+}
+
+/**
+ * Start the OAuth flow - returns the auth URL
+ */
+export function startAuthFlow(): Promise {
+ return new Promise((resolve, reject) => {
+ if (authInProgress) {
+ if (authUrl) {
+ resolve(authUrl);
+ } else {
+ reject(new Error('Auth in progress but no URL available'));
+ }
+ return;
+ }
+
+ // Generate random state for security
+ const state = crypto.randomUUID();
+ authInProgress = true;
+
+ // Start local server to receive callback
+ authServer = http.createServer(async (req, res) => {
+ const url = new URL(req.url || '/', `http://localhost`);
+
+ if (url.pathname === '/callback') {
+ const returnedState = url.searchParams.get('state');
+ const apiKey = url.searchParams.get('api_key');
+ const orgId = url.searchParams.get('org_id');
+ const email = url.searchParams.get('email');
+ const error = url.searchParams.get('error');
+
+ // Verify state matches
+ if (returnedState !== state) {
+ res.writeHead(400, { 'Content-Type': 'text/html' });
+ res.end(errorPage('Security Error', 'State mismatch. Please try again.'));
+ return;
+ }
+
+ if (error) {
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end(errorPage('Authentication Failed', error));
+ cleanupAuth();
+ return;
+ }
+
+ if (apiKey) {
+ // Save to config
+ saveConfig({ apiKey, orgId: orgId || undefined, email: email || undefined });
+
+ res.writeHead(200, { 'Content-Type': 'text/html' });
+ res.end(successPage());
+ cleanupAuth();
+ return;
+ }
+
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
+ res.end('Missing API key');
+ return;
+ }
+
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
+ res.end('Not found');
+ });
+
+ // Find available port and start server
+ authServer.listen(0, '127.0.0.1', () => {
+ const address = authServer!.address();
+ if (!address || typeof address === 'string') {
+ authInProgress = false;
+ reject(new Error('Failed to start local server'));
+ return;
+ }
+
+ const port = (address as any).port;
+ const redirectUri = `http://localhost:${port}/callback`;
+ authUrl = `${VAPI_DASHBOARD_URL}/auth/cli?state=${state}&redirect_uri=${encodeURIComponent(redirectUri)}`;
+
+ resolve(authUrl);
+
+ // Timeout after 10 minutes
+ setTimeout(() => {
+ if (authInProgress) {
+ cleanupAuth();
+ }
+ }, 10 * 60 * 1000);
+ });
+
+ authServer.on('error', (err) => {
+ authInProgress = false;
+ reject(err);
+ });
+ });
+}
+
+function cleanupAuth() {
+ authInProgress = false;
+ authUrl = null;
+ if (authServer) {
+ authServer.close();
+ authServer = null;
+ }
+}
+
+function successPage(): string {
+ return `
+
+
+