tavus-cli 0.9.2__tar.gz → 0.10.0__tar.gz

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 (118) hide show
  1. tavus_cli-0.10.0/.release-please-manifest.json +3 -0
  2. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/CHANGELOG.md +14 -0
  3. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/PKG-INFO +1 -1
  4. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/PROMPT.md +1 -1
  5. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/docs/builder-guidance.md +25 -5
  6. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/packages/tavus/pyproject.toml +2 -2
  7. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/packages/tavus-mcp/pyproject.toml +2 -2
  8. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/plugins/tavus-pal/plugin.json +2 -1
  9. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/pyproject.toml +1 -1
  10. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/skills/tavus-agentic-pals/SKILL.md +6 -4
  11. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/skills/tavus-agentic-pals/references/build-and-verify.md +30 -2
  12. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/__init__.py +1 -1
  13. tavus_cli-0.10.0/src/tavus_mcp/builder_guidance.json +25 -0
  14. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/uv.lock +1 -1
  15. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/package-lock.json +2 -2
  16. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/package.json +1 -1
  17. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/src/oauth.ts +46 -3
  18. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/src/tavus.ts +104 -17
  19. tavus_cli-0.10.0/workers/tavus-mcp/src/version.ts +1 -0
  20. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/test/builder-guidance.test.ts +10 -1
  21. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/test/tavus.test.ts +338 -0
  22. tavus_cli-0.9.2/.release-please-manifest.json +0 -3
  23. tavus_cli-0.9.2/src/tavus_mcp/builder_guidance.json +0 -21
  24. tavus_cli-0.9.2/workers/tavus-mcp/src/version.ts +0 -1
  25. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/.agents/plugins/marketplace.json +0 -0
  26. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/.env.example +0 -0
  27. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/.github/workflows/ci.yml +0 -0
  28. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/.github/workflows/deploy-cloudflare-worker.yml +0 -0
  29. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/.github/workflows/pr-reviews.yml +0 -0
  30. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/.github/workflows/pypi-cli.yml +0 -0
  31. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/.github/workflows/tail-cloudflare-worker.yml +0 -0
  32. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/.github/workflows/validate-staging-deployment.yml +0 -0
  33. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/.gitignore +0 -0
  34. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/AGENTS.md +0 -0
  35. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/README.md +0 -0
  36. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/docs/chatgpt-plugin.md +0 -0
  37. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/docs/gemini-enterprise/README.md +0 -0
  38. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/docs/gemini-enterprise/listing.md +0 -0
  39. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/docs/gemini-enterprise/test-queries.md +0 -0
  40. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/docs/usage-observability.md +0 -0
  41. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/packages/tavus/README.md +0 -0
  42. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/packages/tavus-mcp/README.md +0 -0
  43. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/plugins/tavus-pal/assets/icon-dark.svg +0 -0
  44. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/plugins/tavus-pal/assets/icon.svg +0 -0
  45. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/plugins/tavus-pal/mcp.json +0 -0
  46. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/release-please-config.json +0 -0
  47. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/scripts/__init__.py +0 -0
  48. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/scripts/build_chatgpt_plugin.py +0 -0
  49. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/scripts/check_release_versions.py +0 -0
  50. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/skills/tavus-agentic-pals/agents/openai.yaml +0 -0
  51. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/skills/tavus-agentic-pals/references/builder-workflow.md +0 -0
  52. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/skills/tavus-agentic-pals/references/chat-mode.md +0 -0
  53. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/skills/tavus-agentic-pals/references/examples.md +0 -0
  54. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/builder_guidance.py +0 -0
  55. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/cli/__init__.py +0 -0
  56. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/cli/main.py +0 -0
  57. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/__init__.py +0 -0
  58. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/auth/__init__.py +0 -0
  59. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/auth/keyring_store.py +0 -0
  60. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/auth/oauth.py +0 -0
  61. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/auth/session.py +0 -0
  62. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/client/__init__.py +0 -0
  63. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/client/http.py +0 -0
  64. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/daily_transport.py +0 -0
  65. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/env.py +0 -0
  66. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/errors.py +0 -0
  67. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/pal_urls.py +0 -0
  68. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/patch.py +0 -0
  69. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/__init__.py +0 -0
  70. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/build_and_verify.py +0 -0
  71. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/capabilities.py +0 -0
  72. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/carrier_builder.py +0 -0
  73. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/deployments.py +0 -0
  74. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/knowledge.py +0 -0
  75. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/options.py +0 -0
  76. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/quickstart.py +0 -0
  77. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/scaffold_embed.py +0 -0
  78. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/templates.py +0 -0
  79. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/recipes/tool_reuse.py +0 -0
  80. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/schemas/__init__.py +0 -0
  81. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/schemas/deployment.py +0 -0
  82. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/schemas/file_manifest.py +0 -0
  83. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/schemas/guardrail.py +0 -0
  84. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/schemas/objective.py +0 -0
  85. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/schemas/pal.py +0 -0
  86. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/schemas/pronunciation.py +0 -0
  87. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/sdk/schemas/tool.py +0 -0
  88. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/src/tavus_mcp/server.py +0 -0
  89. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/conftest.py +0 -0
  90. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/integration/test_cli.py +0 -0
  91. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/integration/test_client.py +0 -0
  92. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/integration/test_mcp_server.py +0 -0
  93. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/integration/test_oauth.py +0 -0
  94. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_auth.py +0 -0
  95. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_build_and_verify.py +0 -0
  96. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_builder.py +0 -0
  97. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_capabilities.py +0 -0
  98. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_daily_transport.py +0 -0
  99. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_deployments.py +0 -0
  100. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_env.py +0 -0
  101. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_guardrail_schema.py +0 -0
  102. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_objective_schema.py +0 -0
  103. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_pal_schema.py +0 -0
  104. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_patch.py +0 -0
  105. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_pronunciation_schema.py +0 -0
  106. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_release_versions.py +0 -0
  107. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_scaffold_embed.py +0 -0
  108. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_tool_reuse.py +0 -0
  109. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/tests/unit/test_tool_schema.py +0 -0
  110. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/.dev.vars.example +0 -0
  111. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/src/index.ts +0 -0
  112. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/src/oauth-grant.ts +0 -0
  113. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/src/oauth-session.ts +0 -0
  114. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/test/oauth-session.test.ts +0 -0
  115. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/test/parity.test.ts +0 -0
  116. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/test/worker-runtime.ts +0 -0
  117. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/tsconfig.json +0 -0
  118. {tavus_cli-0.9.2 → tavus_cli-0.10.0}/workers/tavus-mcp/wrangler.jsonc +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.10.0"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.10.0](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.9.3...v0.10.0) (2026-10-14)
4
+
5
+
6
+ ### Features
7
+
8
+ * route agents to build_and_verify and let it change existing PALs ([#72](https://github.com/Tavus-Engineering/tavus-mcp/issues/72)) ([0a993f9](https://github.com/Tavus-Engineering/tavus-mcp/commit/0a993f96219dde3cc6c27405d6741adb8fc69523))
9
+
10
+ ## [0.9.3](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.9.2...v0.9.3) (2026-10-13)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **worker:** accept any loopback port and legacy MCP client IDs at sign-in ([#70](https://github.com/Tavus-Engineering/tavus-mcp/issues/70)) ([231a42c](https://github.com/Tavus-Engineering/tavus-mcp/commit/231a42c39806646c21ae07af3330927dbba5ea4a))
16
+
3
17
  ## [0.9.2](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.9.1...v0.9.2) (2026-10-10)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tavus-cli
3
- Version: 0.9.2
3
+ Version: 0.10.0
4
4
  Summary: Tavus CLI and MCP server for agentic PAL creation and editing.
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: fastmcp>=2.0.0
@@ -37,7 +37,7 @@ How to do the common things properly
37
37
  These are the contracts the server keeps. Exact parameters and response shapes are in the reference below; the order of operations is here, because that is what tends to get invented.
38
38
 
39
39
  - Building a new PAL. Use `tavus_pal_build_and_verify`; it is the standard path. You run the interview first: ask me plain questions, up to 12, about who the PAL talks to, what a good conversation achieves, the tone, what it must never say or do, what it should know (links to documents), and what it should do in my app. Ask only what my description leaves open. Pass my description as `prompt`, the questions and my answers as `interview`, and your summary of each part as `briefs`. The call takes one to three minutes. It drafts the personality, objectives, guardrails and greeting, picks a face unless I asked for one (`face_id`), publishes the PAL, runs test conversations and judges them. Read `verdict.overall` (`pass`, `partial` or `fail`), then the summary and any failing evidence, before you tell me it works. Tools can only be attached once the PAL exists, so the build's test turns run without them and tool checks in the verdict fail; attach the tools, then rerun those turns with chat mode before you tell me the tools work. Give me the `pal_url` it returns; that opens the PAL in the PAL Maker. Use `tavus_pal_create` or `tavus_quickstart` only when I hand you a finished system prompt and ask you to skip the build.
40
- - Changing an existing PAL. Call `tavus_pal_get` and `tavus_describe_pal_options` first, and take ids and patch paths from what they return. Make the smallest change with `tavus_patch_pal` (JSON Patch operations such as `replace` on `/greeting`), or use the dedicated tools for capabilities, Knowledge and tools. A change applies to every conversation the PAL starts from then on, including on my live site, so tell me what will change and get a yes before patching a PAL that is already in use.
40
+ - Changing an existing PAL. For a change to what the PAL does or shows, such as a new step, an on-screen scorecard or quiz, web search, or memory, call `tavus_pal_build_and_verify` with its `pal_id`, my request as `prompt`, and only the `briefs` for the parts that should change. Put built-in capabilities in `briefs.skills`. The same PAL Maker drafters edit the PAL and leave the rest alone, then it publishes and tests the PAL. Read `drafted` and `builtin_changes` in the result to see what changed. For an exact edit, such as a new greeting, call `tavus_pal_get` and `tavus_describe_pal_options` first, and take ids and patch paths from what they return. Make the smallest change with `tavus_patch_pal` (JSON Patch operations such as `replace` on `/greeting`), or use the dedicated tools for capabilities, Knowledge and tools. A change applies to every conversation the PAL starts from then on, including on my live site, so tell me what will change and get a yes before changing a PAL that is already in use.
41
41
  - Giving the PAL a tool. Check `tavus_tool_list` first and attach an existing tool when its name, parameters and delivery fit. When you create a new one that overlaps an existing tool, tell me which tool you saw and why it does not fit. The tool's `description` is what tells the PAL when to call it, so write it as an instruction. `delivery` decides where the call goes: `{ "app_message": true }` (the default) sends it to the page running the conversation, and `delivery.api` makes Tavus call a backend URL. Then write the handler in this repo with the exact tool name and parameter schema, so the two halves match. On a page that uses `<tavus-embed>` or `<tavus-widget>`, the call arrives as `tavus:tool-call` `{ name, arguments, tool_call_id }` with `arguments` as a JSON string, and the page replies with `sendMessage({ event_type: "conversation.tool_result", properties: { tool_call_id, output, status: "success" } })`; the host communication page below has the full contract. A tool that is not attached to the PAL never fires, however the code is wired.
42
42
  - Giving the PAL knowledge. Create a Knowledge document from a URL anyone can reach with `tavus_document_create`, then attach it with `tavus_pal_knowledge_add`. The hosted server cannot read files on my machine; for a local file, ask me to upload it in the PAL Maker's Knowledge section or to host it at a URL. Before you tell me what a document says, read what Tavus indexed with `tavus_document_chunks`.
43
43
  - Testing by text. `tavus_chat_start` starts a text-only conversation with the PAL, `tavus_chat_turn` sends one message and waits for the reply, and `tavus_chat_end` ends it. Always end the conversations you start. The first turn is slower while the conversation starts up. Replies are not deterministic, so judge behaviour (did it ask for the order number, did it refuse the pricing promise) rather than exact wording.
@@ -17,12 +17,32 @@ Portal-only tool names, UI instructions, or capabilities this server cannot
17
17
  configure. In particular, the hosted attach tool takes `capability`, while the
18
18
  local tool takes `capability_id`.
19
19
 
20
+ ## Instruction order and length
21
+
22
+ Some clients keep only the first 4,096 characters of the server instructions.
23
+ Claude Code is one of them, and it also defers MCP tools, so an agent often
24
+ picks a tool from the instructions and tool names alone. Keep the routing rules
25
+ first and the whole set under 4,096 characters; the Worker guidance test fails
26
+ when either rule breaks.
27
+
28
+ - `instructions[0]` routes new builds to `tavus_pal_build_and_verify`. Both
29
+ servers send it first.
30
+ - `hosted_instructions` routes changes to existing PALs through
31
+ `tavus_pal_build_and_verify` with `pal_id`. Only the hosted Worker has that
32
+ parameter, so it inserts these right after `instructions[0]`.
33
+ - The remaining `instructions` are the capability rules.
34
+
20
35
  ## Behavior checks
21
36
 
22
- Use these prompts to evaluate capability selection with an MCP client:
37
+ Use these prompts to evaluate tool selection with an MCP client:
23
38
 
24
39
  | Request | Expected behavior |
25
40
  | --- | --- |
41
+ | Build a PAL that shows trainees a scorecard after each roleplay. | Call `tavus_pal_build_and_verify`, with the scorecard in `briefs.skills` on the hosted connector. Do not call `tavus_pal_create`. |
42
+ | Here is the system prompt I drafted; set it up so trainees see a scorecard. | Same as above. A drafted prompt is a brief, not a request to skip the build. |
43
+ | Add an on-screen scorecard to my PAL p123. | Hosted: `tavus_pal_build_and_verify` with `pal_id` and `briefs.skills`. Local: attach Magic Canvas. |
44
+ | Change the greeting of my PAL p123 to "Hi". | `tavus_patch_pal` on `/greeting`. |
45
+ | Create this PAL from my finished prompt and skip the build. | `tavus_pal_create`. |
26
46
  | Show a training scorecard after each exercise. | Attach Magic Canvas with `text` and/or `chart` and usage guidance in the same call. Do not create `record_training_scorecard`. |
27
47
  | Collect an email address on screen. We have no backend yet. | Attach Magic Canvas with `input`. Explain that delivery can be connected later. Do not invent an endpoint. |
28
48
  | Show the score and save it to our existing API. | Configure Magic Canvas for display and separately reuse or create a tool for the verified API. |
@@ -33,9 +53,9 @@ Use these prompts to evaluate capability selection with an MCP client:
33
53
  | Remember returning visitors. | Attach Memory and add a first-visit fallback in the prompt and greeting. |
34
54
  | Do not show anything on screen. | Respect the decline; do not attach Magic Canvas. |
35
55
 
36
- The automated tests verify that clients receive the shared guidance and that
37
- the scorecard configuration travels through the MCP tools to the skills API and
38
- back, using a mocked API. They do not evaluate a model's tool choice or prove
39
- live rendering. For a live evaluation, record the model's tool calls, read back
56
+ The automated tests verify that clients receive the guidance in the right order
57
+ and length, and that the scorecard configuration travels through the MCP tools
58
+ to the skills API and back, using a mocked API. They do not evaluate a model's
59
+ tool choice or prove live rendering. For a live evaluation, record the model's tool calls, read back
40
60
  the saved capability, and inspect the canvas during a call before claiming the
41
61
  scorecard displayed.
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "tavus"
3
- version = "0.9.2" # x-release-please-version
3
+ version = "0.10.0" # x-release-please-version
4
4
  description = "Official alias for tavus-cli, the Tavus CLI and MCP server."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
8
- "tavus-cli==0.9.2", # x-release-please-version
8
+ "tavus-cli==0.10.0", # x-release-please-version
9
9
  ]
10
10
 
11
11
  [project.urls]
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "tavus-mcp"
3
- version = "0.9.2" # x-release-please-version
3
+ version = "0.10.0" # x-release-please-version
4
4
  description = "Official alias for tavus-cli, which provides the tavus-mcp MCP server."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
8
- "tavus-cli==0.9.2", # x-release-please-version
8
+ "tavus-cli==0.10.0", # x-release-please-version
9
9
  ]
