# TalkToPC Platform MCP — agent playbook

This document is for **AI coding agents** (Cursor, Claude, Claude Code, and any other MCP client) that build or edit TalkToPC voice agents. Humans can follow it too. Read this **before** calling tools.

The embeddable widget / JS SDK lives in this repo (`ttp-agent-sdk`). The MCP does **not** replace the SDK. It builds the **agent** the SDK later connects to with `agentId` + `appId`.

---

## 1. What this MCP is

| | |
|---|---|
| **Name** | TalkToPC platform MCP (`ttp-platform-mcp`) |
| **Job** | Create and edit agents, tools, client scripts, webhooks, external MCP servers, knowledge bases, and phoneme sets on the TalkToPC **control plane** |
| **Public URL** | `https://mcp.talktopc.com/mcp` |
| **Health** | `https://mcp.talktopc.com/health` → `{"ok":true}` |
| **Backend it calls** | `https://backend.talktopc.com` (prod). Sandbox: `https://backend.bidme.co.il` |
| **Auth** | Developer API key `sk_…` from the dashboard **Developers** page (`/agents/developers`) |

It is **not**:

- The widget / `VoiceSDK` / `TTPChatWidget` (this repo)
- The voice runtime (`smartTerminalJavaFlux` / `wss://speech.talktopc.com`)
- The Gmail/Calendar skills server (`gmail-mcp-server` on `:3007`)

If a user says “use the MCP” / “edit the agent” / “add a tool” / “change the prompt”, use **this** MCP.

---

## 2. How to connect

You need a key that starts with `sk_`. Never invent one. Never commit one. Never print it.

### Cursor / Claude Code (stdio)

Local process. Env carries the key:

```json
"ttp-platform": {
  "command": "node",
  "args": ["/absolute/path/to/ttp-platform-mcp/dist/stdio.js"],
  "env": {
    "TTP_API_KEY": "sk_…",
    "TTP_BACKEND_URL": "https://backend.talktopc.com"
  }
}
```

If the tools already appear in this session as `ttp-platform` / `user-ttp-platform`, use them. Do not spawn a second server.

### Claude.ai custom connector (hosted HTTP)

1. Customize → Connectors → Add custom connector.
2. URL: **`https://mcp.talktopc.com/mcp`**
3. Leave **OAuth Client ID** and **Secret empty**.
4. Add → **Connect**. A TalkToPC page opens. Paste the `sk_…` key. Submit.

Claude registers itself (DCR). The sign-in page is how the user hands over the key. Filling a Client ID will fail — there isn’t one to paste.

Optional (beta): request headers `Authorization` = `Bearer sk_…` or `x-api-key` = `sk_…`. If Connect still opens a sign-in page, that is expected.

### Any other remote MCP client

`POST https://mcp.talktopc.com/mcp` (Streamable HTTP, JSON). Send on every call:

```
Authorization: Bearer sk_…
```

or `x-api-key: sk_…` / `x-auth-token: sk_…`.

`GET /mcp` returns **405** on purpose (no hanging SSE). `GET /health` is unauthenticated. Missing/malformed key → **401** with `WWW-Authenticate` pointing at OAuth metadata.

---

## 3. Operating rules (do this every time)

1. **Discover, then change.** `list_agents` / `list_tools` / … before `get_*`. `get_agent` (`view=summary`) before `view=full` or a patch.
2. **Patch by dotted path.** Use `patch_agent` with `{ "systemPrompt": "…", "knowledgeBase.topK": 5 }`. Do not download the whole config, rewrite it, and POST it back unless you are creating a new agent.
3. **Last write wins.** `patch_agent` and every `attach_*` / `detach_*` are get → merge → save. Two writers can clobber each other. Do not parallelize writes to the same agent.
4. **The control plane validates.** Model names, voices, call-summary webhook required, `sessionLifecycle` vs `maxCallDuration` — if the backend rejects, fix the payload. Do not invent a second schema.
5. **Destructive tools need `confirm: true`.** `delete_agent`, `delete_tool`, `delete_script`, `delete_webhook`, `delete_kb`, `delete_doc`, `delete_phoneme_set`. Without it they throw. Use `force: true` on agent/tool/webhook delete when references block a 409.
6. **Attachments are IDs on the agent.** Tools → `configuration.tools[]`. Scripts → `scriptIds[]`. KBs → `knowledgeBase.knowledgeBaseIds[]`. Phonemes → `phonemeSetIds[]`. Create the library object first, then `attach_*`.
7. **IDs look like** `agent_…`, tool UUIDs / `tool_…`, `script_…`, webhook ids, `kb_…`. Always pass the id the list/get call returned.
8. **Secrets.** `get_webhook` includes `auth_token`. `export_agent` strips secrets unless `includeSecrets=true` **and** the key is admin. Never echo tokens into chat, commits, or logs.
9. **SDK after create.** MCP returns `agentid`. The website widget still needs `appId` from the dashboard. MCP does not mint `appId`.
10. **Voice vs text.** Changing `systemPrompt` / tools / KB affects live calls after the next session start (Redis is populated by the control plane). You do not deploy Flux to ship a prompt change.

---

## 4. Standard workflows

### Find an agent

```
list_agents
→ get_agent  { agentId, view: "summary" }
→ get_agent  { agentId, view: "section", section: "systemPrompt" }   // or tools, knowledgeBase, …
→ get_agent  { agentId, view: "full" }   // only when you need the whole blob
```

`view=section` accepts dotted paths: `sessionLifecycle.events.silencePrompt`.

### Change the prompt / greeting / model / voice

```
get_agent { agentId, view: "section", section: "systemPrompt" }
patch_agent {
  agentId,
  changes: {
    "systemPrompt": "<full new prompt>",
    "firstMessage": "…",
    "model": "<id from talktopc://llm-models>",
    "fallbackModel": "<id>",
    "voiceId": "<id from talktopc://voices>",
    "agentLanguage": "he-IL"
  }
}
```

Read `talktopc://llm-models` and `talktopc://voices` before picking ids. Do not guess model strings.

### Create an agent from scratch

```
create_agent {
  config: {
    name: "Support",
    agentLanguage: "en-US",
    model: "<catalog id>",
    voiceId: "<catalog id>",
    firstMessage: "Hi, how can I help?",
    systemPrompt: "You are …"
  }
}
```

Then attach tools / scripts / KB / phonemes by id.

### Add a webhook tool and hang it on the agent

```
create_tool { toolData: { /* dashboard tool payload: name, tool_type, url, … */ } }
attach_tool { agentId, toolId }
```

Tool types the dashboard understands: server (HTTP), client, `switch_agent`, chain. Copy shape from `get_tool` on an existing tool if unsure.

### Add an external MCP server and hang it on the agent

An agent can call tools hosted on a **remote** MCP server (Streamable HTTP or legacy SSE, https only, static bearer/headers auth; no stdio). This is a library object like a webhook — register once, attach to agents. Do not confuse it with *this* MCP.

```
create_mcp_server { name: "CRM", url: "https://mcp.example.com/mcp", auth_type: "bearer", auth_token: "…", name_prefix: "crm" }
get_mcp_server    { serverId, view: "tools" }                         # discovered tools, parameters, exposed names (crm__lookup)
set_mcp_tool_override { serverId, toolName: "lookup", inputs: { contact_number: { source: "variable", variable: "caller_phone" } } }
attach_mcp_server { agentId, serverId, expose: "all" }                 # or expose: "selected", tools: ["lookup"]
```

- `approval_policy: "ask"` on the server (or `approval: "ask"` per tool via `set_mcp_tool_override`) makes the agent describe the action and get the caller's explicit consent before every call.
- An input override hides that parameter from the model and fills it server-side at call time — use it for anything the model must never invent (the caller's phone). Variables: `conversation_id`, `agent_id`, `session_type`, `caller_phone`, `called_number`, `language`.
- Discovery runs on create and on `refresh_mcp_server`; a live call keeps the tool set it started with. If `create_mcp_server` returns `last_sync_error`, fix the URL/token with `update_mcp_server`, or probe first with `test_mcp_server`.
- Tool names collide with the agent's other tools by exact name; set `name_prefix` to avoid it.

### Knowledge base

```
create_kb { name: "FAQ" }
add_doc { kbId, name: "Hours", source_type: "text", text: "…" }
          // or source_type: "url" + url
          // or source_type: "faq" + pairs: [{ q, a }]
attach_kb { agentId, kbId, topK: 5 }
search_kb { agentId, q: "opening hours" }   // what the agent would retrieve
```

`upload_doc` is for files: `filename`, `contentBase64`, optional `mime`. Check `kb_limits` first.

### Client script (runs in the browser widget)

```
create_script { name, code_js, auto_run?: false }
attach_script { agentId, scriptId }
```

This is the same client-script system the SDK docs describe under Client-Script Tools.

### Call-summary webhook

```
create_webhook { name, webhook_url, http_method: "POST" }
test_webhook { webhook_url, http_method: "POST" }
set_call_summary { agentId, enabled: true, webhookId }
```

`enabled: true` requires `webhookId`.

### Clone an agent

```
export_agent { agentId }
import_agent { exportDoc: <that JSON> }
```

`import_agent` returns a **new** `agentId`, counts, warnings, `secretsToFill`.

### Pronunciation (TTS)

```
suggest_ipa { word, locale: "he-IL" }
create_phoneme_set { name, locale: "he-IL" }
add_phoneme_entry { setId, word, ipa }
attach_phoneme_set { agentId, setId }
```

---

## 5. Tool catalog

Every tool returns JSON text in `content[0].text`. Parse it.

### Agents

| Tool | Args | Notes |
|---|---|---|
| `list_agents` | — | id, name, language, model, voice |
| `get_agent` | `agentId`, `view?`=`summary`\|`section`\|`full`, `section?` | `section` required when `view=section` |
| `create_agent` | `config` object | Backend validates. Returns new agent |
| `patch_agent` | `agentId`, `changes` (dotted path → value) | Last write wins |
| `delete_agent` | `agentId`, `confirm`, `force?` | `confirm: true` required |
| `export_agent` | `agentId`, `includeSecrets?` | Secrets stripped unless admin + flag |
| `import_agent` | `exportDoc` | Creates a **new** agent |

Common `patch_agent` paths: `name`, `systemPrompt`, `firstMessage`, `model`, `fallbackModel`, `voiceId`, `voiceSpeed`, `agentLanguage`, `autoDetectLanguage`, `agentGender`, `temperature`, `maxTokens`, `maxCallDuration`, `disableInterruptions`, `recordCall`, `sttTerms`, `tools`, `scriptIds`, `knowledgeBase.enabled`, `knowledgeBase.topK`, `sessionLifecycle`, `variables`.

### Tools (library)

| Tool | Args |
|---|---|
| `list_tools` | — |
| `get_tool` | `toolId` |
| `create_tool` | `toolData` |
| `update_tool` | `toolId`, `toolData` |
| `delete_tool` | `toolId`, `confirm`, `force?` |
| `attach_tool` | `agentId`, `toolId` |
| `detach_tool` | `agentId`, `toolId` |

### Client scripts

| Tool | Args |
|---|---|
| `list_scripts` | — |
| `get_script` | `scriptId` (includes `code_js`) |
| `create_script` | `name`, `code_js`, `description?`, `auto_run?`, `is_enabled?` |
| `update_script` | `scriptId` + any of the create fields |
| `delete_script` | `scriptId`, `confirm` |
| `attach_script` | `agentId`, `scriptId` |
| `detach_script` | `agentId`, `scriptId` |

### Webhooks

| Tool | Args |
|---|---|
| `list_webhooks` | — |
| `get_webhook` | `webhookId` (**includes auth_token**) |
| `create_webhook` | `name`, `webhook_url`, `http_method?`=`GET`\|`POST`, `headers?`, `auth_token?`, `description?`, `is_enabled?` |
| `update_webhook` | `webhookId` + any create fields |
| `delete_webhook` | `webhookId`, `confirm`, `force?` |
| `test_webhook` | `webhook_url`, `http_method?`, `headers?`, `auth_token?` |
| `set_call_summary` | `agentId`, `enabled`, `webhookId?` (required if enabled) |

### External MCP servers

| Tool | Args |
|---|---|
| `list_mcp_servers` | — |
| `get_mcp_server` | `serverId`, `view?`=`summary`\|`tools`\|`full` (secrets masked) |
| `create_mcp_server` | `name`, `url`, `transport?`=`auto`\|`streamable_http`\|`sse`, `auth_type?`=`none`\|`bearer`\|`headers`, `auth_token?`, `headers?`, `description?`, `timeout_ms?`, `name_prefix?`, `default_wait_for_response?`, `pending_message?`, `auto_refresh_hours?`, `approval_policy?`=`auto`\|`ask`, `tool_overrides?`, `is_enabled?` |
| `update_mcp_server` | `serverId` + any create fields (get → merge → put) |
| `set_mcp_tool_override` | `serverId`, `toolName`, `approval?`, `waitForResponse?`, `timeoutMs?`, `inputs?` (`{prop: {source: constant\|variable\|omit, value?, variable?}}`), `clear?` |
| `refresh_mcp_server` | `serverId` |
| `test_mcp_server` | `url` (+ `transport?`, `auth_type?`, `auth_token?`, `headers?`) or `serverId` |
| `mcp_server_usage` | `serverId` |
| `delete_mcp_server` | `serverId`, `confirm`, `force?` |
| `attach_mcp_server` | `agentId`, `serverId`, `expose?`=`all`\|`selected`, `tools?` |
| `detach_mcp_server` | `agentId`, `serverId` |

### Knowledge

| Tool | Args |
|---|---|
| `list_kbs` | — |
| `create_kb` | `name`, `description?` |
| `get_kb` | `kbId` |
| `delete_kb` | `kbId`, `confirm` (detaches + hard-delete) |
| `list_docs` | `kbId` |
| `add_doc` | `kbId`, `name`, `source_type`=`text`\|`url`\|`faq`, plus `text` / `url` / `pairs:[{q,a}]` |
| `upload_doc` | `kbId`, `filename`, `contentBase64`, `mime?`, `name?` |
| `delete_doc` | `docId`, `confirm` |
| `attach_kb` | `agentId`, `kbId`, `topK?`, `minScore?` |
| `detach_kb` | `agentId`, `kbId` |
| `kb_limits` | — |
| `search_kb` | `agentId`, `q` |

### Phonemes

| Tool | Args |
|---|---|
| `list_phoneme_sets` | — |
| `get_phoneme_set` | `setId` |
| `create_phoneme_set` | `name`, `locale`, `description?`, `entries?` (`word`→`ipa`) |
| `add_phoneme_entry` | `setId`, `word`, `ipa` |
| `remove_phoneme_entry` | `setId`, `word` |
| `suggest_ipa` | `word`, `locale` |
| `delete_phoneme_set` | `setId`, `confirm` |
| `attach_phoneme_set` | `agentId`, `setId` |

---

## 6. Resources

| URI | What |
|---|---|
| `talktopc://llm-models` | Enabled LLM catalog (`model_name`, provider, display name, default) |
| `talktopc://voices` | TTS voices |
| `talktopc://website-voice-guide` | Site guide recipe (highlights + `sync_with_speech`); same as [site-voice-guide.md](./site-voice-guide.md) |

Use these instead of hardcoding model or voice ids.

---

## 7. How this maps to the SDK in this repo

```
MCP (control plane)                         This repo (client)
create_agent / patch_agent / attach_*  →    agentId + appId in VoiceSDK / widget
                                           wss://speech.talktopc.com/ws/conv
attach_script                          →    client-script tools in the widget
attach_tool (client / chain)           →    client_tool_call / run_partner_script
```

After you change an agent via MCP, tell the user to test with the widget (`agentId` from MCP, `appId` from the dashboard). You do not rebuild `ttp-agent-sdk` for a prompt or tool change.

---

## 8. Failure cheatsheet

| Symptom | Cause | What to do |
|---|---|---|
| Tools missing in Cursor | stdio not loaded / bad `TTP_API_KEY` | Check `~/.cursor/mcp.json`, key starts with `sk_` |
| Claude: “Couldn’t register with sign-in service” | Old server or Client ID filled | URL `https://mcp.talktopc.com/mcp`, empty Client ID, Connect, paste `sk_` |
| Claude: fetch error on Add | Hitting GET `/mcp` as SSE | Current server returns 405 on GET — remove and re-add the connector |
| 401 on `/mcp` | No key | Bearer / `x-api-key` / OAuth sign-in |
| `confirm:true` error | Delete without confirm | Retry with `confirm: true` |
| 409 on delete | Still referenced | `force: true` or detach first |
| Patch “did nothing” / lost fields | Parallel writes or full-blob overwrite | `get` then single `patch_agent`; no concurrent writers |
| Model / voice rejected | Guessed id | Read `talktopc://llm-models` / `talktopc://voices` |
| Widget can’t connect | Missing `appId` or domain allowlist | `appId` is not an MCP field; whitelist the site in the dashboard |

---

## 9. Environments

| | Control plane | MCP HTTP | Voice WS |
|---|---|---|---|
| Production | `https://backend.talktopc.com` | `https://mcp.talktopc.com/mcp` | `wss://speech.talktopc.com/ws/conv` |
| Sandbox | `https://backend.bidme.co.il` | local `:3108` if deployed | sandbox speech host |
| Laptop | `http://localhost:8765` | `node dist/http.js` → `:3107/mcp` | local Flux |

Stdio `TTP_BACKEND_URL` selects which control plane the tools write to. Hosted `mcp.talktopc.com` always writes **production**.

---

## 10. Website voice guide (SPA + speech-synced highlights)

**When to use:** marketing site where the agent navigates pages, highlights on-page copy **as it speaks**, and optionally fills a contact form.

**Canonical doc (read this):** [site-voice-guide.md](./site-voice-guide.md) — also MCP tool `website_voice_guide_docs` and resource `talktopc://website-voice-guide`.

Quick checklist:

1. Read [site-voice-guide.md](./site-voice-guide.md)
2. `export_agent { agentId: "agent_75f1dd1da" }` — TalkToPC Site Guide template
3. `acme_highlight` → `wait_for_response: false`, `speech_timing: sync_with_speech`
4. `patch_agent { singleClientToolFlight: false }`
5. Frontend: never `updateConfig()` on route change; `maximize()` + `showVoice()` after SPA nav during call
