@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 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,CAgF5C;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,CAwC5C;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,CAyF5C;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA6EzD"}
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"}
@@ -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 **${contactResult.full_name}** at ${params.scheduled_at}.\nBrief: ${params.brief}\nScheduled call ID: ${result.id ?? 'pending'}`,
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. Resolves contacts by ID or name.', {
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,CAsB5C;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA2B5D"}
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"}
@@ -50,6 +50,7 @@ export async function handleCreateContact(params) {
50
50
  email: params.email ?? null,
51
51
  notes: params.notes ?? null,
52
52
  user_id: userId,
53
+ external_source: 'mcp',
53
54
  })
54
55
  .select()
55
56
  .single();
@@ -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.1.3",
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
  },