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.
- tavus_cli-0.6.0/.release-please-manifest.json +3 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/CHANGELOG.md +14 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/PKG-INFO +1 -1
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/docs/usage-observability.md +29 -7
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/packages/tavus/pyproject.toml +2 -2
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/packages/tavus-mcp/pyproject.toml +2 -2
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/pyproject.toml +1 -1
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/__init__.py +1 -1
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/client/http.py +41 -2
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/server.py +57 -22
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/integration/test_mcp_server.py +52 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/uv.lock +2 -2
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/package-lock.json +2 -2
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/package.json +1 -1
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/src/tavus.ts +78 -4
- tavus_cli-0.6.0/workers/tavus-mcp/src/version.ts +1 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/test/tavus.test.ts +72 -0
- tavus_cli-0.4.5/.release-please-manifest.json +0 -3
- tavus_cli-0.4.5/workers/tavus-mcp/src/version.ts +0 -1
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.env.example +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/ci.yml +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/deploy-cloudflare-worker.yml +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/pr-reviews.yml +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/pypi-cli.yml +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/tail-cloudflare-worker.yml +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.github/workflows/validate-staging-deployment.yml +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/.gitignore +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/AGENTS.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/README.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/docs/gemini-enterprise/README.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/docs/gemini-enterprise/listing.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/docs/gemini-enterprise/test-queries.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/packages/tavus/README.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/packages/tavus-mcp/README.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/release-please-config.json +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/scripts/__init__.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/scripts/check_release_versions.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/SKILL.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/agents/openai.yaml +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/build-and-verify.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/builder-workflow.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/chat-mode.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/examples.md +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/cli/__init__.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/cli/main.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/__init__.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/auth/__init__.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/auth/keyring_store.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/auth/oauth.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/auth/session.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/client/__init__.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/daily_transport.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/env.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/errors.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/pal_urls.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/patch.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/__init__.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/build_and_verify.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/capabilities.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/carrier_builder.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/knowledge.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/options.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/quickstart.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/scaffold_embed.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/templates.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/recipes/tool_reuse.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/__init__.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/file_manifest.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/guardrail.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/objective.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/pal.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/pronunciation.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/src/tavus_mcp/sdk/schemas/tool.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/conftest.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/integration/test_cli.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/integration/test_client.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/integration/test_oauth.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_auth.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_build_and_verify.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_builder.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_capabilities.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_daily_transport.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_env.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_guardrail_schema.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_objective_schema.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_pal_schema.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_patch.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_pronunciation_schema.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_release_versions.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_scaffold_embed.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_tool_reuse.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/tests/unit/test_tool_schema.py +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/.dev.vars.example +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/src/index.ts +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/src/oauth-grant.ts +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/src/oauth.ts +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/test/parity.test.ts +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/tsconfig.json +0 -0
- {tavus_cli-0.4.5 → tavus_cli-0.6.0}/workers/tavus-mcp/wrangler.jsonc +0 -0
|
@@ -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
|
|
|
@@ -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.
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
8
|
+
"tavus-cli==0.6.0", # x-release-please-version
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
[project.urls]
|
|
@@ -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
|
-
|
|
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
|
-
|
|
110
|
-
"
|
|
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
|
-
|
|
113
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
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(
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
9
|
+
"version": "0.6.0",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
12
12
|
"agents": "^0.12.2",
|
|
@@ -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
|
-
|
|
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 +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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/build-and-verify.md
RENAMED
|
File without changes
|
{tavus_cli-0.4.5 → tavus_cli-0.6.0}/skills/tavus-agentic-pals/references/builder-workflow.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|