Skip to content

Let an agent carry out an action with a tool

At the end of this tutorial the agent support creates a ticket in Noordwind's ticket system when a customer asks for one. You have registered that system's endpoint as an API connector, allowed the host and tried the tool yourself first.

Building blocks Tools: API connector and allowed destinations. Agents, Keys, capabilities and policy
You need a key with the capabilities agents and tooling; an HTTP endpoint that creates a ticket, with a token for it; an administrator of your organisation, or a key with management, for step 2; for the trace in the check, trace storage must be on for your organisation (Read a trace)
Builds on Build your first agent. The search tool from the second tutorial is not needed, but the examples carry it along

The examples use https://tickets.example.com/api/tickets as the endpoint. Replace it with the address of your own system; the example assumes an API that expects a POST with subject and description and answers 201 with the new ticket.

An API connector is the shortest way to an existing HTTP API: Core calls the endpoint with the model's arguments and keeps the token. When your own application must carry out the action, for example because it updates a screen, use a function tool.

1. Register the API connector

curl -s -X POST "$CORE_URL/api_connectors" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"name": "create_ticket",
         "http_url": "https://tickets.example.com/api/tickets",
         "description": "Creates a support ticket. Use this only when the customer asks for a ticket or when you cannot solve the question yourself.",
         "parameters": {"type": "object",
                        "properties": {"subject": {"type": "string", "description": "Short title of the problem"},
                                       "description": {"type": "string", "description": "What the customer reports, in their own words"}},
                        "required": ["subject", "description"]},
         "request": {"method": "POST",
                     "body": {"subject": "{subject}", "description": "{description}"}},
         "auth": {"type": "bearer", "secret": "Bearer <the ticket system token>"}}'

The answer is 201 (abridged):

{
  "id": "tool_3Lm8...",
  "name": "create_ticket",
  "version": 1,
  "auth": { "type": "bearer", "header_name": "Authorization", "has_secret": true },
  "egress": [{ "host": "tickets.example.com", "allowed": false, "blocked": false }]
}

Four choices in this request:

  • name becomes the tool name api__create_ticket and does not change afterwards.
  • description and parameters are all the model knows about the tool. Write in the description when the tool is meant to be used, and give each argument a description of its own.
  • request translates the arguments into the body your API expects. Leave request out and Core sends a fixed body with tool, arguments, response_id and call_id.
  • auth keeps the token with the connector. The model and your application never see it, and no answer returns it. A bearer secret is sent literally, so the prefix Bearer is part of it.

egress says the host is not allowed yet. You solve that in the next step.

2. Allow the host

An organisation reaches no host until it allows one. An administrator does that in the console: open Settings, the tab Access & policy, and choose Add a host on the card Allowed hosts. Enter tickets.example.com and choose Allow host.

With a key that has the capability management you do the same through the API:

curl -s -X POST "$CORE_URL/organisation/allowed-hosts" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"host": "tickets.example.com"}'
{
  "object": "allowed_hosts",
  "everywhere": false,
  "hosts": [{ "host": "tickets.example.com", "blocked": false }]
}

The list holds for the whole organisation: every key reaches the hosts on it. A key can narrow that with rules of its own over Destinations and never widen it. That way the organisation, and not one application, decides where its data may go. When you see blocked: true, your installation blocks the host for everyone; ask your operator.

3. Try the tool yourself

curl -s -X POST "$CORE_URL/tools/api__create_ticket/call" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"arguments": {"subject": "Test from Core", "description": "This ticket may be removed."}}'
{
  "object": "tool_call",
  "name": "api__create_ticket",
  "status": "ok",
  "error_code": null,
  "text": "{\"id\": \"T-1042\", \"subject\": \"Test from Core\"}"
}

text is what a model would read; the content here is an example of what a ticket system answers. This call creates a real ticket. You make it anyway, because it tests the connector, the token and the host with no model in between. status is ok, denied, error or cancelled.

4. Give the agent the tool

curl -s -X POST "$CORE_URL/agents/$AGENT_ID/versions" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"model": "<a model id>",
         "instructions": "You are the support assistant of Noordwind. Answer briefly. For every question about policy, search the handbook first and refer to the passage with [n]. Create a ticket only when the customer asks for one or when you cannot solve the question. Then name the ticket number.",
         "temperature": 0.2,
         "tools": [{"type": "tool", "name": "knowledge__handbook"},
                   {"type": "tool", "name": "api__create_ticket"}]}'

If you skipped the second tutorial, leave out knowledge__handbook and the sentence about the handbook. The instruction says when the agent may create a ticket. A tool that changes something needs such a boundary: without that sentence a model creates a ticket sooner than you want.

5. Ask for a ticket

curl -s -X POST "$CORE_URL/responses" \
    -H "Authorization: Bearer $CORE_KEY" -H "Content-Type: application/json" \
    -d '{"agent": {"id": "support"},
         "input": "My order 88213 arrived damaged. Will you make a ticket for this?"}'

Check that it works

The answer (abridged) shows the tool call and then the agent's text:

{
  "id": "resp_0KQ2mR...",
  "status": "completed",
  "output": [
    { "type": "mcp_call", "name": "api__create_ticket", "server_label": "api",
      "arguments": "{\"subject\": \"Damaged order 88213\", \"description\": \"...\"}",
      "status": "completed", "output": "{\"id\": \"T-1043\", ...}" },
    { "type": "message", "role": "assistant",
      "content": [{ "type": "output_text", "text": "I have created ticket T-1043 for you." }] }
  ]
}

The ticket is now in your ticket system too. The trace of the turn shows what Core ran and what the endpoint answered:

curl -s "$CORE_URL/responses/resp_0KQ2mR.../trace" -H "Authorization: Bearer $CORE_KEY"
{
  "items": [
    { "id": "...", "round": 0, "status": "ok",
      "tool_calls": [
        { "tool_name": "api__create_ticket", "source": "http", "status": "ok", "vendor_status": 201 }
      ] }
  ]
}

vendor_status is the HTTP status of your own endpoint. In the console the same turn is under Traces (Read a trace).

If it does not work

You see Cause Fix
404 on POST /api_connectors the capability tooling is off on your key or in your organisation's ceiling ask your administrator to switch it on (Capabilities)
422 when registering a {name} that is not in parameters.properties, or a credential header in request.headers declare the argument; put the token in auth (Send your own request)
status: "denied" in step 3, or "Denied by policy." in the agent's answer the host is not allowed. In step 3 it can also be your key's policy refusing the tool; in a turn the model is not even offered such a tool repeat step 2 and check egress on GET /api_connectors/{id}; read the rules over Tools with GET /key
the host is on your list and the call is still refused an organisation above yours does not permit the host (egress then reads allowed: false), or it restricts regions the host is not served in ask the administrator of the organisation above to permit the host (Policy)
"Tool endpoint answered 401." the endpoint refuses the token set the right secret with PATCH /api_connectors/{id}; remember the prefix Bearer
"Tool endpoint unreachable." a timeout or a network error check the address; Core follows no redirects
404 or 403 on POST /organisation/allowed-hosts your key has no management, or the organisation above keeps key management to itself have an administrator allow the host in the console

A failed tool call does not fail the turn: the model gets the message and answers with it.

Read on

Next tutorial: Connect an external capability through MCP.