# 创建工具

`POST` `/api/agent/finx/createTool`

为指定的 HTTP 类型服务创建一个工具（一个工具对应该服务下的一个可调用接口）。

**仅支持 HTTP 类型服务**：MCP 服务的工具由远端 MCP Server 动态提供，不能手工创建。

**关键拼接规则**：最终请求地址 = 服务的 httpConfig.baseUrl + 本工具 httpEndpoint.path；工具默认继承服务级鉴权（inheritAuth 默认 true），仅当该接口用独立凭证时才需在 httpEndpoint 里传 inheritAuth=false 与 auth。

**入参定义**：用 parameterSchema（JSON Schema 字符串）描述该接口的入参；需要走 URL 查询参数或路径变量的字段，在 httpEndpoint.queryParams 中声明，其余入参默认走请求体。

## 请求参数

### 请求体（JSON）

| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| agentId | integer | 是 | 智能体ID，需与该服务归属的智能体一致 |
| serviceId | integer | 是 | 所属服务ID，由 listServices / getServiceDetail 返回。仅支持 HTTP 类型服务；MCP 服务的工具由远端 MCP Server 提供，不能手工创建 |
| toolName | string | 是 | 工具标识名，即 finx 命令中的工具名。必须以英文字母或下划线开头，只能包含字母、数字、下划线和连字符，长度不超过 64；同一服务内不可重名 |
| description | string | 否 | 工具描述。**智能体依赖这段描述判断什么场景该调用该工具**，请写清这个接口能做什么、何时用 |
| parameterSchema | string | 否 | **参数Schema（JSON字符串，不是JSON对象）**
- 内容为标准 JSON Schema，需作为**字符串**传递（注意转义内部的双引号）；保存时会做合法性校验，不合法直接报错
- 作用：告诉智能体调用该接口时要传哪些参数。每个属性的 description 直接影响模型选参的准确度，务必写清楚含义与取值示例
- HTTP 类型的工具 / serviceEndpoint 建议填写；MCP 类型不需要（工具入参由 MCP Server 动态提供）
- Schema 内容示例：`{"type":"object","required":["query"],"properties":{"query":{"type":"string","description":"自然语言查询，如：宁德时代最新研报"},"limit":{"type":"integer","description":"返回条数，默认10"}}}` |
| httpEndpoint | object | 否 | **HTTP端点配置（JSON对象，仅 HTTP 类型服务的工具需要填写）**
- 最终请求地址 = 服务的 baseUrl + 本端点的 path
- method：HTTP 方法（必填），如 GET / POST / PUT / DELETE
- path：相对 baseUrl 的请求路径（必填），支持 `{变量}` 占位，如 `/api/stock/{symbol}`
- contentType：请求体 Content-Type（选填），如 `application/json`
- timeout：超时时间（毫秒，选填），不传时使用服务默认值
- queryParams：字段名数组（选填），声明哪些入参通过 URL 查询参数传递，未声明的入参走请求体
- inheritAuth：是否继承服务级鉴权（选填，默认 true）
- auth：端点级鉴权（选填），仅当 inheritAuth=false、该接口使用独立凭证时才需要，字段说明见下方
- streaming：是否以 SSE（text/event-stream）方式消费该端点（选填，默认 false）
- resultEventName：SSE 流中作为「模型观察结果」的保留事件名（选填，默认 `tool_observation`）。streaming=true 时，中间帧实时透传给终端界面，只有该事件名对应帧的 data 会作为模型观察结果；流中没有该事件时回退取最后一帧

**端点示例**
- 普通 JSON 接口：`{"method":"POST","path":"/api/data/query","contentType":"application/json","timeout":30000,"queryParams":["page","size"]}`
- GET 接口（参数走 URL）：`{"method":"GET","path":"/api/stock/{symbol}","queryParams":["period"]}`
- SSE 流式接口：`{"method":"POST","path":"/api/chat/stream","contentType":"application/json","streaming":true,"resultEventName":"tool_observation"}`

**auth（鉴权配置，选填。不传或 type=NONE 表示该服务无需鉴权）**
- type：鉴权类型。**请求侧键名是 `type`；响应侧（getServiceDetail、getAuthSchema）回显时叫 `authType`，两者不要混用**。可选值：
  - NONE：无需认证
  - API_KEY：API 密钥放在请求头
  - BEARER：Bearer 令牌
  - BASIC：HTTP Basic 认证
  - CUSTOM：一个或多个自定义请求头
  - CUSTOM_HTTP_TOKEN：先调用一个 HTTP 接口动态换取令牌
  - OAUTH：OAuth 2.1 按用户授权（平台预置数据源专用，见下文）
