@talkspresso/mcp-server 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,21 +1,42 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { TalkspressoClient } from './client.js';
5
- import { registerAppointmentTools } from './tools/appointments.js';
6
- import { registerClientTools } from './tools/clients.js';
7
- import { registerServiceTools } from './tools/services.js';
8
- import { registerEarningsTools } from './tools/earnings.js';
9
- import { registerProfileTools } from './tools/profile.js';
10
- const server = new McpServer({
11
- name: 'talkspresso',
12
- version: '1.0.0',
13
- });
14
- const apiClient = new TalkspressoClient();
15
- registerAppointmentTools(server, apiClient);
16
- registerClientTools(server, apiClient);
17
- registerServiceTools(server, apiClient);
18
- registerEarningsTools(server, apiClient);
19
- registerProfileTools(server, apiClient);
20
- const transport = new StdioServerTransport();
21
- await server.connect(transport);
2
+ const args = process.argv.slice(2);
3
+ if (args.includes('--setup')) {
4
+ const { runSetup } = await import('./setup.js');
5
+ await runSetup();
6
+ }
7
+ else if (args.includes('--help')) {
8
+ console.log('Usage: talkspresso-mcp [options]\n');
9
+ console.log('Options:');
10
+ console.log(' --setup Interactive setup wizard');
11
+ console.log(' --help Show this help message');
12
+ console.log(' --version Show version number');
13
+ console.log('\nWith no options, starts the MCP server (stdio transport).');
14
+ process.exit(0);
15
+ }
16
+ else if (args.includes('--version')) {
17
+ console.log('1.1.0');
18
+ process.exit(0);
19
+ }
20
+ else {
21
+ const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js');
22
+ const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
23
+ const { TalkspressoClient } = await import('./client.js');
24
+ const { registerAppointmentTools } = await import('./tools/appointments.js');
25
+ const { registerClientTools } = await import('./tools/clients.js');
26
+ const { registerServiceTools } = await import('./tools/services.js');
27
+ const { registerEarningsTools } = await import('./tools/earnings.js');
28
+ const { registerProfileTools } = await import('./tools/profile.js');
29
+ const server = new McpServer({
30
+ name: 'talkspresso',
31
+ version: '1.1.0',
32
+ });
33
+ const apiClient = new TalkspressoClient();
34
+ registerAppointmentTools(server, apiClient);
35
+ registerClientTools(server, apiClient);
36
+ registerServiceTools(server, apiClient);
37
+ registerEarningsTools(server, apiClient);
38
+ registerProfileTools(server, apiClient);
39
+ const transport = new StdioServerTransport();
40
+ await server.connect(transport);
41
+ }
42
+ export {};
@@ -0,0 +1,18 @@
1
+ export declare class SetupApiClient {
2
+ private http;
3
+ constructor();
4
+ setAccessToken(token: string): void;
5
+ register(data: {
6
+ name: string;
7
+ email: string;
8
+ password: string;
9
+ timezone: string;
10
+ }): Promise<any>;
11
+ login(data: {
12
+ email: string;
13
+ password: string;
14
+ }): Promise<any>;
15
+ createApiKey(name: string): Promise<any>;
16
+ getStatus(err: unknown): number | undefined;
17
+ private formatError;
18
+ }
@@ -0,0 +1,55 @@
1
+ import axios from 'axios';
2
+ export class SetupApiClient {
3
+ http;
4
+ constructor() {
5
+ const baseURL = process.env.TALKSPRESSO_API_URL || 'https://api.talkspresso.com';
6
+ this.http = axios.create({
7
+ baseURL,
8
+ headers: { 'Content-Type': 'application/json' },
9
+ timeout: 30000,
10
+ });
11
+ }
12
+ setAccessToken(token) {
13
+ this.http.defaults.headers['Authorization'] = `Bearer ${token}`;
14
+ }
15
+ async register(data) {
16
+ try {
17
+ const response = await this.http.post('/user/register', data);
18
+ return response.data?.data;
19
+ }
20
+ catch (err) {
21
+ throw this.formatError(err);
22
+ }
23
+ }
24
+ async login(data) {
25
+ try {
26
+ const response = await this.http.post('/user/login', data);
27
+ return response.data?.data;
28
+ }
29
+ catch (err) {
30
+ throw this.formatError(err);
31
+ }
32
+ }
33
+ async createApiKey(name) {
34
+ try {
35
+ const response = await this.http.post('/api-keys', { name });
36
+ return response.data?.data;
37
+ }
38
+ catch (err) {
39
+ throw this.formatError(err);
40
+ }
41
+ }
42
+ getStatus(err) {
43
+ if (err instanceof Error && 'status' in err)
44
+ return err.status;
45
+ return undefined;
46
+ }
47
+ formatError(err) {
48
+ const data = err.response?.data;
49
+ const status = err.response?.status;
50
+ const message = data?.error || err.message || 'API request failed';
51
+ const error = new Error(message);
52
+ error.status = status;
53
+ return error;
54
+ }
55
+ }
@@ -0,0 +1 @@
1
+ export declare function runSetup(): Promise<void>;
package/dist/setup.js ADDED
@@ -0,0 +1,199 @@
1
+ import chalk from 'chalk';
2
+ import { confirm, input, password as passwordPrompt } from '@inquirer/prompts';
3
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
4
+ import { join } from 'path';
5
+ import { homedir } from 'os';
6
+ import { SetupApiClient } from './setup-api.js';
7
+ const LOGO = `
8
+ ${chalk.hex('#032B43').bold('╔╦╗')}${chalk.hex('#0A4B6E')('┌─┐┬ ┬┌─┌─┐┌─┐┬─┐┌─┐┌─┐┌─┐┌─┐')}
9
+ ${chalk.hex('#032B43').bold(' ║ ')}${chalk.hex('#0A4B6E')('├─┤│ ├┴┐└─┐├─┘├┬┘├┤ └─┐└─┐│ │')}
10
+ ${chalk.hex('#032B43').bold(' ╩ ')}${chalk.hex('#0A4B6E')('┴ ┴┴─┘┴ ┴└─┘┴ ┴└─└─┘└─┘└─┘└─┘')}
11
+ `;
12
+ function getClaudeDesktopConfigPath() {
13
+ const home = homedir();
14
+ if (process.platform === 'darwin') {
15
+ return join(home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
16
+ }
17
+ else if (process.platform === 'win32') {
18
+ return join(process.env.APPDATA || join(home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');
19
+ }
20
+ return join(home, '.config', 'Claude', 'claude_desktop_config.json');
21
+ }
22
+ function getClaudeCodeConfigPath() {
23
+ return join(homedir(), '.claude', 'mcp_servers.json');
24
+ }
25
+ function readJsonSafe(path) {
26
+ try {
27
+ return JSON.parse(readFileSync(path, 'utf-8'));
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ function writeJsonSafe(path, data) {
34
+ try {
35
+ const dir = join(path, '..');
36
+ if (!existsSync(dir))
37
+ mkdirSync(dir, { recursive: true });
38
+ writeFileSync(path, JSON.stringify(data, null, 2) + '\n', 'utf-8');
39
+ return true;
40
+ }
41
+ catch {
42
+ return false;
43
+ }
44
+ }
45
+ function mcpEntry(apiKey) {
46
+ return {
47
+ command: 'npx',
48
+ args: ['-y', '@talkspresso/mcp-server'],
49
+ env: { TALKSPRESSO_API_KEY: apiKey },
50
+ };
51
+ }
52
+ export async function runSetup() {
53
+ process.on('SIGINT', () => {
54
+ console.log(`\n\n ${chalk.dim('Setup cancelled. Run')} npx @talkspresso/mcp-server --setup ${chalk.dim('anytime.')}\n`);
55
+ process.exit(0);
56
+ });
57
+ const api = new SetupApiClient();
58
+ // Welcome
59
+ console.log(LOGO);
60
+ console.log(` ${chalk.white.bold('Manage your business with AI.')}`);
61
+ console.log(` ${chalk.dim("Let's get you connected in about 30 seconds.")}\n`);
62
+ // Account
63
+ let accessToken;
64
+ let userName;
65
+ let userEmail;
66
+ const hasAccount = await confirm({ message: 'Do you already have a Talkspresso account?' });
67
+ if (hasAccount) {
68
+ // Login flow
69
+ let attempts = 0;
70
+ while (true) {
71
+ const email = await input({ message: 'Email:' });
72
+ const pw = await passwordPrompt({ message: 'Password:' });
73
+ try {
74
+ const user = await api.login({ email, password: pw });
75
+ accessToken = user.access_token;
76
+ userName = user.name || email.split('@')[0];
77
+ userEmail = email;
78
+ console.log(` ${chalk.green('✓')} Logged in as ${chalk.bold(email)}\n`);
79
+ break;
80
+ }
81
+ catch (err) {
82
+ attempts++;
83
+ if (attempts >= 3) {
84
+ console.log(`\n ${chalk.red('Too many failed attempts.')} Create an account at ${chalk.cyan('app.talkspresso.com')}\n`);
85
+ process.exit(1);
86
+ }
87
+ console.log(` ${chalk.red('✗')} Invalid email or password. Try again.\n`);
88
+ }
89
+ }
90
+ }
91
+ else {
92
+ // Registration flow
93
+ const name = await input({ message: 'Your name:' });
94
+ const email = await input({ message: 'Email:' });
95
+ const pw = await passwordPrompt({ message: 'Password (min 8 characters):' });
96
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
97
+ try {
98
+ const user = await api.register({ name, email, password: pw, timezone });
99
+ accessToken = user.access_token;
100
+ userName = name;
101
+ userEmail = email;
102
+ console.log(` ${chalk.green('✓')} Account created for ${chalk.bold(email)}`);
103
+ console.log(` ${chalk.dim(`Timezone: ${timezone}`)}\n`);
104
+ }
105
+ catch (err) {
106
+ const status = api.getStatus(err);
107
+ if (status === 409) {
108
+ console.log(`\n ${chalk.yellow('Account already exists.')} Let's log you in instead.\n`);
109
+ const pw2 = await passwordPrompt({ message: 'Password:' });
110
+ try {
111
+ const user = await api.login({ email, password: pw2 });
112
+ accessToken = user.access_token;
113
+ userName = user.name || name;
114
+ userEmail = email;
115
+ console.log(` ${chalk.green('✓')} Logged in as ${chalk.bold(email)}\n`);
116
+ }
117
+ catch {
118
+ console.log(`\n ${chalk.red('Could not log in.')} Try again at ${chalk.cyan('app.talkspresso.com')}\n`);
119
+ process.exit(1);
120
+ }
121
+ }
122
+ else {
123
+ console.log(`\n ${chalk.red('Could not create account:')} ${err.message}\n`);
124
+ process.exit(1);
125
+ }
126
+ }
127
+ }
128
+ // Generate API key
129
+ api.setAccessToken(accessToken);
130
+ let apiKey;
131
+ try {
132
+ const keyData = await api.createApiKey('Claude AI Assistant');
133
+ apiKey = keyData.key;
134
+ console.log(` ${chalk.green('✓')} API key created: ${chalk.dim(apiKey.substring(0, 12) + '...')}\n`);
135
+ }
136
+ catch (err) {
137
+ console.log(` ${chalk.red('Could not create API key:')} ${err.message}`);
138
+ console.log(` ${chalk.dim('Create one manually at')} ${chalk.cyan('app.talkspresso.com/settings/api-keys')}\n`);
139
+ process.exit(1);
140
+ }
141
+ // Configure Claude Desktop
142
+ const desktopPath = getClaudeDesktopConfigPath();
143
+ const desktopDir = join(desktopPath, '..');
144
+ let configuredDesktop = false;
145
+ if (existsSync(desktopDir)) {
146
+ const addDesktop = await confirm({ message: 'Add Talkspresso to Claude Desktop?' });
147
+ if (addDesktop) {
148
+ const existing = readJsonSafe(desktopPath) || {};
149
+ if (!existing.mcpServers)
150
+ existing.mcpServers = {};
151
+ existing.mcpServers.talkspresso = mcpEntry(apiKey);
152
+ configuredDesktop = writeJsonSafe(desktopPath, existing);
153
+ if (configuredDesktop) {
154
+ console.log(` ${chalk.green('✓')} Claude Desktop configured\n`);
155
+ }
156
+ else {
157
+ console.log(` ${chalk.red('✗')} Could not write config. Add manually:\n`);
158
+ console.log(chalk.dim(JSON.stringify({ mcpServers: { talkspresso: mcpEntry(apiKey) } }, null, 2)));
159
+ console.log();
160
+ }
161
+ }
162
+ }
163
+ // Configure Claude Code
164
+ const codePath = getClaudeCodeConfigPath();
165
+ let configuredCode = false;
166
+ const addCode = await confirm({ message: 'Add Talkspresso to Claude Code?' });
167
+ if (addCode) {
168
+ const existing = readJsonSafe(codePath) || {};
169
+ existing.talkspresso = mcpEntry(apiKey);
170
+ configuredCode = writeJsonSafe(codePath, existing);
171
+ if (configuredCode) {
172
+ console.log(` ${chalk.green('✓')} Claude Code configured\n`);
173
+ }
174
+ else {
175
+ console.log(` ${chalk.red('✗')} Could not write config. Add manually:\n`);
176
+ console.log(chalk.dim(JSON.stringify({ talkspresso: mcpEntry(apiKey) }, null, 2)));
177
+ console.log();
178
+ }
179
+ }
180
+ // Success
181
+ console.log(chalk.green.bold('\n You\'re all set!\n'));
182
+ const done = [];
183
+ done.push(`Account: ${userEmail}`);
184
+ done.push(`API key generated and saved`);
185
+ if (configuredDesktop)
186
+ done.push('Claude Desktop configured');
187
+ if (configuredCode)
188
+ done.push('Claude Code configured');
189
+ for (const item of done) {
190
+ console.log(` ${chalk.green('✓')} ${item}`);
191
+ }
192
+ console.log(`\n ${chalk.bold('Next steps:')}\n`);
193
+ if (configuredDesktop || configuredCode) {
194
+ console.log(` 1. ${chalk.white('Restart Claude')} (if it\'s open)`);
195
+ console.log(` 2. ${chalk.white('Try:')} "Show me my Talkspresso schedule"`);
196
+ }
197
+ console.log(` ${configuredDesktop || configuredCode ? '3' : '1'}. ${chalk.white('Open your dashboard:')} ${chalk.cyan('https://app.talkspresso.com/dashboard')}`);
198
+ console.log(` ${chalk.dim('Brew will help you set up your first session.')}\n`);
199
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talkspresso/mcp-server",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Manage your Talkspresso business through Claude, ChatGPT, or any MCP-compatible AI",
5
5
  "bin": {
6
6
  "talkspresso-mcp": "./dist/index.js"
@@ -12,17 +12,25 @@
12
12
  "dev": "tsx src/index.ts",
13
13
  "prepublishOnly": "npm run build"
14
14
  },
15
- "keywords": ["mcp", "talkspresso", "ai", "claude", "chatgpt"],
15
+ "keywords": [
16
+ "mcp",
17
+ "talkspresso",
18
+ "ai",
19
+ "claude",
20
+ "chatgpt"
21
+ ],
16
22
  "license": "MIT",
17
23
  "dependencies": {
24
+ "@inquirer/prompts": "^8.2.1",
18
25
  "@modelcontextprotocol/sdk": "^1.12.0",
19
26
  "axios": "^1.7.0",
27
+ "chalk": "^5.6.2",
20
28
  "zod": "^3.23.0"
21
29
  },
22
30
  "devDependencies": {
23
- "typescript": "^5.5.0",
31
+ "@types/node": "^22.0.0",
24
32
  "tsx": "^4.0.0",
25
- "@types/node": "^22.0.0"
33
+ "typescript": "^5.5.0"
26
34
  },
27
35
  "files": [
28
36
  "dist/**/*"