@telitask/mcp-server 0.1.3 → 0.2.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 +2 -0
- package/dist/tools/calls.d.ts.map +1 -1
- package/dist/tools/calls.js +43 -12
- package/dist/tools/contacts.d.ts.map +1 -1
- package/dist/tools/contacts.js +1 -0
- package/dist/tools/phone.d.ts +17 -0
- package/dist/tools/phone.d.ts.map +1 -0
- package/dist/tools/phone.js +89 -0
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { runOAuthLogin } from './auth/oauth.js';
|
|
|
6
6
|
import { registerContactTools } from './tools/contacts.js';
|
|
7
7
|
import { registerTaskTools } from './tools/tasks.js';
|
|
8
8
|
import { registerCallTools } from './tools/calls.js';
|
|
9
|
+
import { registerPhoneTools } from './tools/phone.js';
|
|
9
10
|
import { registerResources } from './resources/index.js';
|
|
10
11
|
const DEFAULT_DASHBOARD_URL = 'https://telitask.ai';
|
|
11
12
|
async function runLogin() {
|
|
@@ -58,6 +59,7 @@ async function runServer() {
|
|
|
58
59
|
registerContactTools(server);
|
|
59
60
|
registerTaskTools(server);
|
|
60
61
|
registerCallTools(server);
|
|
62
|
+
registerPhoneTools(server);
|
|
61
63
|
registerResources(server);
|
|
62
64
|
}
|
|
63
65
|
const transport = new StdioServerTransport();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calls.d.ts","sourceRoot":"","sources":["../../src/tools/calls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4JzE,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"calls.d.ts","sourceRoot":"","sources":["../../src/tools/calls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA4JzE,wBAAsB,cAAc,CAAC,MAAM,EAAE;IAC3C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAiF5C;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAyE5C;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAgC5C;AAED,wBAAsB,eAAe,CAAC,MAAM,EAAE;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAuC5C;AAED,wBAAsB,aAAa,CAAC,MAAM,EAAE;IAC1C,OAAO,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAwC5C;AAED,wBAAsB,YAAY,CAAC,MAAM,EAAE;IACzC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CA0F5C;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6EzD"}
|
package/dist/tools/calls.js
CHANGED
|
@@ -144,6 +144,7 @@ export async function handleMakeCall(params) {
|
|
|
144
144
|
callType: 'on_behalf',
|
|
145
145
|
purpose: params.brief,
|
|
146
146
|
contactId: contactResult.id,
|
|
147
|
+
source: 'mcp',
|
|
147
148
|
}),
|
|
148
149
|
});
|
|
149
150
|
if (!response.ok) {
|
|
@@ -196,26 +197,55 @@ export async function handleScheduleCall(params) {
|
|
|
196
197
|
const userId = getUserId();
|
|
197
198
|
const voiceServerUrl = getVoiceServerUrl();
|
|
198
199
|
const accessToken = getAccessToken();
|
|
199
|
-
const contactResult = await resolveContact(params.contact_id, params.contact_name);
|
|
200
|
-
if (Array.isArray(contactResult))
|
|
201
|
-
return contactResult;
|
|
202
200
|
const personaId = await resolvePersonaId(params.persona_id, userId);
|
|
203
201
|
if (!personaId) {
|
|
204
202
|
return [{ type: 'text', text: 'No persona specified and no default persona set. Please provide a persona_id.' }];
|
|
205
203
|
}
|
|
204
|
+
// Determine if this is a self-call or a contact call
|
|
205
|
+
const isContactCall = !!(params.contact_id || params.contact_name);
|
|
206
|
+
let callType;
|
|
207
|
+
let contactId;
|
|
208
|
+
let targetName;
|
|
209
|
+
if (isContactCall) {
|
|
210
|
+
const contactResult = await resolveContact(params.contact_id, params.contact_name);
|
|
211
|
+
if (Array.isArray(contactResult))
|
|
212
|
+
return contactResult;
|
|
213
|
+
callType = 'on_behalf';
|
|
214
|
+
contactId = contactResult.id;
|
|
215
|
+
targetName = contactResult.full_name;
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
// Self-call: verify user has a phone number
|
|
219
|
+
const supabase = getAuthenticatedClient();
|
|
220
|
+
const { data: userProfile, error } = await supabase
|
|
221
|
+
.from('users')
|
|
222
|
+
.select('phone_number')
|
|
223
|
+
.eq('id', userId)
|
|
224
|
+
.single();
|
|
225
|
+
if (error) {
|
|
226
|
+
return [{ type: 'text', text: `Failed to look up your profile: ${error.message}` }];
|
|
227
|
+
}
|
|
228
|
+
if (!userProfile?.phone_number) {
|
|
229
|
+
return [{ type: 'text', text: 'No phone number on your profile. Please add one in the dashboard settings.' }];
|
|
230
|
+
}
|
|
231
|
+
callType = 'direct';
|
|
232
|
+
targetName = 'you';
|
|
233
|
+
}
|
|
234
|
+
const body = {
|
|
235
|
+
personaId,
|
|
236
|
+
callType,
|
|
237
|
+
purpose: params.brief,
|
|
238
|
+
scheduledAt: params.scheduled_at,
|
|
239
|
+
source: 'mcp',
|
|
240
|
+
...(contactId && { contactId }),
|
|
241
|
+
};
|
|
206
242
|
const response = await fetch(`${voiceServerUrl}/api/scheduled-calls`, {
|
|
207
243
|
method: 'POST',
|
|
208
244
|
headers: {
|
|
209
245
|
'Content-Type': 'application/json',
|
|
210
246
|
Authorization: `Bearer ${accessToken}`,
|
|
211
247
|
},
|
|
212
|
-
body: JSON.stringify(
|
|
213
|
-
personaId,
|
|
214
|
-
callType: 'on_behalf',
|
|
215
|
-
contactId: contactResult.id,
|
|
216
|
-
purpose: params.brief,
|
|
217
|
-
scheduledAt: params.scheduled_at,
|
|
218
|
-
}),
|
|
248
|
+
body: JSON.stringify(body),
|
|
219
249
|
});
|
|
220
250
|
if (!response.ok) {
|
|
221
251
|
const errBody = await response.text().catch(() => '');
|
|
@@ -224,7 +254,7 @@ export async function handleScheduleCall(params) {
|
|
|
224
254
|
const result = await response.json();
|
|
225
255
|
return [{
|
|
226
256
|
type: 'text',
|
|
227
|
-
text: `Call scheduled to **${
|
|
257
|
+
text: `Call scheduled to **${targetName}** at ${params.scheduled_at}.\nBrief: ${params.brief}\nScheduled call ID: ${result.id ?? 'pending'}`,
|
|
228
258
|
}];
|
|
229
259
|
}
|
|
230
260
|
export async function handleCancelCall(params) {
|
|
@@ -351,6 +381,7 @@ export async function handleCallMe(params) {
|
|
|
351
381
|
personaId,
|
|
352
382
|
phoneNumber: userProfile.phone_number,
|
|
353
383
|
callType: 'direct',
|
|
384
|
+
source: 'mcp',
|
|
354
385
|
};
|
|
355
386
|
if (params.brief)
|
|
356
387
|
body.purpose = params.brief;
|
|
@@ -425,7 +456,7 @@ export function registerCallTools(server) {
|
|
|
425
456
|
}, async ({ contact_id, contact_name, brief, persona_id, wait_for_result }) => ({
|
|
426
457
|
content: await handleMakeCall({ contact_id, contact_name, brief, persona_id, wait_for_result }),
|
|
427
458
|
}));
|
|
428
|
-
server.tool('schedule_call', 'Schedule a phone call for a future time.
|
|
459
|
+
server.tool('schedule_call', 'Schedule a phone call for a future time. If contact_id or contact_name is provided, schedules a call to that contact. If neither is provided, schedules a call to the user\'s own phone number.', {
|
|
429
460
|
contact_id: z.string().optional().describe('Contact ID to call'),
|
|
430
461
|
contact_name: z.string().optional().describe('Contact name to search for'),
|
|
431
462
|
brief: z.string().describe('Purpose/brief for the call'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contacts.d.ts","sourceRoot":"","sources":["../../src/tools/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAwC5C;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"contacts.d.ts","sourceRoot":"","sources":["../../src/tools/contacts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAKzE,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAwC5C;AAED,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAuB5C;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2B5D"}
|
package/dist/tools/contacts.js
CHANGED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
export declare function handleSendPhoneCode(params: {
|
|
3
|
+
phone_number: string;
|
|
4
|
+
country_code?: string;
|
|
5
|
+
}): Promise<{
|
|
6
|
+
type: 'text';
|
|
7
|
+
text: string;
|
|
8
|
+
}[]>;
|
|
9
|
+
export declare function handleVerifyPhoneCode(params: {
|
|
10
|
+
phone_number: string;
|
|
11
|
+
code: string;
|
|
12
|
+
}): Promise<{
|
|
13
|
+
type: 'text';
|
|
14
|
+
text: string;
|
|
15
|
+
}[]>;
|
|
16
|
+
export declare function registerPhoneTools(server: McpServer): void;
|
|
17
|
+
//# sourceMappingURL=phone.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phone.d.ts","sourceRoot":"","sources":["../../src/tools/phone.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAqBzE,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CA8C5C;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAAC,CAsB5C;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAwB1D"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { parsePhoneNumberFromString } from 'libphonenumber-js';
|
|
3
|
+
import { refreshSessionIfNeeded, getVoiceServerUrl, getAccessToken, } from '../lib/supabase.js';
|
|
4
|
+
/** Map voice server error codes to user-friendly messages */
|
|
5
|
+
const ERROR_MESSAGES = {
|
|
6
|
+
invalid_code: 'Invalid code. Please try again.',
|
|
7
|
+
code_expired: "Code expired. Say 'resend' and I'll send a new one.",
|
|
8
|
+
max_attempts_exceeded: 'Too many failed attempts. Request a new code.',
|
|
9
|
+
phone_already_verified: 'This phone number is already in use by another account.',
|
|
10
|
+
rate_limit_exceeded: 'Too many attempts. Please wait a few minutes.',
|
|
11
|
+
country_not_supported: 'Phone numbers from this country are not currently supported.',
|
|
12
|
+
cooldown: 'Please wait before requesting another code.',
|
|
13
|
+
};
|
|
14
|
+
export async function handleSendPhoneCode(params) {
|
|
15
|
+
await refreshSessionIfNeeded();
|
|
16
|
+
const voiceServerUrl = getVoiceServerUrl();
|
|
17
|
+
const accessToken = getAccessToken();
|
|
18
|
+
// Normalize phone number to E.164
|
|
19
|
+
let e164;
|
|
20
|
+
if (params.phone_number.startsWith('+')) {
|
|
21
|
+
// International format — parse directly
|
|
22
|
+
const parsed = parsePhoneNumberFromString(params.phone_number);
|
|
23
|
+
if (!parsed?.isValid()) {
|
|
24
|
+
return [{ type: 'text', text: 'Could not parse that phone number. Please provide a valid phone number.' }];
|
|
25
|
+
}
|
|
26
|
+
e164 = parsed.format('E.164');
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
// Local format — need country_code
|
|
30
|
+
if (!params.country_code) {
|
|
31
|
+
return [{ type: 'text', text: "I need your country to verify this number. Which country are you in? (e.g., US, GB, KE)" }];
|
|
32
|
+
}
|
|
33
|
+
const parsed = parsePhoneNumberFromString(params.phone_number, params.country_code);
|
|
34
|
+
if (!parsed?.isValid()) {
|
|
35
|
+
return [{ type: 'text', text: `Could not parse "${params.phone_number}" as a valid phone number for ${params.country_code}.` }];
|
|
36
|
+
}
|
|
37
|
+
e164 = parsed.format('E.164');
|
|
38
|
+
}
|
|
39
|
+
// POST to voice server
|
|
40
|
+
const response = await fetch(`${voiceServerUrl}/api/phone/send-code`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: {
|
|
43
|
+
'Content-Type': 'application/json',
|
|
44
|
+
Authorization: `Bearer ${accessToken}`,
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify({ phoneNumber: e164 }),
|
|
47
|
+
});
|
|
48
|
+
if (!response.ok) {
|
|
49
|
+
const body = await response.json().catch(() => ({}));
|
|
50
|
+
const errorKey = body.error ?? 'unknown';
|
|
51
|
+
const message = ERROR_MESSAGES[errorKey] ?? body.message ?? 'Failed to send verification code. Please try again.';
|
|
52
|
+
return [{ type: 'text', text: message }];
|
|
53
|
+
}
|
|
54
|
+
return [{ type: 'text', text: `Verification code sent to ${e164}. What's the 6-digit code?` }];
|
|
55
|
+
}
|
|
56
|
+
export async function handleVerifyPhoneCode(params) {
|
|
57
|
+
await refreshSessionIfNeeded();
|
|
58
|
+
const voiceServerUrl = getVoiceServerUrl();
|
|
59
|
+
const accessToken = getAccessToken();
|
|
60
|
+
const response = await fetch(`${voiceServerUrl}/api/phone/verify-code`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
Authorization: `Bearer ${accessToken}`,
|
|
65
|
+
},
|
|
66
|
+
body: JSON.stringify({ phoneNumber: params.phone_number, code: params.code }),
|
|
67
|
+
});
|
|
68
|
+
if (!response.ok) {
|
|
69
|
+
const body = await response.json().catch(() => ({}));
|
|
70
|
+
const errorKey = body.error ?? 'unknown';
|
|
71
|
+
const message = ERROR_MESSAGES[errorKey] ?? body.message ?? 'Failed to verify code. Please try again.';
|
|
72
|
+
return [{ type: 'text', text: message }];
|
|
73
|
+
}
|
|
74
|
+
return [{ type: 'text', text: `Phone number verified: ${params.phone_number}` }];
|
|
75
|
+
}
|
|
76
|
+
export function registerPhoneTools(server) {
|
|
77
|
+
server.tool('send_phone_code', "Send a verification code to a phone number. Accepts local format (e.g., '0712345678') with a country_code, or international format (e.g., '+254712345678'). Use this when the user wants to add or verify their phone number.", {
|
|
78
|
+
phone_number: z.string().describe("Phone number in local or international format (e.g., '0712345678' or '+254712345678')"),
|
|
79
|
+
country_code: z.string().optional().describe("ISO 3166-1 alpha-2 country code (e.g., 'KE', 'US'). Required when phone_number is in local format."),
|
|
80
|
+
}, async ({ phone_number, country_code }) => ({
|
|
81
|
+
content: await handleSendPhoneCode({ phone_number, country_code }),
|
|
82
|
+
}));
|
|
83
|
+
server.tool('verify_phone_code', 'Verify a phone number with the 6-digit code sent via SMS. Use the E.164 phone number from the previous send_phone_code step.', {
|
|
84
|
+
phone_number: z.string().describe("Phone number in E.164 format (e.g., '+254712345678') — use the number returned by send_phone_code"),
|
|
85
|
+
code: z.string().describe('The 6-digit verification code from the SMS'),
|
|
86
|
+
}, async ({ phone_number, code }) => ({
|
|
87
|
+
content: await handleVerifyPhoneCode({ phone_number, code }),
|
|
88
|
+
}));
|
|
89
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telitask/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "TeliTask MCP server — manage contacts, tasks, and calls from AI assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
50
50
|
"@supabase/supabase-js": "^2.49.4",
|
|
51
|
+
"libphonenumber-js": "^1.12.36",
|
|
51
52
|
"open": "^10.1.0",
|
|
52
53
|
"zod": "^3.24.2"
|
|
53
54
|
},
|