tavus-cli 0.4.5__tar.gz → 0.6.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 (99) hide show
  1. tavus_cli-0.6.0/.release-please-manifest.json +3 -0
  2. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/CHANGELOG.md +14 -0
  3. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/PKG-INFO +1 -1
  4. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/docs/usage-observability.md +29 -7
  5. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/packages/tavus/pyproject.toml +2 -2
  6. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/packages/tavus-mcp/pyproject.toml +2 -2
  7. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/pyproject.toml +1 -1
  8. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/__init__.py +1 -1
  9. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/client/http.py +41 -2
  10. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/server.py +57 -22
  11. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/integration/test_mcp_server.py +52 -0
  12. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/uv.lock +2 -2
  13. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/package-lock.json +2 -2
  14. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/package.json +1 -1
  15. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/src/tavus.ts +78 -4
  16. tavus_cli-0.6.0/workers/tavus-mcp/src/version.ts +1 -0
  17. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/test/tavus.test.ts +72 -0
  18. tavus_cli-0.4.5/.release-please-manifest.json +0 -3
  19. tavus_cli-0.4.5/workers/tavus-mcp/src/version.ts +0 -1
  20. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.env.example +0 -0
  21. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/ci.yml +0 -0
  22. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/deploy-cloudflare-worker.yml +0 -0
  23. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/pr-reviews.yml +0 -0
  24. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/pypi-cli.yml +0 -0
  25. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/tail-cloudflare-worker.yml +0 -0
  26. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/validate-staging-deployment.yml +0 -0
  27. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.gitignore +0 -0
  28. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/AGENTS.md +0 -0
  29. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/README.md +0 -0
  30. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/docs/gemini-enterprise/README.md +0 -0
  31. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/docs/gemini-enterprise/listing.md +0 -0
  32. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/docs/gemini-enterprise/test-queries.md +0 -0
  33. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/packages/tavus/README.md +0 -0
  34. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/packages/tavus-mcp/README.md +0 -0
  35. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/release-please-config.json +0 -0
  36. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/scripts/__init__.py +0 -0
  37. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/scripts/check_release_versions.py +0 -0
  38. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/SKILL.md +0 -0
  39. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/agents/openai.yaml +0 -0
  40. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/build-and-verify.md +0 -0
  41. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/builder-workflow.md +0 -0
  42. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/chat-mode.md +0 -0
  43. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/examples.md +0 -0
  44. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/cli/__init__.py +0 -0
  45. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/cli/main.py +0 -0
  46. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/__init__.py +0 -0
  47. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/auth/__init__.py +0 -0
  48. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/auth/keyring_store.py +0 -0
  49. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/auth/oauth.py +0 -0
  50. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/auth/session.py +0 -0
  51. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/client/__init__.py +0 -0
  52. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/daily_transport.py +0 -0
  53. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/env.py +0 -0
  54. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/errors.py +0 -0
  55. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/pal_urls.py +0 -0
  56. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/patch.py +0 -0
  57. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/__init__.py +0 -0
  58. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/build_and_verify.py +0 -0
  59. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/capabilities.py +0 -0
  60. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/carrier_builder.py +0 -0
  61. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/knowledge.py +0 -0
  62. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/options.py +0 -0
  63. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/quickstart.py +0 -0
  64. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/scaffold_embed.py +0 -0
  65. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/templates.py +0 -0
  66. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/tool_reuse.py +0 -0
  67. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/__init__.py +0 -0
  68. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/file_manifest.py +0 -0
  69. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/guardrail.py +0 -0
  70. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/objective.py +0 -0
  71. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/pal.py +0 -0
  72. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/pronunciation.py +0 -0
  73. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/tool.py +0 -0
  74. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/conftest.py +0 -0
  75. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/integration/test_cli.py +0 -0
  76. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/integration/test_client.py +0 -0
  77. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/integration/test_oauth.py +0 -0
  78. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_auth.py +0 -0
  79. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_build_and_verify.py +0 -0
  80. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_builder.py +0 -0
  81. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_capabilities.py +0 -0
  82. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_daily_transport.py +0 -0
  83. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_env.py +0 -0
  84. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_guardrail_schema.py +0 -0
  85. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_objective_schema.py +0 -0
  86. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_pal_schema.py +0 -0
  87. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_patch.py +0 -0
  88. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_pronunciation_schema.py +0 -0
  89. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_release_versions.py +0 -0
  90. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_scaffold_embed.py +0 -0
  91. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_tool_reuse.py +0 -0
  92. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_tool_schema.py +0 -0
  93. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/.dev.vars.example +0 -0
  94. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/src/index.ts +0 -0
  95. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/src/oauth-grant.ts +0 -0
  96. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/src/oauth.ts +0 -0
  97. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/test/parity.test.ts +0 -0
  98. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/tsconfig.json +0 -0
  99. {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/wrangler.jsonc +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.6.0"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.6.0](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.5.0...v0.6.0) (2026-10-07)
4
+
5
+
6
+ ### Features
7
+
8
+ * let tavus_pal_list return the newest PALs first ([#54](https://github.com/Tavus-Engineering/tavus-mcp/issues/54)) ([1a48ac9](https://github.com/Tavus-Engineering/tavus-mcp/commit/1a48ac9d825e5c8d4bf708b99a7f2874a909bac2))
9
+
10
+ ## [0.5.0](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.4.5...v0.5.0) (2026-09-30)
11
+
12
+
13
+ ### Features
14
+
15
+ * record why agents call each MCP tool ([#51](https://github.com/Tavus-Engineering/tavus-mcp/issues/51)) ([1059388](https://github.com/Tavus-Engineering/tavus-mcp/commit/10593882a1f08a87a29e14f24bfdc7a3f53f0797))
16
+
3
17
  ## [0.4.5](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.4.4...v0.4.5) (2026-09-29)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: tavus-cli
3
- Version: 0.4.5
3
+ Version: 0.6.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
@@ -9,9 +9,27 @@ Tavus API requests with a source-specific user agent:
9
9
  | Local MCP server | `tavus-mcp/<version>` |
10
10
  | Hosted MCP Worker | `tavus-mcp-worker/<version>` |
11
11
 
12
- They also send the matching low-cardinality `X-Tavus-Client-Source` header. The
13
- header is available for future structured attribution; the user agent works with
14
- request-handler's current access logging today.
12
+ They also send the matching low-cardinality `X-Tavus-Client-Source` header.
13
+
14
+ ## Tool intent
15
+
16
+ Every MCP tool on both servers accepts an optional `intent` argument. The
17
+ calling agent uses it to say, in one sentence, why it called the tool and what
18
+ the user is trying to get done. Agents may leave it out, and a call never fails
19
+ because of it.
20
+
21
+ Each Tavus API request made during a tool call carries two more headers:
22
+
23
+ | Header | Value |
24
+ | --- | --- |
25
+ | `X-Tavus-Client-Tool` | The MCP tool name, such as `tavus_pal_get` |
26
+ | `X-Tavus-Client-Intent` | The intent, when given: whitespace collapsed, cut to 500 characters, and percent-encoded outside printable ASCII (`%` becomes `%25`) |
27
+
28
+ Request-handler logs all three `X-Tavus-Client-*` headers in the `headers` field
29
+ of `log_request_data`, so intent sits next to the URL and joins to `user_id` on
30
+ `trace_id` like the rest of this data. Decode the intent with any URL decoder,
31
+ such as Python's `urllib.parse.unquote`. One tool call can make several API
32
+ requests, and each one carries the same tool name and intent.
15
33
 
16
34
  ## SigNoz data path
17
35
 
@@ -59,7 +77,11 @@ historical rows until this source tagging is released.
59
77
  - Counts are downstream Tavus API requests, not top-level MCP tool calls or CLI
60
78
  invocations. Local-only operations such as help, auth status, path listing, and
61
79
  embed scaffolding do not generate an API request and are not counted.
62
- - No API key, prompt, or request body is added to telemetry by this change. The
63
- source tags contain only the product surface and package version. Request-handler
64
- continues applying its existing API-key truncation and 64 KiB body-redaction
65
- policy.
80
+ - The source tags contain only the product surface and package version.
81
+ Request-handler continues applying its existing API-key truncation and 64 KiB
82
+ body-redaction policy.
83
+ - Intent is free text written by the calling agent. The argument description
84
+ asks agents to leave out secrets and personal details, but nothing enforces
85
+ that, so treat logged intent with the same care as logged request bodies.
86
+ - Tool calls that make no Tavus API request, such as `tavus_scaffold_embed`, do
87
+ not record their intent.
@@ -1,11 +1,11 @@
1
1
  [project]
2
2
  name = "tavus"
3
- version = "0.4.5" # x-release-please-version
3
+ version = "0.6.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.4.5", # x-release-please-version
8
+ "tavus-cli==0.6.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.4.5" # x-release-please-version
3
+ version = "0.6.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.4.5", # x-release-please-version
8
+ "tavus-cli==0.6.0", # 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.5"
3
+ version = "0.6.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"
@@ -1,4 +1,4 @@
1
1
  """Tavus CLI and MCP server package."""
2
2
 
3
3
  __all__ = ["__version__"]
4
- __version__ = "0.4.5" # x-release-please-version
4
+ __version__ = "0.6.0" # x-release-please-version
@@ -2,9 +2,13 @@ from __future__ import annotations
2
2
 
3
3
  import json as _json
4
4
  import mimetypes
5
+ from collections.abc import Iterator
6
+ from contextlib import contextmanager
7
+ from contextvars import ContextVar
5
8
  from importlib.metadata import PackageNotFoundError, version
6
9
  from pathlib import Path
7
- from typing import Any, Self
10
+ from typing import Any, NamedTuple, Self
11
+ from urllib.parse import quote
8
12
 
9
13
  import httpx
10
14
 
@@ -25,6 +29,33 @@ from tavus_mcp.sdk.schemas.tool import AttachToolsBody, ToolCreate, ToolUpdate
25
29
 
26
30
  BUILDER_LLM_TIMEOUT_S = 120.0
27
31
  CLIENT_SOURCE_HEADER = "X-Tavus-Client-Source"
32
+ CLIENT_TOOL_HEADER = "X-Tavus-Client-Tool"
33
+ CLIENT_INTENT_HEADER = "X-Tavus-Client-Intent"
34
+ MAX_INTENT_LENGTH = 500
35
+ # Header values must be printable ASCII. Everything else, and "%" itself, is
36
+ # percent-encoded so request-handler logs stay readable and decodable.
37
+ _INTENT_HEADER_SAFE = "".join(chr(code) for code in range(0x20, 0x7F) if chr(code) != "%")
38
+
39
+
40
+ class _ToolCall(NamedTuple):
41
+ tool: str
42
+ intent: str | None
43
+
44
+
45
+ _current_tool_call: ContextVar[_ToolCall | None] = ContextVar(
46
+ "tavus_current_tool_call", default=None
47
+ )
48
+
49
+
50
+ @contextmanager
51
+ def tool_call_context(tool: str, intent: str | None) -> Iterator[None]:
52
+ """Tag Tavus API requests made inside an MCP tool call with its name and intent."""
53
+ normalized = " ".join((intent or "").split())[:MAX_INTENT_LENGTH] or None
54
+ token = _current_tool_call.set(_ToolCall(tool, normalized))
55
+ try:
56
+ yield
57
+ finally:
58
+ _current_tool_call.reset(token)
28
59
 
29
60
 
30
61
  def _public_pal_error_body(value: Any) -> Any:
@@ -59,11 +90,19 @@ def _client_headers(api_key: str, surface: str) -> dict[str, str]:
59
90
  raise ValueError(
60
91
  "surface must contain only lowercase letters, numbers, '.', '_', or '-'"
61
92
  )
62
- return {
93
+ headers = {
63
94
  "x-api-key": api_key,
64
95
  "User-Agent": f"tavus-{normalized_surface}/{_package_version()}",
65
96
  CLIENT_SOURCE_HEADER: normalized_surface,
66
97
  }
98
+ tool_call = _current_tool_call.get()
99
+ if tool_call is not None:
100
+ headers[CLIENT_TOOL_HEADER] = tool_call.tool
101
+ if tool_call.intent:
102
+ headers[CLIENT_INTENT_HEADER] = quote(
103
+ tool_call.intent, safe=_INTENT_HEADER_SAFE, errors="replace"
104
+ )
105
+ return headers
67
106
 
68
107
 
69
108
  class TavusClient:
@@ -1,13 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import functools
4
+ import inspect
3
5
  import time
4
- from typing import Any, Literal
6
+ from typing import Annotated, Any, Literal
5
7
 
6
8
  from fastmcp import FastMCP
9
+ from pydantic import Field
7
10
 
8
11
  from tavus_mcp.sdk import daily_transport
9
12
  from tavus_mcp.sdk.auth.session import get_session
10
- from tavus_mcp.sdk.client.http import TavusClient
13
+ from tavus_mcp.sdk.client.http import TavusClient, tool_call_context
11
14
  from tavus_mcp.sdk.env import load_config
12
15
  from tavus_mcp.sdk.recipes.build_and_verify import build_and_verify
13
16
  from tavus_mcp.sdk.recipes.capabilities import (
@@ -106,27 +109,52 @@ def _annotations_for_tool(name: str) -> dict[str, bool]:
106
109
  }
107
110
 
108
111
 
109
- class AnnotatedFastMCP(FastMCP):
110
- """Apply a conservative annotation policy to every registered Tavus tool."""
112
+ INTENT_DESCRIPTION = (
113
+ "Optional. One sentence on why you are calling this tool: what the user is "
114
+ "trying to get done. Tavus stores it to improve these tools. Leave out "
115
+ "secrets and personal details."
116
+ )
111
117
 
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:
118
+ _INTENT_PARAMETER = inspect.Parameter(
119
+ "intent",
120
+ inspect.Parameter.KEYWORD_ONLY,
121
+ default=None,
122
+ annotation=Annotated[str | None, Field(description=INTENT_DESCRIPTION)],
123
+ )
114
124
 
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
125
 
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)
126
+ def _with_intent(fn: Any, tool_name: str) -> Any:
127
+ """Add the optional ``intent`` argument and tag the call's API requests with it."""
128
+
129
+ @functools.wraps(fn)
130
+ async def call_with_intent(*args: Any, intent: str | None = None, **kwargs: Any) -> Any:
131
+ with tool_call_context(tool_name, intent):
132
+ return await fn(*args, **kwargs)
133
+
134
+ signature = inspect.signature(fn)
135
+ call_with_intent.__signature__ = signature.replace( # type: ignore[attr-defined]
136
+ parameters=[*signature.parameters.values(), _INTENT_PARAMETER]
137
+ )
138
+ call_with_intent.__annotations__ = {
139
+ **fn.__annotations__,
140
+ "intent": _INTENT_PARAMETER.annotation,
141
+ }
142
+ return call_with_intent
143
+
144
+
145
+ class AnnotatedFastMCP(FastMCP):
146
+ """Apply the Tavus annotation and intent policy to every registered tool."""
147
+
148
+ def tool(self, name_or_fn: Any = None, **kwargs: Any) -> Any:
149
+ if isinstance(name_or_fn, str):
150
+ kwargs["name"] = name_or_fn
151
+ name_or_fn = None
152
+ if name_or_fn is None:
153
+ return lambda fn: self.tool(fn, **kwargs)
154
+ name = kwargs.get("name") or name_or_fn.__name__
155
+ if kwargs.get("annotations") is None:
156
+ kwargs["annotations"] = _annotations_for_tool(name)
157
+ return super().tool(_with_intent(name_or_fn, name), **kwargs)
130
158
 
131
159
 
132
160
  mcp = AnnotatedFastMCP(
@@ -285,10 +313,17 @@ async def tavus_pal_list(
285
313
  limit: int = 25,
286
314
  page: int = 0,
287
315
  pal_type: str | None = None,
316
+ sort: Literal["asc", "desc"] | None = None,
288
317
  ) -> Any:
289
- """List compact PAL summaries. Use ``tavus_pal_get`` for full settings."""
318
+ """List compact PAL summaries, oldest first by creation time.
319
+
320
+ Set ``sort`` to ``"desc"`` for the newest PALs first. Use ``tavus_pal_get``
321
+ for full settings.
322
+ """
290
323
  async with TavusClient.from_env(surface="mcp") as client:
291
- response = await client.pals.list(limit=limit, page=page, pal_type=pal_type)
324
+ response = await client.pals.list(
325
+ limit=limit, page=page, pal_type=pal_type, sort=sort
326
+ )
292
327
  return _compact_list(response, _pal_summary)
293
328
 
294
329
 
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from pathlib import Path
4
+ from urllib.parse import unquote
4
5
 
5
6
  import pytest
6
7
  import respx
8
+ from fastmcp import Client
7
9
  from httpx import Response
8
10
 
9
11
  from tavus_mcp.sdk.errors import TavusApiError
@@ -102,6 +104,56 @@ async def test_mcp_requests_identify_the_mcp_surface(clean_env: Path) -> None:
102
104
  request = route.calls.last.request
103
105
  assert request.headers["user-agent"].startswith("tavus-mcp/")
104
106
  assert request.headers["x-tavus-client-source"] == "mcp"
107
+ assert request.headers["x-tavus-client-tool"] == "tavus_pal_list"
108
+ assert "x-tavus-client-intent" not in request.headers
109
+
110
+
111
+ @pytest.mark.asyncio
112
+ @respx.mock
113
+ async def test_pal_list_forwards_sort_order(clean_env: Path) -> None:
114
+ (clean_env / ".env").write_text(
115
+ "TAVUS_ENV=TEST\n"
116
+ "TAVUS_API_KEY=test-key\n"
117
+ "TAVUS_PUBLIC_API_BASE_URL=https://api.example.test/v2\n"
118
+ )
119
+ route = respx.get("https://api.example.test/v2/pals").mock(
120
+ return_value=Response(200, json={"data": [], "total_count": 0})
121
+ )
122
+
123
+ await tavus_pal_list(sort="desc")
124
+
125
+ assert route.calls.last.request.url.params["sort"] == "desc"
126
+
127
+
128
+ @pytest.mark.asyncio
129
+ async def test_every_tool_offers_an_optional_intent() -> None:
130
+ for tool in await mcp.list_tools():
131
+ assert "intent" in tool.parameters["properties"], tool.name
132
+ assert "intent" not in tool.parameters.get("required", []), tool.name
133
+
134
+
135
+ @pytest.mark.asyncio
136
+ @respx.mock
137
+ async def test_mcp_requests_carry_the_encoded_intent(clean_env: Path) -> None:
138
+ (clean_env / ".env").write_text(
139
+ "TAVUS_ENV=TEST\n"
140
+ "TAVUS_API_KEY=test-key\n"
141
+ "TAVUS_PUBLIC_API_BASE_URL=https://api.example.test/v2\n"
142
+ )
143
+ route = respx.get("https://api.example.test/v2/pals/p1").mock(
144
+ return_value=Response(200, json={"pal_id": "p1"})
145
+ )
146
+
147
+ async with Client(mcp) as client:
148
+ await client.call_tool(
149
+ "tavus_pal_get",
150
+ {"pal_id": "p1", "intent": " Check the café PAL\nbefore 100% launch "},
151
+ )
152
+
153
+ headers = route.calls.last.request.headers
154
+ assert headers["x-tavus-client-tool"] == "tavus_pal_get"
155
+ assert headers["x-tavus-client-intent"] == "Check the caf%C3%A9 PAL before 100%25 launch"
156
+ assert unquote(headers["x-tavus-client-intent"]) == "Check the café PAL before 100% launch"
105
157
 
106
158
 
107
159
  @pytest.mark.asyncio
@@ -1,5 +1,5 @@
1
1
  version = 1
2
- revision = 3
2
+ revision = 5
3
3
  requires-python = ">=3.11"
4
4
 
5
5
  [[package]]
@@ -1303,7 +1303,7 @@ wheels = [
1303
1303
 
1304
1304
  [[package]]
1305
1305
  name = "tavus-cli"
1306
- version = "0.4.5"
1306
+ version = "0.6.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.4.5",
3
+ "version": "0.6.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@tavus/tavus-mcp-worker",
9
- "version": "0.4.5",
9
+ "version": "0.6.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.4.5",
3
+ "version": "0.6.0",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,4 +1,8 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+
1
3
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import type { RegisteredTool } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
2
6
  import { z } from "zod";
3
7
 
4
8
  import type { OAuthGrantKind } from "./oauth-grant";
@@ -83,6 +87,66 @@ export const DEFAULT_PAL_LLM_MODEL = "tavus-gemma-4";
83
87
  const CLIENT_SOURCE = "mcp-worker";
84
88
  const CLIENT_VERSION = VERSION;
85
89
 
90
+ export const INTENT_DESCRIPTION =
91
+ "Optional. One sentence on why you are calling this tool: what the user is " +
92
+ "trying to get done. Tavus stores it to improve these tools. Leave out " +
93
+ "secrets and personal details.";
94
+ const MAX_INTENT_LENGTH = 500;
95
+
96
+ interface ToolCall {
97
+ tool: string;
98
+ intent?: string;
99
+ }
100
+
101
+ type ToolHandler = (args: Record<string, unknown>, extra: unknown) => unknown;
102
+
103
+ const currentToolCall = new AsyncLocalStorage<ToolCall>();
104
+
105
+ function normalizeIntent(intent: unknown): string | undefined {
106
+ if (typeof intent !== "string") return undefined;
107
+ const collapsed = intent.trim().split(/\s+/).join(" ");
108
+ // Truncate by code point so an emoji is never split into a lone surrogate.
109
+ return Array.from(collapsed).slice(0, MAX_INTENT_LENGTH).join("") || undefined;
110
+ }
111
+
112
+ // Header values must be printable ASCII. Everything else, and "%" itself, is
113
+ // percent-encoded so request-handler logs stay readable and decodable.
114
+ function intentHeaderValue(intent: string): string {
115
+ return intent.replace(/[^\x20-\x24\x26-\x7e]/gu, (char) =>
116
+ /\p{Cs}/u.test(char) ? "?" : encodeURIComponent(char),
117
+ );
118
+ }
119
+
120
+ // Adds the optional intent argument to every tool and tags the Tavus API
121
+ // requests a call makes with the tool name and intent.
122
+ function addToolIntent(server: McpServer): void {
123
+ const register = server.tool.bind(server) as (...args: unknown[]) => RegisteredTool;
124
+ server.tool = ((...args: unknown[]) => {
125
+ if (args.length !== 5) {
126
+ throw new Error(
127
+ `Register ${String(args[0])} with server.tool(name, description, schema, annotations, handler).`,
128
+ );
129
+ }
130
+ const [name, description, schema, annotations, handler] = args as [
131
+ string,
132
+ string,
133
+ z.ZodRawShape,
134
+ ToolAnnotations,
135
+ ToolHandler,
136
+ ];
137
+ return register(
138
+ name,
139
+ description,
140
+ { ...schema, intent: z.string().optional().describe(INTENT_DESCRIPTION) },
141
+ annotations,
142
+ ({ intent, ...toolArgs }: Record<string, unknown>, extra: unknown) =>
143
+ currentToolCall.run({ tool: name, intent: normalizeIntent(intent) }, () =>
144
+ handler(toolArgs, extra),
145
+ ),
146
+ );
147
+ }) as McpServer["tool"];
148
+ }
149
+
86
150
  const DEFAULT_PUBLIC_API_BASE_URLS: Record<TavusEnvironment, string> = {
87
151
  TEST: "https://test.rqh.tavusapi.com/v2",
88
152
  STG: "https://stg.rqh.tavusapi.com/v2",
@@ -449,6 +513,7 @@ export function createTavusMcpServer(env: Env, apiKey?: string): McpServer {
449
513
  name: "tavus",
450
514
  version: VERSION,
451
515
  });
516
+ addToolIntent(server);
452
517
 
453
518
  server.tool(
454
519
  "tavus_mcp_health_check",
@@ -475,11 +540,12 @@ export function createTavusMcpServer(env: Env, apiKey?: string): McpServer {
475
540
 
476
541
  server.tool(
477
542
  "tavus_pal_list",
478
- "List compact PAL summaries. Use tavus_pal_get for full settings.",
543
+ "List compact PAL summaries, oldest first by creation time. Set sort to \"desc\" for the newest PALs first. Use tavus_pal_get for full settings.",
479
544
  {
480
545
  limit: z.number().int().positive().max(100).default(25),
481
546
  page: z.number().int().nonnegative().default(0),
482
547
  pal_type: z.string().optional(),
548
+ sort: z.enum(["asc", "desc"]).optional(),
483
549
  },
484
550
  {
485
551
  title: "List pals",
@@ -488,9 +554,9 @@ export function createTavusMcpServer(env: Env, apiKey?: string): McpServer {
488
554
  idempotentHint: true,
489
555
  openWorldHint: false,
490
556
  },
491
- async ({ limit, page, pal_type }) =>
557
+ async ({ limit, page, pal_type, sort }) =>
492
558
  toolResult(
493
- compactPalList(await client.list("pals", clean({ limit, page, pal_type }))),
559
+ compactPalList(await client.list("pals", clean({ limit, page, pal_type, sort }))),
494
560
  ),
495
561
  );
496
562
 
@@ -2520,12 +2586,20 @@ class TavusApiClient {
2520
2586
  }
2521
2587
 
2522
2588
  export function tavusApiRequestHeaders(apiKey: string): Record<string, string> {
2523
- return {
2589
+ const headers: Record<string, string> = {
2524
2590
  "Content-Type": "application/json",
2525
2591
  "User-Agent": `tavus-${CLIENT_SOURCE}/${CLIENT_VERSION}`,
2526
2592
  "X-Tavus-Client-Source": CLIENT_SOURCE,
2527
2593
  "x-api-key": apiKey,
2528
2594
  };
2595
+ const toolCall = currentToolCall.getStore();
2596
+ if (toolCall) {
2597
+ headers["X-Tavus-Client-Tool"] = toolCall.tool;
2598
+ if (toolCall.intent) {
2599
+ headers["X-Tavus-Client-Intent"] = intentHeaderValue(toolCall.intent);
2600
+ }
2601
+ }
2602
+ return headers;
2529
2603
  }
2530
2604
 
2531
2605
  async function firstStockFaceId(client: TavusApiClient): Promise<string> {
@@ -0,0 +1 @@
1
+ export const VERSION = "0.6.0"; // x-release-please-version
@@ -1,4 +1,5 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
+ import type { z } from "zod";
2
3
 
3
4
  import {
4
5
  handleOAuthRequest,
@@ -275,6 +276,32 @@ describe("Cloudflare Tavus MCP helpers", () => {
275
276
  }
276
277
  });
277
278
 
279
+ it("forwards the PAL list sort order", async () => {
280
+ const fetchMock = vi.fn(
281
+ async (_input: string | URL | Request) =>
282
+ new Response(JSON.stringify({ data: [], total_count: 0 }), {
283
+ status: 200,
284
+ headers: { "Content-Type": "application/json" },
285
+ }),
286
+ );
287
+ vi.stubGlobal("fetch", fetchMock);
288
+ try {
289
+ const server = createTavusMcpServer({ TAVUS_API_KEY: "secret" });
290
+ await registeredTools(server).tavus_pal_list.handler({
291
+ limit: 5,
292
+ page: 0,
293
+ pal_type: undefined,
294
+ sort: "desc",
295
+ });
296
+
297
+ expect(new URL(String(fetchMock.mock.calls[0][0])).searchParams.get("sort")).toBe(
298
+ "desc",
299
+ );
300
+ } finally {
301
+ vi.unstubAllGlobals();
302
+ }
303
+ });
304
+
278
305
  it("uses PAL vocabulary in public errors", () => {
279
306
  expect(publicPalErrorBody({ message: "Invalid persona_id" })).toEqual({
280
307
  message: "Invalid pal_id",
@@ -322,6 +349,51 @@ describe("Cloudflare Tavus MCP helpers", () => {
322
349
  });
323
350
  });
324
351
 
352
+ it("offers an optional intent argument on every hosted tool", () => {
353
+ const tools = registeredTools(createTavusMcpServer({ TAVUS_API_KEY: "secret" }));
354
+ for (const [name, tool] of Object.entries(tools)) {
355
+ const shape = (tool as unknown as { inputSchema: { shape: Record<string, z.ZodType> } })
356
+ .inputSchema.shape;
357
+ expect(shape.intent, `${name} intent`).toBeDefined();
358
+ expect(shape.intent.safeParse(undefined).success, `${name} intent optional`).toBe(true);
359
+ }
360
+ });
361
+
362
+ it("tags API requests with the tool name and encoded intent", async () => {
363
+ const fetchMock = vi.fn(async () =>
364
+ new Response(JSON.stringify({ pal_id: "p1" }), {
365
+ status: 200,
366
+ headers: { "Content-Type": "application/json" },
367
+ }),
368
+ );
369
+ vi.stubGlobal("fetch", fetchMock);
370
+ try {
371
+ const tool = registeredTools(createTavusMcpServer({ TAVUS_API_KEY: "secret" }))
372
+ .tavus_pal_get;
373
+ await tool.handler({
374
+ pal_id: "p1",
375
+ include_settings: false,
376
+ intent: " Check the café PAL\nbefore 100% launch ",
377
+ });
378
+ await tool.handler({ pal_id: "p1", include_settings: false });
379
+
380
+ const [withIntent, withoutIntent] = fetchMock.mock.calls.map(
381
+ (call) => (call as unknown as [string, RequestInit])[1].headers as Record<string, string>,
382
+ );
383
+ expect(withIntent).toMatchObject({
384
+ "X-Tavus-Client-Tool": "tavus_pal_get",
385
+ "X-Tavus-Client-Intent": "Check the caf%C3%A9 PAL before 100%25 launch",
386
+ });
387
+ expect(decodeURIComponent(withIntent["X-Tavus-Client-Intent"])).toBe(
388
+ "Check the café PAL before 100% launch",
389
+ );
390
+ expect(withoutIntent["X-Tavus-Client-Tool"]).toBe("tavus_pal_get");
391
+ expect(withoutIntent).not.toHaveProperty("X-Tavus-Client-Intent");
392
+ } finally {
393
+ vi.unstubAllGlobals();
394
+ }
395
+ });
396
+
325
397
  it("identifies hosted MCP requests without exposing the API key in the user agent", () => {
326
398
  expect(tavusApiRequestHeaders("secret")).toMatchObject({
327
399
  "User-Agent": `tavus-mcp-worker/${VERSION}`,
@@ -1,3 +0,0 @@
1
- {
2
- ".": "0.4.5"
3
- }
@@ -1 +0,0 @@
1
- export const VERSION = "0.4.5"; // x-release-please-version
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes