This guide walks through a complete integration where each end user gets their own MCP session, scoped to their identity. Tool calls are attributed to the individual user, and member identity is injected into every outbound request to your APIs.
Prerequisites: Complete Authentication — User-Scoped Sessions first — you need a registered assertion key before running this code.
A reusable TengineAIClient class that:
member_assertion JWT with your private keytng_mst_... session tokentengine_...)pip install anthropic PyJWT cryptography requests
export ANTHROPIC_API_KEY="sk-ant-..."
export TENGINEAI_API_KEY="tengine_..."
export TENGINEAI_PROJECT_ID="42"
export TENGINEAI_ASSERTION_KID="my-backend-v1"
export TENGINEAI_PRIVATE_KEY_PATH="/secrets/private_key.pem"
import os
import time
import threading
from dataclasses import dataclass
from typing import Optional
import jwt
import requests
import anthropic
TENGINEAI_MCP_URL = "https://app.tengine.ai/mcp/"
TENGINEAI_SESSION_TOKEN_URL = "https://app.tengine.ai/api/v1/mcp/session-token"
TOKEN_REFRESH_BUFFER_SECONDS = 60
@dataclass
class CachedToken:
token: str
expires_at: float
class TokenCache:
"""Thread-safe per-user session token cache."""
def __init__(self):
self._cache: dict[str, CachedToken] = {}
self._lock = threading.Lock()
def get(self, user_id: str) -> Optional[str]:
with self._lock:
entry = self._cache.get(user_id)
if entry and time.time() < entry.expires_at - TOKEN_REFRESH_BUFFER_SECONDS:
return entry.token
return None
def set(self, user_id: str, token: str, expires_in_minutes: int) -> None:
with self._lock:
self._cache[user_id] = CachedToken(
token=token,
expires_at=time.time() + (expires_in_minutes * 60),
)
class TengineAIClient:
"""
Anthropic SDK wrapper with per-user TengineAI session management.
Handles member_assertion minting, session token exchange, caching,
and automatic refresh — callers just pass a user_id.
"""
def __init__(
self,
anthropic_api_key: str,
tengineai_api_key: str,
project_id: str,
private_key_pem: str,
assertion_kid: str,
):
self._anthropic = anthropic.Anthropic(api_key=anthropic_api_key)
self._tengineai_api_key = tengineai_api_key
self._project_id = project_id
self._private_key_pem = private_key_pem
self._assertion_kid = assertion_kid
self._cache = TokenCache()
def _mint_assertion(self, user_id: str, email: Optional[str] = None) -> str:
now = int(time.time())
payload = {
"sub": user_id,
"aud": f"tengine:project:{self._project_id}",
"iat": now,
"exp": now + 60, # Short-lived — only used for the exchange
}
if email:
payload["email"] = email
return jwt.encode(
payload,
self._private_key_pem,
algorithm="ES256",
headers={"kid": self._assertion_kid},
)
def _fetch_session_token(self, user_id: str, email: Optional[str] = None) -> str:
assertion = self._mint_assertion(user_id, email)
response = requests.post(
TENGINEAI_SESSION_TOKEN_URL,
headers={"Authorization": f"Bearer {self._tengineai_api_key}"},
json={"member_assertion": assertion},
timeout=10,
)
response.raise_for_status()
data = response.json()
self._cache.set(user_id, data["token"], data["expires_in_minutes"])
return data["token"]
def get_session_token(self, user_id: str, email: Optional[str] = None) -> str:
cached = self._cache.get(user_id)
if cached:
return cached
return self._fetch_session_token(user_id, email)
def chat(
self,
user_id: str,
message: str,
*,
email: Optional[str] = None,
model: str = "claude-sonnet-4-6",
max_tokens: int = 1024,
) -> str:
token = self.get_session_token(user_id, email)
response = self._anthropic.beta.messages.create(
model=model,
max_tokens=max_tokens,
messages=[{"role": "user", "content": message}],
mcp_servers=[
{
"type": "url",
"url": TENGINEAI_MCP_URL,
"name": "tengineai",
"authorization_token": token,
}
],
betas=["mcp-client-2025-04-04"],
)
text_blocks = [block.text for block in response.content if block.type == "text"]
if not text_blocks:
raise ValueError("Model response did not contain any text blocks.")
return "\n".join(text_blocks)
def create_client_from_env() -> TengineAIClient:
with open(os.environ["TENGINEAI_PRIVATE_KEY_PATH"]) as f:
private_key_pem = f.read()
return TengineAIClient(
anthropic_api_key=os.environ["ANTHROPIC_API_KEY"],
tengineai_api_key=os.environ["TENGINEAI_API_KEY"],
project_id=os.environ["TENGINEAI_PROJECT_ID"],
private_key_pem=private_key_pem,
assertion_kid=os.environ["TENGINEAI_ASSERTION_KID"],
)
client = create_client_from_env()
# Each user gets their own scoped session
# X-Tengine-Member-Id: user_alice_123 is injected on every tool call
alice_reply = client.chat(
user_id="user_alice_123",
message="Fetch post 7 and summarize it for me.",
email="alice@example.com",
)
print(alice_reply)
# Bob gets his own independent session
bob_reply = client.chat(
user_id="user_bob_456",
message="Look up user 3 in the system.",
email="bob@example.com",
)
print(bob_reply)
# Second call for Alice reuses the cached token — no re-mint until buffer expires
alice_reply2 = client.chat(
user_id="user_alice_123",
message="Now fetch post 8 and compare it to the one you just fetched.",
)
print(alice_reply2)
When Alice's session is active, every tool call your API receives includes:
X-Tengine-Member-Id: user_alice_123
Use this header in your API to identify which user the model is acting on behalf of.
from collections import defaultdict
message_history: dict[str, list] = defaultdict(list)
def chat_with_history(client: TengineAIClient, user_id: str, message: str) -> str:
message_history[user_id].append({"role": "user", "content": message})
token = client.get_session_token(user_id)
response = client._anthropic.beta.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=message_history[user_id],
mcp_servers=[
{
"type": "url",
"url": TENGINEAI_MCP_URL,
"name": "tengineai",
"authorization_token": token,
}
],
betas=["mcp-client-2025-04-04"],
)
text_blocks = [block.text for block in response.content if block.type == "text"]
if not text_blocks:
raise ValueError("Model response did not contain any text blocks.")
reply = "\n".join(text_blocks)
message_history[user_id].append({"role": "assistant", "content": reply})
return reply
client = create_client_from_env()
print(chat_with_history(client, "user_alice_123", "Fetch post 1."))
print(chat_with_history(client, "user_alice_123", "Now fetch post 2. Which is longer?"))
import requests as req_lib
import anthropic
try:
reply = client.chat(user_id="user_123", message="Run the lookup")
print(reply)
except req_lib.HTTPError as e:
if e.response.status_code == 401:
print("Auth failed — check TENGINEAI_API_KEY and assertion key registration")
elif e.response.status_code == 403:
print("Forbidden:", e.response.json().get("detail"))
else:
raise
except anthropic.AuthenticationError:
print("Invalid Anthropic API key")
except anthropic.RateLimitError:
print("Rate limit exceeded — back off and retry")
X-Tengine-Member-Id to serve per-user data