Claude Managed Agents
Run Claude Managed Agents with Scalekit-authenticated tools using Virtual MCP Servers and Anthropic vaults.
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 an Anthropic vault. The agent accesses the MCP server using the vault credential.
Prerequisites
Section titled “Prerequisites”- A Scalekit account with Gmail and Google Calendar connections configured. See Configure a connection.
- An Anthropic API key with access to the Managed Agents beta.
- An Anthropic environment ID set as
ANTHROPIC_ENVIRONMENT_ID.
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 an Anthropic vault, and start a session.
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-20251001", 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 osimport sysfrom datetime import timedeltaimport 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."# Created on the first run; add the printed IDs to .env to reuse themvault_id = os.getenv("ANTHROPIC_VAULT_ID")credential_id = os.getenv("ANTHROPIC_CREDENTIAL_ID") -
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 an Anthropic vault. Claude Managed Agents access the MCP server using the vault credential, not a direct bearer header.
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# Create the vault and credential on the first run; update the token on later runsif vault_id and credential_id:anthropic_client.beta.vaults.credentials.update(credential_id,vault_id=vault_id,auth={"type": "static_bearer", "token": token},)else:vault = anthropic_client.beta.vaults.create(display_name="email-calendar-vault")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,},)credential_id = credential.idprint("Add these to your .env file to reuse the vault:")print(f"ANTHROPIC_VAULT_ID={vault_id}")print(f"ANTHROPIC_CREDENTIAL_ID={credential_id}") -
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 in ("session.status_idle", "session.status_terminated"):break