10
10
 
11
11
  [project.urls]
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "tavus-pal",
4
- "version": "0.1.0",
4
+ "version": "0.1.1",
5
5
  "description": "Create, edit, and test Tavus PALs: digital humans with a face, voice, knowledge, and conversational behavior. Uses the Tavus hosted MCP server and your connected Tavus account.",
6
6
  "author": {
7
7
  "name": "Tavus",
@@ -37,6 +37,7 @@
37
37
  "logoDark": "./assets/icon-dark.svg"
38
38
  },
39
39
  "review": {
40
+ "demo_recording_url": "https://tavus-mcp-review.pages.dev/tavus-openai-review-demo-2026-09-10-19f4128cd7a5cbc4.mp4",
40
41
  "commerce": false,
41
42
  "commerce_description": "Users access their existing Tavus account entitlements. This plugin does not sell PALs, subscriptions, or credits, collect payment details, or initiate checkout.",
42
43
  "test_cases": {
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tavus-cli"
3
- version = "0.9.2"
3
+ version = "0.10.0"
4
4
  description = "Tavus CLI and MCP server for agentic PAL creation and editing."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -220,10 +220,12 @@ Autonomous build + verify:
220
220
  persona, joins its Daily room, executes its tool calls, publishes, selects a
221
221
  face, smoke-tests the PAL over Daily, and returns a judge verdict.
222
222
  - Hosted connector: `tavus_pal_build_and_verify(prompt, interview?, briefs?,
223
- face_id?, pal_name?, probes?)` requires the calling agent to interview the
224
- creator first. It records that transcript, runs the portal's artifact
225
- drafters, publishes, selects a face, smoke-tests through the Tavus HTTP
226
- bridge, and returns a judge verdict.
223
+ face_id?, pal_name?, probes?, pal_id?)` requires the calling agent to
224
+ interview the creator first. It records that transcript, runs the portal's
225
+ artifact drafters, publishes, selects a face, smoke-tests through the Tavus
226
+ HTTP bridge, and returns a judge verdict. With `pal_id`, it changes that PAL
227
+ instead: only the briefed drafters run, and the PAL keeps its face. Put
228
+ built-in capabilities such as an on-screen scorecard in `briefs.skills`.
227
229
 
228
230
  Pass `probes` on either surface to pin exact validation prompts; otherwise Tavus
229
231
  generates them from the PAL spec. Neither path needs a browser or a client-side
@@ -43,9 +43,28 @@ tavus_pal_build_and_verify(
43
43
  face_id?, # optional override; RQH selects one when omitted
44
44
  pal_name?, # optional explicit name
45
45
  probes?, # optional exact validation prompts; generated when omitted
46
+ pal_id?, # optional existing PAL to change instead of building one
46
47
  )
47
48
  ```
48
49
 
50
+ ### Changing an existing PAL (hosted connector)
51
+
52
+ Pass `pal_id` to run the same drafters on an existing PAL. Put the requested
53
+ change in `prompt` and fill only the `briefs` for the parts that should
54
+ change; at least one brief is required. The other parts of the PAL are left
55
+ as they are, and the PAL keeps its face unless `face_id` is given.
56
+
57
+ Use `briefs.skills` for built-in capabilities: on-screen scorecards, quizzes,
58
+ forms, and charts (Magic Canvas), web search, memory, and slide decks. The
59
+ skills drafter attaches and configures them. Use `briefs.tools` only for an
60
+ external action such as a webhook or API call.
61
+
62
+ When the PAL has been used outside PAL Maker (a deployment, a conferencing
63
+ identity, or a real conversation), RQH opens the session on the PAL's private
64
+ PAL Maker draft. The edit lands there and publish copies it onto the live PAL.
65
+ Publishing also publishes any unpublished PAL Maker edits on that PAL, so get
66
+ the creator's agreement before changing a PAL they are editing in PAL Maker.
67
+
49
68
  The lower-level Python recipe retains `model` for backwards compatibility but
50
69
  ignores it. The authenticated RQH boundary always selects
51
70
  `tavus-pal-maker-gemma-4`; GPT-OSS, the old Gemma thinking model, and inline
@@ -66,6 +85,9 @@ tool payloads cannot override it.
66
85
  summary,
67
86
  },
