> **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/)

---

# Use built-in tools

Find the tools a user can call, run one with execute_tool, and pass the tool list to your model. Python, Node.js and cURL.
Every connector ships prebuilt tools, such as `github_user_repos_list` or `slack_send_message`. Each has a JSON Schema for its input and returns structured output. Your code names the tool and the user; Scalekit adds that user's credentials, calls the app's API and returns the result. This page finds a tool, runs it, and hands the tool list to your model.

## Before you start

- An `ACTIVE` connected account for the user. [Authorize a user](/agentkit/tools/authorize/) shows how to get one.
- The connection name from **AgentKit** > **Connections**. These examples use `github-connect`, the GitHub connection new environments include.
- A client set up as in the [Quickstart](/agentkit/quickstart/): `actions` in Python, `scalekit` in Node.js. For cURL, get an access token and keep it in `TOKEN`:

  ```bash
  TOKEN=$(curl -sS "$SCALEKIT_ENVIRONMENT_URL/oauth/token" \
    -d grant_type=client_credentials \
    -d client_id="$SCALEKIT_CLIENT_ID" \
    -d client_secret="$SCALEKIT_CLIENT_SECRET" | jq -r .access_token)
  ```

1. ## Find the right tool

   Search by the job you want done. Pass the user's identifier to see, for each tool, whether that user can call it now.

   **Python**

   ```python
   from scalekit.v1.tools.tools_pb2 import ToolReadinessState

   results, _ = actions.tools.search_tools(
       query="list my GitHub repositories",
       identifier="user_123",
       top_k=5,
   )
   for tool in results.tools:
       ready = [
           c.connected_account_id
           for c in tool.connections
           if c.readiness_state == ToolReadinessState.TOOL_READINESS_STATE_READY
       ]
       print(tool.name, round(tool.score, 2), "ready" if ready else "not connected")
   ```

   **Node.js**

   ```ts
   import { ToolReadinessState } from '@scalekit-sdk/node';

   const { tools } = await scalekit.tools.searchTools('list my GitHub repositories', {
     identifier: 'user_123',
     topK: 5,
   });
   for (const tool of tools) {
     const ready = tool.connections.filter((c) => c.readinessState === ToolReadinessState.READY);
     console.log(tool.name, tool.score.toFixed(2), ready.length ? 'ready' : 'not connected');
   }
   ```

   **cURL**

   ```bash
   curl -sS "$SCALEKIT_ENVIRONMENT_URL/api/v1/tools:search" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{"query": "list my GitHub repositories", "identifier": "user_123", "top_k": 5}' |
     jq '.tools[] | {name, score, connections}'
   ```

   Each result has a `score` (higher is closer) and a `connections` list with a `readiness_state` per connection:

   - `READY`: the user has an `ACTIVE` account on that connection. Call the tool now.
   - `NEEDS_CONNECTION`: the account exists but isn't active. [Send a new authorization link](/agentkit/tools/authorize/).
   - `NEEDS_REAUTH`: the token expired. Send a new authorization link.

   A connector the user has never connected isn't in `connections` at all.

   To get the tools of specific connections instead, list the scoped tools. Pass the connections your agent works with, such as the ones the user picked in your app, and get the tools of those connections in one list:

   - The user needs a connected account on each connection you pass. Otherwise the call fails.
   - Pass one connection per connector. Two connections of the same connector, such as two GitHub connections, fail with `INVALID_ARGUMENT`.
   - Pages hold up to 100 tools. Pass `next_page_token` back until it's empty.

   The samples list the tools of `github-connect` and of a Gmail connection named `gmail`. Change `gmail` to a connection in your environment, or remove it.

   **Python**

   ```python
   from google.protobuf.json_format import MessageToDict

   definitions = []
   page_token = None
   while True:
       page, _ = actions.tools.list_scoped_tools(
           identifier="user_123",
           filter={"connection_names": ["github-connect", "gmail"]},
           page_size=100,
           page_token=page_token,
       )
       definitions += [MessageToDict(t.tool)["definition"] for t in page.tools]
       page_token = page.next_page_token
       if not page_token:
           break

   print(len(definitions), [d["name"] for d in definitions[:5]])
   ```

   **Node.js**

   ```ts
   const definitions = [];
   let pageToken: string | undefined;
   do {
     const page = await scalekit.tools.listScopedTools('user_123', {
       filter: { connectionNames: ['github-connect', 'gmail'] },
       pageSize: 100,
       pageToken,
     });
     for (const t of page.tools) {
       if (t.tool?.definition) definitions.push(t.tool.definition);
     }
     pageToken = page.nextPageToken || undefined;
   } while (pageToken);

   console.log(definitions.length, definitions.slice(0, 5).map((d) => d.name));
   ```

   **cURL**

   ```bash
   curl -sS -G "$SCALEKIT_ENVIRONMENT_URL/api/v1/tools/scoped" \
     -H "Authorization: Bearer $TOKEN" \
     --data-urlencode "identifier=user_123" \
     --data-urlencode "filter.connection_names=github-connect" \
     --data-urlencode "filter.connection_names=gmail" \
     --data-urlencode "page_size=100" |
     jq '{names: [.tools[].tool.definition.name], next_page_token}'
   ```

