ttp-agent-sdk 2.48.16 → 2.48.18

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.
@@ -0,0 +1,403 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Seabreeze Hotel — Concierge</title>
7
+ <style>
8
+ :root {
9
+ --ink: #14202b;
10
+ --sand: #f4efe6;
11
+ --sea: #1c4e6b;
12
+ --foam: #e8f2f6;
13
+ --gold: #c4a46a;
14
+ }
15
+ * { box-sizing: border-box; margin: 0; padding: 0; }
16
+ body {
17
+ font-family: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
18
+ color: var(--ink);
19
+ background: var(--sand);
20
+ min-height: 100vh;
21
+ }
22
+ header {
23
+ background: linear-gradient(160deg, #0d2c3d 0%, #1c4e6b 55%, #3a7a8c 100%);
24
+ color: #f7f3ea;
25
+ padding: 56px 24px 88px;
26
+ }
27
+ .wrap { max-width: 920px; margin: 0 auto; }
28
+ .header-top {
29
+ display: flex;
30
+ align-items: flex-start;
31
+ justify-content: space-between;
32
+ gap: 20px;
33
+ flex-wrap: wrap;
34
+ }
35
+ nav {
36
+ display: flex;
37
+ gap: 8px;
38
+ font-family: system-ui, sans-serif;
39
+ }
40
+ nav a {
41
+ color: #f7f3ea;
42
+ text-decoration: none;
43
+ font-size: 13px;
44
+ letter-spacing: 0.08em;
45
+ text-transform: uppercase;
46
+ padding: 8px 14px;
47
+ border: 1px solid rgba(247, 243, 234, 0.28);
48
+ border-radius: 999px;
49
+ }
50
+ nav a.active, nav a:hover {
51
+ background: rgba(247, 243, 234, 0.14);
52
+ border-color: var(--gold);
53
+ color: #fff;
54
+ }
55
+ .eyebrow {
56
+ letter-spacing: 0.22em;
57
+ text-transform: uppercase;
58
+ font-size: 12px;
59
+ color: var(--gold);
60
+ margin-bottom: 12px;
61
+ }
62
+ h1 { font-size: clamp(2.2rem, 5vw, 3.4rem); font-weight: 500; line-height: 1.15; }
63
+ h2 { font-size: 1.55rem; font-weight: 500; }
64
+ .lead { margin-top: 16px; max-width: 36rem; font-size: 1.15rem; line-height: 1.55; opacity: 0.92; }
65
+ main { margin-top: -40px; padding: 0 24px 80px; }
66
+ .card {
67
+ background: #fff;
68
+ border-radius: 18px;
69
+ padding: 28px;
70
+ box-shadow: 0 18px 40px rgba(20, 32, 43, 0.08);
71
+ }
72
+ .rooms { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); margin-top: 20px; }
73
+ .room { background: var(--foam); border-radius: 12px; padding: 16px; }
74
+ .room h3 { font-size: 1.05rem; margin-bottom: 6px; }
75
+ .room p { font-size: 0.95rem; line-height: 1.45; color: #3d4d5a; }
76
+ .hint {
77
+ margin-top: 22px;
78
+ padding: 14px 16px;
79
+ border-left: 3px solid var(--gold);
80
+ background: #fbf7ef;
81
+ font-family: system-ui, sans-serif;
82
+ font-size: 14px;
83
+ line-height: 1.45;
84
+ }
85
+ .status { margin-top: 12px; font-family: ui-monospace, monospace; font-size: 12px; color: #4b5d6b; }
86
+ .status.ok { color: #1b6b45; }
87
+ .status.err { color: #9b1c1c; }
88
+ form.booking {
89
+ display: grid;
90
+ gap: 16px;
91
+ margin-top: 20px;
92
+ font-family: system-ui, sans-serif;
93
+ }
94
+ .field {
95
+ display: grid;
96
+ gap: 6px;
97
+ }
98
+ .field label {
99
+ font-size: 13px;
100
+ letter-spacing: 0.04em;
101
+ text-transform: uppercase;
102
+ color: #4b5d6b;
103
+ }
104
+ .field input, .field select {
105
+ font: inherit;
106
+ padding: 12px 14px;
107
+ border: 1px solid #d5dde3;
108
+ border-radius: 10px;
109
+ background: #fff;
110
+ color: var(--ink);
111
+ }
112
+ .field input:focus, .field select:focus {
113
+ outline: 2px solid #1c4e6b;
114
+ outline-offset: 1px;
115
+ }
116
+ .field.just-filled input, .field.just-filled select {
117
+ border-color: #c4a46a;
118
+ background: #fff8ea;
119
+ box-shadow: 0 0 0 3px rgba(196, 164, 106, 0.28);
120
+ }
121
+ .actions {
122
+ display: flex;
123
+ gap: 12px;
124
+ align-items: center;
125
+ flex-wrap: wrap;
126
+ }
127
+ button[type="submit"] {
128
+ font: inherit;
129
+ background: var(--sea);
130
+ color: #f7f3ea;
131
+ border: 0;
132
+ border-radius: 999px;
133
+ padding: 12px 20px;
134
+ cursor: pointer;
135
+ }
136
+ .confirm {
137
+ display: none;
138
+ margin-top: 18px;
139
+ padding: 16px;
140
+ border-radius: 12px;
141
+ background: #eef7f1;
142
+ color: #1b6b45;
143
+ font-family: system-ui, sans-serif;
144
+ line-height: 1.45;
145
+ }
146
+ .confirm.show { display: block; }
147
+ </style>
148
+ </head>
149
+ <body>
150
+ <header>
151
+ <div class="wrap">
152
+ <div class="header-top">
153
+ <p class="eyebrow">Praia do Guincho · Cascais</p>
154
+ <nav>
155
+ <a href="#home" data-nav="home">Rooms</a>
156
+ <a href="#booking" data-nav="booking">Request a stay</a>
157
+ </nav>
158
+ </div>
159
+ <h1>Seabreeze Hotel</h1>
160
+ <p class="lead">A 42-room boutique stay above the Atlantic. Ask Mila about rooms, or say you want to book — she will open the form and fill each field as you speak.</p>
161
+ </div>
162
+ </header>
163
+ <main>
164
+ <div id="view-home" class="wrap card">
165
+ <p class="eyebrow">Stay</p>
166
+ <h2>Rooms overlooking the cliff</h2>
167
+ <div class="rooms">
168
+ <div class="room">
169
+ <h3>Garden Room</h3>
170
+ <p>24 m², garden view, queen bed. Quiet and close to the path down to the beach.</p>
171
+ </div>
172
+ <div class="room">
173
+ <h3>Ocean Room</h3>
174
+ <p>30 m², balcony with a full Atlantic view, king bed.</p>
175
+ </div>
176
+ <div class="room">
177
+ <h3>Cliff Suite</h3>
178
+ <p>48 m², living room, soaking tub, and a large terrace.</p>
179
+ </div>
180
+ <div class="room">
181
+ <h3>Lighthouse Penthouse</h3>
182
+ <p>70 m², private rooftop plunge pool, sleeps four.</p>
183
+ </div>
184
+ </div>
185
+ <p class="hint">Tap TalkToPC and speak with <strong>Mila</strong>. Say you want to book a stay — she opens this page’s form and fills one field at a time from what you say.</p>
186
+ <p id="status" class="status">Loading widget…</p>
187
+ </div>
188
+
189
+ <div id="view-booking" class="wrap card" hidden>
190
+ <p class="eyebrow">Request</p>
191
+ <h2>Tell Mila each detail</h2>
192
+ <p class="hint">Stay on this page and speak. After each answer she writes it into the matching field. Required: check-in, nights, guests, room, name, and email or phone.</p>
193
+ <form id="booking-form" class="booking">
194
+ <div class="field" data-field="check_in">
195
+ <label for="field-check_in">Check-in date</label>
196
+ <input id="field-check_in" name="check_in" type="text" autocomplete="off" placeholder="For example 12 September">
197
+ </div>
198
+ <div class="field" data-field="nights">
199
+ <label for="field-nights">Nights</label>
200
+ <input id="field-nights" name="nights" type="text" inputmode="numeric" placeholder="Number of nights">
201
+ </div>
202
+ <div class="field" data-field="guests">
203
+ <label for="field-guests">Guests</label>
204
+ <input id="field-guests" name="guests" type="text" inputmode="numeric" placeholder="Number of guests">
205
+ </div>
206
+ <div class="field" data-field="room_type">
207
+ <label for="field-room_type">Room type</label>
208
+ <select id="field-room_type" name="room_type">
209
+ <option value="">Choose a room</option>
210
+ <option value="Garden Room">Garden Room</option>
211
+ <option value="Ocean Room">Ocean Room</option>
212
+ <option value="Cliff Suite">Cliff Suite</option>
213
+ <option value="Lighthouse Penthouse">Lighthouse Penthouse</option>
214
+ </select>
215
+ </div>
216
+ <div class="field" data-field="full_name">
217
+ <label for="field-full_name">Full name</label>
218
+ <input id="field-full_name" name="full_name" type="text" autocomplete="name" placeholder="Guest name">
219
+ </div>
220
+ <div class="field" data-field="contact">
221
+ <label for="field-contact">Email or phone</label>
222
+ <input id="field-contact" name="contact" type="text" autocomplete="off" placeholder="Email or phone number">
223
+ </div>
224
+ <div class="actions">
225
+ <button type="submit">Send request</button>
226
+ </div>
227
+ </form>
228
+ <div id="booking-confirm" class="confirm" role="status"></div>
229
+ <p id="status-booking" class="status"></p>
230
+ </div>
231
+ </main>
232
+
233
+ <script src="demo-sdk-loader.js"></script>
234
+ <script>
235
+ const AGENT_ID = 'agent_3160ec419';
236
+ const APP_ID = 'app_wNGQKiMUfUT5JdVIaxzXOJfdcgBegxGY5hZo';
237
+ const FIELDS = ['check_in', 'nights', 'guests', 'room_type', 'full_name', 'contact'];
238
+ const ROOM_ALIASES = {
239
+ 'garden': 'Garden Room',
240
+ 'garden room': 'Garden Room',
241
+ 'ocean': 'Ocean Room',
242
+ 'ocean room': 'Ocean Room',
243
+ 'cliff': 'Cliff Suite',
244
+ 'cliff suite': 'Cliff Suite',
245
+ 'suite': 'Cliff Suite',
246
+ 'lighthouse': 'Lighthouse Penthouse',
247
+ 'penthouse': 'Lighthouse Penthouse',
248
+ 'lighthouse penthouse': 'Lighthouse Penthouse'
249
+ };
250
+
251
+ function setStatus(msg, kind) {
252
+ const el = document.getElementById('status');
253
+ if (el) {
254
+ el.textContent = msg;
255
+ el.className = 'status' + (kind ? ' ' + kind : '');
256
+ }
257
+ const booking = document.getElementById('status-booking');
258
+ if (booking) {
259
+ booking.textContent = msg;
260
+ booking.className = 'status' + (kind ? ' ' + kind : '');
261
+ }
262
+ }
263
+
264
+ function currentPage() {
265
+ return document.getElementById('view-booking').hidden ? 'home' : 'booking';
266
+ }
267
+
268
+ function showPage(page) {
269
+ const next = page === 'booking' ? 'booking' : 'home';
270
+ document.getElementById('view-home').hidden = next !== 'home';
271
+ document.getElementById('view-booking').hidden = next !== 'booking';
272
+ document.querySelectorAll('nav [data-nav]').forEach((link) => {
273
+ link.classList.toggle('active', link.getAttribute('data-nav') === next);
274
+ });
275
+ if (location.hash !== '#' + next) {
276
+ history.replaceState(null, '', '#' + next);
277
+ }
278
+ return next;
279
+ }
280
+
281
+ function formValues() {
282
+ const values = {};
283
+ FIELDS.forEach((name) => {
284
+ const el = document.getElementById('field-' + name);
285
+ values[name] = el ? String(el.value || '').trim() : '';
286
+ });
287
+ return values;
288
+ }
289
+
290
+ function formSnapshot() {
291
+ const values = formValues();
292
+ const filled = FIELDS.filter((name) => values[name]);
293
+ const missing = FIELDS.filter((name) => !values[name]);
294
+ return { values, filled, missing };
295
+ }
296
+
297
+ function normalizeRoom(value) {
298
+ const raw = String(value || '').trim();
299
+ const key = raw.toLowerCase();
300
+ return ROOM_ALIASES[key] || raw;
301
+ }
302
+
303
+ function fillBookingField(field, value) {
304
+ if (!FIELDS.includes(field)) {
305
+ return { ok: false, error: 'Unknown field', field, allowed: FIELDS };
306
+ }
307
+ showPage('booking');
308
+ const wrap = document.querySelector('[data-field="' + field + '"]');
309
+ const el = document.getElementById('field-' + field);
310
+ const nextValue = field === 'room_type' ? normalizeRoom(value) : String(value || '').trim();
311
+ if (!el) {
312
+ return { ok: false, error: 'Field is missing from the page', field };
313
+ }
314
+ el.value = nextValue;
315
+ if (wrap) {
316
+ wrap.classList.add('just-filled');
317
+ setTimeout(() => wrap.classList.remove('just-filled'), 1600);
318
+ }
319
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
320
+ const snap = formSnapshot();
321
+ return { ok: true, field, value: nextValue, filled: snap.filled, missing: snap.missing };
322
+ }
323
+
324
+ function submitBooking() {
325
+ showPage('booking');
326
+ const snap = formSnapshot();
327
+ if (snap.missing.length) {
328
+ return { ok: false, error: 'Form is incomplete', missing: snap.missing, filled: snap.filled };
329
+ }
330
+ const box = document.getElementById('booking-confirm');
331
+ box.textContent = 'Request received for ' + snap.values.full_name + '. The front desk will email a confirmation shortly. This demo does not create a real reservation.';
332
+ box.classList.add('show');
333
+ return { ok: true, submitted: true, values: snap.values };
334
+ }
335
+
336
+ function registerSiteTools(widget) {
337
+ widget.registerToolHandler('navigate_page', async (params) => {
338
+ const page = showPage(params && params.page);
339
+ return { ok: true, page };
340
+ });
341
+ widget.registerToolHandler('fill_booking_field', async (params) => {
342
+ const field = params && (params.field || params.name);
343
+ const value = params && (params.value != null ? params.value : params.text);
344
+ return fillBookingField(field, value);
345
+ });
346
+ widget.registerToolHandler('submit_booking', async () => submitBooking());
347
+ }
348
+
349
+ function startWidget() {
350
+ const TTPChatWidget = window.TTPAgentSDK && window.TTPAgentSDK.TTPChatWidget;
351
+ if (!TTPChatWidget) {
352
+ setStatus('SDK loaded but TTPChatWidget is missing.', 'err');
353
+ return;
354
+ }
355
+ if (window.widgetInstance) {
356
+ try { window.widgetInstance.destroy(); } catch (_) {}
357
+ }
358
+ const config = window.TTPDemoSdkLoader.applyWebSocketUrl({
359
+ agentId: AGENT_ID,
360
+ appId: APP_ID,
361
+ language: 'en',
362
+ agentName: 'Mila',
363
+ agentRole: 'Seabreeze Concierge',
364
+ headline: 'Welcome to Seabreeze',
365
+ subline: 'Ask about rooms, or say you want to book a stay.',
366
+ behavior: { mode: 'unified', startOpen: true },
367
+ flavor: { type: 'hotels', partnerId: 'mock-hotel', callView: 'minimized' },
368
+ icon: { type: 'custom', size: 'medium', backgroundColor: '#FFFFFF' }
369
+ });
370
+ window.widgetInstance = new TTPChatWidget(config);
371
+ registerSiteTools(window.widgetInstance);
372
+ setStatus('Mila is ready. Say you want to book, then speak one field at a time.', 'ok');
373
+ }
374
+
375
+ document.querySelectorAll('nav [data-nav]').forEach((link) => {
376
+ link.addEventListener('click', (event) => {
377
+ event.preventDefault();
378
+ showPage(link.getAttribute('data-nav'));
379
+ });
380
+ });
381
+
382
+ document.getElementById('booking-form').addEventListener('submit', (event) => {
383
+ event.preventDefault();
384
+ const result = submitBooking();
385
+ if (!result.ok) {
386
+ setStatus('Still missing: ' + result.missing.join(', '), 'err');
387
+ }
388
+ });
389
+
390
+ window.addEventListener('hashchange', () => {
391
+ showPage(location.hash.replace('#', '') || 'home');
392
+ });
393
+
394
+ showPage(location.hash.replace('#', '') || 'home');
395
+
396
+ window.TTPDemoSdkLoader.waitForReady()
397
+ .then(startWidget)
398
+ .catch((err) => setStatus(String(err && err.message || err), 'err'));
399
+
400
+ window.addEventListener('ttp-demo-sdk-ready', startWidget);
401
+ </script>
402
+ </body>
403
+ </html>
package/dist/index.html CHANGED
@@ -23,7 +23,7 @@
23
23
  <img src="https://talktopc.com/logo192.png" alt="TTP Logo" style="width: 40px; height: 40px; border-radius: 8px;">
24
24
  <div>
25
25
  <h1 style="margin: 0; font-size: 1.4rem;">TTP Agent SDK</h1>
26
- <p class="version" style="margin: 0;">v2.48.15</p>
26
+ <p class="version" style="margin: 0;">v2.48.16</p>
27
27
  </div>
28
28
  </div>
29
29
  </div>
@@ -62,6 +62,7 @@
62
62
  <li><a href="#widget-flavors" class="nav-link">Widget Flavors</a></li>
63
63
  <li><a href="#client-script-tools" class="nav-link">Client-Script Tools</a></li>
64
64
  <li><a href="#chain-tools" class="nav-link">Chain Tools</a></li>
65
+ <li><a href="#platform-mcp" class="nav-link">Platform MCP</a></li>
65
66
  </ul>
66
67
  </div>
67
68
 
@@ -153,6 +154,13 @@
153
154
  <p>Backend SDK for server-side TTS</p>
154
155
  <span class="quick-link-arrow">→</span>
155
156
  </a>
157
+
158
+ <a href="#platform-mcp" class="quick-link-card">
159
+ <div class="quick-link-icon">🤖</div>
160
+ <h3>Platform MCP</h3>
161
+ <p>Build agents from Cursor or Claude</p>
162
+ <span class="quick-link-arrow">→</span>
163
+ </a>
156
164
  </div>
157
165
  </div>
158
166
 
@@ -5258,6 +5266,108 @@ Files.write(Paths.get("output.wav"), response.getAudio());</code></pre>
5258
5266
  <li><strong>Examples:</strong> Check <code>src/main/java/com/talktopc/sdk/examples/</code></li>
5259
5267
  </ul>
5260
5268
  </section>
5269
+
5270
+ <section id="platform-mcp" class="doc-section">
5271
+ <h1>Platform MCP</h1>
5272
+ <p class="lead">The TalkToPC platform MCP lets Cursor, Claude, and other MCP clients create and edit voice agents on the control plane — prompts, tools, scripts, webhooks, knowledge bases, phonemes — without the dashboard.</p>
5273
+
5274
+ <div class="info-box">
5275
+ <strong>For AI agents:</strong> the full playbook (connect, operating rules, workflows, every tool argument) is
5276
+ <a href="mcp.md"><code>docs/mcp.md</code></a>. Read that file before calling tools.
5277
+ </div>
5278
+
5279
+ <h2>What it is (and is not)</h2>
5280
+ <p>This MCP talks to <code>backend.talktopc.com</code> and writes the same agent records the dashboard writes. After you change an agent, the <strong>SDK in this repo</strong> still connects with <code>agentId</code> + <code>appId</code> to <code>wss://speech.talktopc.com/ws/conv</code>.</p>
5281
+ <ul>
5282
+ <li><strong>URL:</strong> <code>https://mcp.talktopc.com/mcp</code></li>
5283
+ <li><strong>Auth:</strong> developer API key <code>sk_…</code> from the dashboard <strong>Developers</strong> page</li>
5284
+ <li><strong>Not</strong> the widget SDK, not the voice WebSocket, not the Gmail/Calendar MCP on port 3007</li>
5285
+ </ul>
5286
+
5287
+ <h2>Connect</h2>
5288
+ <h3>Claude.ai</h3>
5289
+ <ol>
5290
+ <li>Customize → Connectors → Add custom connector</li>
5291
+ <li>URL: <code>https://mcp.talktopc.com/mcp</code></li>
5292
+ <li>Leave OAuth Client ID / Secret <strong>empty</strong></li>
5293
+ <li>Add → Connect → paste <code>sk_…</code> on the TalkToPC sign-in page</li>
5294
+ </ol>
5295
+ <p>Do not invent a Client ID. Claude registers itself; the sign-in page is how you hand over the key.</p>
5296
+
5297
+ <h3>Cursor / Claude Code</h3>
5298
+ <pre><code>"ttp-platform": {
5299
+ "command": "node",
5300
+ "args": ["/absolute/path/to/ttp-platform-mcp/dist/stdio.js"],
5301
+ "env": {
5302
+ "TTP_API_KEY": "sk_…",
5303
+ "TTP_BACKEND_URL": "https://backend.talktopc.com"
5304
+ }
5305
+ }</code></pre>
5306
+
5307
+ <h3>Any HTTP client</h3>
5308
+ <p><code>POST https://mcp.talktopc.com/mcp</code> with <code>Authorization: Bearer sk_…</code> (or <code>x-api-key</code>). <code>GET /mcp</code> is 405 on purpose. <code>GET /health</code> is public.</p>
5309
+
5310
+ <h2>Rules for agents using the tools</h2>
5311
+ <ol>
5312
+ <li>List / get before you write. Prefer <code>get_agent</code> <code>view=summary</code> or <code>view=section</code> over <code>full</code>.</li>
5313
+ <li>Change fields with <code>patch_agent</code> dotted paths. Last write wins — do not patch the same agent in parallel.</li>
5314
+ <li>Deletes require <code>confirm: true</code>.</li>
5315
+ <li>Create library objects (tool / script / KB / webhook / phoneme set) first, then <code>attach_*</code>.</li>
5316
+ <li>Pick <code>model</code> and <code>voiceId</code> from resources <code>talktopc://llm-models</code> and <code>talktopc://voices</code>.</li>
5317
+ <li>MCP returns <code>agentid</code>. The widget still needs <code>appId</code> from the dashboard — MCP does not mint it.</li>
5318
+ <li>Never print <code>sk_</code> keys or webhook <code>auth_token</code>s.</li>
5319
+ </ol>
5320
+
5321
+ <h2>Typical flow</h2>
5322
+ <pre><code>list_agents
5323
+ get_agent { agentId, view: "section", section: "systemPrompt" }
5324
+ patch_agent { agentId, changes: { "systemPrompt": "…" } }
5325
+
5326
+ create_tool { toolData }
5327
+ attach_tool { agentId, toolId }
5328
+
5329
+ create_kb { name: "FAQ" }
5330
+ add_doc { kbId, name: "Hours", source_type: "text", text: "…" }
5331
+ attach_kb { agentId, kbId }</code></pre>
5332
+
5333
+ <h2>Tool groups</h2>
5334
+ <table class="properties-table">
5335
+ <thead>
5336
+ <tr>
5337
+ <th>Group</th>
5338
+ <th>Tools</th>
5339
+ </tr>
5340
+ </thead>
5341
+ <tbody>
5342
+ <tr>
5343
+ <td>Agents</td>
5344
+ <td><code>list_agents</code> <code>get_agent</code> <code>create_agent</code> <code>patch_agent</code> <code>delete_agent</code> <code>export_agent</code> <code>import_agent</code></td>
5345
+ </tr>
5346
+ <tr>
5347
+ <td>Tools</td>
5348
+ <td><code>list_tools</code> <code>get_tool</code> <code>create_tool</code> <code>update_tool</code> <code>delete_tool</code> <code>attach_tool</code> <code>detach_tool</code></td>
5349
+ </tr>
5350
+ <tr>
5351
+ <td>Scripts</td>
5352
+ <td><code>list_scripts</code> <code>get_script</code> <code>create_script</code> <code>update_script</code> <code>delete_script</code> <code>attach_script</code> <code>detach_script</code></td>
5353
+ </tr>
5354
+ <tr>
5355
+ <td>Webhooks</td>
5356
+ <td><code>list_webhooks</code> <code>get_webhook</code> <code>create_webhook</code> <code>update_webhook</code> <code>delete_webhook</code> <code>test_webhook</code> <code>set_call_summary</code></td>
5357
+ </tr>
5358
+ <tr>
5359
+ <td>Knowledge</td>
5360
+ <td><code>list_kbs</code> <code>create_kb</code> <code>get_kb</code> <code>delete_kb</code> <code>list_docs</code> <code>add_doc</code> <code>upload_doc</code> <code>delete_doc</code> <code>attach_kb</code> <code>detach_kb</code> <code>kb_limits</code> <code>search_kb</code></td>
5361
+ </tr>
5362
+ <tr>
5363
+ <td>Phonemes</td>
5364
+ <td><code>list_phoneme_sets</code> <code>get_phoneme_set</code> <code>create_phoneme_set</code> <code>add_phoneme_entry</code> <code>remove_phoneme_entry</code> <code>suggest_ipa</code> <code>delete_phoneme_set</code> <code>attach_phoneme_set</code></td>
5365
+ </tr>
5366
+ </tbody>
5367
+ </table>
5368
+
5369
+ <p>Arguments, resources, clone/export flow, and failure cheatsheet: <a href="mcp.md">docs/mcp.md</a>.</p>
5370
+ </section>
5261
5371
  </div>
5262
5372
 
5263
5373
  <!-- Footer -->