- headerName：API_KEY 使用的请求头名，不传时默认 `X-API-Key`
- tokenPrefix：BEARER 使用的令牌前缀，不传时默认 `Bearer`（BASIC 固定使用 `Basic`，此字段对它无效）
- credential：主凭证值，API_KEY / BEARER / BASIC 使用。BEARER 只填令牌本身、**不要带 `Bearer ` 前缀**；BASIC 填 `base64(用户名:密码)` 的结果
- customHeaders：自定义请求头键值对，CUSTOM 使用，运行时按原样作为请求头发送
- queryParams：URL 查询参数键值对，**可与任意 type 叠加**，运行时拼接到请求 URL 上（如 `?apiKey=xxx`）
- requireUserProvided：凭证由谁提供，不传时默认 false
  - false（平台统一凭证）：在 auth 里直接写真实凭证值，该服务的所有用户共用这一份凭证
  - true（用户各自提供）：auth 只作为模板，仅声明字段名、凭证值留空串；每个用户后续通过 saveAuthCredential 提交自己的凭证
- tokenFetchConfig：动态换取令牌的配置，仅 type=CUSTOM_HTTP_TOKEN 时需要，子字段：
  - method：换取令牌的请求方法，不传时默认 POST
  - url：换取令牌的接口地址（必填）
  - headers：换取令牌时携带的请求头，客户端凭证放在这里（如 `{"Authorization":"Basic xxx"}`）
  - body：换取令牌的请求体字符串（如 `grant_type=client_credentials`）
  - tokenField：从响应 JSON 中取令牌的字段名，不传时默认 `access_token`
  - expiresInField：从响应 JSON 中取有效期（秒）的字段名，不传时默认 `expires_in`
  - targetHeaderName：把令牌注入业务请求时使用的请求头名，不传时默认 `Authorization`
  - targetTokenPrefix：注入令牌时使用的前缀，不传时默认 `Bearer`
  - refreshBufferSeconds：在令牌到期前多少秒主动刷新，不传时默认 60

**各 type 的 auth 写法示例**
- NONE：`{"type":"NONE"}`
- API_KEY（默认请求头 X-API-Key）：`{"type":"API_KEY","credential":"sk-abcdef123456"}`
- API_KEY（自定义请求头名）：`{"type":"API_KEY","headerName":"X-Custom-Key","credential":"sk-abcdef123456"}`
- BEARER：`{"type":"BEARER","credential":"eyJhbGciOiJIUzI1NiJ9.payload.sig"}`
- BASIC：`{"type":"BASIC","credential":"dXNlcjpwYXNz"}`（`dXNlcjpwYXNz` 即 base64 编码后的 `user:pass`）
- CUSTOM（多个自定义头）：`{"type":"CUSTOM","customHeaders":{"X-App-Id":"123","X-App-Secret":"abc"}}`
- CUSTOM_HTTP_TOKEN：`{"type":"CUSTOM_HTTP_TOKEN","tokenFetchConfig":{"method":"POST","url":"https://auth.example.com/oauth2/token","headers":{"Authorization":"Basic dXNlcjpwYXNz","Content-Type":"application/x-www-form-urlencoded"},"body":"grant_type=client_credentials","tokenField":"access_token","expiresInField":"expires_in","targetHeaderName":"Authorization","targetTokenPrefix":"Bearer","refreshBufferSeconds":60}}`
- 用户各自提供凭证（只声明字段名、值留空）：`{"type":"CUSTOM","customHeaders":{"X-App-Secret":""},"requireUserProvided":true}`
- 鉴权信息放 URL 参数（与 BEARER 叠加）：`{"type":"BEARER","credential":"my-token","queryParams":{"appId":"123"}}`
- OAUTH：仅用于平台预置的「按用户授权」数据源，需平台侧先完成 OAuth 客户端注册，通过本接口自助创建服务时暂不支持自行配置，如有此类需求请联系平台

**凭证安全**
- credential、customHeaders 与 queryParams 的值落库前会加密存储；getServiceDetail 只回显含 `***` 的脱敏值，不会返回明文
- 编辑服务时把脱敏值原样回传即表示「保留原值」，星号不会被写入库；要真正修改就传新的明文值 |

## 响应

### 200

| 参数名 | 类型 | 说明 |
|--------|------|------|
| httpStatusCode | integer | HTTP状态码 |
| message | string | 响应消息 |
| data | integer | 响应数据 |
| success | boolean | 是否成功 |
| requestId | string | 上游请求ID，用于跨系统链路追踪排查问题 |
| errCode | string | 上游错误码，前端可据此做特殊UI处理 |

### 400

| 参数名 | 类型 | 说明 |
|--------|------|------|
| httpStatusCode | integer | HTTP状态码 |
| message | string | 响应消息 |
| data | object | 响应数据 |
| success | boolean | 是否成功 |
| requestId | string | 上游请求ID，用于跨系统链路追踪排查问题 |
| errCode | string | 上游错误码，前端可据此做特殊UI处理 |

### 500

| 参数名 | 类型 | 说明 |
|--------|------|------|
| httpStatusCode | integer | HTTP状态码 |
| message | string | 响应消息 |
| data | object | 响应数据 |
| success | boolean | 是否成功 |
| requestId | string | 上游请求ID，用于跨系统链路追踪排查问题 |
| errCode | string | 上游错误码，前端可据此做特殊UI处理 |

