Skip to content

Commit c5daf98

Browse files
lukatmyshuclaude
andcommitted
feat: add vapi_logout, auto-open browser on login, remove vapi_auth_status
- Add vapi_logout tool to clear stored credentials and reset client - Add clearConfig() to auth module to delete ~/.vapi/config.json - Auto-open browser when OAuth flow starts using spawn - Validate token with API call in vapi_login, clear if stale - Remove redundant vapi_auth_status tool (vapi_login covers it) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 93bbeab commit c5daf98

5 files changed

Lines changed: 62 additions & 42 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ Connect to Vapi's hosted MCP server from any MCP client:
199199
| Tool | Description |
200200
|------|-------------|
201201
| `vapi_login` | Start OAuth flow |
202-
| `vapi_auth_status` | Check auth status |
202+
| `vapi_logout` | Log out and clear credentials |
203203

204204
---
205205

skill/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Use these guidelines to craft effective voice assistant prompts based on what th
3939

4040
### Authentication
4141
- `vapi_login` - Start OAuth authentication flow
42-
- `vapi_auth_status` - Check if authenticated
42+
- `vapi_logout` - Log out and clear stored credentials
4343

4444
### Assistants
4545
- `vapi_list_assistants` - List all assistants

src/auth.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as path from 'path';
33
import * as os from 'os';
44
import * as http from 'http';
55
import * as crypto from 'crypto';
6+
import { spawn } from 'child_process';
67

78
const CONFIG_DIR = path.join(os.homedir(), '.vapi');
89
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
@@ -56,6 +57,20 @@ export function saveConfig(config: VapiConfig): void {
5657
}
5758
}
5859

60+
/**
61+
* Clear stored Vapi configuration and reset in-memory state
62+
*/
63+
export function clearConfig(): void {
64+
try {
65+
if (fs.existsSync(CONFIG_FILE)) {
66+
fs.unlinkSync(CONFIG_FILE);
67+
}
68+
} catch (error) {
69+
// Ignore errors
70+
}
71+
cachedConfig = null;
72+
}
73+
5974
/**
6075
* Check if we have a valid API token
6176
*/
@@ -169,6 +184,7 @@ export function startAuthFlow(): Promise<string> {
169184
const redirectUri = `http://localhost:${port}/callback`;
170185
authUrl = `${VAPI_DASHBOARD_URL}/auth/cli?state=${state}&redirect_uri=${encodeURIComponent(redirectUri)}`;
171186

187+
openBrowser(authUrl);
172188
resolve(authUrl);
173189

174190
// Timeout after 10 minutes
@@ -186,6 +202,23 @@ export function startAuthFlow(): Promise<string> {
186202
});
187203
}
188204

205+
function openBrowser(url: string) {
206+
try {
207+
const cmd = process.platform === 'darwin'
208+
? 'open'
209+
: process.platform === 'win32'
210+
? 'start'
211+
: 'xdg-open';
212+
const child = spawn(cmd, [url], {
213+
detached: true,
214+
stdio: 'ignore',
215+
});
216+
child.unref();
217+
} catch {
218+
// Ignore — URL is still returned in the response as fallback
219+
}
220+
}
221+
189222
function cleanupAuth() {
190223
authInProgress = false;
191224
authUrl = null;

src/index.ts

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
44
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
55
import { VapiClient } from '@vapi-ai/server-sdk';
6-
import { hasValidToken, getToken, startAuthFlow, isAuthInProgress, getAuthUrl } from './auth.js';
6+
import { hasValidToken, getToken, startAuthFlow, isAuthInProgress, getAuthUrl, clearConfig } from './auth.js';
77
import { registerAllTools } from './tools/index.js';
88

99
import dotenv from 'dotenv';
@@ -37,16 +37,24 @@ function createMcpServer() {
3737
'Authenticate with Vapi. Call this first if other tools return authentication errors.',
3838
{},
3939
async () => {
40-
// Check if already authenticated
40+
// Check if we have a token and validate it
4141
if (hasValidToken()) {
42-
return {
43-
content: [
44-
{
45-
type: 'text' as const,
46-
text: 'Already authenticated with Vapi! You can now use other Vapi tools.',
47-
},
48-
],
49-
};
42+
try {
43+
const client = getVapiClient();
44+
await client.assistants.list({ limit: 1 });
45+
return {
46+
content: [
47+
{
48+
type: 'text' as const,
49+
text: 'Already authenticated with Vapi! You can now use other Vapi tools.',
50+
},
51+
],
52+
};
53+
} catch {
54+
// Token is stale — clear it and restart auth
55+
clearConfig();
56+
vapiClient = null;
57+
}
5058
}
5159

5260
// Check if auth is already in progress
@@ -56,7 +64,7 @@ function createMcpServer() {
5664
content: [
5765
{
5866
type: 'text' as const,
59-
text: `Authentication in progress. Please complete sign-in at:\n\n${url}\n\nAfter signing in, try your request again.`,
67+
text: `Authentication in progress. Please complete sign-in:\n\n${url}\n\nAfter signing in, try your request again.`,
6068
},
6169
],
6270
};
@@ -69,7 +77,7 @@ function createMcpServer() {
6977
content: [
7078
{
7179
type: 'text' as const,
72-
text: `Please sign in to Vapi by opening this URL:\n\n${authUrl}\n\nAfter signing in, try your request again.`,
80+
text: `Please sign in to Vapi:\n\n${authUrl}\n\nAfter signing in, try your request again.`,
7381
},
7482
],
7583
};
@@ -87,40 +95,19 @@ function createMcpServer() {
8795
}
8896
);
8997

90-
// Register status tool
98+
// Register logout tool
9199
mcpServer.tool(
92-
'vapi_auth_status',
93-
'Check Vapi authentication status',
100+
'vapi_logout',
101+
'Log out of Vapi and clear stored credentials. Use this if your auth token is stale or you want to switch accounts.',
94102
{},
95103
async () => {
96-
if (hasValidToken()) {
97-
return {
98-
content: [
99-
{
100-
type: 'text' as const,
101-
text: 'Authenticated with Vapi and ready to use.',
102-
},
103-
],
104-
};
105-
}
106-
107-
if (isAuthInProgress()) {
108-
const url = getAuthUrl();
109-
return {
110-
content: [
111-
{
112-
type: 'text' as const,
113-
text: `Authentication in progress. Please complete sign-in at:\n\n${url}`,
114-
},
115-
],
116-
};
117-
}
118-
104+
clearConfig();
105+
vapiClient = null;
119106
return {
120107
content: [
121108
{
122109
type: 'text' as const,
123-
text: 'Not authenticated. Use the vapi_login tool to sign in.',
110+
text: 'Logged out of Vapi. Use vapi_login to sign in again.',
124111
},
125112
],
126113
};

src/tools/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export function createAuthRequiredResponse(url: string): ToolResponse {
3434
content: [
3535
{
3636
type: 'text' as const,
37-
text: `Authentication required. Please sign in at:\n\n${url}\n\nAfter signing in, try your request again.`,
37+
text: `Authentication required. Please sign in:\n\n${url}\n\nAfter signing in, try your request again.`,
3838
},
3939
],
4040
isError: true,

0 commit comments

Comments
 (0)