tavus-cli 0.4.0__tar.gz → 0.4.2__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 (100) hide show
  1. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/.github/workflows/pypi-cli.yml +0 -46
  2. tavus_cli-0.4.2/.release-please-manifest.json +3 -0
  3. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/CHANGELOG.md +14 -0
  4. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/PKG-INFO +1 -1
  5. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/packages/tavus/pyproject.toml +2 -2
  6. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/packages/tavus-mcp/pyproject.toml +2 -2
  7. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/pyproject.toml +1 -1
  8. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/__init__.py +1 -1
  9. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/cli/main.py +1 -1
  10. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/client/http.py +50 -0
  11. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/schemas/tool.py +1 -1
  12. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/server.py +158 -9
  13. tavus_cli-0.4.2/tests/integration/test_mcp_server.py +213 -0
  14. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_release_versions.py +0 -30
  15. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/uv.lock +1 -1
  16. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/package-lock.json +2 -2
  17. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/package.json +1 -1
  18. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/src/tavus.ts +106 -8
  19. tavus_cli-0.4.2/workers/tavus-mcp/src/version.ts +1 -0
  20. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/test/tavus.test.ts +191 -1
  21. tavus_cli-0.4.0/.release-please-manifest.json +0 -3
  22. tavus_cli-0.4.0/tests/integration/test_mcp_server.py +0 -79
  23. tavus_cli-0.4.0/workers/tavus-mcp/src/version.ts +0 -1
  24. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/.env.example +0 -0
  25. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/.github/workflows/ci.yml +0 -0
  26. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/.github/workflows/deploy-cloudflare-worker.yml +0 -0
  27. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/.github/workflows/pr-reviews.yml +0 -0
  28. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/.github/workflows/tail-cloudflare-worker.yml +0 -0
  29. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/.github/workflows/validate-staging-deployment.yml +0 -0
  30. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/.gitignore +0 -0
  31. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/AGENTS.md +0 -0
  32. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/README.md +0 -0
  33. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/docs/gemini-enterprise/README.md +0 -0
  34. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/docs/gemini-enterprise/listing.md +0 -0
  35. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/docs/gemini-enterprise/test-queries.md +0 -0
  36. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/docs/usage-observability.md +0 -0
  37. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/packages/tavus/README.md +0 -0
  38. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/packages/tavus-mcp/README.md +0 -0
  39. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/release-please-config.json +0 -0
  40. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/scripts/__init__.py +0 -0
  41. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/scripts/check_release_versions.py +0 -0
  42. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/skills/tavus-agentic-pals/SKILL.md +0 -0
  43. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/skills/tavus-agentic-pals/agents/openai.yaml +0 -0
  44. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/skills/tavus-agentic-pals/references/build-and-verify.md +0 -0
  45. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/skills/tavus-agentic-pals/references/builder-workflow.md +0 -0
  46. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/skills/tavus-agentic-pals/references/chat-mode.md +0 -0
  47. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/skills/tavus-agentic-pals/references/examples.md +0 -0
  48. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/cli/__init__.py +0 -0
  49. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/__init__.py +0 -0
  50. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/auth/__init__.py +0 -0
  51. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/auth/keyring_store.py +0 -0
  52. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/auth/oauth.py +0 -0
  53. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/auth/session.py +0 -0
  54. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/client/__init__.py +0 -0
  55. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/daily_transport.py +0 -0
  56. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/env.py +0 -0
  57. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/errors.py +0 -0
  58. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/pal_urls.py +0 -0
  59. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/patch.py +0 -0
  60. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/__init__.py +0 -0
  61. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/build_and_verify.py +0 -0
  62. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/capabilities.py +0 -0
  63. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/carrier_builder.py +0 -0
  64. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/knowledge.py +0 -0
  65. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/options.py +0 -0
  66. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/quickstart.py +0 -0
  67. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/scaffold_embed.py +0 -0
  68. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/templates.py +0 -0
  69. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/recipes/tool_reuse.py +0 -0
  70. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/schemas/__init__.py +0 -0
  71. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/schemas/file_manifest.py +0 -0
  72. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/schemas/guardrail.py +0 -0
  73. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/schemas/objective.py +0 -0
  74. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/schemas/pal.py +0 -0
  75. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/src/tavus_mcp/sdk/schemas/pronunciation.py +0 -0
  76. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/conftest.py +0 -0
  77. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/integration/test_cli.py +0 -0
  78. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/integration/test_client.py +0 -0
  79. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/integration/test_oauth.py +0 -0
  80. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_auth.py +0 -0
  81. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_build_and_verify.py +0 -0
  82. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_builder.py +0 -0
  83. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_capabilities.py +0 -0
  84. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_daily_transport.py +0 -0
  85. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_env.py +0 -0
  86. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_guardrail_schema.py +0 -0
  87. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_objective_schema.py +0 -0
  88. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_pal_schema.py +0 -0
  89. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_patch.py +0 -0
  90. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_pronunciation_schema.py +0 -0
  91. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_scaffold_embed.py +0 -0
  92. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_tool_reuse.py +0 -0
  93. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/tests/unit/test_tool_schema.py +0 -0
  94. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/.dev.vars.example +0 -0
  95. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/src/index.ts +0 -0
  96. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/src/oauth-grant.ts +0 -0
  97. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/src/oauth.ts +0 -0
  98. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/test/parity.test.ts +0 -0
  99. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/tsconfig.json +0 -0
  100. {tavus_cli-0.4.0 → tavus_cli-0.4.2}/workers/tavus-mcp/wrangler.jsonc +0 -0
@@ -322,52 +322,6 @@ jobs:
322
322
  - name: Publish Tavus CLI and its alias packages to PyPI
323
323
  run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/ dist/*
324
324
 
325
- notify-cvi-automations:
326
- name: 'Notify #cvi-automations (prod deploy)'
327
- needs: [preflight, production]
328
- if: ${{ always() && !cancelled() && needs.preflight.result == 'success' }}
329
- runs-on: ubuntu-latest
330
- # A Slack or mention-resolution failure must not change the deploy result.
331
- continue-on-error: true
332
- timeout-minutes: 5
333
- steps:
334
- - name: Resolve release author mentions
335
- id: mentions
336
- continue-on-error: true
337
- timeout-minutes: 2
338
- uses: Tavus-Engineering/pr-changelog-action/release-mentions@main
339
- with:
340
- github-token: ${{ secrets.GITHUB_TOKEN }}
341
- deploy-tag: ${{ needs.preflight.outputs.release_tag }}
342
- environment: prod
343
-
344
- - name: 'Post outcome to #cvi-automations'
345
- env:
346
- SLACK_BOT_TOKEN: ${{ secrets.CVI_BOT_SLACK_TOKEN }}
347
- MENTIONS: ${{ steps.mentions.outputs.mentions }}
348
- DEPLOY_RESULT: ${{ needs.production.result }}
349
- RELEASE_TAG: ${{ needs.preflight.outputs.release_tag }}
350
- RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
351
- run: |
352
- set -euo pipefail
353
- AUTHOR_TAGS="${MENTIONS:+${MENTIONS} }"
354
- CVI_TEAM="<!subteam^S08HUM36HMZ>"
355
- if [ "$DEPLOY_RESULT" = "success" ]; then
356
- TEXT=":white_check_mark: ${AUTHOR_TAGS}Tavus MCP <${RUN_URL}|${RELEASE_TAG}> is live in production."
357
- else
358
- TEXT=":x: ${CVI_TEAM} Tavus MCP production deploy failed — <${RUN_URL}|run>."
359
- fi
360
-
361
- RESPONSE=$(curl --fail-with-body --silent --show-error \
362
- --connect-timeout 5 --max-time 30 \
363
- -X POST https://slack.com/api/chat.postMessage \
364
- -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
365
- -H "Content-Type: application/json; charset=utf-8" \
366
- --data "$(jq -n --arg channel "C0B9S5Y556V" --arg text "$TEXT" \
367
- '{channel:$channel, text:$text, unfurl_links:false}')")
368
- echo "$RESPONSE" | jq .
369
- echo "$RESPONSE" | jq -e '.ok == true' >/dev/null
370
-
371
325
  update-final-board:
372
326
  name: Refresh Slack after production
373
327
  needs: [preflight, update-pending-board, production]
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.4.2"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.4.2](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.4.1...v0.4.2) (2026-08-28)
4
+
5
+
6
+ ### Reverts
7
+
8
+ * "feat: notify cvi-automations after MCP deploys" ([5aa2112](https://github.com/Tavus-Engineering/tavus-mcp/commit/5aa2112d044e0c956f6e6a1ac4d792c626aa7728))
9
+
10
+ ## [0.4.1](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.4.0...v0.4.1) (2026-08-27)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * make MCP list tools compact and complete ([#33](https://github.com/Tavus-Engineering/tavus-mcp/issues/33)) ([9798cad](https://github.com/Tavus-Engineering/tavus-mcp/commit/9798cad82540a188162b1758731f04ae59f082f3))
16
+
3
17
  ## [0.4.0](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.3.4...v0.4.0) (2026-08-26)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tavus-cli
3
- Version: 0.4.0
3
+ Version: 0.4.2
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
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "tavus"
3
- version = "0.4.0" # x-release-please-version
3
+ version = "0.4.2" # 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.4.0", # x-release-please-version
8
+ "tavus-cli==0.4.2", # 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.4.0" # x-release-please-version
3
+ version = "0.4.2" # 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.4.0", # x-release-please-version
8
+ "tavus-cli==0.4.2", # x-release-please-version
9
9
  ]
10
10
 
11
11
  [project.urls]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "tavus-cli"
3
- version = "0.4.0"
3
+ version = "0.4.2"
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"
@@ -1,4 +1,4 @@
1
1
  """Tavus CLI and MCP server package."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.4.0" # x-release-please-version
4
+ __version__ = "0.4.2" # x-release-please-version
@@ -1462,7 +1462,7 @@ def tool_list(
1462
1462
  page: int = typer.Option(1),
1463
1463
  type: str = typer.Option("user", help="user | system | all"),
1464
1464
  name_or_uuid: str | None = typer.Option(None),
1465
- sort: str = typer.Option("ascending"),
1465
+ sort: str = typer.Option("asc"),
1466
1466
  json_output: bool = typer.Option(False, "--json"),
1467
1467
  ) -> None:
1468
1468
  """List tools."""
@@ -27,6 +27,20 @@ BUILDER_LLM_TIMEOUT_S = 120.0
27
27
  CLIENT_SOURCE_HEADER = "X-Tavus-Client-Source"
28
28
 
29
29
 
30
+ def _public_pal_error_body(value: Any) -> Any:
31
+ """Keep internal persona vocabulary out of public PAL errors."""
32
+ if isinstance(value, str):
33
+ return value.replace("persona_id", "pal_id")
34
+ if isinstance(value, list):
35
+ return [_public_pal_error_body(item) for item in value]
36
+ if isinstance(value, dict):
37
+ return {
38
+ str(key).replace("persona_id", "pal_id"): _public_pal_error_body(item)
39
+ for key, item in value.items()
40
+ }
41
+ return value
42
+
43
+
30
44
  def _package_version() -> str:
31
45
  try:
32
46
  return version("tavus-cli")
@@ -128,6 +142,8 @@ class TavusClient:
128
142
  body: Any = response.json()
129
143
  except ValueError:
130
144
  body = response.text
145
+ if path.strip("/").startswith("pals"):
146
+ body = _public_pal_error_body(body)
131
147
  raise TavusApiError(
132
148
  f"Tavus API {method.upper()} {path} failed with {response.status_code}",
133
149
  status_code=response.status_code,
@@ -149,6 +165,40 @@ class Resource:
149
165
  async def list(self, **params: Any) -> Any:
150
166
  return await self.client.request("GET", f"/{self.name}", params=_clean(params))
151
167
 
168
+ async def list_all(
169
+ self,
170
+ *,
171
+ page_size: int = 100,
172
+ start_page: int = 1,
173
+ **params: Any,
174
+ ) -> dict[str, Any]:
175
+ """Collect a 1-indexed paginated resource without hiding later pages."""
176
+ data: list[Any] = []
177
+ total_count: int | None = None
178
+ for page in range(start_page, start_page + 100):
179
+ response = await self.list(limit=page_size, page=page, **params)
180
+ page_data = (
181
+ response.get("data", [])
182
+ if isinstance(response, dict)
183
+ else response
184
+ if isinstance(response, list)
185
+ else []
186
+ )
187
+ if not isinstance(page_data, list):
188
+ page_data = []
189
+ data.extend(page_data)
190
+ if isinstance(response, dict) and isinstance(response.get("total_count"), int):
191
+ total_count = response["total_count"]
192
+ if not page_data:
193
+ break
194
+ if total_count is not None and len(data) >= total_count:
195
+ break
196
+ if len(page_data) < page_size:
197
+ break
198
+ else:
199
+ raise RuntimeError(f"Tavus API {self.name} pagination exceeded 100 pages")
200
+ return {"data": data, "total_count": total_count or len(data)}
201
+
152
202
  async def get(self, resource_id: str, **params: Any) -> Any:
153
203
  return await self.client.request(
154
204
  "GET",
@@ -249,7 +249,7 @@ class ToolListParams(BaseModel):
249
249
  limit: int = 25
250
250
  page: int = 1
251
251
  type: ToolListType = "user"
252
- sort: Literal["ascending", "descending"] = "ascending"
252
+ sort: Literal["asc", "desc"] = "asc"
253
253
  name_or_uuid: str | None = None
254
254
 
255
255
 
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import time
4
- from typing import Any
4
+ from typing import Any, Literal
5
5
 
6
6
  from fastmcp import FastMCP
7
7
 
@@ -31,7 +31,105 @@ from tavus_mcp.sdk.recipes.scaffold_embed import EmbedTarget, scaffold_embed
31
31
  from tavus_mcp.sdk.recipes.templates import PalTemplate, pal_from_template
32
32
  from tavus_mcp.sdk.recipes.tool_reuse import build_library_match_advisory
33
33
 
34
- mcp = FastMCP(
34
+ _READ_ONLY_TOOL_NAMES = {
35
+ "tavus_mcp_health_check",
36
+ "tavus_pal_list",
37
+ "tavus_pal_get",
38
+ "tavus_describe_pal_options",
39
+ "tavus_document_list",
40
+ "tavus_document_get",
41
+ "tavus_document_tags",
42
+ "tavus_document_chunks",
43
+ "tavus_pal_knowledge_list",
44
+ "tavus_skill_list",
45
+ "tavus_skill_get",
46
+ "tavus_pal_skills_list",
47
+ "tavus_pal_capability_catalog",
48
+ "tavus_pal_capabilities_list",
49
+ "tavus_face_list",
50
+ "tavus_scaffold_embed",
51
+ "tavus_resource_list",
52
+ "tavus_voice_list",
53
+ "tavus_guardrail_list",
54
+ "tavus_guardrail_get",
55
+ "tavus_guardrail_tags",
56
+ "tavus_objective_list",
57
+ "tavus_objective_get",
58
+ "tavus_objective_validate",
59
+ "tavus_tool_list",
60
+ "tavus_tool_get",
61
+ "tavus_pronunciation_dictionary_list",
62
+ "tavus_pronunciation_dictionary_get",
63
+ "tavus_pal_tools_list",
64
+ "tavus_builder_list",
65
+ "tavus_builder_get",
66
+ "tavus_builder_chat_history",
67
+ }
68
+
69
+ _DESTRUCTIVE_TOOL_NAMES = {
70
+ "tavus_pal_delete",
71
+ "tavus_pal_knowledge_remove",
72
+ "tavus_pal_knowledge_replace",
73
+ "tavus_pal_skill_detach",
74
+ "tavus_pal_capability_detach",
75
+ "tavus_conversation_end",
76
+ "tavus_guardrail_delete",
77
+ "tavus_objective_delete",
78
+ "tavus_tool_delete",
79
+ "tavus_pronunciation_dictionary_delete",
80
+ "tavus_pal_tools_detach",
81
+ "tavus_builder_delete",
82
+ "tavus_chat_end",
83
+ }
84
+
85
+
86
+ def _annotations_for_tool(name: str) -> dict[str, bool]:
87
+ if name in _READ_ONLY_TOOL_NAMES:
88
+ return {
89
+ "readOnlyHint": True,
90
+ "destructiveHint": False,
91
+ "idempotentHint": True,
92
+ "openWorldHint": True,
93
+ }
94
+ if name in _DESTRUCTIVE_TOOL_NAMES:
95
+ return {
96
+ "readOnlyHint": False,
97
+ "destructiveHint": True,
98
+ "idempotentHint": True,
99
+ "openWorldHint": True,
100
+ }
101
+ return {
102
+ "readOnlyHint": False,
103
+ "destructiveHint": False,
104
+ "idempotentHint": False,
105
+ "openWorldHint": True,
106
+ }
107
+
108
+
109
+ class AnnotatedFastMCP(FastMCP):
110
+ """Apply a conservative annotation policy to every registered Tavus tool."""
111
+
112
+ def tool(self, name_or_fn: Any = None, **kwargs: Any) -> Any:
113
+ if name_or_fn is None and kwargs.get("annotations") is None:
114
+
115
+ def register(fn: Any) -> Any:
116
+ return super(AnnotatedFastMCP, self).tool(
117
+ fn,
118
+ annotations=_annotations_for_tool(kwargs.get("name") or fn.__name__),
119
+ **kwargs,
120
+ )
121
+
122
+ return register
123
+ if callable(name_or_fn) and kwargs.get("annotations") is None:
124
+ kwargs["annotations"] = _annotations_for_tool(
125
+ kwargs.get("name") or name_or_fn.__name__
126
+ )
127
+ elif isinstance(name_or_fn, str) and kwargs.get("annotations") is None:
128
+ kwargs["annotations"] = _annotations_for_tool(name_or_fn)
129
+ return super().tool(name_or_fn, **kwargs)
130
+
131
+
132
+ mcp = AnnotatedFastMCP(
35
133
  "tavus",
36
134
  instructions=(
37
135
  "PAL tools are first-class objects, not inlined on the PAL. "
@@ -63,6 +161,47 @@ def _list_data(value: Any) -> list[Any]:
63
161
  return []
64
162
 
65
163
 
164
+ def _summary_value(item: dict[str, Any], *names: str) -> Any:
165
+ for name in names:
166
+ if name in item:
167
+ return item[name]
168
+ return None
169
+
170
+
171
+ def _compact_list(value: Any, summarize: Any) -> dict[str, Any]:
172
+ data = [summarize(item) for item in _list_data(value) if isinstance(item, dict)]
173
+ result: dict[str, Any] = {"data": data, "returned_count": len(data)}
174
+ if isinstance(value, dict) and isinstance(value.get("total_count"), int):
175
+ result["total_count"] = value["total_count"]
176
+ else:
177
+ result["total_count"] = len(data)
178
+ return result
179
+
180
+
181
+ def _pal_summary(item: dict[str, Any]) -> dict[str, Any]:
182
+ return {
183
+ "pal_id": _summary_value(item, "pal_id", "persona_id", "uuid"),
184
+ "name": _summary_value(item, "name", "pal_name", "persona_name"),
185
+ "description": _summary_value(
186
+ item,
187
+ "description",
188
+ "pal_description",
189
+ "persona_description",
190
+ ),
191
+ "is_published": item.get("is_published"),
192
+ "pipeline_mode": item.get("pipeline_mode"),
193
+ "updated_at": item.get("updated_at"),
194
+ }
195
+
196
+
197
+ def _tool_summary(item: dict[str, Any]) -> dict[str, Any]:
198
+ return {
199
+ "tool_id": _summary_value(item, "tool_id", "uuid"),
200
+ "name": item.get("name"),
201
+ "is_system_tool": item.get("is_system_tool"),
202
+ }
203
+
204
+
66
205
  def _response_shape(value: Any) -> str:
67
206
  if isinstance(value, list):
68
207
  return "list"
@@ -147,8 +286,10 @@ async def tavus_pal_list(
147
286
  page: int = 0,
148
287
  pal_type: str | None = None,
149
288
  ) -> Any:
289
+ """List compact PAL summaries. Use ``tavus_pal_get`` for full settings."""
150
290
  async with TavusClient.from_env(surface="mcp") as client:
151
- return await client.pals.list(limit=limit, page=page, pal_type=pal_type)
291
+ response = await client.pals.list(limit=limit, page=page, pal_type=pal_type)
292
+ return _compact_list(response, _pal_summary)
152
293
 
153
294
 
154
295
  @mcp.tool()
@@ -974,23 +1115,31 @@ async def tavus_objective_validate(
974
1115
 
975
1116
  @mcp.tool()
976
1117
  async def tavus_tool_list(
977
- limit: int = 25,
1118
+ limit: int | None = None,
978
1119
  page: int = 1,
979
1120
  type: str = "user",
980
1121
  name_or_uuid: str | None = None,
981
- sort: str = "ascending",
1122
+ sort: Literal["asc", "desc"] = "asc",
982
1123
  ) -> Any:
983
- """List tools. `type` is "user" / "system" / "all"."""
1124
+ """List compact tool summaries.
1125
+
1126
+ Omit ``limit`` to return every matching tool across all API pages. Set
1127
+ ``limit`` and ``page`` when one explicit page is enough. ``type`` accepts
1128
+ ``"user"``, ``"system"``, or ``"all"``; ``sort`` accepts ``"asc"`` or
1129
+ ``"desc"``. Use ``tavus_tool_get`` for the full tool definition.
1130
+ """
984
1131
  params: dict[str, Any] = {
985
- "limit": limit,
986
- "page": page,
987
1132
  "type": type,
988
1133
  "sort": sort,
989
1134
  }
990
1135
  if name_or_uuid:
991
1136
  params["name_or_uuid"] = name_or_uuid
992
1137
  async with TavusClient.from_env(surface="mcp") as client:
993
- return await client.tools.list(**params)
1138
+ if limit is None:
1139
+ response = await client.tools.list_all(**params)
1140
+ else:
1141
+ response = await client.tools.list(limit=limit, page=page, **params)
1142
+ return _compact_list(response, _tool_summary)
994
1143
 
995
1144
 
996
1145
  @mcp.tool()
@@ -0,0 +1,213 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+ import respx
7
+ from httpx import Response
8
+
9
+ from tavus_mcp.sdk.errors import TavusApiError
10
+ from tavus_mcp.server import (
11
+ mcp,
12
+ tavus_pal_create,
13
+ tavus_pal_get,
14
+ tavus_pal_list,
15
+ tavus_tool_list,
16
+ )
17
+
18
+
19
+ @pytest.mark.asyncio
20
+ async def test_mcp_server_imports_and_registers_tools() -> None:
21
+ tools = {tool.name: tool for tool in await mcp.list_tools()}
22
+
23
+ assert "tavus_mcp_health_check" in tools
24
+ assert "tavus_pal_list" in tools
25
+ assert "tavus_describe_pal_options" in tools
26
+ assert "tavus_skill_list" in tools
27
+ assert "tavus_document_list" in tools
28
+ assert "tavus_document_upload" in tools
29
+ assert "tavus_document_chunks" in tools
30
+ assert "tavus_pal_knowledge_list" in tools
31
+ assert "tavus_pal_knowledge_add" in tools
32
+ assert "tavus_pal_knowledge_remove" in tools
33
+ assert "tavus_pal_skills_list" in tools
34
+ assert "tavus_pal_capabilities_list" in tools
35
+ assert "tavus_pal_capability_attach" in tools
36
+ assert "tavus_pal_capability_detach" in tools
37
+ assert "tavus_pal_from_template" in tools
38
+ assert "tavus_scaffold_embed" in tools
39
+ build_schema = tools["tavus_pal_build_and_verify"].parameters
40
+ assert "probes" in build_schema["properties"]
41
+ assert tools["tavus_tool_list"].parameters["properties"]["sort"]["enum"] == [
42
+ "asc",
43
+ "desc",
44
+ ]
45
+
46
+ assert all(tool.annotations is not None for tool in tools.values())
47
+ health_annotations = tools["tavus_mcp_health_check"].annotations
48
+ assert health_annotations.readOnlyHint is True
49
+ assert health_annotations.destructiveHint is False
50
+ assert health_annotations.idempotentHint is True
51
+ assert health_annotations.openWorldHint is True
52
+ assert tools["tavus_pal_create"].annotations.readOnlyHint is False
53
+ assert tools["tavus_pal_create"].annotations.destructiveHint is False
54
+ assert tools["tavus_pal_delete"].annotations.destructiveHint is True
55
+
56
+
57
+ @pytest.mark.asyncio
58
+ @respx.mock
59
+ async def test_mcp_requests_identify_the_mcp_surface(clean_env: Path) -> None:
60
+ (clean_env / ".env").write_text(
61
+ "TAVUS_ENV=TEST\n"
62
+ "TAVUS_API_KEY=test-key\n"
63
+ "TAVUS_PUBLIC_API_BASE_URL=https://api.example.test/v2\n"
64
+ )
65
+ route = respx.get("https://api.example.test/v2/pals").mock(
66
+ return_value=Response(
67
+ 200,
68
+ json={
69
+ "data": [
70
+ {
71
+ "pal_id": "p-summary",
72
+ "pal_name": "Summary PAL",
73
+ "pal_description": "A compact example.",
74
+ "is_published": True,
75
+ "pipeline_mode": "full",
76
+ "updated_at": "2026-08-25T12:00:00Z",
77
+ "system_prompt": "This must not appear in a list response.",
78
+ "context": "Nor should this.",
79
+ "layers": {"llm": {"model": "private-detail"}},
80
+ "owner_id": 123,
81
+ }
82
+ ],
83
+ "total_count": 312,
84
+ },
85
+ )
86
+ )
87
+
88
+ assert await tavus_pal_list() == {
89
+ "data": [
90
+ {
91
+ "pal_id": "p-summary",
92
+ "name": "Summary PAL",
93
+ "description": "A compact example.",
94
+ "is_published": True,
95
+ "pipeline_mode": "full",
96
+ "updated_at": "2026-08-25T12:00:00Z",
97
+ }
98
+ ],
99
+ "returned_count": 1,
100
+ "total_count": 312,
101
+ }
102
+ request = route.calls.last.request
103
+ assert request.headers["user-agent"].startswith("tavus-mcp/")
104
+ assert request.headers["x-tavus-client-source"] == "mcp"
105
+
106
+
107
+ @pytest.mark.asyncio
108
+ @respx.mock
109
+ async def test_tool_list_collects_every_page_and_returns_summaries(clean_env: Path) -> None:
110
+ (clean_env / ".env").write_text(
111
+ "TAVUS_ENV=TEST\n"
112
+ "TAVUS_API_KEY=test-key\n"
113
+ "TAVUS_PUBLIC_API_BASE_URL=https://api.example.test/v2\n"
114
+ )
115
+ first_page = [
116
+ {
117
+ "tool_id": f"t-{index}",
118
+ "name": f"tool_{index}",
119
+ "description": f"Tool {index}",
120
+ "origin": "llm",
121
+ "trigger_type": "in_call",
122
+ "is_system_tool": False,
123
+ "updated_at": "2026-08-25T12:00:00Z",
124
+ "parameters": {"large": "detail"},
125
+ "owner_id": 123,
126
+ }
127
+ for index in range(100)
128
+ ]
129
+ route = respx.get("https://api.example.test/v2/tools").mock(
130
+ side_effect=[
131
+ Response(200, json={"data": first_page, "total_count": 101}),
132
+ Response(
133
+ 200,
134
+ json={
135
+ "data": [
136
+ {
137
+ "tool_id": "t-100",
138
+ "name": "tool_100",
139
+ "description": "Tool 100",
140
+ "origin": "llm",
141
+ "trigger_type": "in_call",
142
+ "is_system_tool": False,
143
+ "updated_at": "2026-08-25T12:00:00Z",
144
+ "parameters": {"large": "detail"},
145
+ }
146
+ ],
147
+ "total_count": 101,
148
+ },
149
+ ),
150
+ ]
151
+ )
152
+
153
+ result = await tavus_tool_list(sort="desc")
154
+
155
+ assert result["returned_count"] == 101
156
+ assert result["total_count"] == 101
157
+ assert result["data"][-1] == {
158
+ "tool_id": "t-100",
159
+ "name": "tool_100",
160
+ "is_system_tool": False,
161
+ }
162
+ assert len(route.calls) == 2
163
+ assert route.calls[0].request.url.params["sort"] == "desc"
164
+ assert route.calls[0].request.url.params["page"] == "1"
165
+ assert route.calls[1].request.url.params["page"] == "2"
166
+ assert "parameters" not in result["data"][-1]
167
+
168
+
169
+ @pytest.mark.asyncio
170
+ @respx.mock
171
+ async def test_pal_get_uses_public_vocabulary_in_errors(clean_env: Path) -> None:
172
+ (clean_env / ".env").write_text(
173
+ "TAVUS_ENV=TEST\n"
174
+ "TAVUS_API_KEY=test-key\n"
175
+ "TAVUS_PUBLIC_API_BASE_URL=https://api.example.test/v2\n"
176
+ )
177
+ respx.get("https://api.example.test/v2/pals/p-missing").mock(
178
+ return_value=Response(400, json={"message": "Invalid persona_id"})
179
+ )
180
+
181
+ with pytest.raises(TavusApiError) as error:
182
+ await tavus_pal_get("p-missing")
183
+
184
+ assert error.value.status_code == 400
185
+ assert error.value.body == {"message": "Invalid pal_id"}
186
+
187
+
188
+ @pytest.mark.asyncio
189
+ @respx.mock
190
+ async def test_pal_create_returns_matching_pal_maker_url(clean_env: Path) -> None:
191
+ (clean_env / ".env").write_text(
192
+ "TAVUS_ENV=PROD\n"
193
+ "TAVUS_API_KEY=test-key\n"
194
+ "TAVUS_PUBLIC_API_BASE_URL=https://api.example.test/v2\n"
195
+ "TAVUS_PAL_MAKER_URL=https://maker.example.test\n"
196
+ )
197
+ respx.post("https://api.example.test/v2/pals").mock(
198
+ return_value=Response(
199
+ 200,
200
+ json={"pal_id": "p-created", "pal_name": "Builder Link Test"},
201
+ )
202
+ )
203
+
204
+ created = await tavus_pal_create(
205
+ system_prompt="You are a test PAL.",
206
+ pal_name="Builder Link Test",
207
+ )
208
+
209
+ assert created == {
210
+ "pal_id": "p-created",
211
+ "pal_name": "Builder Link Test",
212
+ "pal_url": "https://maker.example.test/dev/pals/update?pal_id=p-created",
213
+ }