# 创建 Go 自定义工具

把业务查询封装为类型化工具，注册进程内 MCP 并验证输入与返回值。

## 让 Agent 调用现有业务函数

Go 自定义工具运行在宿主进程中，通过进程内 MCP 提供给运行时。它适合调用已有服务层、读取当前用户的业务记录，或执行带授权检查的操作，不需要为每个工具启动一个 HTTP 服务。

先设计一个范围明确的工具。名称说明动作，描述说明何时使用，输入只包含完成任务所需字段。不要让模型传入任意 SQL、任意文件路径或任意命令来代替业务接口。

## 完整示例

下面的工具读取示例订单状态，没有外部网络和写操作。保存为 main.go，在[快速开始](/docs/sdk-quickstart)建立的模块中运行。

```go
package main

import (
    "context"
    "fmt"
    "log"
    "time"

    "github.com/nexus-research-lab/nexus-agent-sdk-bridge/client"
    "github.com/nexus-research-lab/nexus-agent-sdk-bridge/tools"
)

type OrderInput struct {
    ID string `json:"id"`
}

func run() error {
    lookup, err := tools.NewTyped[OrderInput]("order_status", "查询指定订单的处理状态。",
        func(ctx context.Context, input OrderInput, _ *tools.Context) (tools.Result, error) {
            if err := ctx.Err(); err != nil { return tools.Result{}, err }
            if input.ID != "demo-001" {
                return tools.Error("没有找到当前用户可访问的订单。"), nil
            }
            return tools.Text("订单 demo-001 已发货。"), nil
        }, tools.ReadOnly())
    if err != nil { return err }
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
    defer cancel()
    options := client.NewOptions().WithRuntime(client.RuntimeNXS).
        WithCustomTools("orders", lookup).
        WithAllowedTools("mcp__orders__order_status")
    result, err := client.Prompt(ctx, client.PromptRequest{
        Prompt: "使用订单工具查询 demo-001 的状态。", Options: options,
    })
    if err != nil { return err }
    if result.IsError { return fmt.Errorf("任务失败：%s", result.Subtype) }
    fmt.Println(result.Result)
    return nil
}

func main() {
    if err := run(); err != nil { log.Fatal(err) }
}
```

## 输入类型与业务验证

`NewTyped[T]` 从 Go 类型推导 JSON Schema，并在调用时解码为 T。它减少手工转换，不替代业务验证：空 ID、超出范围的数量、资源归属和重复请求仍要检查。

有复杂 Schema 时可用 `tools.New` 显式传入定义。错误应说明哪项输入无效，让模型可以纠正；不要把数据库堆栈或访问令牌放进错误内容。

实际服务中的当前用户应来自宿主可信上下文，而不是让模型传一个 user_id 决定权限。本例只接受 demo-001，是为了让可访问范围可见、可测试。

## 注册、名称和权限

`WithCustomTools("orders", lookup)` 建立名为 orders 的进程内服务。运行时使用完整名称 `mcp__orders__order_status` 调用，因此允许规则要与注册名称一致。添加了工具但调用失败时，先检查服务名、工具名和权限规则，不要扩大成允许全部工具。

需要自行管理服务对象时，使用 `tools.CreateSDKMCPServer` 与 `WithSDKMCPServer`。它们与外部 MCP 服务配置不同，详见[MCP 接入](/docs/sdk-mcp)。

## 返回结果和失败

`tools.Text` 返回模型可读文本，`tools.Error` 返回带错误标记的工具结果。订单不存在等预期业务失败适合返回受控错误文本；连接中断等调用失败可以返回 Go error。失败不能返回“成功”再把错误藏在日志里。

结果应只包含当前任务需要的信息。大量记录应分页或先聚合。工具输出进入模型上下文，不能把内部字段全量序列化给模型。

## 并发与副作用

`ReadOnly` 和 `Concurrent` 是对工具行为的声明，不会自动加锁，也不会把写操作变成安全操作。只有实现确实允许并发时才使用相应选项。

写操作应有宿主授权、幂等键和可核对的结果。运行时重试或用户再次提交任务，都可能触发重复调用。业务状态不能依赖模型保证“只调用一次”。
