vapi_login
Authenticate with Vapi API by calling this tool first to resolve authentication errors when using other tools.
Instructions
Authenticate with Vapi. Call this first if other tools return authentication errors.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Implementation Reference
- src/index.ts:35-96 (handler)The main handler for the vapi_login tool. It checks for existing valid tokens, validates them by making a test API call, handles in-progress auth, or starts a new OAuth flow via startAuthFlow().
mcpServer.tool( 'vapi_login', 'Authenticate with Vapi. Call this first if other tools return authentication errors.', {}, async () => { // Check if we have a token and validate it if (hasValidToken()) { try { const client = getVapiClient(); await client.assistants.list({ limit: 1 }); return { content: [ { type: 'text' as const, text: 'Already authenticated with Vapi! You can now use other Vapi tools.', }, ], }; } catch { // Token is stale — clear it and restart auth clearConfig(); vapiClient = null; } } // Check if auth is already in progress if (isAuthInProgress()) { const url = getAuthUrl(); return { content: [ { type: 'text' as const, text: `Authentication in progress. Please complete sign-in:\n\n${url}\n\nAfter signing in, try your request again.`, }, ], }; } // Start auth flow try { const authUrl = await startAuthFlow(); return { content: [ { type: 'text' as const, text: `Please sign in to Vapi:\n\n${authUrl}\n\nAfter signing in, try your request again.`, }, ], }; } catch (error: any) { return { content: [ { type: 'text' as const, text: `Failed to start authentication: ${error.message}`, }, ], isError: true, }; } } ); - src/index.ts:35-96 (registration)The vapi_login tool is registered directly on the McpServer instance in createMcpServer() via mcpServer.tool().
mcpServer.tool( 'vapi_login', 'Authenticate with Vapi. Call this first if other tools return authentication errors.', {}, async () => { // Check if we have a token and validate it if (hasValidToken()) { try { const client = getVapiClient(); await client.assistants.list({ limit: 1 }); return { content: [ { type: 'text' as const, text: 'Already authenticated with Vapi! You can now use other Vapi tools.', }, ], }; } catch { // Token is stale — clear it and restart auth clearConfig(); vapiClient = null; } } // Check if auth is already in progress if (isAuthInProgress()) { const url = getAuthUrl(); return { content: [ { type: 'text' as const, text: `Authentication in progress. Please complete sign-in:\n\n${url}\n\nAfter signing in, try your request again.`, }, ], }; } // Start auth flow try { const authUrl = await startAuthFlow(); return { content: [ { type: 'text' as const, text: `Please sign in to Vapi:\n\n${authUrl}\n\nAfter signing in, try your request again.`, }, ], }; } catch (error: any) { return { content: [ { type: 'text' as const, text: `Failed to start authentication: ${error.message}`, }, ], isError: true, }; } } ); - src/auth.ts:115-203 (helper)The startAuthFlow() helper function that initiates OAuth flow by starting a local HTTP server for the callback, generating a secure state parameter, constructing the Vapi dashboard auth URL, and opening the browser.
export function startAuthFlow(): Promise<string> { 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)}`; openBrowser(authUrl); resolve(authUrl); // Timeout after 10 minutes setTimeout(() => { if (authInProgress) { cleanupAuth(); } }, 10 * 60 * 1000); }); authServer.on('error', (err) => { authInProgress = false; reject(err); }); }); } - src/auth.ts:77-96 (helper)Helper functions supporting vapi_login: hasValidToken() checks VAPI_TOKEN env or stored apiKey; getToken() retrieves the token; isAuthInProgress() checks OAuth state; getAuthUrl() returns the current auth URL; clearConfig() removes stored credentials.
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; }