tavus-cli 0.9.2__tar.gz → 0.9.3__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.9.3/.release-please-manifest.json +3 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/CHANGELOG.md +7 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/PKG-INFO +1 -1
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/packages/tavus/pyproject.toml +2 -2
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/packages/tavus-mcp/pyproject.toml +2 -2
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/plugins/tavus-pal/plugin.json +2 -1
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/pyproject.toml +1 -1
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/__init__.py +1 -1
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/uv.lock +1 -1
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/package-lock.json +2 -2
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/package.json +1 -1
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/src/oauth.ts +46 -3
- tavus_cli-0.9.3/workers/tavus-mcp/src/version.ts +1 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/test/tavus.test.ts +220 -0
- tavus_cli-0.9.2/.release-please-manifest.json +0 -3
- tavus_cli-0.9.2/workers/tavus-mcp/src/version.ts +0 -1
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/.agents/plugins/marketplace.json +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/.env.example +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/.github/workflows/ci.yml +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/.github/workflows/deploy-cloudflare-worker.yml +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/.github/workflows/pr-reviews.yml +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/.github/workflows/pypi-cli.yml +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/.github/workflows/tail-cloudflare-worker.yml +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/.github/workflows/validate-staging-deployment.yml +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/.gitignore +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/AGENTS.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/PROMPT.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/README.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/docs/builder-guidance.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/docs/chatgpt-plugin.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/docs/gemini-enterprise/README.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/docs/gemini-enterprise/listing.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/docs/gemini-enterprise/test-queries.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/docs/usage-observability.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/packages/tavus/README.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/packages/tavus-mcp/README.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/plugins/tavus-pal/assets/icon-dark.svg +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/plugins/tavus-pal/assets/icon.svg +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/plugins/tavus-pal/mcp.json +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/release-please-config.json +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/scripts/__init__.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/scripts/build_chatgpt_plugin.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/scripts/check_release_versions.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/skills/tavus-agentic-pals/SKILL.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/skills/tavus-agentic-pals/agents/openai.yaml +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/skills/tavus-agentic-pals/references/build-and-verify.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/skills/tavus-agentic-pals/references/builder-workflow.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/skills/tavus-agentic-pals/references/chat-mode.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/skills/tavus-agentic-pals/references/examples.md +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/builder_guidance.json +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/builder_guidance.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/cli/__init__.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/cli/main.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/__init__.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/auth/__init__.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/auth/keyring_store.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/auth/oauth.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/auth/session.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/client/__init__.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/client/http.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/daily_transport.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/env.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/errors.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/pal_urls.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/patch.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/__init__.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/build_and_verify.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/capabilities.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/carrier_builder.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/deployments.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/knowledge.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/options.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/quickstart.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/scaffold_embed.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/templates.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/recipes/tool_reuse.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/schemas/__init__.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/schemas/deployment.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/schemas/file_manifest.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/schemas/guardrail.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/schemas/objective.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/schemas/pal.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/schemas/pronunciation.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/sdk/schemas/tool.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/src/tavus_mcp/server.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/conftest.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/integration/test_cli.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/integration/test_client.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/integration/test_mcp_server.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/integration/test_oauth.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_auth.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_build_and_verify.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_builder.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_capabilities.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_daily_transport.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_deployments.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_env.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_guardrail_schema.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_objective_schema.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_pal_schema.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_patch.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_pronunciation_schema.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_release_versions.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_scaffold_embed.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_tool_reuse.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/tests/unit/test_tool_schema.py +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/.dev.vars.example +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/src/index.ts +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/src/oauth-grant.ts +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/src/oauth-session.ts +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/src/tavus.ts +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/test/builder-guidance.test.ts +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/test/oauth-session.test.ts +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/test/parity.test.ts +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/test/worker-runtime.ts +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/tsconfig.json +0 -0
- {tavus_cli-0.9.2 → tavus_cli-0.9.3}/workers/tavus-mcp/wrangler.jsonc +0 -0
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.9.3](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.9.2...v0.9.3) (2026-10-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **worker:** accept any loopback port and legacy MCP client IDs at sign-in ([#70](https://github.com/Tavus-Engineering/tavus-mcp/issues/70)) ([231a42c](https://github.com/Tavus-Engineering/tavus-mcp/commit/231a42c39806646c21ae07af3330927dbba5ea4a))
|
|
9
|
+
|
|
3
10
|
## [0.9.2](https://github.com/Tavus-Engineering/tavus-mcp/compare/v0.9.1...v0.9.2) (2026-10-10)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tavus"
|
|
3
|
-
version = "0.9.
|
|
3
|
+
version = "0.9.3" # x-release-please-version
|
|
4
4
|
description = "Official alias for tavus-cli, the Tavus CLI and MCP server."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"tavus-cli==0.9.
|
|
8
|
+
"tavus-cli==0.9.3", # x-release-please-version
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
[project.urls]
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "tavus-mcp"
|
|
3
|
-
version = "0.9.
|
|
3
|
+
version = "0.9.3" # x-release-please-version
|
|
4
4
|
description = "Official alias for tavus-cli, which provides the tavus-mcp MCP server."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
7
7
|
dependencies = [
|
|
8
|
-
"tavus-cli==0.9.
|
|
8
|
+
"tavus-cli==0.9.3", # x-release-please-version
|
|
9
9
|
]
|
|
10
10
|
|
|
11
11
|
[project.urls]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
|
|
3
3
|
"name": "tavus-pal",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.1",
|
|
5
5
|
"description": "Create, edit, and test Tavus PALs: digital humans with a face, voice, knowledge, and conversational behavior. Uses the Tavus hosted MCP server and your connected Tavus account.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Tavus",
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"logoDark": "./assets/icon-dark.svg"
|
|
38
38
|
},
|
|
39
39
|
"review": {
|
|
40
|
+
"demo_recording_url": "https://tavus-mcp-review.pages.dev/tavus-openai-review-demo-2026-09-10-19f4128cd7a5cbc4.mp4",
|
|
40
41
|
"commerce": false,
|
|
41
42
|
"commerce_description": "Users access their existing Tavus account entitlements. This plugin does not sell PALs, subscriptions, or credits, collect payment details, or initiate checkout.",
|
|
42
43
|
"test_cases": {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tavus/tavus-mcp-worker",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@tavus/tavus-mcp-worker",
|
|
9
|
-
"version": "0.9.
|
|
9
|
+
"version": "0.9.3",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
12
12
|
"agents": "^0.12.2",
|
|
@@ -18,6 +18,13 @@ const REGISTER_ROUTE = "/register";
|
|
|
18
18
|
const CALLBACK_ROUTE = "/oauth/callback";
|
|
19
19
|
const AUTHORIZATION_REQUEST_ROUTE = "/oauth/authorization-request";
|
|
20
20
|
const DYNAMIC_CLIENT_PREFIX = "tavus-mcp-dcr";
|
|
21
|
+
// Client IDs that /register issued before 2026-08-06 (#21). The Worker never
|
|
22
|
+
// stored them, and /authorize accepted any client ID whose redirect was a
|
|
23
|
+
// loopback URL. Clients such as Claude Code keep replaying the ID they cached
|
|
24
|
+
// instead of registering again (anthropics/claude-code#84614), and the
|
|
25
|
+
// rejection they get is a browser page they never see. Keeping the old rule
|
|
26
|
+
// for these IDs, loopback only, lets them sign in without any stored state.
|
|
27
|
+
const LEGACY_DYNAMIC_CLIENT_ID = /^tavus-mcp-[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/;
|
|
21
28
|
const AUTHORIZATION_STATE_TTL_SECONDS = 10 * 60;
|
|
22
29
|
const AUTHORIZATION_CODE_TTL_SECONDS = 5 * 60;
|
|
23
30
|
|
|
@@ -191,10 +198,37 @@ function findRegisteredRedirect(
|
|
|
191
198
|
redirectUri: string,
|
|
192
199
|
): RegisteredOAuthClient | undefined {
|
|
193
200
|
return clients.find(
|
|
194
|
-
(c) =>
|
|
201
|
+
(c) =>
|
|
202
|
+
c.client_id === clientId &&
|
|
203
|
+
c.redirect_uris.some((registered) => redirectUriMatches(registered, redirectUri)),
|
|
195
204
|
);
|
|
196
205
|
}
|
|
197
206
|
|
|
207
|
+
// RFC 8252 section 7.3: a native client takes an ephemeral port when it starts
|
|
208
|
+
// the flow, so the authorization server MUST accept any port on a loopback
|
|
209
|
+
// redirect URI. Everything else about the URI still has to match. HTTPS
|
|
210
|
+
// callbacks need an exact match.
|
|
211
|
+
function redirectUriMatches(registered: string, requested: string): boolean {
|
|
212
|
+
if (registered === requested) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
if (!isLoopbackUrl(registered) || !isLoopbackUrl(requested)) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
const a = new URL(registered);
|
|
219
|
+
const b = new URL(requested);
|
|
220
|
+
return (
|
|
221
|
+
a.protocol === b.protocol &&
|
|
222
|
+
a.hostname === b.hostname &&
|
|
223
|
+
a.pathname === b.pathname &&
|
|
224
|
+
a.search === b.search
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function isLegacyDynamicClient(clientId: string): boolean {
|
|
229
|
+
return LEGACY_DYNAMIC_CLIENT_ID.test(clientId);
|
|
230
|
+
}
|
|
231
|
+
|
|
198
232
|
async function resolveRegisteredRedirect(
|
|
199
233
|
env: Env,
|
|
200
234
|
clientId: string,
|
|
@@ -208,7 +242,7 @@ async function resolveRegisteredRedirect(
|
|
|
208
242
|
const signingSecret = oauthSigningSecret(env);
|
|
209
243
|
if (signingSecret) {
|
|
210
244
|
const dynamic = await verifyClientPayload(clientId, signingSecret);
|
|
211
|
-
if (dynamic?.redirect_uris.
|
|
245
|
+
if (dynamic?.redirect_uris.some((registered) => redirectUriMatches(registered, redirectUri))) {
|
|
212
246
|
return {
|
|
213
247
|
client_id: clientId,
|
|
214
248
|
redirect_uris: dynamic.redirect_uris,
|
|
@@ -217,6 +251,10 @@ async function resolveRegisteredRedirect(
|
|
|
217
251
|
}
|
|
218
252
|
}
|
|
219
253
|
|
|
254
|
+
if (isLegacyDynamicClient(clientId) && isLoopbackUrl(redirectUri)) {
|
|
255
|
+
return { client_id: clientId, redirect_uris: [redirectUri], name: "Tavus MCP Client" };
|
|
256
|
+
}
|
|
257
|
+
|
|
220
258
|
return undefined;
|
|
221
259
|
}
|
|
222
260
|
|
|
@@ -378,9 +416,10 @@ async function handleAuthorize(request: Request, env: Env, mcpRoute: string): Pr
|
|
|
378
416
|
|
|
379
417
|
const registeredRedirect = await resolveRegisteredRedirect(env, clientId, redirectUri);
|
|
380
418
|
if (!registeredRedirect) {
|
|
419
|
+
// This page is all the person sees; the MCP client never learns about it.
|
|
381
420
|
return oauthError(
|
|
382
421
|
"invalid_request",
|
|
383
|
-
"redirect_uri is not registered for this client.",
|
|
422
|
+
"redirect_uri is not registered for this client. Remove and re-add the Tavus MCP server in your client so it registers again.",
|
|
384
423
|
400,
|
|
385
424
|
);
|
|
386
425
|
}
|
|
@@ -654,6 +693,10 @@ async function authenticateClient(request: Request, params: URLSearchParams, env
|
|
|
654
693
|
}
|
|
655
694
|
return secret ? null : clientId;
|
|
656
695
|
}
|
|
696
|
+
// Legacy IDs were always public clients; see LEGACY_DYNAMIC_CLIENT_ID.
|
|
697
|
+
if (isLegacyDynamicClient(clientId)) {
|
|
698
|
+
return !secret && !basic ? clientId : null;
|
|
699
|
+
}
|
|
657
700
|
const signingSecret = oauthSigningSecret(env);
|
|
658
701
|
const dynamic = signingSecret ? await verifyClientPayload(clientId, signingSecret) : null;
|
|
659
702
|
if (!dynamic || !signingSecret) return null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = "0.9.3"; // x-release-please-version
|
|
@@ -1017,6 +1017,226 @@ describe("Cloudflare Tavus MCP helpers", () => {
|
|
|
1017
1017
|
expect(response?.status).toBe(400);
|
|
1018
1018
|
});
|
|
1019
1019
|
|
|
1020
|
+
function authorizeUrl(clientId: string, redirectUri: string): URL {
|
|
1021
|
+
const url = new URL("https://mcp.tavus-preview.io/authorize");
|
|
1022
|
+
url.search = new URLSearchParams({
|
|
1023
|
+
response_type: "code",
|
|
1024
|
+
client_id: clientId,
|
|
1025
|
+
redirect_uri: redirectUri,
|
|
1026
|
+
state: "state-token",
|
|
1027
|
+
code_challenge: "challenge",
|
|
1028
|
+
code_challenge_method: "S256",
|
|
1029
|
+
resource: "https://mcp.tavus-preview.io/mcp",
|
|
1030
|
+
scope: "tavus",
|
|
1031
|
+
}).toString();
|
|
1032
|
+
return url;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
async function registerLoopbackClient(env: Env, redirectUri: string): Promise<string> {
|
|
1036
|
+
const registration = await handleOAuthRequest(
|
|
1037
|
+
new Request("https://mcp.tavus-preview.io/register", {
|
|
1038
|
+
method: "POST",
|
|
1039
|
+
headers: { "Content-Type": "application/json" },
|
|
1040
|
+
body: JSON.stringify({ redirect_uris: [redirectUri], client_name: "Claude Code" }),
|
|
1041
|
+
}),
|
|
1042
|
+
env,
|
|
1043
|
+
"/mcp",
|
|
1044
|
+
);
|
|
1045
|
+
expect(registration?.status).toBe(201);
|
|
1046
|
+
return ((await registration?.json()) as { client_id: string }).client_id;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
// RFC 8252 section 7.3: loopback clients take an ephemeral port per attempt.
|
|
1050
|
+
it("accepts any loopback port for a dynamically registered callback", async () => {
|
|
1051
|
+
const env = withOAuthGrants({
|
|
1052
|
+
MCP_OAUTH_SIGNING_SECRET: "s".repeat(32),
|
|
1053
|
+
TAVUS_DEV_PORTAL_URL: "https://platform.example.test",
|
|
1054
|
+
});
|
|
1055
|
+
const clientId = await registerLoopbackClient(env, "http://localhost:4000/callback");
|
|
1056
|
+
|
|
1057
|
+
const response = await handleOAuthRequest(
|
|
1058
|
+
new Request(authorizeUrl(clientId, "http://localhost:53117/callback")),
|
|
1059
|
+
env,
|
|
1060
|
+
"/mcp",
|
|
1061
|
+
);
|
|
1062
|
+
|
|
1063
|
+
expect(response?.status).toBe(302);
|
|
1064
|
+
const location = new URL(response?.headers.get("Location") ?? "");
|
|
1065
|
+
expect(location.origin + location.pathname).toBe("https://platform.example.test/dev/cli-authorize");
|
|
1066
|
+
});
|
|
1067
|
+
|
|
1068
|
+
it.each([
|
|
1069
|
+
["a different path", "http://localhost:4000/other"],
|
|
1070
|
+
["a different loopback host", "http://127.0.0.1:4000/callback"],
|
|
1071
|
+
["an added query string", "http://localhost:4000/callback?next=https://attacker.example"],
|
|
1072
|
+
["an HTTPS callback", "https://example.test/callback"],
|
|
1073
|
+
])("rejects a dynamically registered loopback client's callback with %s", async (_label, redirectUri) => {
|
|
1074
|
+
const env = withOAuthGrants({ MCP_OAUTH_SIGNING_SECRET: "s".repeat(32) });
|
|
1075
|
+
const clientId = await registerLoopbackClient(env, "http://localhost:4000/callback");
|
|
1076
|
+
|
|
1077
|
+
const response = await handleOAuthRequest(new Request(authorizeUrl(clientId, redirectUri)), env, "/mcp");
|
|
1078
|
+
|
|
1079
|
+
expect(response?.status).toBe(400);
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
it("accepts any loopback port for a pre-registered loopback callback", async () => {
|
|
1083
|
+
const env = withOAuthGrants({
|
|
1084
|
+
MCP_OAUTH_SIGNING_SECRET: "s".repeat(32),
|
|
1085
|
+
MCP_OAUTH_CLIENTS: JSON.stringify([
|
|
1086
|
+
{ client_id: "static-cli", redirect_uris: ["http://127.0.0.1:4000/callback"] },
|
|
1087
|
+
]),
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
const response = await handleOAuthRequest(
|
|
1091
|
+
new Request(authorizeUrl("static-cli", "http://127.0.0.1:4001/callback")),
|
|
1092
|
+
env,
|
|
1093
|
+
"/mcp",
|
|
1094
|
+
);
|
|
1095
|
+
|
|
1096
|
+
expect(response?.status).toBe(302);
|
|
1097
|
+
});
|
|
1098
|
+
|
|
1099
|
+
it("still needs an exact match for a pre-registered HTTPS callback", async () => {
|
|
1100
|
+
const env = withOAuthGrants({
|
|
1101
|
+
MCP_OAUTH_SIGNING_SECRET: "s".repeat(32),
|
|
1102
|
+
MCP_OAUTH_CLIENTS: JSON.stringify([
|
|
1103
|
+
{ client_id: "static-web", redirect_uris: ["https://example.test/callback"] },
|
|
1104
|
+
]),
|
|
1105
|
+
});
|
|
1106
|
+
|
|
1107
|
+
const response = await handleOAuthRequest(
|
|
1108
|
+
new Request(authorizeUrl("static-web", "https://example.test:8443/callback")),
|
|
1109
|
+
env,
|
|
1110
|
+
"/mcp",
|
|
1111
|
+
);
|
|
1112
|
+
|
|
1113
|
+
expect(response?.status).toBe(400);
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
// Issued by /register before #21; the Worker never stored them.
|
|
1117
|
+
const legacyClientId = "tavus-mcp-db38e103-83e8-4046-87d6-026739d5133c";
|
|
1118
|
+
|
|
1119
|
+
it("accepts a legacy dynamic client ID with a loopback callback", async () => {
|
|
1120
|
+
const env = withOAuthGrants({
|
|
1121
|
+
MCP_OAUTH_SIGNING_SECRET: "s".repeat(32),
|
|
1122
|
+
TAVUS_DEV_PORTAL_URL: "https://platform.example.test",
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
const response = await handleOAuthRequest(
|
|
1126
|
+
new Request(authorizeUrl(legacyClientId, "http://localhost:3118/callback")),
|
|
1127
|
+
env,
|
|
1128
|
+
"/mcp",
|
|
1129
|
+
);
|
|
1130
|
+
|
|
1131
|
+
expect(response?.status).toBe(302);
|
|
1132
|
+
const location = new URL(response?.headers.get("Location") ?? "");
|
|
1133
|
+
const requestInfoUrl = new URL("https://mcp.tavus-preview.io/oauth/authorization-request");
|
|
1134
|
+
requestInfoUrl.searchParams.set("state", location.searchParams.get("state")!);
|
|
1135
|
+
const requestInfo = await handleOAuthRequest(
|
|
1136
|
+
new Request(requestInfoUrl, { headers: { Origin: "https://platform.example.test" } }),
|
|
1137
|
+
env,
|
|
1138
|
+
"/mcp",
|
|
1139
|
+
);
|
|
1140
|
+
await expect(requestInfo?.json()).resolves.toMatchObject({
|
|
1141
|
+
client_name: "Tavus MCP Client",
|
|
1142
|
+
redirect_uri: "http://localhost:3118/callback",
|
|
1143
|
+
});
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
it("rejects a legacy dynamic client ID with a non-loopback callback", async () => {
|
|
1147
|
+
const env = withOAuthGrants({ MCP_OAUTH_SIGNING_SECRET: "s".repeat(32) });
|
|
1148
|
+
|
|
1149
|
+
const response = await handleOAuthRequest(
|
|
1150
|
+
new Request(authorizeUrl(legacyClientId, "https://attacker.example/callback")),
|
|
1151
|
+
env,
|
|
1152
|
+
"/mcp",
|
|
1153
|
+
);
|
|
1154
|
+
|
|
1155
|
+
expect(response?.status).toBe(400);
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1158
|
+
it.each([
|
|
1159
|
+
["a signed-looking ID with a bad signature", "tavus-mcp-dcr.bad.sig"],
|
|
1160
|
+
["an unknown ID", "random-client"],
|
|
1161
|
+
["a legacy-looking ID that is not a UUID", "tavus-mcp-not-a-uuid"],
|
|
1162
|
+
])("tells the person how to recover when %s is used", async (_label, clientId) => {
|
|
1163
|
+
const env = withOAuthGrants({ MCP_OAUTH_SIGNING_SECRET: "s".repeat(32) });
|
|
1164
|
+
|
|
1165
|
+
const response = await handleOAuthRequest(
|
|
1166
|
+
new Request(authorizeUrl(clientId, "http://localhost:3118/callback")),
|
|
1167
|
+
env,
|
|
1168
|
+
"/mcp",
|
|
1169
|
+
);
|
|
1170
|
+
|
|
1171
|
+
expect(response?.status).toBe(400);
|
|
1172
|
+
await expect(response?.json()).resolves.toEqual({
|
|
1173
|
+
error: "invalid_request",
|
|
1174
|
+
error_description:
|
|
1175
|
+
"redirect_uri is not registered for this client. Remove and re-add the Tavus MCP server in your client so it registers again.",
|
|
1176
|
+
});
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
it("exchanges a legacy dynamic client's code as a public client only", async () => {
|
|
1180
|
+
const env = withOAuthGrants({
|
|
1181
|
+
MCP_OAUTH_SIGNING_SECRET: "s".repeat(32),
|
|
1182
|
+
TAVUS_DEV_PORTAL_URL: "https://platform.example.test",
|
|
1183
|
+
});
|
|
1184
|
+
const redirectUri = "http://localhost:3118/callback";
|
|
1185
|
+
const resource = "https://mcp.tavus-preview.io/mcp";
|
|
1186
|
+
const verifier = "legacy-verifier-0123456789abcdefghijklmnopqrstuvwxyz";
|
|
1187
|
+
const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier));
|
|
1188
|
+
const challenge = btoa(String.fromCharCode(...new Uint8Array(digest)))
|
|
1189
|
+
.replace(/\+/g, "-")
|
|
1190
|
+
.replace(/\//g, "_")
|
|
1191
|
+
.replace(/=+$/g, "");
|
|
1192
|
+
|
|
1193
|
+
const authorizationUrl = authorizeUrl(legacyClientId, redirectUri);
|
|
1194
|
+
authorizationUrl.searchParams.set("code_challenge", challenge);
|
|
1195
|
+
const authorization = await handleOAuthRequest(new Request(authorizationUrl), env, "/mcp");
|
|
1196
|
+
const portalUrl = new URL(authorization?.headers.get("Location") ?? "");
|
|
1197
|
+
const callback = await handleOAuthRequest(
|
|
1198
|
+
new Request("https://mcp.tavus-preview.io/oauth/callback", {
|
|
1199
|
+
method: "POST",
|
|
1200
|
+
headers: {
|
|
1201
|
+
Accept: "application/json",
|
|
1202
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1203
|
+
Origin: "https://platform.example.test",
|
|
1204
|
+
},
|
|
1205
|
+
body: new URLSearchParams({ code: "tavus-user-key", state: portalUrl.searchParams.get("state")! }),
|
|
1206
|
+
}),
|
|
1207
|
+
env,
|
|
1208
|
+
"/mcp",
|
|
1209
|
+
);
|
|
1210
|
+
const callbackResult = (await callback?.json()) as { redirect_uri: string };
|
|
1211
|
+
const clientUrl = new URL(callbackResult.redirect_uri);
|
|
1212
|
+
expect(clientUrl.origin + clientUrl.pathname).toBe(redirectUri);
|
|
1213
|
+
|
|
1214
|
+
const tokenRequest = (withSecret: boolean) =>
|
|
1215
|
+
new Request("https://mcp.tavus-preview.io/token", {
|
|
1216
|
+
method: "POST",
|
|
1217
|
+
body: new URLSearchParams({
|
|
1218
|
+
grant_type: "authorization_code",
|
|
1219
|
+
code: clientUrl.searchParams.get("code")!,
|
|
1220
|
+
client_id: legacyClientId,
|
|
1221
|
+
...(withSecret ? { client_secret: "guess" } : {}),
|
|
1222
|
+
redirect_uri: redirectUri,
|
|
1223
|
+
code_verifier: verifier,
|
|
1224
|
+
resource,
|
|
1225
|
+
}),
|
|
1226
|
+
});
|
|
1227
|
+
const withSecret = await handleOAuthRequest(tokenRequest(true), env, "/mcp");
|
|
1228
|
+
expect(withSecret?.status).toBe(401);
|
|
1229
|
+
await expect(withSecret?.json()).resolves.toMatchObject({ error: "invalid_client" });
|
|
1230
|
+
|
|
1231
|
+
const token = await handleOAuthRequest(tokenRequest(false), env, "/mcp");
|
|
1232
|
+
expect(token?.status).toBe(200);
|
|
1233
|
+
await expect(token?.json()).resolves.toMatchObject({
|
|
1234
|
+
access_token: expect.stringMatching(/^tavus_mcp_access\./),
|
|
1235
|
+
token_type: "Bearer",
|
|
1236
|
+
scope: "tavus",
|
|
1237
|
+
});
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1020
1240
|
it("validates client_secret before exchanging a pre-registered client code", async () => {
|
|
1021
1241
|
const env = {
|
|
1022
1242
|
MCP_OAUTH_CLIENTS: JSON.stringify([
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const VERSION = "0.9.2"; // 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
|
|
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.9.2 → tavus_cli-0.9.3}/skills/tavus-agentic-pals/references/build-and-verify.md
RENAMED
|
File without changes
|
{tavus_cli-0.9.2 → tavus_cli-0.9.3}/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
|
|
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
|