68
87
  validated,
88
+ drafted?, # hosted path: drafters that returned a change
89
+ builtin_changes?, # hosted path: built-ins attached or detached on the
90
+ # tools drafter's recommendation
69
91
  build_transcript?, # local path
70
92
  refine_rounds_used?, # local path
71
93
  }
@@ -75,8 +97,14 @@ tool payloads cannot override it.
75
97
 
76
98
  - A missing or inaccessible builder persona fails the local path; MCP never
77
99
  falls back to a synthetic prompt.
78
- - The hosted path always drafts the four core artifacts, even when the calling
79
- agent supplies only a sparse set of briefs.
100
+ - A new PAL on the hosted path always drafts the four core artifacts, even
101
+ when the calling agent supplies only a sparse set of briefs. A `pal_id`
102
+ change runs only the briefed drafters.
103
+ - The tools drafter only recommends built-in capabilities. The hosted path
104
+ attaches a recommended Magic Canvas, Web Search, or Memory with default
105
+ settings when the skills drafter has not already attached it. It does not
106
+ attach Slide Presenter, which needs a deck's document ids; that case is
107
+ reported in `builtin_changes`.
80
108
  - Each path ends the CVI conversations it starts, including failed smoke tests.
81
109
  - Deployment tools are not run by the build-only recipe.
82
110
 
@@ -1,4 +1,4 @@
1
1
  """Tavus CLI and MCP server package."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.9.2" # x-release-please-version
4
+ __version__ = "0.10.0" # x-release-please-version
@@ -0,0 +1,25 @@
1
+ {
2
+ "instructions": [
3
+ "Build a new PAL with tavus_pal_build_and_verify. It runs the PAL Maker pipeline that Charlie uses, then publishes and tests the PAL. Use tavus_pal_create or tavus_quickstart only when the creator hands over a finished system prompt and asks to skip the build.",
4
+ "For an existing PAL, read tavus_pal_capabilities_list first and preserve unrelated settings and attachments. Configure built-ins with tavus_pal_capability_attach, tavus_pal_capability_patch, and tavus_pal_capability_detach.",
5
+ "Magic Canvas (magic_canvas) shows scorecards, running scores, charts, quizzes, forms, contact capture, text, and links during a call. A request to show a training scorecard means enabling Magic Canvas, not creating a tool such as record_training_scorecard. The PAL's own model drives the canvas; a saved tool or API response cannot draw on it. Model-generated quiz questions, scores, dice rolls, and random picks need no custom tool. Saving results to a backend is a separate step; recording data does not display it.",
6
+ "When the creator asks for on-screen interaction, attach magic_canvas with the needed magic_canvas_components and magic_canvas_usage_guidance in the same call: text and/or chart for a scorecard, input for forms, question for quizzes. Put one or two lines of when-to-show choreography in magic_canvas_usage_guidance, not in the system prompt. On attach, omitted components are disabled. If the use case only suggests a canvas, offer it once; a decline stays declined. Without a backend, say collection works on screen and delivery can be connected later; do not create a placeholder delivery tool.",
7
+ "Magic Canvas calendar collects date and time preferences and books nothing. Scheduling needs the creator's real HTTPS calendly.com booking link in scheduling_url, not a placeholder or scheduling_embed. Image and map output are not available.",
8
+ "Use slide_presenter to present an uploaded deck: display-ready document_ids, with slides_trigger=walk_the_deck for automatic narration or on_demand when the PAL should wait. For a presentation or sales use case, offer a deck once; Magic Canvas does not replace Slide Presenter.",
9
+ "Use web_search for current information the PAL needs during calls, such as weather, news, prices, and scores, and add when to look things up to the PAL's instructions; for weather, ask for the location first. Do not create get_weather or invent an endpoint. Research for building the PAL does not need Web Search attached.",
10
+ "Use perception for camera and screen awareness, not an invented view_screen tool. Use memory for returning participants, with a prompt and greeting that fall back gracefully for first-time visitors. Do not invent a custom tool for a missing integration or unsupported capability.",
11
+ "Create a saved tool only for a real external action, data lookup, or application event that built-ins do not cover. Check tavus_tool_list or tavus_describe_pal_options first and reuse a matching tool; if a new tool overlaps one, say why the existing one does not fit. Use an endpoint the creator supplied or that exists in their code, or an app-message handler their application implements. Never invent URLs or claim an unimplemented handler works. Attach tools with tavus_pal_tools_attach; inline layers.*.tools writes through tavus_patch_pal are rejected.",
12
+ "Knowledge uses document_ids, document_tags, and layers.knowledge_base. Use tavus_document_* for the library and tavus_pal_knowledge_* to attach. Reading a document does not attach it.",
13
+ "After a capability write, read tavus_pal_capabilities_list to confirm the saved config, and report only what the tools confirm. A build or text-test verdict does not prove a canvas displayed; do not claim that without a live visual check."
14
+ ],
15
+ "hosted_instructions": [
16
+ "To change what an existing PAL does or shows, including adding a built-in such as an on-screen scorecard, call tavus_pal_build_and_verify with its pal_id and only the briefs that should change. Put built-ins in briefs.skills and external actions in briefs.tools. Use tavus_patch_pal or the capability tools only for an exact setting the creator names."
17
+ ],
18
+ "tool_descriptions": {
19
+ "tavus_tool_create": "Create a saved tool for a real external action or application handler. Check built-in capabilities first: scorecards, quizzes, forms, charts, and on-screen text use tavus_pal_capability_attach with magic_canvas. A saved tool cannot render Magic Canvas. Backend recording is separate from on-screen display. Use web_search for weather or current information. Never invent an endpoint or placeholder handler.",
20
+ "tavus_pal_capability_attach": "Enable a built-in capability by intent. Use magic_canvas for scorecards, quizzes, forms, charts, and on-screen text; pass magic_canvas_components and magic_canvas_usage_guidance in this call. Use slide_presenter for decks, web_search for current information, perception for screen/camera awareness, and memory for returning participants. Read tavus_pal_capabilities_list afterward to verify the saved config.",
21
+ "tavus_pal_capability_patch": "Update an enabled built-in capability. For Magic Canvas, keep display choreography in magic_canvas_usage_guidance. The patch merges top-level config fields but replaces config.components when supplied. Read the saved config and preserve unchanged component settings, including any booking link, when changing components. Read tavus_pal_capabilities_list afterward to verify the saved config."
22
+ },
23
+ "magic_canvas_description": "Show scorecards, running scores, quizzes, forms, charts, text, and links during a call. The PAL's model drives the canvas; saved tools and API responses cannot render it. Use text or chart for a training scorecard, question for quizzes, and input for contact capture. Calendar collects preferences but books nothing. Scheduling needs a real HTTPS Calendly booking link. Image and map output are not available.",
24
+ "magic_canvas_usage_guidance": "One or two lines describing when to show each element, with one fixed variant per step. Keep this display choreography here rather than in the PAL's system prompt."
25
+ }
@@ -1303,7 +1303,7 @@ wheels = [
1303
1303
 
1304
1304
  [[package]]
1305
1305
  name = "tavus-cli"
1306
- version = "0.9.2"
1306
+ version = "0.10.0"
1307
1307
  source = { editable = "." }
1308
1308
  dependencies = [
1309
1309
  { name = "fastmcp" },
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@tavus/tavus-mcp-worker",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@tavus/tavus-mcp-worker",
9
- "version": "0.9.2",
9
+ "version": "0.10.0",
10
10
  "dependencies": {
11
11
  "@modelcontextprotocol/sdk": "^1.29.0",
12
12
  "agents": "^0.12.2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tavus/tavus-mcp-worker",
3
- "version": "0.9.2",
3
+ "version": "0.10.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -18,6 +18,13 @@ const REGISTER_ROUTE = "/register";
18
18
  const CALLBACK_ROUTE = "/oauth/callback";
19
19
  const AUTHORIZATION_REQUEST_ROUTE = "/oauth/authorization-request";
20
20
  const DYNAMIC_CLIENT_PREFIX = "tavus-mcp-dcr";
21
+ // Client IDs that /register issued before 2026-08-06 (#21). The Worker never
22
+ // stored them, and /authorize accepted any client ID whose redirect was a
23
+ // loopback URL. Clients such as Claude Code keep replaying the ID they cached
24
+ // instead of registering again (anthropics/claude-code#84614), and the
25
+ // rejection they get is a browser page they never see. Keeping the old rule
26
+ // for these IDs, loopback only, lets them sign in without any stored state.
27
+ const LEGACY_DYNAMIC_CLIENT_ID = /^tavus-mcp-[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/;
21
28
  const AUTHORIZATION_STATE_TTL_SECONDS = 10 * 60;
22
29
  const AUTHORIZATION_CODE_TTL_SECONDS = 5 * 60;
23
30
 
@@ -191,10 +198,37 @@ function findRegisteredRedirect(
191
198
  redirectUri: string,
192
199
  ): RegisteredOAuthClient | undefined {
193
200
  return clients.find(
194
- (c) => c.client_id === clientId && c.redirect_uris.includes(redirectUri),
201
+ (c) =>
202
+ c.client_id === clientId &&
203
+ c.redirect_uris.some((registered) => redirectUriMatches(registered, redirectUri)),
195
204
  );
196
205
  }
197
206
 
207
+ // RFC 8252 section 7.3: a native client takes an ephemeral port when it starts
208
+ // the flow, so the authorization server MUST accept any port on a loopback
209
+ // redirect URI. Everything else about the URI still has to match. HTTPS
210
+ // callbacks need an exact match.
211
+ function redirectUriMatches(registered: string, requested: string): boolean {
212
+ if (registered === requested) {
213
+ return true;
214
+ }
215
+ if (!isLoopbackUrl(registered) || !isLoopbackUrl(requested)) {
216
+ return false;
217
+ }
218
+ const a = new URL(registered);
219
+ const b = new URL(requested);
220
+ return (
221
+ a.protocol === b.protocol &&
222
+ a.hostname === b.hostname &&
223
+ a.pathname === b.pathname &&
224
+ a.search === b.search
225
+ );
226
+ }
227
+
228
+ function isLegacyDynamicClient(clientId: string): boolean {
229
+ return LEGACY_DYNAMIC_CLIENT_ID.test(clientId);
230
+ }
231
+
198
232
  async function resolveRegisteredRedirect(
199
233
  env: Env,
200
234
  clientId: string,
@@ -208,7 +242,7 @@ async function resolveRegisteredRedirect(
208
242
  const signingSecret = oauthSigningSecret(env);
209
243
  if (signingSecret) {
210
244
  const dynamic = await verifyClientPayload(clientId, signingSecret);
211
- if (dynamic?.redirect_uris.includes(redirectUri)) {
245
+ if (dynamic?.redirect_uris.some((registered) => redirectUriMatches(registered, redirectUri))) {
212
246
  return {
213
247
  client_id: clientId,
214
248
  redirect_uris: dynamic.redirect_uris,
@@ -217,6 +251,10 @@ async function resolveRegisteredRedirect(
217
251
  }
218
252
  }
219
253
 
254
+ if (isLegacyDynamicClient(clientId) && isLoopbackUrl(redirectUri)) {
255
+ return { client_id: clientId, redirect_uris: [redirectUri], name: "Tavus MCP Client" };
256
+ }
257
+
220
258
  return undefined;
221
259
  }
222
260
 
@@ -378,9 +416,10 @@ async function handleAuthorize(request: Request, env: Env, mcpRoute: string): Pr
378
416
 
379
417
  const registeredRedirect = await resolveRegisteredRedirect(env, clientId, redirectUri);
380
418
  if (!registeredRedirect) {
419
+ // This page is all the person sees; the MCP client never learns about it.
381
420
  return oauthError(
382
421
  "invalid_request",
383
- "redirect_uri is not registered for this client.",
422
+ "redirect_uri is not registered for this client. Remove and re-add the Tavus MCP server in your client so it registers again.",
384
423
  400,
385
424
  );
386
425
  }
@@ -654,6 +693,10 @@ async function authenticateClient(request: Request, params: URLSearchParams, env
654
693
  }
655
694
  return secret ? null : clientId;
656
695
  }
696
+ // Legacy IDs were always public clients; see LEGACY_DYNAMIC_CLIENT_ID.
697
+ if (isLegacyDynamicClient(clientId)) {
698
+ return !secret && !basic ? clientId : null;
699
+ }
657
700
  const signingSecret = oauthSigningSecret(env);
658
701
  const dynamic = signingSecret ? await verifyClientPayload(clientId, signingSecret) : null;
659
702
  if (!dynamic || !signingSecret) return null;
@@ -347,6 +347,13 @@ const CAPABILITY_ALIASES: Record<string, CapabilityId> = {
347
347
  "skill:memory": "memory",
348
348
  };
349
349
 
350
+ // Some clients keep only the first 4,096 characters of server instructions,
351
+ // so the routing rules lead and the whole set must stay under that limit.
352
+ export function hostedInstructions(): string {
353
+ const [buildRouting, ...capabilityRules] = builderGuidance.instructions;
354
+ return [buildRouting, ...builderGuidance.hosted_instructions, ...capabilityRules].join("\n\n");
355
+ }
356
+
350
357
  const KNOWLEDGE_BASE_SETTING_KEYS = new Set([
351
358
  "rag_score_threshold",
352
359
  "rag_n_chunks",
@@ -534,7 +541,7 @@ export function createTavusMcpServer(
534
541
  theme: "dark",
535
542
  },
536
543
  ],
537
- }, { instructions: builderGuidance.instructions.join("\n\n") });
544
+ }, { instructions: hostedInstructions() });
538
545
  addToolIntent(server, authChallenge);
539
546
 
540
547
  server.tool(
@@ -608,7 +615,7 @@ export function createTavusMcpServer(
608
615
 
609
616
  server.tool(
610
617
  "tavus_pal_create",
611
- "Create a PAL directly from supplied configuration and return its PAL Maker editor URL. This low-level operation does not draft or verify the PAL through the guided PAL Maker pipeline. Full-pipeline PALs default to tavus-gemma-4 unless layers.llm.model is supplied. Tools are first-class records referenced by top-level tool_ids; inline layers.llm.tools configuration is unsupported.",
618
+ "Create a PAL directly from supplied configuration and return its PAL Maker editor URL. This low-level operation does not draft or verify the PAL through the guided PAL Maker pipeline; for a new PAL, use tavus_pal_build_and_verify unless the creator hands over a finished system prompt and asks to skip the build. Full-pipeline PALs default to tavus-gemma-4 unless layers.llm.model is supplied. Tools are first-class records referenced by top-level tool_ids; inline layers.llm.tools configuration is unsupported.",
612
619
  {
613
620
  system_prompt: z.string().min(1).optional(),
614
621
  pal_name: z.string().default("Agentic Tavus PAL"),
@@ -666,7 +673,7 @@ export function createTavusMcpServer(
666
673
 
667
674
  server.tool(
668
675
  "tavus_patch_pal",
669
- "Patch a PAL with validated JSON Patch operations. Attach first-class tools through top-level tool_ids or tavus_pal_tools_attach; legacy layers.llm.tools writes are rejected.",
676
+ "Patch a PAL with validated JSON Patch operations for an exact field edit. To change what a PAL does or shows, such as adding an on-screen scorecard, use tavus_pal_build_and_verify with pal_id instead. Attach first-class tools through top-level tool_ids or tavus_pal_tools_attach; legacy layers.llm.tools writes are rejected.",
670
677
  {
671
678
  pal_id: z.string().min(1),
672
679
  ops: z.array(JsonPatchOperationSchema).min(1),
@@ -1062,7 +1069,8 @@ export function createTavusMcpServer(
1062
1069
 
1063
1070
  server.tool(
1064
1071
  "tavus_tool_create",
1065
- builderGuidance.tool_descriptions.tavus_tool_create + " Attach the returned tool_id to a PAL with tavus_pal_tools_attach or top-level tool_ids.",
1072
+ builderGuidance.tool_descriptions.tavus_tool_create +
1073
+ " Attach the returned tool_id to a PAL with tavus_pal_tools_attach or top-level tool_ids. When the tool is part of building or changing a PAL, tavus_pal_build_and_verify (with pal_id for an existing PAL) drafts saved tools and built-ins together through briefs.tools and briefs.skills.",
1066
1074
  toolWriteFields,
1067
1075
  writeAnnotations("Create tool"),
1068
1076
  async (body) => toolResult(await client.create("tools", clean(body))),
@@ -1302,7 +1310,8 @@ export function createTavusMcpServer(
1302
1310
 
1303
1311
  server.tool(
1304
1312
  "tavus_pal_capability_attach",
1305
- builderGuidance.tool_descriptions.tavus_pal_capability_attach,
1313
+ builderGuidance.tool_descriptions.tavus_pal_capability_attach +
1314
+ " When the creator describes a change to an existing PAL rather than an exact setting, use tavus_pal_build_and_verify with pal_id and briefs.skills instead.",
1306
1315
  capabilityWriteSchema,
1307
1316
  {
1308
1317
  title: "Attach PAL capability",
@@ -1759,7 +1768,7 @@ export function createTavusMcpServer(
1759
1768
 
1760
1769
  server.tool(
1761
1770
  "tavus_quickstart",
1762
- "Create a PAL from a complete system prompt, select a stock face when none is supplied, and start a conversation. Returns the PAL, conversation, and selected face. This quick setup does not gather an interview or run the guided PAL Maker build and verification pipeline.",
1771
+ "Create a PAL from a complete system prompt, select a stock face when none is supplied, and start a conversation. Returns the PAL, conversation, and selected face. This quick setup does not gather an interview or run the guided PAL Maker build and verification pipeline; use it only when the creator hands over a finished system prompt and asks to skip the build, and otherwise use tavus_pal_build_and_verify.",
1763
1772
  {
1764
1773
  system_prompt: z.string().min(1),
1765
1774
  pal_name: z.string().default("Agentic Tavus PAL"),
@@ -1835,8 +1844,13 @@ export function createTavusMcpServer(
1835
1844
 
1836
1845
  server.tool(
1837
1846
  "tavus_pal_build_and_verify",
1838
- "Build and publish a PAL through the guided PAL Maker pipeline from a completed brief and optional interview Q&A. Drafts the personality, objectives, guardrails, and greeting, selects a face, publishes the PAL, runs conversation probes, and returns the PAL ID, editor URL, and judged verification result. The briefs fields describe each artifact, including knowledge, tools, skills, perception, and face preferences. Connected tools or knowledge sources may access external services during building or testing. Usually takes one to three minutes.",
1847
+ "The standard way to build a PAL. Builds and publishes a PAL through the guided PAL Maker pipeline that Charlie uses, from a completed brief and optional interview Q&A. Drafts the personality, objectives, guardrails, and greeting, selects a face, publishes the PAL, runs conversation probes, and returns the PAL ID, editor URL, and judged verification result. The briefs fields describe each artifact, including knowledge, tools, skills, perception, and face preferences. Connected tools or knowledge sources may access external services during building or testing. Usually takes one to three minutes.\n\nTo change an existing PAL, pass its pal_id, put the requested change in prompt, and fill only the briefs for the parts that should change. The same PAL Maker drafters edit the PAL and leave the other parts alone. Put built-in capabilities in briefs.skills: on-screen scorecards, quizzes, forms, and charts (Magic Canvas), web search, memory, and slide decks. Use briefs.tools only for an external action such as a webhook or API call. The PAL keeps its face unless face_id is given. The change is published, together with any unpublished PAL Maker edits on that PAL.",
1839
1848
  {
1849
+ pal_id: z
1850
+ .string()
1851
+ .min(1)
1852
+ .optional()
1853
+ .describe("An existing PAL to change instead of building a new one."),
1840
1854
  prompt: z.string().min(1),
1841
1855
  interview: z
1842
1856
  .array(z.object({ question: z.string(), answer: z.string() }))
@@ -1860,9 +1874,10 @@ export function createTavusMcpServer(
1860
1874
  probes: z.array(z.string()).max(5).optional(),
1861
1875
  },
1862
1876
  writeAnnotations("Build and verify PAL with PAL Maker", { destructiveHint: true, openWorldHint: true }),
1863
- async ({ prompt, interview, briefs, face_id, pal_name, probes }) =>
1877
+ async ({ pal_id, prompt, interview, briefs, face_id, pal_name, probes }) =>
1864
1878
  toolResult(
1865
1879
  await runGuidedBuild(client, config.palMakerUrl, {
1880
+ pal_id,
1866
1881
  prompt,
1867
1882
  interview,
1868
1883
  briefs,
@@ -2756,6 +2771,7 @@ export interface InterviewEntry {
2756
2771
  }
2757
2772
 
2758
2773
  export interface GuidedBuildOptions {
2774
+ pal_id?: string;
2759
2775
  prompt: string;
2760
2776
  interview?: InterviewEntry[];
2761
2777
  briefs?: Partial<Record<DraftBriefKey, string>>;
@@ -2784,12 +2800,16 @@ export function interviewMessages(
2784
2800
 
2785
2801
  export function draftBriefs(
2786
2802
  briefs: Partial<Record<DraftBriefKey, string>> | undefined,
2803
+ editing = false,
2787
2804
  ): Record<string, string> {
2788
- // The four core artifacts are always drafted so a sparse agent-supplied
2789
- // brief set cannot produce a PAL missing objectives or guardrails.
2805
+ // A new PAL always drafts the four core artifacts so a sparse agent-supplied
2806
+ // brief set cannot produce a PAL missing objectives or guardrails. An edit
2807
+ // runs only the briefed drafters, so the rest of the PAL stays as it is.
2790
2808
  const mapped: Record<string, string> = {};
2791
- for (const key of DRAFT_CORE_ARTIFACTS) {
2792
- mapped[key] = DRAFT_FALLBACK_BRIEF;
2809
+ if (!editing) {
2810
+ for (const key of DRAFT_CORE_ARTIFACTS) {
2811
+ mapped[key] = DRAFT_FALLBACK_BRIEF;
2812
+ }
2793
2813
  }
2794
2814
  for (const key of DRAFT_BRIEF_KEYS) {
2795
2815
  const value = briefs?.[key];
@@ -3015,8 +3035,24 @@ async function runGuidedBuild(
3015
3035
  }
3016
3036
  }
3017
3037
 
3018
- const sessionName = options.pal_name?.trim() || derivePalName(prompt);
3019
- const session = expectObject(await client.post("builder", { name: sessionName }));
3038
+ const editPalId = options.pal_id?.trim();
3039
+ const briefs = draftBriefs(options.briefs, Boolean(editPalId));
3040
+ if (editPalId && !Object.keys(briefs).length) {
3041
+ throw new Error(
3042
+ "Changing an existing PAL needs at least one brief naming what to change, " +
3043
+ "for example briefs.skills for an on-screen scorecard or briefs.personality for prompt wording.",
3044
+ );
3045
+ }
3046
+ let sessionName = options.pal_name?.trim();
3047
+ if (editPalId && !sessionName) {
3048
+ const existing = expectObject(await client.get("pals", editPalId));
3049
+ sessionName = String(existing.pal_name ?? existing.persona_name ?? "").trim();
3050
+ }
3051
+ // RQH opens the session on the PAL's private PAL Maker draft when it has
3052
+ // one; that row takes the edits and publish copies them onto the live PAL.
3053
+ const session = expectObject(
3054
+ await client.post("builder", clean({ name: sessionName || derivePalName(prompt), persona_id: editPalId })),
3055
+ );
3020
3056
  const builderId = String(session.builder_id ?? "");
3021
3057
  let palId = String(session.persona_id ?? "");
3022
3058
  if (!builderId || !palId) {
@@ -3030,7 +3066,7 @@ async function runGuidedBuild(
3030
3066
  const current = expectObject(await client.get("pals", palId, { include_settings: "true" }));
3031
3067
  const terminal = await client.postNdjson(`builder/${builderId}/draft-fanout`, {
3032
3068
  message: prompt,
3033
- briefs: draftBriefs(options.briefs),
3069
+ briefs,
3034
3070
  current: {
3035
3071
  system_prompt: current.system_prompt ?? "",
3036
3072
  greeting: current.greeting ?? "",
@@ -3045,8 +3081,10 @@ async function runGuidedBuild(
3045
3081
  if (operations.length) {
3046
3082
  await client.patch("pals", palId, operations);
3047
3083
  }
3084
+ const builtinChanges = await applyBuiltinRecommendations(client, palId, content.tools);
3048
3085
 
3049
- await client.post(`builder/${builderId}/publish`, {});
3086
+ const published = expectObject(await client.post(`builder/${builderId}/publish`, {}));
3087
+ palId = String(published.persona_id ?? session.live_persona_id ?? palId);
3050
3088
 
3051
3089
  let faceSelection: Record<string, unknown> | null = null;
3052
3090
  let faceId = options.face_id;
@@ -3054,7 +3092,7 @@ async function runGuidedBuild(
3054
3092
  await client.patch("pals", palId, [
3055
3093
  { op: "replace", path: "/default_face_id", value: faceId },
3056
3094
  ]);
3057
- } else {
3095
+ } else if (!editPalId) {
3058
3096
  faceSelection = expectObject(
3059
3097
  await client.post("persona-builder/select-replica", { persona_id: palId }),
3060
3098
  );
@@ -3111,6 +3149,11 @@ async function runGuidedBuild(
3111
3149
  face_id: faceId ?? spec.default_replica_id,
3112
3150
  face_selection: faceSelection,
3113
3151
  pal_spec: spec,
3152
+ drafted: Object.keys(content).filter((kind) => {
3153
+ const value = content[kind];
3154
+ return value != null && !(isRecord(value) && (value.error || value.status === "superseded"));
3155
+ }),
3156
+ builtin_changes: builtinChanges,
3114
3157
  probes,
3115
3158
  smoke_transcript: smokeTranscript,
3116
3159
  verdict,
@@ -3118,6 +3161,50 @@ async function runGuidedBuild(
3118
3161
  };
3119
3162
  }
3120
3163
 
3164
+ // RQH's tools drafter only recommends built-in capabilities: the client that
3165
+ // asked for the draft attaches them, as the portal does for Charlie.
3166
+ async function applyBuiltinRecommendations(
3167
+ client: TavusApiClient,
3168
+ palId: string,
3169
+ tools: unknown,
3170
+ ): Promise<string[]> {
3171
+ if (!isRecord(tools) || tools.error || tools.status === "superseded") {
3172
+ return [];
3173
+ }
3174
+ const attach = stringArray(tools.attach_builtins);
3175
+ const detach = stringArray(tools.detach_builtins);
3176
+ if (!attach.length && !detach.length) {
3177
+ return [];
3178
+ }
3179
+ // Read after the fan-out: the skills drafter may have attached and
3180
+ // configured the same capability, and an empty attach would reset it.
3181
+ const attached = skillMap(await client.getPath(`pals/${palId}/skills/`));
3182
+ const changes: string[] = [];
3183
+ for (const [builtin, attaching] of [
3184
+ ...attach.map((id) => [id, true] as const),
3185
+ ...detach.map((id) => [id, false] as const),
3186
+ ]) {
3187
+ const capability = CAPABILITY_ALIASES[builtin];
3188
+ const skillId = capability ? CAPABILITY_CATALOG[capability].rqh_skill_id : null;
3189
+ if (!capability || !skillId || attaching === (skillId in attached)) {
3190
+ continue;
3191
+ }
3192
+ if (attaching && capability === "slide_presenter") {
3193
+ changes.push(
3194
+ "slide_presenter recommended but not attached: attach it with tavus_pal_capability_attach and the deck's document_ids.",
3195
+ );
3196
+ continue;
3197
+ }
3198
+ if (attaching) {
3199
+ await client.put(`pals/${palId}/skills/${skillId}`, { config: {} });
3200
+ } else {
3201
+ await client.deletePath(`pals/${palId}/skills/${skillId}`);
3202
+ }
3203
+ changes.push(`${attaching ? "attached" : "detached"} ${capability}`);
3204
+ }
3205
+ return changes;
3206
+ }
3207
+
3121
3208
  function isRecord(value: unknown): value is Record<string, unknown> {
3122
3209
  return typeof value === "object" && value !== null && !Array.isArray(value);
3123
3210
  }