> **Building with AI coding agents?** Install the authstack plugin with one command. This equips your agent with accurate Scalekit implementation patterns.
>
> **Recommended** (the `-y` flags skip prompts, so the command runs without an interactive terminal):
> ```bash
> npx -y @scalekit-inc/cli setup -y
> ```
>
> Global:
> ```bash
> npm install -g @scalekit-inc/cli
> scalekit setup -y
> ```
>
> Supports Claude Code, Cursor, GitHub Copilot, Codex + skills for other Agent Skills-compatible agents in `~/.agents/skills`.
> Skills: integrate-agentkit, implement-saaskit, add-mcp-oauth, implement-sso, implement-scim.
> [Full setup guide](https://docs.scalekit.com/dev-kit/build-with-ai/)

---

# OpenAI Agents SDK

Build an OpenAI Agents SDK agent that reads Gmail as the user with AgentKit: turn Scalekit tools into function tools, or connect a Virtual MCP server.
Build an agent with the [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) that reads a user's Gmail inbox. Each Scalekit tool becomes an Agents SDK function tool that runs the call through Scalekit as the user, and the SDK's runner handles the tool-calling loop.

Calling the Chat Completions or Responses API directly instead? See [OpenAI](/agentkit/examples/openai/).

## Before you start

- Create a Gmail connection in **AgentKit** > **Connections**. The samples use the connection name `gmail`, so change it if yours differs. See [Set up a connection](/agentkit/connections/).
- Have the `.env` file from the [Quickstart](/agentkit/quickstart/), and add your OpenAI API key as `OPENAI_API_KEY` to it.
- The samples load `.env` with `load_dotenv()` from `python-dotenv` in Python, and `import 'dotenv/config'` in Node.js.

## Install

  **Python**

  ```sh
  pip install scalekit-sdk-python python-dotenv openai-agents
  ```

  **Node.js**

  ```sh
  npm install @scalekit-sdk/node dotenv @openai/agents
  ```

For Node.js, save the code in a `.mts` file, such as `agent.mts`, and run it with `npx tsx agent.mts`. The `.mts` extension makes the file an ES module, so it can use top-level `await`.

## Initialize

  **Python**

  ```python
  import os
  from dotenv import load_dotenv
  from scalekit import ScalekitClient

  load_dotenv()

  scalekit_client = ScalekitClient(
      client_id=os.getenv("SCALEKIT_CLIENT_ID"),
      client_secret=os.getenv("SCALEKIT_CLIENT_SECRET"),
      env_url=os.getenv("SCALEKIT_ENVIRONMENT_URL"),
  )
  actions = scalekit_client.actions
  ```

  **Node.js**

  ```typescript
  import 'dotenv/config';
  import { ScalekitClient } from '@scalekit-sdk/node';

  const scalekit = new ScalekitClient(
    process.env.SCALEKIT_ENVIRONMENT_URL!,
    process.env.SCALEKIT_CLIENT_ID!,
    process.env.SCALEKIT_CLIENT_SECRET!,
  );
  ```

Later blocks reuse this setup.

## Connect the user to Gmail

  **Python**

  ```python
  # 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."
      )
  ```

  **Node.js**

  ```typescript
  import { createInterface } from 'node:readline/promises';
  import { ConnectorStatus } from '@scalekit-sdk/node';

  // Connect the user's Gmail account, and wait until it's ACTIVE before calling tools
  const connectionName = 'gmail';
  const identifier = 'user_123'; // your app's unique user ID

  let { connectedAccount } = await scalekit.actions.getOrCreateConnectedAccount({
    connectionName,
    identifier,
  });
  if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
    const { link } = await scalekit.actions.getAuthorizationLink({ connectionName, identifier });
    console.log('Authorize Gmail:', link);
    const rl = createInterface({ input: process.stdin, output: process.stdout });
    await rl.question('Press Enter after authorizing...');
    rl.close();
    // Fetch the account again to pick up the new status
    ({ connectedAccount } = await scalekit.actions.getOrCreateConnectedAccount({
      connectionName,
      identifier,
    }));
  }

  if (connectedAccount?.status !== ConnectorStatus.ACTIVE) {
    throw new Error(`Gmail is not ACTIVE (status ${connectedAccount?.status}). Authorize it and run again.`);
  }
  ```

See [Authorize a user](/agentkit/tools/authorize/) for production auth handling.

## Run the agent

Fetch the tools scoped to this user and turn each one into a function tool. The tool's JSON Schema is passed through as is, with strict mode off. When the model calls a tool, the function runs it with `execute_tool` (`executeTool` in Node.js) for this user and returns the app's response. [Use built-in tools](/agentkit/tools/scalekit-optimized-tools/) explains the scoped tool list, paging past 100 tools, and how to read `data` when the app returns an error.

  **Python**

  ```python
  import asyncio
  import json

  from agents import Agent, FunctionTool, Runner
  from google.protobuf.json_format import MessageToDict

  scoped_response, _ = actions.tools.list_scoped_tools(
      identifier=identifier,
      filter={"connection_names": [connection_name]},
      page_size=100,  # up to 100 tools per page; page with next_page_token for more
  )
  definitions = [MessageToDict(t.tool).get("definition", {}) for t in scoped_response.tools]


  def scalekit_tool(definition):
      async def run_tool(ctx, args_json: str) -> str:
          # execute_tool is a blocking call, so run it off the event loop
          result = await asyncio.to_thread(
              actions.execute_tool,
              tool_name=definition["name"],
              identifier=identifier,
              connection_name=connection_name,
              tool_input=json.loads(args_json or "{}"),
          )
          return json.dumps(result.data)

      return FunctionTool(
          name=definition["name"],
          description=definition.get("description", ""),
          params_json_schema=definition.get("input_schema", {"type": "object", "properties": {}}),
          on_invoke_tool=run_tool,
          strict_json_schema=False,  # Scalekit schemas don't follow strict mode's rules
      )


  agent = Agent(
      name="Gmail assistant",
      instructions="Answer questions about the user's Gmail inbox.",
      model="gpt-4o",
      tools=[scalekit_tool(definition) for definition in definitions],
  )
  result = Runner.run_sync(agent, "Fetch my last 5 unread emails and summarize them")
  print(result.final_output)
  ```

  **Node.js**

  ```typescript
  import { Agent, run, tool } from '@openai/agents';

  const { tools: scopedTools } = await scalekit.tools.listScopedTools(identifier, {
    filter: { connectionNames: [connectionName] },
    pageSize: 100, // up to 100 tools per page; page with nextPageToken for more
  });
  // Tool definitions are typed as generic JSON, so read each field explicitly
  const definitions = scopedTools.flatMap((t) => (t.tool?.definition ? [t.tool.definition] : []));
  const tools = definitions.map((definition) =>
    tool({
      name: String(definition.name),
      description: String(definition.description ?? ''),
      parameters: definition.input_schema as any,
      strict: false, // Scalekit schemas don't follow strict mode's rules
      execute: async (input) => {
        const result = await scalekit.actions.executeTool({
          toolName: String(definition.name),
          identifier,
          connector: connectionName,
          toolInput: input as Record<string, unknown>,
        });
        return JSON.stringify(result.data);
      },
    }),
  );

  const agent = new Agent({
    name: 'Gmail assistant',
    instructions: "Answer questions about the user's Gmail inbox.",
    model: 'gpt-4o',
    tools,
  });
  const result = await run(agent, 'Fetch my last 5 unread emails and summarize them');
  console.log(result.finalOutput);
  ```

If a tool function raises an error, the Agents SDK tells the model the call failed, so the model can retry or explain the failure. When the app itself rejects a request, `execute_tool` doesn't raise: the app's error response comes back in `data`, and the model reads it like any other result.

## Check it worked

The script prints a summary of the user's unread emails. To see each tool call and the Gmail response it returned, read `result.new_items` (`result.newItems` in Node.js).

## Use MCP instead

The Agents SDK connects to MCP servers natively. Point it at a [Virtual MCP server](/agentkit/mcp/overview/) with a session token for this user, and the agent discovers the server's tools on its own. Add this below the code above. It reuses the setup and, in Node.js, the `Agent` and `run` imports.

  **Python**

  ```python
  import asyncio
  import os
  from datetime import timedelta

  from agents import Agent, Runner
  from agents.mcp import MCPServerStreamableHttp

  # Returned by create_config when you created the Virtual MCP server
  config_id = os.environ["SCALEKIT_MCP_CONFIG_ID"]
  mcp_url = os.environ["SCALEKIT_MCP_SERVER_URL"]

  # Mint a fresh session token for this user before each agent run
  mcp_token = actions.mcp.create_session_token(
      mcp_config_id=config_id,
      identifier=identifier,
      expiry=timedelta(hours=1),
  ).token


  async def run():
      async with MCPServerStreamableHttp(
          name="scalekit",
          params={"url": mcp_url, "headers": {"Authorization": f"Bearer {mcp_token}"}},
      ) as server:
          agent = Agent(
              name="Gmail assistant",
              instructions="Answer questions about the user's Gmail inbox.",
              model="gpt-4o",
              mcp_servers=[server],
          )
          result = await Runner.run(agent, "Fetch my last 5 unread emails and summarize them")
          print(result.final_output)


  asyncio.run(run())
  ```

  **Node.js**

  ```typescript
  import { MCPServerStreamableHttp } from '@openai/agents';

  // Returned by createConfig when you created the Virtual MCP server
  const configId = process.env.SCALEKIT_MCP_CONFIG_ID!;
  const mcpUrl = process.env.SCALEKIT_MCP_SERVER_URL!;

  // Mint a fresh session token for this user before each agent run
  const session = await scalekit.actions.mcp.createSessionToken({
    mcpConfigId: configId,
    identifier,
    expirySeconds: 60 * 60,
  });

  const mcpServer = new MCPServerStreamableHttp({
    name: 'scalekit',
    url: mcpUrl,
    requestInit: { headers: { Authorization: `Bearer ${session.token}` } },
  });
  await mcpServer.connect();
  try {
    const mcpAgent = new Agent({
      name: 'Gmail assistant',
      instructions: "Answer questions about the user's Gmail inbox.",
      model: 'gpt-4o',
      mcpServers: [mcpServer],
    });
    const mcpResult = await run(mcpAgent, 'Fetch my last 5 unread emails and summarize them');
    console.log(mcpResult.finalOutput);
  } finally {
    await mcpServer.close();
  }
  ```

See [Create a Virtual MCP server](/agentkit/mcp/configure-mcp-server/) and [Mint session tokens](/agentkit/mcp/session-tokens/) to create the server, check the user's connections and mint a token.

## Next

  - [Manage connected accounts](/agentkit/connected-accounts/): Check an account's status and ask users to reconnect.
  - [AgentKit launch checklist](/agentkit/advanced/launch-checklist/): Everything to finish before real users connect.

## API reference

The endpoints this page's code calls, with every field and the SDK method for each:

- `POST` [Execute a tool](https://docs.scalekit.com/agentkit/reference/tools/execute-a-tool.md)
- `GET` [List a user's scoped tools](https://docs.scalekit.com/agentkit/reference/tools/list-scoped-tools.md)
- `POST` [Mint a session token](https://docs.scalekit.com/agentkit/reference/virtual-mcp-servers/mint-a-session-token.md)


---

## More Scalekit documentation

| Resource | What it contains | When to use it |
|----------|-----------------|----------------|
| [/llms.txt](/llms.txt) | Structured index with routing hints per product area | Start here — find which documentation set covers your topic before loading full content |
| [/llms-full.txt](/llms-full.txt) | Complete documentation for all Scalekit products in one file | Use when you need exhaustive context across multiple products or when the topic spans several areas |
| [sitemap-0.xml](https://docs.scalekit.com/sitemap-0.xml) | Full URL list of every documentation page | Use to discover specific page URLs you can fetch for targeted, page-level answers |