2. ## Call the tool

   Pass the tool name, its input, and the user. Identify the user with `identifier` plus the connection name, or with `connected_account_id` alone, which you get from a search result's `connections` or from `get_connected_account`. When you pass `connected_account_id`, Scalekit ignores `identifier`.

   **Python**

   ```python
   from scalekit.common.exceptions import ScalekitServerException

   try:
       result = actions.execute_tool(
           tool_name="github_user_repos_list",
           identifier="user_123",
           connection_name="github-connect",
           tool_input={"per_page": 5, "sort": "updated"},
       )
       print(result.execution_id)
       print(result.data)
   except ScalekitServerException as e:
       print("Tool call failed:", e.error_code, e.message)
   ```

   **Node.js**

   ```ts
   import { ScalekitServerException } from '@scalekit-sdk/node';

   try {
     const result = await scalekit.actions.executeTool({
       toolName: 'github_user_repos_list',
       identifier: 'user_123',
       connector: 'github-connect',
       toolInput: { per_page: 5, sort: 'updated' },
     });
     console.log(result.executionId);
     console.log(result.data);
   } catch (err) {
     if (!(err instanceof ScalekitServerException)) throw err;
     console.error('Tool call failed:', err.errorCode, err.message);
   }
   ```

   **cURL**

   ```bash
   curl -sS "$SCALEKIT_ENVIRONMENT_URL/api/v1/execute_tool" \
     -H "Authorization: Bearer $TOKEN" \
     -H "Content-Type: application/json" \
     -d '{
       "tool_name": "github_user_repos_list",
       "connector": "github-connect",
       "identifier": "user_123",
       "params": {"per_page": 5, "sort": "updated"}
     }' |
     jq '{execution_id, data}'
   ```

3. ## Read the result

   The response wraps the app's output. Read the tool's fields from `data`, and keep `execution_id` to find the call in your logs.

   Some apps report an error in a successful (`2xx`) response. Then the call still returns, and `data` holds the app's error. Slack, for example, returns:

   ```json
   { "ok": false, "error": "missing_scope", "needed": "users:read" }
   ```

   Check `data` for the app's error shape before you use it. When the app answers with an error status instead, such as `401`, `403`, `429` or another `4xx` or `5xx`, the call fails with an error whose message starts with `tool execution failed`. See [Common problems](#common-problems).

4. ## Give the tools to your model

   Tool definitions carry more than a model needs. Keep `name`, `description` and `input_schema`, a JSON Schema object for the tool's input. Anthropic's API takes it as `input_schema` unchanged, and OpenAI's takes it as a function's `parameters`. This continues from the list in step 1.

   **Python**

   ```python
   llm_tools = [
       {
           "name": d["name"],
           "description": d.get("description", ""),
           "input_schema": d.get("input_schema", {"type": "object", "properties": {}}),
       }
       for d in definitions
   ]
   read_only = [d["name"] for d in definitions if d.get("annotations", {}).get("read_only_hint")]
   print(len(llm_tools), "tools,", len(read_only), "read-only")
   ```

   **Node.js**

   ```ts
   const llmTools = definitions.map((d) => ({
     name: String(d.name),
     description: String(d.description ?? ''),
     input_schema: d.input_schema ?? { type: 'object', properties: {} },
   }));
   const readOnly = definitions.filter(
     (d) => (d.annotations as { read_only_hint?: boolean } | undefined)?.read_only_hint,
   );
   console.log(llmTools.length, 'tools,', readOnly.length, 'read-only');
   ```

   **cURL**

   ```bash
   curl -sS -G "$SCALEKIT_ENVIRONMENT_URL/api/v1/tools/scoped" \
     -H "Authorization: Bearer $TOKEN" \
     --data-urlencode "identifier=user_123" \
     --data-urlencode "filter.connection_names=github-connect" \
     --data-urlencode "page_size=100" |
     jq '[.tools[].tool.definition | {name, description, input_schema}]'
   ```

   Each definition's `annotations` include `read_only_hint` and `destructive_hint`. Use them to start an agent with read-only tools, or to ask the user before a destructive call.

   When the model asks for a tool, call it as in step 2 and send `data` back as the tool result. LangChain and Google ADK get native tool objects from `actions.langchain.get_tools` and `actions.google.get_tools`. [Choose a framework](/agentkit/examples/) has a complete agent for each framework.

## Check it worked

The call returns an `execution_id` and a `data` object with the app's response, such as a list of repositories for `github_user_repos_list`. Search results for the user show `READY` for the connection you called.

## Common problems

### `failed to get tool: <tool_name>`

That tool name doesn't exist in your environment. Use a name from search or from the scoped list, exactly as returned.

### `connection not found for the given key`

The connection name doesn't match **AgentKit** > **Connections** exactly. Connection names can differ between environments, so read them from configuration rather than hard-coding them.

### `connected account not found`

No connected account exists for this identifier on this connection. Check that you pass the same identifier you used when the user authorized, or [authorize the user](/agentkit/tools/authorize/) first.

### `connected account is not active`

The user hasn't finished authorizing, or the account expired or was disconnected. Send a new [authorization link](/agentkit/tools/authorize/) and wait until the status is `ACTIVE`.

### `missing or invalid <parameter> parameter`

The input doesn't match the tool's `input_schema`. Check required fields and types in the tool's definition.

### The call returns, but `data` holds an error such as `missing_scope`

The app refused the call in a `2xx` response. For a missing scope, add the scope to the connection, then have the user authorize again so their token includes it.

### `tool execution failed - unauthorized access`

The app answered `401`: it rejected the user's token. Check the connected account's status, and if the user revoked access, send a new [authorization link](/agentkit/tools/authorize/).

### `tool execution failed - forbidden access`

The app answered `403`. The user's token is usually missing a scope the tool needs, or the user lacks permission in the app. Add the scope to the connection, then have the user authorize again.

### `tool execution failed - rate limited`

The app answered `429`. Wait before you retry, and slow down calls for this user.

### `tool execution failed - <message>`

The app answered with another error status. The rest of the message is the app's own error. Check the input against the tool's `input_schema` and the app's API reference.

## Next

  - [Call any API](/agentkit/tools/custom-tools/): Reach an endpoint no built-in tool covers, with the user's credentials.
  - [Choose a framework](/agentkit/examples/): A complete agent in LangChain, OpenAI, Anthropic and more.

To serve these tools to an MCP client instead, [create a Virtual MCP server](/agentkit/mcp/configure-mcp-server/).

## 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` [Search tools](https://docs.scalekit.com/agentkit/reference/tools/search-tools.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 |
