elevenlabs-voice-agent-mcp 1.0.0 → 1.0.2

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.
Files changed (45) hide show
  1. package/.claude/settings.local.json +23 -0
  2. package/.env.example +3 -0
  3. package/AGENTS.md +37 -0
  4. package/CLAUDE.md +233 -0
  5. package/README.md +9 -30
  6. package/call-exact-format.ts +66 -0
  7. package/call-wait-null.ts +71 -0
  8. package/create-agent-simple.ts +84 -0
  9. package/create-new-agent.ts +98 -0
  10. package/demo-agent-system-prompt.xml +166 -0
  11. package/dist/index.js +1 -3
  12. package/dist/index.js.map +1 -1
  13. package/elevenlabs-voice-agent-mcp-1.0.0.tgz +0 -0
  14. package/em_positive_leads.csv +25 -0
  15. package/list-resources.ts +93 -0
  16. package/make-call-now.ts +66 -0
  17. package/make-test-call.ts +80 -0
  18. package/openapi.json +3238 -0
  19. package/package.json +3 -33
  20. package/src/constants.ts +62 -0
  21. package/src/index.ts +141 -0
  22. package/src/schemas/agent-schemas.ts +193 -0
  23. package/src/schemas/batch-calling-schemas.ts +96 -0
  24. package/src/schemas/common-schemas.ts +83 -0
  25. package/src/schemas/conversation-schemas.ts +44 -0
  26. package/src/schemas/outbound-schemas.ts +70 -0
  27. package/src/schemas/phone-number-schemas.ts +140 -0
  28. package/src/schemas/tool-schemas.ts +161 -0
  29. package/src/services/elevenlabs-api.ts +113 -0
  30. package/src/services/formatters.ts +623 -0
  31. package/src/tools/agent-tools.ts +425 -0
  32. package/src/tools/batch-calling-tools.ts +237 -0
  33. package/src/tools/conversation-tools.ts +167 -0
  34. package/src/tools/knowledge-tools.ts +73 -0
  35. package/src/tools/outbound-tools.ts +95 -0
  36. package/src/tools/phone-number-tools.ts +346 -0
  37. package/src/tools/tool-tools.ts +195 -0
  38. package/src/tools/utility-tools.ts +147 -0
  39. package/src/types.ts +327 -0
  40. package/src/utils/error-handlers.ts +98 -0
  41. package/src/utils/truncation.ts +97 -0
  42. package/test-call-wait-for-hello.ts +76 -0
  43. package/test-call.json +5 -0
  44. package/tsconfig.json +21 -0
  45. package/LICENSE +0 -21
@@ -0,0 +1,623 @@
1
+ /**
2
+ * Response formatting utilities
3
+ *
4
+ * Converts API responses to human-readable Markdown or structured JSON format.
5
+ * Provides consistent formatting across all MCP tools.
6
+ */
7
+
8
+ import {
9
+ Agent,
10
+ ConversationMetadata,
11
+ ToolConfig,
12
+ Voice,
13
+ ResponseFormat,
14
+ PaginatedResponse,
15
+ OutboundCallResponse,
16
+ BatchCallResponse,
17
+ BatchCallDetailedResponse,
18
+ WorkspaceBatchCallsResponse,
19
+ PhoneNumber,
20
+ ImportPhoneNumberResponse
21
+ } from "../types.js";
22
+ import { truncateIfNeeded, formatJSON, truncateMiddle } from "../utils/truncation.js";
23
+
24
+ /**
25
+ * Formats an agent in Markdown format
26
+ */
27
+ export function formatAgentMarkdown(agent: Agent): string {
28
+ const config = agent.conversation_config;
29
+ const prompt = config.agent.prompt;
30
+
31
+ let markdown = `# Agent: ${agent.name} (${agent.agent_id})\n\n`;
32
+ markdown += `**Created**: ${new Date(agent.created_at).toISOString()}\n\n`;
33
+
34
+ // Configuration section
35
+ markdown += `## Configuration\n`;
36
+ markdown += `- **LLM**: ${prompt.llm}\n`;
37
+ markdown += `- **Voice ID**: ${config.tts.voice_id}\n`;
38
+ markdown += `- **Voice Model**: ${config.tts.model_id}\n`;
39
+ markdown += `- **Language**: ${config.agent.language}\n`;
40
+
41
+ if (prompt.temperature !== undefined) {
42
+ markdown += `- **Temperature**: ${prompt.temperature}\n`;
43
+ }
44
+
45
+ if (config.agent.first_message) {
46
+ markdown += `- **First Message**: "${config.agent.first_message}"\n`;
47
+ }
48
+
49
+ markdown += `\n`;
50
+
51
+ // Prompt section
52
+ markdown += `## System Prompt\n\`\`\`\n${prompt.prompt}\n\`\`\`\n\n`;
53
+
54
+ // Tools section
55
+ if (prompt.tools && prompt.tools.length > 0) {
56
+ markdown += `## Tools (${prompt.tools.length})\n`;
57
+ prompt.tools.forEach((tool, idx) => {
58
+ markdown += `${idx + 1}. **${tool.name}** - ${tool.description}\n`;
59
+ });
60
+ markdown += `\n`;
61
+ }
62
+
63
+ // Knowledge Base section
64
+ if (prompt.knowledge_base && prompt.knowledge_base.length > 0) {
65
+ markdown += `## Knowledge Base (${prompt.knowledge_base.length} documents)\n`;
66
+ markdown += `Documents have been added to the agent's knowledge base.\n\n`;
67
+ }
68
+
69
+ // Widget section
70
+ if (agent.platform_settings?.widget) {
71
+ markdown += `## Widget Settings\n`;
72
+ if (agent.platform_settings.widget.color) {
73
+ markdown += `- **Color**: ${agent.platform_settings.widget.color}\n`;
74
+ }
75
+ if (agent.platform_settings.widget.avatar_url) {
76
+ markdown += `- **Avatar**: ${agent.platform_settings.widget.avatar_url}\n`;
77
+ }
78
+ markdown += `\n`;
79
+ }
80
+
81
+ return markdown;
82
+ }
83
+
84
+ /**
85
+ * Formats a list of agents in Markdown format
86
+ */
87
+ export function formatAgentListMarkdown(
88
+ agents: Agent[],
89
+ total: number,
90
+ offset: number,
91
+ hasMore: boolean
92
+ ): string {
93
+ let markdown = `# Agents (${agents.length} of ${total})\n\n`;
94
+
95
+ if (agents.length === 0) {
96
+ return markdown + "No agents found.\n";
97
+ }
98
+
99
+ agents.forEach((agent, idx) => {
100
+ const num = offset + idx + 1;
101
+ markdown += `## ${num}. ${agent.name}\n`;
102
+ markdown += `- **ID**: ${agent.agent_id}\n`;
103
+ markdown += `- **LLM**: ${agent.conversation_config.agent.prompt.llm}\n`;
104
+ markdown += `- **Language**: ${agent.conversation_config.agent.language}\n`;
105
+ markdown += `- **Created**: ${new Date(agent.created_at).toISOString()}\n\n`;
106
+ });
107
+
108
+ if (hasMore) {
109
+ const nextOffset = offset + agents.length;
110
+ markdown += `\n**More agents available.** Use \`offset=${nextOffset}\` to see the next page.\n`;
111
+ }
112
+
113
+ return truncateIfNeeded(markdown, `Use offset=${offset + agents.length} to continue`);
114
+ }
115
+
116
+ /**
117
+ * Formats a conversation in Markdown format
118
+ */
119
+ export function formatConversationMarkdown(conversation: ConversationMetadata): string {
120
+ let markdown = `# Conversation: ${conversation.conversation_id}\n\n`;
121
+ markdown += `- **Agent ID**: ${conversation.agent_id}\n`;
122
+ markdown += `- **Status**: ${conversation.status}\n`;
123
+ markdown += `- **Started**: ${new Date(conversation.started_at).toISOString()}\n`;
124
+
125
+ if (conversation.ended_at) {
126
+ markdown += `- **Ended**: ${new Date(conversation.ended_at).toISOString()}\n`;
127
+ }
128
+
129
+ if (conversation.duration_seconds !== undefined) {
130
+ markdown += `- **Duration**: ${conversation.duration_seconds}s\n`;
131
+ }
132
+
133
+ markdown += `\n`;
134
+
135
+ // Transcript
136
+ if (conversation.transcript && conversation.transcript.length > 0) {
137
+ markdown += `## Transcript (${conversation.transcript.length} messages)\n\n`;
138
+
139
+ conversation.transcript.forEach((entry, idx) => {
140
+ const time = new Date(entry.timestamp).toLocaleTimeString();
141
+ markdown += `**[${time}] ${entry.role.toUpperCase()}**: ${entry.message}\n`;
142
+
143
+ if (entry.tool_calls && entry.tool_calls.length > 0) {
144
+ markdown += ` *Tool calls: ${entry.tool_calls.map(t => t.tool_name).join(", ")}*\n`;
145
+ }
146
+
147
+ markdown += `\n`;
148
+ });
149
+ }
150
+
151
+ // Analysis
152
+ if (conversation.analysis) {
153
+ markdown += `## Analysis\n`;
154
+ if (conversation.analysis.user_sentiment) {
155
+ markdown += `- **User Sentiment**: ${conversation.analysis.user_sentiment}\n`;
156
+ }
157
+ if (conversation.analysis.agent_performance !== undefined) {
158
+ markdown += `- **Agent Performance**: ${conversation.analysis.agent_performance}/10\n`;
159
+ }
160
+ if (conversation.analysis.key_topics && conversation.analysis.key_topics.length > 0) {
161
+ markdown += `- **Key Topics**: ${conversation.analysis.key_topics.join(", ")}\n`;
162
+ }
163
+ markdown += `\n`;
164
+ }
165
+
166
+ return truncateIfNeeded(markdown);
167
+ }
168
+
169
+ /**
170
+ * Formats a list of conversations in Markdown format
171
+ */
172
+ export function formatConversationListMarkdown(
173
+ conversations: ConversationMetadata[],
174
+ total: number,
175
+ offset: number,
176
+ hasMore: boolean
177
+ ): string {
178
+ let markdown = `# Conversations (${conversations.length} of ${total})\n\n`;
179
+
180
+ if (conversations.length === 0) {
181
+ return markdown + "No conversations found.\n";
182
+ }
183
+
184
+ conversations.forEach((conv, idx) => {
185
+ const num = offset + idx + 1;
186
+ markdown += `## ${num}. ${conv.conversation_id}\n`;
187
+ markdown += `- **Status**: ${conv.status}\n`;
188
+ markdown += `- **Started**: ${new Date(conv.started_at).toISOString()}\n`;
189
+
190
+ if (conv.duration_seconds !== undefined) {
191
+ markdown += `- **Duration**: ${conv.duration_seconds}s\n`;
192
+ }
193
+
194
+ markdown += `\n`;
195
+ });
196
+
197
+ if (hasMore) {
198
+ const nextOffset = offset + conversations.length;
199
+ markdown += `\n**More conversations available.** Use \`offset=${nextOffset}\` to see the next page.\n`;
200
+ }
201
+
202
+ return truncateIfNeeded(markdown, `Use offset=${offset + conversations.length} to continue`);
203
+ }
204
+
205
+ /**
206
+ * Formats a tool configuration in Markdown format
207
+ */
208
+ export function formatToolMarkdown(tool: ToolConfig): string {
209
+ let markdown = `# Tool: ${tool.name}\n\n`;
210
+ markdown += `**Type**: ${tool.type}\n`;
211
+ markdown += `**Description**: ${tool.description}\n\n`;
212
+
213
+ if (tool.url) {
214
+ markdown += `**URL**: ${tool.url}\n`;
215
+ markdown += `**Method**: ${tool.method || "POST"}\n\n`;
216
+ }
217
+
218
+ if (tool.parameters && tool.parameters.length > 0) {
219
+ markdown += `## Parameters\n\n`;
220
+ tool.parameters.forEach((param) => {
221
+ markdown += `- **${param.name}** (${param.type})${param.required ? " *required*" : ""}\n`;
222
+ markdown += ` ${param.description}\n`;
223
+ if (param.enum) {
224
+ markdown += ` Options: ${param.enum.join(", ")}\n`;
225
+ }
226
+ markdown += `\n`;
227
+ });
228
+ }
229
+
230
+ return markdown;
231
+ }
232
+
233
+ /**
234
+ * Formats a list of tools in Markdown format
235
+ */
236
+ export function formatToolListMarkdown(tools: ToolConfig[]): string {
237
+ if (tools.length === 0) {
238
+ return "# Tools\n\nNo tools configured for this agent.\n";
239
+ }
240
+
241
+ let markdown = `# Tools (${tools.length})\n\n`;
242
+
243
+ tools.forEach((tool, idx) => {
244
+ markdown += `## ${idx + 1}. ${tool.name}\n`;
245
+ markdown += `- **Type**: ${tool.type}\n`;
246
+ markdown += `- **Description**: ${tool.description}\n`;
247
+
248
+ if (tool.url) {
249
+ markdown += `- **URL**: ${truncateMiddle(tool.url, 80)}\n`;
250
+ }
251
+
252
+ markdown += `\n`;
253
+ });
254
+
255
+ return truncateIfNeeded(markdown);
256
+ }
257
+
258
+ /**
259
+ * Formats a list of voices in Markdown format
260
+ */
261
+ export function formatVoiceListMarkdown(voices: Voice[]): string {
262
+ if (voices.length === 0) {
263
+ return "# Voices\n\nNo voices found matching the criteria.\n";
264
+ }
265
+
266
+ let markdown = `# Voices (${voices.length})\n\n`;
267
+
268
+ voices.forEach((voice, idx) => {
269
+ markdown += `## ${idx + 1}. ${voice.name}\n`;
270
+ markdown += `- **ID**: ${voice.voice_id}\n`;
271
+
272
+ if (voice.labels?.gender) {
273
+ markdown += `- **Gender**: ${voice.labels.gender}\n`;
274
+ }
275
+
276
+ if (voice.labels?.age) {
277
+ markdown += `- **Age**: ${voice.labels.age}\n`;
278
+ }
279
+
280
+ if (voice.labels?.accent) {
281
+ markdown += `- **Accent**: ${voice.labels.accent}\n`;
282
+ }
283
+
284
+ if (voice.description) {
285
+ markdown += `- **Description**: ${voice.description}\n`;
286
+ }
287
+
288
+ if (voice.preview_url) {
289
+ markdown += `- **Preview**: ${voice.preview_url}\n`;
290
+ }
291
+
292
+ markdown += `\n`;
293
+ });
294
+
295
+ return truncateIfNeeded(markdown);
296
+ }
297
+
298
+ /**
299
+ * Formats widget embed code
300
+ */
301
+ export function formatWidgetCode(agentId: string, color?: string, avatarUrl?: string): string {
302
+ let markdown = `# Widget Embed Code\n\n`;
303
+ markdown += `Add this HTML to your website to embed the voice agent:\n\n`;
304
+ markdown += `\`\`\`html\n`;
305
+ markdown += `<script>\n`;
306
+ markdown += ` window.elevenLabsConfig = {\n`;
307
+ markdown += ` agentId: "${agentId}",\n`;
308
+
309
+ if (color) {
310
+ markdown += ` color: "${color}",\n`;
311
+ }
312
+
313
+ if (avatarUrl) {
314
+ markdown += ` avatarUrl: "${avatarUrl}",\n`;
315
+ }
316
+
317
+ markdown += ` };\n`;
318
+ markdown += `</script>\n`;
319
+ markdown += `<script src="https://elevenlabs.io/convai-widget/index.js" async></script>\n`;
320
+ markdown += `\`\`\`\n\n`;
321
+ markdown += `The widget will appear as a floating button on your page.\n`;
322
+
323
+ return markdown;
324
+ }
325
+
326
+ /**
327
+ * Formats an outbound call response in Markdown format
328
+ */
329
+ export function formatOutboundCallMarkdown(response: OutboundCallResponse): string {
330
+ let markdown = `# Outbound Call ${response.success ? "Initiated" : "Failed"}\n\n`;
331
+ markdown += `**Status**: ${response.success ? "✓ Success" : "✗ Failed"}\n`;
332
+ markdown += `**Message**: ${response.message}\n`;
333
+
334
+ if (response.conversation_id) {
335
+ markdown += `**Conversation ID**: ${response.conversation_id}\n`;
336
+ }
337
+
338
+ if (response.callSid) {
339
+ markdown += `**Twilio Call SID**: ${response.callSid}\n`;
340
+ }
341
+
342
+ if (response.success) {
343
+ markdown += `\nThe call has been initiated. Use the conversation ID to track and retrieve the conversation transcript.\n`;
344
+ }
345
+
346
+ return markdown;
347
+ }
348
+
349
+ /**
350
+ * Formats a batch call response in Markdown format
351
+ */
352
+ export function formatBatchCallMarkdown(batch: BatchCallResponse): string {
353
+ let markdown = `# Batch Call: ${batch.name}\n\n`;
354
+ markdown += `**Batch ID**: ${batch.id}\n`;
355
+ markdown += `**Status**: ${batch.status}\n`;
356
+ markdown += `**Agent**: ${batch.agent_name} (${batch.agent_id})\n`;
357
+
358
+ if (batch.phone_number_id) {
359
+ markdown += `**Phone Number ID**: ${batch.phone_number_id}\n`;
360
+ }
361
+
362
+ if (batch.phone_provider) {
363
+ markdown += `**Provider**: ${batch.phone_provider}\n`;
364
+ }
365
+
366
+ markdown += `\n## Timing\n`;
367
+ markdown += `- **Created**: ${new Date(batch.created_at_unix * 1000).toISOString()}\n`;
368
+ markdown += `- **Scheduled**: ${new Date(batch.scheduled_time_unix * 1000).toISOString()}\n`;
369
+ markdown += `- **Last Updated**: ${new Date(batch.last_updated_at_unix * 1000).toISOString()}\n`;
370
+
371
+ markdown += `\n## Call Statistics\n`;
372
+ markdown += `- **Calls Dispatched**: ${batch.total_calls_dispatched}\n`;
373
+ markdown += `- **Calls Scheduled**: ${batch.total_calls_scheduled}\n`;
374
+
375
+ return markdown;
376
+ }
377
+
378
+ /**
379
+ * Formats a batch call list in Markdown format
380
+ */
381
+ export function formatBatchCallListMarkdown(response: WorkspaceBatchCallsResponse): string {
382
+ const batches = response.batch_calls;
383
+
384
+ if (batches.length === 0) {
385
+ return "# Batch Calls\n\nNo batch calling jobs found.\n";
386
+ }
387
+
388
+ let markdown = `# Batch Calls (${batches.length})\n\n`;
389
+
390
+ batches.forEach((batch, idx) => {
391
+ markdown += `## ${idx + 1}. ${batch.name}\n`;
392
+ markdown += `- **Batch ID**: ${batch.id}\n`;
393
+ markdown += `- **Status**: ${batch.status}\n`;
394
+ markdown += `- **Agent**: ${batch.agent_name}\n`;
395
+ markdown += `- **Dispatched/Scheduled**: ${batch.total_calls_dispatched}/${batch.total_calls_scheduled}\n`;
396
+ markdown += `- **Created**: ${new Date(batch.created_at_unix * 1000).toISOString()}\n\n`;
397
+ });
398
+
399
+ if (response.has_more) {
400
+ markdown += `\n**More batches available.** Use \`last_doc="${response.next_doc}"\` to see the next page.\n`;
401
+ }
402
+
403
+ return truncateIfNeeded(markdown, response.has_more ? `Use last_doc="${response.next_doc}" to continue` : undefined);
404
+ }
405
+
406
+ /**
407
+ * Formats detailed batch call information with recipient statuses
408
+ */
409
+ export function formatBatchCallDetailMarkdown(batch: BatchCallDetailedResponse): string {
410
+ let markdown = formatBatchCallMarkdown(batch);
411
+
412
+ if (batch.recipients && batch.recipients.length > 0) {
413
+ markdown += `\n## Recipients (${batch.recipients.length})\n\n`;
414
+
415
+ // Group recipients by status
416
+ const statusGroups: Record<string, typeof batch.recipients> = {};
417
+ batch.recipients.forEach(recipient => {
418
+ if (!statusGroups[recipient.status]) {
419
+ statusGroups[recipient.status] = [];
420
+ }
421
+ statusGroups[recipient.status].push(recipient);
422
+ });
423
+
424
+ // Show summary
425
+ markdown += `**Status Summary**:\n`;
426
+ Object.entries(statusGroups).forEach(([status, recipients]) => {
427
+ markdown += `- ${status}: ${recipients.length}\n`;
428
+ });
429
+ markdown += `\n`;
430
+
431
+ // Show first 20 recipients
432
+ const displayLimit = 20;
433
+ batch.recipients.slice(0, displayLimit).forEach((recipient, idx) => {
434
+ markdown += `### ${idx + 1}. `;
435
+ if (recipient.phone_number) {
436
+ markdown += `${recipient.phone_number}`;
437
+ } else if (recipient.whatsapp_user_id) {
438
+ markdown += `WhatsApp: ${recipient.whatsapp_user_id}`;
439
+ }
440
+ markdown += `\n`;
441
+ markdown += `- **Status**: ${recipient.status}\n`;
442
+ markdown += `- **Conversation ID**: ${recipient.conversation_id}\n`;
443
+ markdown += `- **Updated**: ${new Date(recipient.updated_at_unix * 1000).toISOString()}\n\n`;
444
+ });
445
+
446
+ if (batch.recipients.length > displayLimit) {
447
+ markdown += `\n*Showing ${displayLimit} of ${batch.recipients.length} recipients. Use JSON format for complete list.*\n`;
448
+ }
449
+ }
450
+
451
+ return truncateIfNeeded(markdown);
452
+ }
453
+
454
+ /**
455
+ * Formats a list of phone numbers in Markdown format
456
+ */
457
+ export function formatPhoneNumberListMarkdown(phoneNumbers: PhoneNumber[]): string {
458
+ if (phoneNumbers.length === 0) {
459
+ return "# Phone Numbers\n\nNo phone numbers found.\n";
460
+ }
461
+
462
+ let markdown = `# Phone Numbers (${phoneNumbers.length})\n\n`;
463
+
464
+ phoneNumbers.forEach((phone, idx) => {
465
+ markdown += `## ${idx + 1}. ${phone.label}\n`;
466
+ markdown += `- **Number**: ${phone.phone_number}\n`;
467
+ markdown += `- **ID**: ${phone.phone_number_id}\n`;
468
+ markdown += `- **Provider**: ${phone.provider}\n`;
469
+ markdown += `- **Inbound**: ${phone.supports_inbound ? "✓" : "✗"}\n`;
470
+ markdown += `- **Outbound**: ${phone.supports_outbound ? "✓" : "✗"}\n`;
471
+
472
+ if (phone.assigned_agent) {
473
+ markdown += `- **Assigned Agent**: ${phone.assigned_agent.agent_name} (${phone.assigned_agent.agent_id})\n`;
474
+ } else {
475
+ markdown += `- **Assigned Agent**: None\n`;
476
+ }
477
+
478
+ if (phone.provider === "sip_trunk") {
479
+ markdown += `- **LiveKit Stack**: ${phone.livekit_stack}\n`;
480
+ }
481
+
482
+ markdown += `\n`;
483
+ });
484
+
485
+ return truncateIfNeeded(markdown);
486
+ }
487
+
488
+ /**
489
+ * Formats a single phone number in Markdown format
490
+ */
491
+ export function formatPhoneNumberMarkdown(phone: PhoneNumber): string {
492
+ let markdown = `# Phone Number: ${phone.label}\n\n`;
493
+ markdown += `**Number**: ${phone.phone_number}\n`;
494
+ markdown += `**ID**: ${phone.phone_number_id}\n`;
495
+ markdown += `**Provider**: ${phone.provider}\n\n`;
496
+
497
+ markdown += `## Capabilities\n`;
498
+ markdown += `- **Inbound Calls**: ${phone.supports_inbound ? "Enabled" : "Disabled"}\n`;
499
+ markdown += `- **Outbound Calls**: ${phone.supports_outbound ? "Enabled" : "Disabled"}\n\n`;
500
+
501
+ if (phone.assigned_agent) {
502
+ markdown += `## Assigned Agent\n`;
503
+ markdown += `- **Name**: ${phone.assigned_agent.agent_name}\n`;
504
+ markdown += `- **ID**: ${phone.assigned_agent.agent_id}\n\n`;
505
+ } else {
506
+ markdown += `## Assigned Agent\n`;
507
+ markdown += `No agent currently assigned to this phone number.\n\n`;
508
+ }
509
+
510
+ if (phone.provider === "sip_trunk") {
511
+ markdown += `## SIP Trunk Configuration\n`;
512
+ markdown += `- **LiveKit Stack**: ${phone.livekit_stack}\n`;
513
+ if (phone.inbound_trunk) {
514
+ markdown += `- **Inbound Trunk**: Configured\n`;
515
+ }
516
+ if (phone.outbound_trunk) {
517
+ markdown += `- **Outbound Trunk**: Configured\n`;
518
+ }
519
+ markdown += `\n`;
520
+ }
521
+
522
+ return markdown;
523
+ }
524
+
525
+ /**
526
+ * Formats phone number import response in Markdown format
527
+ */
528
+ export function formatPhoneNumberImportMarkdown(response: ImportPhoneNumberResponse): string {
529
+ let markdown = `# Phone Number Imported Successfully\n\n`;
530
+ markdown += `**Phone Number ID**: ${response.phone_number_id}\n\n`;
531
+ markdown += `The phone number has been successfully imported and is ready to use.\n\n`;
532
+ markdown += `**Next Steps**:\n`;
533
+ markdown += `1. Assign an agent to this phone number using \`elevenlabs_update_phone_number\`\n`;
534
+ markdown += `2. Start making outbound calls with \`elevenlabs_start_outbound_call\`\n`;
535
+
536
+ return markdown;
537
+ }
538
+
539
+ /**
540
+ * Formats any response as JSON
541
+ */
542
+ export function formatAsJSON(data: unknown): string {
543
+ return formatJSON(data);
544
+ }
545
+
546
+ /**
547
+ * Main formatting function that routes to appropriate formatter
548
+ */
549
+ export function formatResponse(
550
+ data: unknown,
551
+ format: ResponseFormat,
552
+ type: "agent" | "agent_list" | "conversation" | "conversation_list" | "tool" | "tool_list" | "voice_list" | "widget" | "outbound_call" | "batch_call" | "batch_call_list" | "batch_call_detail" | "phone_number_list" | "phone_number" | "phone_number_import" | "generic"
553
+ ): string {
554
+ if (format === ResponseFormat.JSON) {
555
+ return formatAsJSON(data);
556
+ }
557
+
558
+ // Markdown formatting based on type
559
+ switch (type) {
560
+ case "agent":
561
+ return formatAgentMarkdown(data as Agent);
562
+
563
+ case "agent_list": {
564
+ const paginated = data as PaginatedResponse<Agent>;
565
+ return formatAgentListMarkdown(
566
+ paginated.items,
567
+ paginated.total,
568
+ paginated.offset,
569
+ paginated.has_more
570
+ );
571
+ }
572
+
573
+ case "conversation":
574
+ return formatConversationMarkdown(data as ConversationMetadata);
575
+
576
+ case "conversation_list": {
577
+ const paginated = data as PaginatedResponse<ConversationMetadata>;
578
+ return formatConversationListMarkdown(
579
+ paginated.items,
580
+ paginated.total,
581
+ paginated.offset,
582
+ paginated.has_more
583
+ );
584
+ }
585
+
586
+ case "tool":
587
+ return formatToolMarkdown(data as ToolConfig);
588
+
589
+ case "tool_list":
590
+ return formatToolListMarkdown(data as ToolConfig[]);
591
+
592
+ case "voice_list":
593
+ return formatVoiceListMarkdown(data as Voice[]);
594
+
595
+ case "widget":
596
+ return data as string; // Already formatted by formatWidgetCode
597
+
598
+ case "outbound_call":
599
+ return formatOutboundCallMarkdown(data as OutboundCallResponse);
600
+
601
+ case "batch_call":
602
+ return formatBatchCallMarkdown(data as BatchCallResponse);
603
+
604
+ case "batch_call_list":
605
+ return formatBatchCallListMarkdown(data as WorkspaceBatchCallsResponse);
606
+
607
+ case "batch_call_detail":
608
+ return formatBatchCallDetailMarkdown(data as BatchCallDetailedResponse);
609
+
610
+ case "phone_number_list":
611
+ return formatPhoneNumberListMarkdown(data as PhoneNumber[]);
612
+
613
+ case "phone_number":
614
+ return formatPhoneNumberMarkdown(data as PhoneNumber);
615
+
616
+ case "phone_number_import":
617
+ return formatPhoneNumberImportMarkdown(data as ImportPhoneNumberResponse);
618
+
619
+ case "generic":
620
+ default:
621
+ return formatAsJSON(data);
622
+ }
623
+ }