Skip to content

feat(mcp): resolve per-tenant endpoint with shared-URL fallback#154

Draft
aisling404 wants to merge 3 commits into
mainfrom
edge-compute-shim-phase-0
Draft

feat(mcp): resolve per-tenant endpoint with shared-URL fallback#154
aisling404 wants to merge 3 commits into
mainfrom
edge-compute-shim-phase-0

Conversation

@aisling404
Copy link
Copy Markdown
Collaborator

@aisling404 aisling404 commented Apr 23, 2026

Status: Draft. Phase 0 of the per-tenant Edge Compute MCP plan. Code is complete and tests pass — held in draft pending contract confirmation from the Edge Compute team (see Open questions). No user-visible behavior change on merge: shim falls back to today's shared hosted URL until both the new deploy API ships and a follow-up PR flips the plugin manifests.

What this PR does

@telnyx/mcp becomes a small resolver-then-proxy:

  1. On first invocation (with explicit consent), ensure a Telnyx Edge Compute secret exists holding the user's API key — deterministic name by key fingerprint, idempotent across cache resets via the upsert-by-name semantics of secrets add.
  2. Call POST /v2/compute/mcp/deploy with api_key_secret_id. Cache the returned per-tenant URL and the server-generated SHARED_SECRET at ~/.telnyx/mcp-endpoint.json (mode 0600).
  3. Proxy stdio ↔ Streamable HTTP to the per-tenant URL using SHARED_SECRET as the inbound bearer.

If any step fails, or the user hasn't opted in, the shim transparently falls back to today's shared hosted endpoint https://api.telnyx.com/v2/mcp using the API key as bearer — current behavior preserved exactly.

What's NOT in this PR (deliberately)

  • Plugin manifest flips for providers/claude/, providers/cursor/, or gemini-extension.json — those wait until @telnyx/mcp@0.2.0 is on npm.
  • Any changes to today's shared hosted MCP endpoint or its routing.
  • Calls to the deploy API endpoint (/v2/compute/mcp/deploy) from any production code path. The path 404s today; the shim swallows that and falls back.

Background

Two distinct credentials — explicit for the first time

The reference example surfaces a split that the original deployment doc didn't fully spell out. This PR encodes it:

Credential Used by Where stored
TELNYX_API_KEY The deployed func, to call Telnyx APIs upstream Telnyx secret (org-scoped), referenced by api_key_secret_id
SHARED_SECRET The MCP client (this shim), to authenticate inbound to the func Returned by deploy API, cached locally; server-generated, returned once

Server-generation of SHARED_SECRET is intentional. Telnyx secrets list returns names only — values are unrecoverable from the API once written. A client-generated value stored as a Telnyx secret would be lost forever on local cache loss. Server-managed avoids the dual-source-of-truth and keeps recovery simple: cache loss → --reset → new value.

Consent gate

Provisioning is opt-in via TELNYX_MCP_ACCEPT_FULL_SCOPE=1. The injected upstream secret is the user's raw, full-scope Telnyx API key — VM-internal compromise inside the Code Mode MCP func (Deno eval on agent-written TS, --allow-env unrestricted) would exfiltrate it. Firecracker protects cluster blast radius but not account blast radius. Without the env var, the shim stays on the shared URL.

v2 hardening candidate: swap raw API key for a scoped, short-lived credential. Tracked as open question 5 below; not blocking.

Validated against the live API (read-only probing)

Endpoint Status Meaning
GET /v2/compute/funcs 401 Path exists, needs auth
GET /v2/compute/secrets 401 Path exists, needs auth
GET /v2/compute/mcp/deploy 404 Endpoint genuinely net-new; Xingda's team to build
GET /v2/compute/funcs/{id}/secrets 404 No nested secrets resource

Public docs further confirm: secrets are org-scoped, secrets add is upsert by name, secret values are never returned via list, Telnyx API key auth IS supported by the Edge Compute control plane (auth api-key set), per-tenant URL pattern is https://{funcName}-{orgId}.telnyxcompute.com.

Tests

npm test — 17/17 passing. Coverage:

  • Override (TELNYX_MCP_URL) wins, uses API key as bearer
  • Consent gate (TELNYX_MCP_ACCEPT_FULL_SCOPE unset / 0 / false all skip provisioning, return shared URL)
  • Provisioning happy path (new secret + existing secret reuse + deploy with api_key_secret_id)
  • Failure modes (secret 500, deploy 404, missing url / func_id / shared_secret in response, network error, abort)
  • Cache hits (URL and SHARED_SECRET returned without any fetch calls)
  • Reset (--reset + TELNYX_MCP_RESET env var)
  • Request headers (Bearer + JSON content-type on every control-plane call)
  • remoteAuthToken assertions across every source: API key for shared/override/no-consent, SHARED_SECRET for cache/provisioned

Open questions for Edge Compute team

The shim is built against best-guess shapes for the new mcp/deploy endpoint. Each unconfirmed assumption below has a fallback path that keeps users on the shared URL — wrong guesses don't break anything, they just leave per-tenant URLs unused. Still want explicit answers before flipping plugin manifests:

  1. Idempotency — is POST /v2/compute/mcp/deploy idempotent by name within an org? Shim calls deploy on every cache miss and assumes "yes."
  2. Response envelope — shim parses { data: { url, func_id, shared_secret, status, ... } }. Confirm field names and JSON envelope.
  3. Server-generated shared_secret — confirmed approach? Alternative is shim-generated; server-managed is cleaner per the unrecoverability constraint.
  4. Synchronous vs polling — shim treats res.ok === false as failure. If the endpoint can return 202 for long provisioning with a separate poll URL, first-run users silently stay on shared URL until we add polling logic.
  5. Scoped credentials (v2 hardening) — is there (or can there be) an API to mint a short-lived, narrower-scope credential the shim could inject as TELNYX_API_KEY instead of the user's raw key?
  6. SHARED_SECRET rotation — separate endpoint to rotate without redeploying, or is --reset the only path?

Sequencing

  • Code complete on branch (3 commits)
  • Tests passing (17/17)
  • Endpoint shapes validated via probing
  • (blocking) Xingda confirms questions 1–4
  • Address any contract deltas in this branch
  • Mark PR ready for review, get review, merge
  • Publish @telnyx/mcp@0.2.0 via manual workflow_dispatch on publish-npm.yml
  • Follow-up PR flips plugin manifests to spawn the shim
  • Edge Compute team ships pre-baked MCP image + deploy API
  • End-to-end smoke test with real API key
  • Monitor fallback rate; investigate any non-cache-hit fallback in steady state

Files changed

tools/mcp/ only:

  • src/endpoint.ts (new) — resolver, secret ensure, deploy call, cache
  • src/index.tscreateProxy takes remoteAuthToken instead of apiKey; drops hardcoded URL constant
  • src/cli.ts — wires resolver to proxy, expanded help text
  • tests/endpoint.test.ts (new) — 17 cases
  • package.json — bump to 0.2.0, add test + typecheck scripts, add tsx devDep
  • package-lock.json — tsx
  • README.md — documents dual-credential model and opt-in flow

🤖 Generated with Claude Code

On first run, provision an isolated MCP endpoint on Telnyx Edge Compute
(POST /v2/compute/mcp/deploy) and cache the URL at
~/.telnyx/mcp-endpoint.json keyed by a sha256 fingerprint of the API
key. If provisioning is unavailable (404/5xx/network/timeout), fall
back transparently to the shared hosted endpoint so connections keep
working. TELNYX_MCP_URL overrides resolution; --reset / TELNYX_MCP_RESET
discards the cached endpoint.

Bumps @telnyx/mcp to 0.2.0. Plugin manifests are unchanged — once this
is on npm, a follow-up PR will flip providers/claude, providers/cursor,
and gemini-extension.json from httpUrl to spawning the shim so users
auto-get per-tenant URLs without reconfiguring.
Align the shim with Xingda's deploy-API contract: POST requires a
pre-created api_key_secret_id. The shim now ensures a secret exists via
a deterministic-name lookup (GET /v2/compute/secrets?filter[name]=…)
and creates one if missing, then calls deploy with the returned ID.

Gate per-account provisioning behind TELNYX_MCP_ACCEPT_FULL_SCOPE. The
injected secret is the user's raw Telnyx API key, and VM-internal
compromise inside the MCP func (Deno eval in Code Mode) would leak it
full-scope. Without the consent env var, the shim stays on the shared
hosted endpoint — existing user behavior. README documents the trade
and flags the scoped-credential follow-up as a v2 hardening.

ResolveResult.source split into fallback-no-consent vs fallback-error
so CLI can surface the right nudge (opt-in vs transient failure).
Comment thread tools/mcp/src/endpoint.ts
}

function fingerprint(apiKey: string): string {
return createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
Per the team-telnyx/edge-compute reference example, deployed MCP funcs
require a SHARED_SECRET distinct from TELNYX_API_KEY:
  - TELNYX_API_KEY: used by the func to call Telnyx APIs upstream
  - SHARED_SECRET:  bearer token clients send inbound to the func

Without inbound auth a deployed Code Mode MCP exposes the upstream
TELNYX_API_KEY to anyone who can reach the URL — the example returns
503 unless SHARED_SECRET is set.

The shim now:
  - Expects the deploy API to generate SHARED_SECRET server-side and
    return it in the response (cleaner than client-side generation
    given that Telnyx secrets list returns names only — values are
    unrecoverable, so server-managed avoids dual-source-of-truth)
  - Caches the value alongside the per-tenant URL at
    ~/.telnyx/mcp-endpoint.json (mode 0600)
  - Returns a remoteAuthToken alongside the URL so callers don't have
    to track which bearer goes with which endpoint
  - Uses shared_secret for per-tenant URLs; falls back to api_key for
    the shared hosted URL (today's behavior)

createProxy now takes remoteAuthToken explicitly and no longer
requires apiKey — the proxy doesn't need to know about the API key,
just which bearer to send to the resolved endpoint.

Validated via probing: /v2/compute/funcs and /v2/compute/secrets both
return 401 (exist), /v2/compute/mcp/deploy returns 404 (Xingda's
proposed endpoint is genuinely net-new). Secrets are org-scoped and
upsert by name per the public CLI docs. Telnyx API key auth is
supported by the Edge Compute control plane (auth api-key set).

Tests: 17 passing (added shared_secret in deploy response, missing
shared_secret as failure, remoteAuthToken assertions per source).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants