> ## Documentation Index
> Fetch the complete documentation index at: https://docs.meshagent.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Create Custom Tools

Custom tools let you add capabilities beyond the built-in MeshAgent toolkits. A `Toolkit` groups related `Tool`s together so they can be registered in a room and used by people or agents.

In this guide you'll:

* Write custom tools
* Create a toolkit to group your related tools
* Register a toolkit in a room
* Invoke tools using the MeshAgent CLI, MeshAgent Studio UI, and code
* Give a process agent access to the toolkit
* Let a process agent dynamically discover annotated room toolkits

## Creating a custom toolkit

### Step 1: Writing custom tools

Let's create a simple toolkit with two tools, one that can add two numbers, and one that can subtract two numbers.

First write the logic for the `add` and `subtract` tools. Then bundle them into a `Toolkit` that a `SingleRoomAgent` can expose for room calls.

Save this example as `tools-adder.py`:

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  from meshagent.api import TOOL_SEARCH_ANNOTATION
  from meshagent.agents import SingleRoomAgent
  from meshagent.tools import FunctionTool, ToolContext, Toolkit
  from meshagent.otel import otel_config

  otel_config(service_name="math_tools")


  class Add(FunctionTool):
      def __init__(self):
          super().__init__(
              name="add",
              title="adding tool",
              description="a tool that adds two numbers",
              input_schema={
                  "type": "object",
                  "additionalProperties": False,
                  "required": ["a", "b"],
                  "properties": {
                      "a": {"type": "integer"},
                      "b": {"type": "integer"},
                  },
              },
          )

      async def execute(self, context: ToolContext, *, a: int, b: int):
          result = {"result": a + b}
          print(result)
          return result


  class Subtract(FunctionTool):
      def __init__(self):
          super().__init__(
              name="subtract",
              title="subtracting tool",
              description="a tool that subtracts two numbers",
              input_schema={
                  "type": "object",
                  "additionalProperties": False,
                  "required": ["a", "b"],
                  "properties": {
                      "a": {"type": "integer"},
                      "b": {"type": "integer"},
                  },
              },
          )

      async def execute(self, context: ToolContext, *, a: int, b: int):
          result = {"result": a - b}
          print(result)
          return result


  class MathToolkit(Toolkit):
      def __init__(self):
          super().__init__(
              name="math-toolkit",
              title="math-toolkit",
              description="a toolkit for adding and subtracting numbers",
              annotations={TOOL_SEARCH_ANNOTATION: "true"},
              tools=[Add(), Subtract()],
          )


  class MathAgent(SingleRoomAgent):
      async def get_exposed_toolkits(self) -> list[Toolkit]:
          return [MathToolkit()]


  async def main() -> None:
      agent = MathAgent(title="math-agent")
      await agent.run()


  if __name__ == "__main__":
      asyncio.run(main())

  ```
</CodeGroup>

### Step 2: Register the toolkit in a room

Create the room and start the toolkit locally. This guide uses `gettingstarted`, the same room created in the CLI quickstart, so you can reuse a room you already own.

<CodeGroup>
  ```bash bash theme={null}
  meshagent setup # authenticate to MeshAgent
  meshagent rooms create gettingstarted --if-not-exists
  meshagent room connect --room=gettingstarted --identity=math-tools -- python3 tools-adder.py
  ```
</CodeGroup>

This command runs the local Python agent and registers the `math-toolkit` toolkit into `gettingstarted`.

The toolkit is available while that terminal session is still running. `SingleRoomAgent.run()` creates the room connection, starts the agent, and unregisters the exposed toolkit when the process stops.

### Step 3: Invoke and inspect the toolkit

#### Invoking tools programmatically

While the agent is running, invoke the toolkit from another terminal or from code.

Run the Python and .NET examples through `meshagent room connect` so MeshAgent injects the room connection environment. The Python tab can be saved as `tools-calling.py` and run directly. For .NET, add the C# tab to a .NET project that references `Meshagent.Api`, then run the project through `meshagent room connect`.

<CodeGroup>
  ```bash CLI theme={null}
  meshagent room agents invoke-tool \
    --room=gettingstarted \
    --toolkit=math-toolkit \
    --tool=add \
    --arguments='{"a": 5, "b":7}'
  ```

  ```python Python theme={null}
  # Run with:
  # meshagent room connect --room=gettingstarted --identity=sample-participant -- python3 tools-calling.py

  import asyncio
  import logging
  from meshagent.api import RoomClient
  from meshagent.otel import otel_config

  otel_config()
  log = logging.getLogger(__name__)


  async def main():
      try:
          async with RoomClient() as room:
              log.info("Connected to room: %s", room.room_name)

              add_result = await room.agents.invoke_tool(
                  toolkit="math-toolkit", tool="add", input={"a": 1, "b": 2}
              )
              log.info("The result from adding the numbers is: %s", add_result)

              subtract_result = await room.agents.invoke_tool(
                  toolkit="math-toolkit", tool="subtract", input={"a": 1, "b": 2}
              )
              log.info("The result from subtracting the numbers is: %s", subtract_result)
      except Exception as e:
          log.error("Error invoking tool: %s", e)
          raise


  asyncio.run(main())

  ```

  ```dotnet C# theme={null}
  using System;
  using System.Collections.Generic;
  using System.Text.Json;
  using System.Threading.Tasks;
  using Meshagent.Api.Room;

  // Run with:
  // meshagent room connect --room=gettingstarted --identity=sample-participant -- dotnet run

  class Program
  {
      static async Task Main()
      {
          await using var room = new RoomClient();
          await room.ConnectAsync();

          var arguments = new Dictionary<string, object> { ["a"] = 1, ["b"] = 2 };
          var addResult = await room.Agents.InvokeTool("math-toolkit", "add", arguments);
          Console.WriteLine($"The result from adding the numbers is: {JsonSerializer.Serialize(addResult.ToJson())}");

          var subtractResult = await room.Agents.InvokeTool("math-toolkit", "subtract", arguments);
          Console.WriteLine($"The result from subtracting the numbers is: {JsonSerializer.Serialize(subtractResult.ToJson())}");
      }
  }

  ```
</CodeGroup>

Invoking the tools using the RoomClient will give you the following result:

<CodeGroup>
  ```text Output theme={null}
  The result from adding the numbers is: Json: json={"result": 3}
  The result from subtracting the numbers is: Json: json={"result": -1}
  ```
</CodeGroup>

#### Inspecting available toolkits

You can also verify which toolkits are registered in the room.

Run the Python and .NET discovery examples through the same `meshagent room connect` pattern.

<CodeGroup>
  ```bash CLI theme={null}
  meshagent room agents list-toolkits --room=gettingstarted
  ```

  ```python Python theme={null}
  # Run with:
  # meshagent room connect --room=gettingstarted --identity=sample-participant -- python3 tools-discovery.py

  import asyncio
  import logging
  from meshagent.api import RoomClient
  from meshagent.otel import otel_config

  otel_config()
  log = logging.getLogger(__name__)


  async def main():
      try:
          async with RoomClient() as room:
              toolkits = await room.agents.list_toolkits()

              print("The tools connected to our room are:")
              for toolkit in toolkits:
                  print(
                      f"\n Toolkit: {toolkit.name}: {toolkit.title} - {toolkit.description}"
                  )
                  for tool in toolkit.tools:
                      print(f" Tool: {tool.name}: {tool.title} - {tool.description}")

      except Exception as e:
          log.error("Error listing available toolkits: %s", e)
          raise


  asyncio.run(main())

  ```

  ```dotnet C# theme={null}
  using System;
  using System.Threading.Tasks;
  using Meshagent.Api.Room;

  // Run with:
  // meshagent room connect --room=gettingstarted --identity=sample-participant -- dotnet run

  class Program
  {
      static async Task Main()
      {
          await using var room = new RoomClient();
          await room.ConnectAsync();

          var toolkits = await room.Agents.ListToolkits();

          Console.WriteLine("The tools connected to our room are:");
          foreach (var toolkit in toolkits)
          {
              Console.WriteLine($"\n Toolkit: {toolkit.Name}: {toolkit.Title} - {toolkit.Description}");
              foreach (var tool in toolkit.Tools)
              {
                  Console.WriteLine($" Tool: {tool.Name}: {tool.Title} - {tool.Description}");
              }
          }
      }
  }

  ```
</CodeGroup>

The output should include `math-toolkit`, its `add` and `subtract` tools, and this annotation:

```json theme={null}
"annotations": {
  "meshagent.tool_search": "true"
}
```

#### Invoking tools in MeshAgent Studio

1. Go to [studio.meshagent.com](https://studio.meshagent.com) and login
2. From the **Sessions** tab enter `gettingstarted`
3. From the menu in the upper left, click **Toolkits**
4. Find the tool you want to run and click **Invoke**
5. Enter any required values and click **Ok**

The tool will execute and display the result in the Studio UI.

From the "Developer Console" on the bottom half of the screen you'll be able to see logs, traces, and metrics from tool calls as they happen in the room.

Because this example is running locally through `meshagent room connect`, the toolkit is only available while that terminal session is still running.

### Step 4: Give a process agent access to the toolkit

There are two common ways to make a room toolkit available to a process agent.

#### Option A: Require the toolkit by name

Use `--require-toolkit` when the agent should always have access to a specific room toolkit. This is the simpler path when you already know which toolkit the agent should use.

Leave the terminal running `meshagent room connect --room=gettingstarted --identity=math-tools -- python3 tools-adder.py`; that process is hosting `math-toolkit` in the room. In a second terminal, start a chat agent that requires `math-toolkit`:

```bash bash theme={null}
meshagent process join \
  --room gettingstarted \
  --agent-name math-helper \
  --channel chat \
  --model gpt-5.5 \
  --require-toolkit math-toolkit
```

Open the Studio link printed by the command and ask the agent an arithmetic question, such as:

```text theme={null}
Use the available math tool to add 389 and 457.
```

If you deploy the process agent with the same required toolkit, the agent will request that toolkit by name whenever it handles a turn in the room.

#### Option B: Let the agent discover annotated room toolkits

Use `--tool-search room` when the agent should be able to dynamically discover annotated toolkits that are already present in the room. This is useful when you do not want to configure every toolkit directly on the agent.

This option uses OpenAI Responses tool search behind the scenes, so it only applies to OpenAI-backed process-agent models.

The sample toolkit sets the `meshagent.tool_search` annotation. Start a chat agent with room tool search enabled:

```bash bash theme={null}
meshagent process join \
  --room gettingstarted \
  --agent-name math-helper \
  --channel chat \
  --model gpt-5.5 \
  --tool-search room \
  --log-llm-requests
```

Open the Studio link printed by the command and ask the agent an arithmetic question:

```text theme={null}
Use the available math tool to add 389 and 457.
```

`--tool-search room` adds annotated room toolkits to the model's tool-search candidate set. In the process-agent logs, `math-toolkit` should appear as a deferred toolkit available to the model.

## Next Steps

If you want the toolkit to stay available after your local terminal session ends, package and deploy it as a MeshAgent service.

* [Dynamic UI Tools](./dynamic_ui_tools): Learn what Dynamic UI tools are and how to build and use them in your applications
* Learn how to use custom tools inside a [Process Agent](../process/overview) or [Voice Agent](../standard/voicebot)
* [Service YAML](../../services/deployment/deploy_services): write service manifests for agents and tools.
