Skip to content
Scalekit Docs

Mastra

Connect a Mastra agent to Scalekit tools using MCP. Mastra's MCP client connects to a Virtual MCP Server URL with a session token.

Connect a Mastra agent to Scalekit tools using MCP. Mastra has native MCP support via @mastra/mcp. Pass a Scalekit Virtual MCP Server URL and a session token, and Mastra handles tool discovery automatically.

The Mastra agent runs in Node.js. Creating the Virtual MCP server and minting session tokens use the Python SDK, because the Node.js SDK doesn’t create session tokens yet. Run those parts on your backend.

Terminal window
pip install scalekit-sdk-python python-dotenv
npm install @mastra/core@1 @mastra/mcp@2 @ai-sdk/openai@4 dotenv

For Node.js, save the agent code in a .mts file, such as agent.mts, and run it with npx tsx agent.mts.

Both backend scripts below start with this client:

import os
from scalekit import ScalekitClient
from 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"],
)
actions = scalekit_client.actions

Create the server once per agent role, not once per user. Every user and every session reuses its static mcp_server_url.

# Backend (Python): run once
from scalekit.actions.models.mcp_config import McpConfigConnectionToolMapping
vmcp_response = actions.mcp.create_config(
name="gmail-user-tools",
connection_tool_mappings=[
McpConfigConnectionToolMapping(
connection_name="gmail",
tools=["gmail_fetch_mails"],
),
],
)
print("Virtual MCP ID:", vmcp_response.config.id)

See Set up and connect a Virtual MCP server for the full setup, including how to choose which tools to expose.

The Virtual MCP server only calls tools for users who have authorized the connection. Before each agent run, make sure the user’s Gmail account is ACTIVE:

# Connect the user's Gmail account, and wait until it's ACTIVE before calling tools
connection_name = "gmail"
identifier = "user_123" # your app's unique user ID
response = actions.get_or_create_connected_account(
connection_name=connection_name, identifier=identifier
)
if response.connected_account.status != "ACTIVE":
link = actions.get_authorization_link(
connection_name=connection_name, identifier=identifier
)
print("Authorize Gmail:", link.link)
input("Press Enter after authorizing...")
# Fetch the account again to pick up the new status
response = actions.get_or_create_connected_account(
connection_name=connection_name, identifier=identifier
)
if response.connected_account.status != "ACTIVE":
raise RuntimeError(
f"Gmail is {response.connected_account.status}, not ACTIVE. Authorize it and run again."
)

See Authorize a user for production auth handling.

The server URL is static. The session token carries the user identity. Mint a fresh token before each agent run and pass it to your Mastra app. Put this after the connect step in the same script:

# Backend (Python): run before each agent session
from datetime import timedelta
from scalekit.common.exceptions import (
ScalekitNotFoundException,
ScalekitUnauthorizedException,
ScalekitServerException,
)
try:
list_response = actions.mcp.list_configs(filter_name="gmail-user-tools")
mcp_server_url = list_response.configs[0].mcp_server_url
config_id = list_response.configs[0].id
token_response = actions.mcp.create_session_token(
mcp_config_id=config_id,
identifier=identifier, # the user who authorized Gmail above
expiry=timedelta(hours=1),
)
except ScalekitNotFoundException:
# The server was deleted or renamed — recreate it, then retry
raise
except ScalekitUnauthorizedException:
# Scalekit client credentials are wrong or expired — fix the environment variables
raise
except ScalekitServerException as e:
# Unexpected platform error — do not start the agent without a token
print(e.error_code, e.http_status)
raise
# Hand these to the Mastra app for this user only. For local testing, add them to .env.
print(f"SCALEKIT_MCP_SERVER_URL={mcp_server_url}")
print(f"SCALEKIT_MCP_SESSION_TOKEN={token_response.token}")

Do not start the agent when minting fails. An agent that runs without a token calls every tool and gets a 401. See Error handling for the full exception list.

Set expiry longer than the expected agent run. create_session_token also mints replacements. Call it again whenever you need a new token.

Pass the static server URL to MCPClient, and the user’s session token as a bearer header in requestInit. Mastra fetches the tool list and schemas automatically. It names each tool after the server key, so gmail_fetch_mails appears to the model as scalekit_gmail_fetch_mails.

import { Agent } from '@mastra/core/agent';
import { MCPClient } from '@mastra/mcp';
import { openai } from '@ai-sdk/openai';
import 'dotenv/config';
// Minted by your backend for the current user; never shared between users
const mcpServerUrl = process.env.SCALEKIT_MCP_SERVER_URL;
const mcpToken = process.env.SCALEKIT_MCP_SESSION_TOKEN;
if (!mcpServerUrl || !mcpToken) {
throw new Error('Set SCALEKIT_MCP_SERVER_URL and SCALEKIT_MCP_SESSION_TOKEN from the mint step');
}
const mcp = new MCPClient({
servers: {
scalekit: {
url: new URL(mcpServerUrl),
requestInit: {
headers: { Authorization: `Bearer ${mcpToken}` },
},
},
},
});
try {
const tools = await mcp.listTools();
const agent = new Agent({
id: 'gmail-assistant',
name: 'Gmail assistant',
instructions: 'You are a helpful Gmail assistant.',
model: openai('gpt-4o'),
tools,
});
const result = await agent.generate('Fetch my last 5 unread emails and summarize them');
console.log(result.text);
} finally {
await mcp.disconnect();
}