Claude Managed Agents
Give Claude Managed Agents per-user tools with AgentKit: create a Virtual MCP server, keep each user's session token in their own Anthropic vault, and run it.
Run a background agent that reads Gmail and creates Google Calendar events — without managing any agent loop. Anthropic handles tool discovery, execution, retries, and session state. You provide the task.
Scalekit connects the agent to user-authorized tools via a Virtual MCP server. Before each run, you mint a short-lived session token and store it in that user’s own Anthropic vault. The agent accesses the MCP server using the vault credential.
Before you start
Section titled “Before you start”- Create Gmail and Google Calendar connections in AgentKit > Connections. The samples use the connection names
gmailandgooglecalendar, so change them if yours differ. See Set up a connection. - Have the
.envfile from the Quickstart, and add to it your Anthropic API key with access to the Managed Agents beta asANTHROPIC_API_KEY, and your Anthropic environment ID asANTHROPIC_ENVIRONMENT_ID. - The samples load
.envwithload_dotenv()frompython-dotenv.
How it works
Section titled “How it works”The flow has three phases:
- Build (one-time) — Create a Virtual MCP server and a Claude Managed Agent. Save
mcp_idandagent_id. - Authorize user for external connections (once per user) — Authorize the user’s Gmail and Google Calendar accounts.
- Run a session (per agent run) — Check connections, mint a session token, store it in that user’s own Anthropic vault, and start a session. Never share one vault across users: a session acts as whichever user’s token is in its vault.
Install
Section titled “Install”pip install anthropic scalekit-sdk-python python-dotenvRun this once to create your Virtual MCP server and Claude Managed Agent. Save the returned mcp_id and agent_id — you reuse them for every user and every session.
import osimport anthropicfrom scalekit import ScalekitClientfrom scalekit.actions.models.mcp_config import McpConfigConnectionToolMappingfrom dotenv import load_dotenv
load_dotenv()
anthropic_client = anthropic.Anthropic()scalekit_client = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],)
GMAIL_TOOLS = ["gmail_fetch_mails"]GCAL_TOOLS = [ "googlecalendar_list_calendars", "googlecalendar_list_events", "googlecalendar_get_event_by_id", "googlecalendar_create_event", "googlecalendar_update_event",]
vmcp_response = scalekit_client.actions.mcp.create_config( name="email-calendar-demo", connection_tool_mappings=[ McpConfigConnectionToolMapping( connection_name="gmail", tools=GMAIL_TOOLS, ), McpConfigConnectionToolMapping( connection_name="googlecalendar", tools=GCAL_TOOLS, ), ],)
mcp_id = vmcp_response.config.idmcp_server_url = vmcp_response.config.mcp_server_url
agent = anthropic_client.beta.agents.create( name="Email Meeting Manager", model="claude-haiku-4-5", system=( "You are an email and calendar assistant. When invoked, you will:\n" "1. Fetch the single most recent unread email from Gmail.\n" "2. Summarize it in 2-3 sentences.\n" "3. Create a Google Calendar event titled 'Action Required: <email subject>' " "with your summary as the description." ), mcp_servers=[ { "type": "url", "name": "email-calendar-mcp", "url": mcp_server_url, } ], tools=[ {"type": "agent_toolset_20260401", "default_config": {"enabled": True}}, { "type": "mcp_toolset", "mcp_server_name": "email-calendar-mcp", "default_config": { "enabled": True, "permission_policy": {"type": "always_allow"}, }, }, ],)
print("Add these to your .env file:")print(f"SCALEKIT_MCP_CONFIG_ID={mcp_id}")print(f"ANTHROPIC_AGENT_ID={agent.id}")Run python builder.py once and add the two printed lines to your .env file. The next two scripts read them from there.
The agent definition references the mcp_server_url but carries no auth credentials. Authentication is injected at runtime via the Anthropic vault.
Authorize user for external connections
Section titled “Authorize user for external connections”Each user authorizes their Gmail and Google Calendar accounts once. All future agent sessions for that user reuse those connections.
import osfrom scalekit import ScalekitClientfrom dotenv import load_dotenv
load_dotenv()
scalekit_client = ScalekitClient( env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"], client_id=os.environ["SCALEKIT_CLIENT_ID"], client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],)
mcp_id = os.environ["SCALEKIT_MCP_CONFIG_ID"] # printed by builder.pyidentifier = "user_123" # your app's unique user ID
accounts_response = scalekit_client.actions.mcp.list_mcp_connected_accounts( config_id=mcp_id, identifier=identifier,)
for account in accounts_response.connected_accounts: status = (account.connected_account_status or "").upper() if status != "ACTIVE": auth_response = scalekit_client.actions.get_authorization_link( identifier=identifier, connection_name=account.connection_name, ) print(f"{account.connection_name} needs auth: {auth_response.link}") else: print(f"✓ {account.connection_name} is ACTIVE")Surface the auth link in your app UI or send it via email. Users only need to do this once.
Run a session
Section titled “Run a session”Run executor.py for each agent run. It stops before starting a session if any connection isn’t ACTIVE, so the agent never runs with a tool it can’t call.
-
Set up the clients and the run
Section titled “Set up the clients and the run”executor.py import jsonimport osimport sysfrom datetime import timedeltafrom pathlib import Pathimport anthropicfrom dotenv import load_dotenvfrom scalekit import ScalekitClientload_dotenv()anthropic_client = anthropic.Anthropic()scalekit_client = ScalekitClient(env_url=os.environ["SCALEKIT_ENVIRONMENT_URL"],client_id=os.environ["SCALEKIT_CLIENT_ID"],client_secret=os.environ["SCALEKIT_CLIENT_SECRET"],)mcp_id = os.environ["SCALEKIT_MCP_CONFIG_ID"] # printed by builder.pyagent_id = os.environ["ANTHROPIC_AGENT_ID"] # printed by builder.pyidentifier = "user_123" # your app's unique user IDprompt = "Process my most recent unread email."# Each user gets their own vault, created on their first run.# This example keeps the IDs in a local file. In production, store them# in your database, keyed by the user.VAULTS_FILE = Path("vaults.json")vaults = json.loads(VAULTS_FILE.read_text()) if VAULTS_FILE.exists() else {}user_vault = vaults.get(identifier) -
Check that connections are active
Section titled “Check that connections are active”OAuth tokens for connected accounts can expire or be revoked, so check every connection before minting a token. If any isn’t
ACTIVE, stop and send the user back throughexecutor_setup.py.executor.py accounts_response = scalekit_client.actions.mcp.list_mcp_connected_accounts(config_id=mcp_id,identifier=identifier,)inactive = [a.connection_namefor a in accounts_response.connected_accountsif (a.connected_account_status or "").upper() != "ACTIVE"]if inactive:sys.exit(f"Not ACTIVE: {', '.join(inactive)}. Run executor_setup.py to reauthorize.") -
Mint a session token and store it in the vault
Section titled “Mint a session token and store it in the vault”Mint a short-lived session token and store it in this user’s Anthropic vault. Claude Managed Agents access the MCP server using the vault credential, not a direct bearer header, and the session acts as whichever user’s token is in the vault. So give each user their own vault, and never put two users’ tokens in the same one.
executor.py configs_response = scalekit_client.actions.mcp.list_configs(filter_id=mcp_id)mcp_server_url = configs_response.configs[0].mcp_server_urltoken_response = scalekit_client.actions.mcp.create_session_token(mcp_config_id=mcp_id,identifier=identifier,expiry=timedelta(hours=1),)token = token_response.token# Update this user's own credential, or create their vault on their first run.# Never reuse one vault across users: a session acts as whoever's token is in it.if user_vault:vault_id = user_vault["vault_id"]anthropic_client.beta.vaults.credentials.update(user_vault["credential_id"],vault_id=vault_id,auth={"type": "static_bearer", "token": token},)else:vault = anthropic_client.beta.vaults.create(display_name=f"email-calendar-{identifier}")vault_id = vault.idcredential = anthropic_client.beta.vaults.credentials.create(vault_id,display_name="email-calendar-credential",auth={"type": "static_bearer","mcp_server_url": mcp_server_url,"token": token,},)vaults[identifier] = {"vault_id": vault_id, "credential_id": credential.id}VAULTS_FILE.write_text(json.dumps(vaults, indent=2)) -
Start the session
Section titled “Start the session”Pass
vault_idsto the session so the agent can authenticate against the MCP server.executor.py session = anthropic_client.beta.sessions.create(agent=agent_id,environment_id=os.environ["ANTHROPIC_ENVIRONMENT_ID"],vault_ids=[vault_id],)with anthropic_client.beta.sessions.events.stream(session_id=session.id) as stream:anthropic_client.beta.sessions.events.send(session_id=session.id,events=[{"type": "user.message", "content": [{"type": "text", "text": prompt}]}],)for event in stream:if event.type == "agent.message":for block in event.content:if block.type == "text":print(block.text, end="", flush=True)elif event.type == "agent.mcp_tool_use":print(f"\n→ {event.name}", flush=True)elif event.type == "session.status_terminated":breakelif event.type == "session.status_idle":# The session also goes idle while it waits on you (requires_action).# Stop only when it's idle for another reason, such as end_turn.if event.stop_reason.type != "requires_action":break
API reference
The endpoints this page's code calls, with every field and the SDK method for each: