---
title: "Get a tool"
method: GET
path: "/v1/projects/{project_id}/tools/{tool_id}"
tags: ["Tools"]
---

# Get a tool

`GET /v1/projects/{project_id}/tools/{tool_id}`

## Request

- Base URL: `https://api.naturali.ai`
- URL: `https://api.naturali.ai/v1/projects/{project_id}/tools/{tool_id}`
- Auth: one of:
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.naturali.ai/authorize`, token URL `https://api.naturali.ai/token`, scopes: `mcp:access`)

Returns a single tool by ID. A credential scoped to a project the tool is shared with, through an accepted share, reads its `id`, `name`, `description` and `parameters` only.

## Path parameters

- `tool_id` string, required

## Response `200`

Tool

- Tool
  - `id` string: Public ID of the tool
  - `project_id` string: Public ID of the owning project
  - `name` string: Tool name
  - `type` 'http' | 'client' | 'mcp' | 'pipeline' | 'naturali': Tool type
  - `description` string, nullable: What the tool does (sent to the model)
  - `parameters` object, nullable: JSON Schema for tool input
  - `execute` object, nullable: Execution config for http tools. Supported fields: `url` (required), `method` (default `POST`), `headers`, `body_mode` and `response_mode`. The `url` may contain `{paramName}` placeholders (e.g. `/users/{userId}`) that are replaced at call time with the corresponding tool argument value (URL-encoded). Arguments consumed as path parameters are excluded from the query string and request body. `body_mode` is `json` (default) or `multipart`. In `multipart` mode the merged tool arguments are sent as a `multipart/form-data` body: scalar fields become plain form fields and a field shaped like `{ content_type, filename, data_base64 }` is decoded from base64 and attached as a file part (the hardcoded `Content-Type: application/json` is dropped so `fetch` sets the multipart boundary itself). `response_mode` is `json` (default: the body parsed as JSON, else returned as text) or `base64`, which returns a 2xx body as `{ content_type, filename, data_base64 }` — the same file shape `multipart` accepts, so a pipeline step can relay a downloaded file. `filename` comes from `Content-Disposition` and is omitted when the target names none; a body over `TOOL_RESPONSE_MAX_BYTES` is `502 TOOL_RESPONSE_TOO_LARGE`. `auth` adds a computed request credential, for targets whose `Authorization` value cannot be expressed as a static header. Supported `auth.type` values: - `aws_sigv4` — signs the request with AWS Signature Version 4. Requires `region`, `service`, `access_key_id` and `secret_access_key`; `session_token` is optional (temporary credentials). Incompatible with `body_mode: multipart`, whose body bytes are not known at signing time. - `gcp_service_account` — mints a Google OAuth 2.0 access token from a signed service account assertion and sends it as a bearer token. Requires `credentials` (the service account key file JSON, as a string) and `scopes` (a non-empty array). Tokens are cached per service account and scope set until shortly before they expire. Credential fields accept `{{secret:...}}` references and should use them — a tool is readable by anyone who can `GET /tools`, and the stored reference is what is echoed back, never the resolved value. A credential slot takes a reference, never a literal: a literal `secret_access_key`, `session_token` or `credentials` under `auth`, a literal credential-named `headers` value (`Authorization`, `Cookie`, or a name containing `api-key`/`token`/`secret`/`password`), or a `url` carrying a password or a literal credential-named query parameter (those names, plus `key` and `sig`/`signature`) is refused with `400 LITERAL_CREDENTIAL`, `meta.paths` naming each one. A stored literal reads back as `{"no_echo": true}` (a `url` holding one is masked whole), an object rather than a string so a read-edit-write round trip fails the schema check instead of writing the placeholder in as the credential. A value carrying a `{{secret:...}}` reference is the wiring, not the credential, and stays readable. `headers` values additionally accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a per-user credential can be placed in the real header the target expects (`Authorization: Bearer {{context:ocaToken}}`) instead of only in a prefixed context header. Valid **only** inside `headers` — a context value is caller-supplied, so it may not steer the `url` — and a key missing from the `tool_context` at call time fails the tool call with `MISSING_TOOL_CONTEXT_KEY` rather than sending an empty credential. See the Tool Context reference.
  - `mcp` object, nullable: MCP server config (`url`, `headers`). `headers` values accept `{{secret:...}}` and `{{context:<key>}}` references, resolved right before the outbound MCP request; `url` accepts `{{secret:...}}` only. A literal credential in `headers` or `url` is refused, exactly as in `execute`.
  - `actions` string[], nullable: Allowlist of actions to expose. For `mcp` tools: an optional allowlist of MCP tool names — when set, only those tools are exposed to the model and callable via `/call`; when `null`, the entire MCP server surface is exposed. Ignored for other tool types.
  - `denied_actions` string[], nullable: For `mcp` tools: an optional denylist of MCP tool names to hide. Applied after `actions` and taking precedence over it — a name in both lists is denied. This is the ergonomic way to scope a read+write MCP server read-only: deny just the write tools instead of enumerating every read tool in `actions`. Names not listed are exposed. `null` (default) denies nothing. Ignored for other tool types.
  - `context_keys` string[], nullable: Optional allowlist of `tool_context` keys that may be forwarded to this tool as prefixed context headers (`X-Naturali-Context-<key>` by default). When `null`, every key in the caller's `tool_context` is forwarded. When set, only the listed keys are, so a per-user credential in `tool_context` can be confined to the tools that need it; `[]` forwards none. The server-pinned identity keys (`session_id`, `actor_id`, `actor_external_id`) are always forwarded. A key consumed by a `{{context:<key>}}` token in this tool's own headers is substituted regardless of this list — the tool declared that header itself.
  - `preset_parameters` object, nullable: Fixed parameters pinned on every call this tool makes, whatever its type. Keys matching fields in the input schema are removed from the schema shown to the model, and a pinned value wins over one the model or a direct caller supplies for the same key. On an `mcp` tool a preset reaches each action whose input schema declares it; an action whose schema declares no properties receives every one. Values accept `{{context:<key>}}` references, resolved per call from the caller's `tool_context`, so a pin can be the run's own value — the one account this run may act on — rather than one fixed when the tool was created. A resolved value is retyped to the parameter's declared schema type; a key missing from the call's `tool_context` fails the call with `MISSING_TOOL_CONTEXT_KEY` rather than sending the literal placeholder. `{{secret:...}}` is not resolved here. See the Tool Context reference.
  - `pipeline` object, nullable: Pipeline definition for `pipeline` tools: an ordered `steps` array, each step invoking a tool (optional `action`) and building its `input` from earlier results via JSON Logic evaluated over `{ input, steps }`. A step references its tool either by `tool_id` (an existing, persisted tool) or by an inline `tool` definition — the same shape as `CreateToolRequest` minus `project_id` — executed directly without a Tool row, but never both. An inline step `tool` cannot itself be of type `pipeline`. An optional `output` maps the final result; `steps` may be empty when `output` is set, which computes `output` over the input alone. A literal credential in an inline step `tool` is refused, exactly as in `execute`.
  - `output_mapping` object, nullable: Universal JSON Logic mapping applied to the tool's raw result, for every tool type (`http`, `mcp`, `pipeline`, `client`). Evaluated over `{ output: <raw result>, input: <merged input>, action: <action> }`, so `{ "var": "output.text" }` extracts a bare scalar field instead of requiring a wrapping `pipeline` tool, `{ "var": "input.title" }` echoes back a field of the request that produced the response, and `{ "var": "action" }` reads the `mcp` or `builtin` action that ran (`null` on other types). For `pipeline` tools this runs *after* the pipeline's own `output` mapping, over that mapping's result.
  - `guardrail_ids` string[], nullable: Guardrails attached at the tool scope, governing this tool wherever it is used, by any agent.
  - `created_at` string, date-time
  - `updated_at` string, date-time

## Other responses

- `401`: Unauthorized
- `403`: Forbidden
- `404`: Not found

---

[API](https://skmtc.dev/naturali/apis/naturali-ai-api.md) · [All operations](https://skmtc.dev/naturali/apis/naturali-ai-api/llms.txt) · [OpenAPI document](https://skmtc.dev/naturali/apis/naturali-ai-api/revisions/e4905b78f422?raw)
