Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/claudesdk/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func validateMCPServers(servers []proto.MCPHTTPServer) error {
endpoint, err := url.Parse(server.ServerURL)
if !mcpLabel.MatchString(server.ServerLabel) || server.ServerLabel == "functions" || labels[server.ServerLabel] ||
err != nil || (endpoint.Scheme != "http" && endpoint.Scheme != "https") || endpoint.Hostname() == "" || endpoint.User != nil ||
strings.ContainsAny(server.ServerURL, "?#") || endpoint.Opaque != "" {
strings.Contains(server.ServerURL, "#") || endpoint.Opaque != "" {
return fmt.Errorf("claudesdk: unsupported HTTP MCP declaration")
}
if server.BearerToken != nil && (endpoint.Scheme != "https" || !agent.ValidMCPHTTPBearerToken(*server.BearerToken)) {
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/claudesdk/mcp_bearer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func TestMCPBearerRejectsInvalidCredentialBeforeStateCreation(t *testing.T) {
t.Fatal("invalid credential wrote execution state", err)
}
}
for _, url := range []string{"http://example.invalid/mcp", "https://example.invalid/mcp#", "https://example.invalid/mcp?", "https://user:secret@example.invalid/mcp"} {
for _, url := range []string{"http://example.invalid/mcp", "https://example.invalid/mcp#", "https://user:secret@example.invalid/mcp"} {
token := "synthetic-token"
servers := []proto.MCPHTTPServer{{ConnectionOrigin: "service", ServerLabel: "fixture", ServerURL: url, BearerToken: &token}}
if err := validateMCP(proto.PromptRequestPayload{DisableExecutionEnvironment: true, MCPHTTPServers: &servers}); err == nil {
Expand Down
4 changes: 2 additions & 2 deletions apps/daemon/internal/agent/claudesdk/mcp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func TestHTTPMCPDeclaration(t *testing.T) {
case "url-auth":
servers[0].ServerURL = "https://user:secret@example.invalid/mcp"
case "query":
servers[0].ServerURL += "?"
servers[0].ServerURL += "?tenant=a"
case "wildcard":
tools = []string{"*"}
servers[0].AllowedTools = &tools
Expand All @@ -47,7 +47,7 @@ func TestHTTPMCPDeclaration(t *testing.T) {
req.DisableExecutionEnvironment = false
}
start, _, err := prepare(config, req)
valid := mode == "unrestricted" || mode == "selected" || mode == "empty" || mode == "nil-slice" || mode == "auth" || mode == "required"
valid := mode == "unrestricted" || mode == "selected" || mode == "empty" || mode == "nil-slice" || mode == "auth" || mode == "required" || mode == "query"
if (err == nil) != valid {
t.Fatalf("unexpected admission: %v", err)
}
Expand Down
3 changes: 0 additions & 3 deletions apps/daemon/internal/agent/codex/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ type functionCalls struct {

func prepareFunctionTools(tools []proto.FunctionTool) (*functionCalls, error) {
state := &functionCalls{names: map[string]bool{}, pending: map[string]*pendingFunction{}}
if len(tools) > 64 {
return nil, errors.New("at most 64 function tools are supported")
}
for _, tool := range tools {
var schema map[string]any
if strings.TrimSpace(tool.Name) == "" || state.names[tool.Name] || json.Unmarshal(tool.Parameters, &schema) != nil || schema == nil {
Expand Down
1 change: 0 additions & 1 deletion apps/daemon/internal/agent/codex/mcp_http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ func TestPublicMCPHTTPRejectsInvalidProfileAndStoredCredentials(t *testing.T) {
for _, server := range []proto.MCPHTTPServer{
{ConnectionOrigin: "service", ServerLabel: "codex_apps", ServerURL: "https://docs.example/mcp"},
{ConnectionOrigin: "service", ServerLabel: "docs", ServerURL: "https://user:synthetic-secret@docs.example/mcp"},
{ConnectionOrigin: "service", ServerLabel: "docs", ServerURL: "https://docs.example/mcp?token=synthetic-secret"},
{ConnectionOrigin: "service", ServerLabel: "docs", ServerURL: "file:///tmp/mcp"},
} {
servers := []proto.MCPHTTPServer{server}
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/mcode/environment_mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func runtimeMCP(req proto.PromptRequestPayload) ([]map[string]any, error) {
// pinned native HTTP client can forward them across redirect origins.
func environmentHTTPMCP(item agent.MCPBinding) (map[string]any, error) {
endpoint, err := url.Parse(item.ServerURL)
if err != nil || (endpoint.Scheme != "http" && endpoint.Scheme != "https") || endpoint.Hostname() == "" || endpoint.User != nil || endpoint.Fragment != "" || endpoint.RawQuery != "" || endpoint.ForceQuery || endpoint.Opaque != "" || len(item.HTTPHeaders) != 0 {
if err != nil || (endpoint.Scheme != "http" && endpoint.Scheme != "https") || endpoint.Hostname() == "" || endpoint.User != nil || endpoint.Fragment != "" || endpoint.Opaque != "" || len(item.HTTPHeaders) != 0 {
return nil, fmt.Errorf("mcode: unsupported environment HTTP MCP declaration")
}
headers := []map[string]string{}
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/mcp_binding.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func ResolveMCPBindings(req proto.PromptRequestPayload) ([]MCPBinding, error) {
}
case "http":
endpoint, err := url.Parse(item.ServerURL)
if err != nil || endpoint.Hostname() == "" || (endpoint.Scheme != "https" && endpoint.Scheme != "http") || endpoint.User != nil || endpoint.Fragment != "" || endpoint.Opaque != "" || (item.ConnectionOrigin == "service" && (endpoint.RawQuery != "" || endpoint.ForceQuery)) {
if err != nil || endpoint.Hostname() == "" || (endpoint.Scheme != "https" && endpoint.Scheme != "http") || endpoint.User != nil || endpoint.Fragment != "" || endpoint.Opaque != "" {
return nil, errors.New("invalid MCP endpoint")
}
if item.BearerToken != nil && (endpoint.Scheme != "https" || !ValidMCPHTTPBearerToken(*item.BearerToken)) {
Expand Down
4 changes: 2 additions & 2 deletions contracts/agents-api/execution-tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ An Agent declares application functions, controls and MCP servers in `tools`, an

## Functions

A function declaration requires `name`, `description` and a JSON Schema in `parameters`; `defer_loading` defaults to false and cannot be null. Names are nonblank, unique and at most 512 bytes; a Session holds at most 64 function definitions. Saved references resolve into the Session snapshot, and the definitions stay fixed through native preparation and continuation.
A function declaration requires `name`, `description` and a JSON Schema in `parameters`; `defer_loading` defaults to false and cannot be null. Names are nonblank and unique. Saved references resolve into the Session snapshot, and the definitions stay fixed through native preparation and continuation.

**Results.** A caller submits `agent.session.input.tool_result` events with `turn_id`, `call_id` and `success`, and optional nullable `error` and `output`. Output is a string or ordered text and image parts, subject to the Harness. Batches are atomic and keep field presence, original content and retry identity; public result Items and events always carry `output` and `error`, null when not submitted.

Expand Down Expand Up @@ -85,7 +85,7 @@ Execution carries the resolved values to the Harness and validates them against
}
```

- `server_label` is nonempty and unique within the Session. Only the `http` transport is accepted; `server_url` is an absolute HTTP or HTTPS URL without credentials, query or fragment. Nonempty `headers` and `request_metadata` and an inline `authorization` are rejected.
- `server_label` is nonempty and unique within the Session. Only the `http` transport is accepted; `server_url` is an absolute HTTP or HTTPS URL without credentials or fragment. Nonempty `headers` and `request_metadata` and an inline `authorization` are rejected.
- [Public MCP connection origin](./environments.md#public-mcp-connection-origin) owns origin defaults, placement and credential authority; [Harness capabilities](./harness-capabilities.md#tools) owns per-Harness support.
- Omitted or null `allowed_tools` permits every server tool; `[]` permits none.
- `required: true` makes native thread creation and cold resume wait for the server to initialize; a failure stops execution without replacing retained history. It needs the Runtime's `mcp_http_required` capability. Public work can be accepted or queued during the wait.
Expand Down
2 changes: 1 addition & 1 deletion contracts/agents-api/wire-semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ A Session's effective configuration must also pass execution admission, which ap
| Configuration | Message |
| --- | --- |
| A `reasoning.effort`, `reasoning.summary`, `service_tier`, `web_search` mode or setting, `programmatic_tool_calling` value or `text.verbosity` the Harness does not declare | A message naming the field, such as `reasoning.effort "max" is not supported by this Harness.`; see [tool policy](./execution-tools.md#web-search-and-programmatic-tool-calling) and [Harness capabilities](./harness-capabilities.md#execution-and-input) |
| More than 64 functions, or a function name that is blank or longer than 512 bytes | "This service supports at most 64 function tools." or "Function names must be nonempty, unique and at most 512 bytes." |
| A function name that is blank or repeated | "Function names must be nonempty and unique." |
| Two MCP servers with one label | "Execution requires distinct MCP server labels." |

A per-Session `tools` replacement admits a Session whose saved tools would be rejected. Support for each tool and Harness is in [execution and tools](./execution-tools.md).
Expand Down
6 changes: 3 additions & 3 deletions contracts/agents-api/zh/execution-tools.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "执行工具"
source: contracts/agents-api/execution-tools.md
source_hash: 7608c08f707c1970f1365d9b5d5f0979a71af35c4b9ca82d64ec6d2d95344a2a
source_hash: db52d727ddf370a8047089457227584e89faf2132546a164b170e6b475b28abb
---

Agent 在 `tools` 中声明应用函数、控制项和 MCP 服务器,并可在 `text.format` 中声明输出 schema。本契约说明 Core 如何验证声明、哪些内容跨越 Runtime 边界,以及调用方如何恢复待执行操作。[Harness 能力](harness-capabilities.md)列出各 Harness 在不同部署位置支持的操作。原生工作区工具和 Environment Plugin MCP 属于 [Environment](environments.md#skills-plugins-and-environment-mcp)。
Expand All @@ -15,7 +15,7 @@ Agent 在 `tools` 中声明应用函数、控制项和 MCP 服务器,并可在

## 函数 {#functions}

函数声明要求 `name`、`description` 和 `parameters` 中的 JSON Schema;`defer_loading` 默认为 false,不能为 null。名称必须非空白、唯一且最多 512 字节;每个 Session 最多有 64 个函数定义。保存的引用解析到 Session 快照,定义在原生准备和继续执行期间保持固定。
函数声明要求 `name`、`description` 和 `parameters` 中的 JSON Schema;`defer_loading` 默认为 false,不能为 null。名称必须非空白且唯一。保存的引用解析到 Session 快照,定义在原生准备和继续执行期间保持固定。

**结果。** 调用方提交 `agent.session.input.tool_result` 事件,包含 `turn_id`、`call_id`、`success`,以及可选且可空的 `error` 和 `output`。输出为字符串或有序文本与图像部分,受 Harness 支持范围约束。批次原子处理并保留字段存在性、原始内容和重试身份;公开结果 Item 与事件始终包含 `output` 和 `error`,未提交时为 null。

Expand Down Expand Up @@ -87,7 +87,7 @@ Core 发送 `PromptRequestPayload.ToolSearch` 和每个 `FunctionTool.DeferLoadi
}
```

- `server_label` 非空且在 Session 中唯一。仅接受 `http` 传输;`server_url` 为不带凭据、查询或片段的绝对 HTTP 或 HTTPS URL。非空 `headers`、`request_metadata` 和内联 `authorization` 被拒绝。
- `server_label` 非空且在 Session 中唯一。仅接受 `http` 传输;`server_url` 为不带凭据或片段的绝对 HTTP 或 HTTPS URL。非空 `headers`、`request_metadata` 和内联 `authorization` 被拒绝。
- [公开 MCP 连接来源](environments.md#public-mcp-connection-origin)定义来源默认值、部署位置和凭据权限;[Harness 能力](harness-capabilities.md#tools)定义各 Harness 支持范围。
- 省略或 null 的 `allowed_tools` 允许所有服务器工具;`[]` 不允许任何工具。
- `required: true` 使原生线程创建和冷恢复等待服务器初始化;失败会停止执行,不替换保留历史。它要求 Runtime 的 `mcp_http_required` 能力。等待期间公开工作可被接受或排队。
Expand Down
4 changes: 2 additions & 2 deletions contracts/agents-api/zh/wire-semantics.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "API 行为"
source: contracts/agents-api/wire-semantics.md
source_hash: c84b3d24f75bdc6fc608a7944bd0b23e7b9d6f969c12691667c72a5013ea59b9
source_hash: 204cc23703043518da5b2dfcadfb3521be7cce417cf80d0b4fe7356b4dfdfa02
---

已锁定版本的 OpenAI Python SDK([upstream.json](https://github.com/MiniMax-AI/OpenAgentCore/blob/main/contracts/agents-api/upstream.json))定义了 `/v1` 路由、字段和类型。本页面说明这些类型未作规定之处 Core 的行为,例如状态码、错误字段、默认值和列表边界,以及 Core 与官方服务存在差异的地方。[OpenAI 兼容性](index.md) 列出了这些差异和尚存缺口;[Sessions, events and history](sessions-events.md)、[message content](message-content.md)、[Vaults](vaults.md)、[source Files and Skills](source-files.md) 和 [Environment files and Artifacts](environment-files.md) 分别负责各自资源的规则。
Expand Down Expand Up @@ -188,7 +188,7 @@ Session 的有效配置还必须通过执行准入,无论配置来自保存还
| 配置 | 消息 |
| --- | --- |
| Harness 未声明的 `reasoning.effort`、`reasoning.summary`、`service_tier`、`web_search` 模式或设置、`programmatic_tool_calling` 取值或 `text.verbosity` | 指出字段的消息,例如 `reasoning.effort "max" is not supported by this Harness.`;见 [tool policy](execution-tools.md#web-search-and-programmatic-tool-calling) 和 [Harness 能力](harness-capabilities.md#execution-and-input) |
| 超过 64 个函数,或函数名称为空或超过 512 字节 | "This service supports at most 64 function tools." 或 "Function names must be nonempty, unique and at most 512 bytes." |
| 函数名称为空或重复 | "Function names must be nonempty and unique." |
| 两个使用同一标签的 MCP 服务器 | "Execution requires distinct MCP server labels." |

每 Session 的 `tools` 替换可以允许一个其已保存 tools 原本会被拒绝的 Session。每种工具和 Harness 的支持情况见 [execution and tools](execution-tools.md)。
Expand Down
2 changes: 1 addition & 1 deletion internal/harnessconfig/claudesdk/declaration.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ func validateConfiguration(admission harnessconfig.Admission) error {
// resolution still owns URL, transport and credential-binding validation.
func validateTools(tools []proto.FunctionTool, servers []proto.MCPHTTPServer) error {
for _, server := range servers {
if !mcpLabel.MatchString(server.ServerLabel) || server.ServerLabel == "functions" || strings.ContainsAny(server.ServerURL, "?#") {
if !mcpLabel.MatchString(server.ServerLabel) || server.ServerLabel == "functions" || strings.Contains(server.ServerURL, "#") {
return errors.New("The configured engine does not support this HTTP MCP declaration.")
}
if server.AllowedTools != nil {
Expand Down
2 changes: 1 addition & 1 deletion packages/claude-sdk-adapter/src/mcp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function parseHTTPServers(value: unknown): HTTPServer[] | undefined {
}
const url = new URL(server.server_url);
if (!["http:", "https:"].includes(url.protocol) || !url.hostname || url.username || url.password ||
server.server_url.includes("?") || server.server_url.includes("#")) throw new Error("invalid_request");
server.server_url.includes("#")) throw new Error("invalid_request");
if (server.bearer_token_env_var !== undefined) {
const reference = server.bearer_token_env_var;
if (url.protocol !== "https:" || typeof reference !== "string" ||
Expand Down
4 changes: 3 additions & 1 deletion packages/claude-sdk-adapter/tests/mcp.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@ test("invalid remote declarations and wildcard injection fail at the bridge boun
const start = { type: "start", input: [{ content: [{ type: "input_text", text: "hello" }] }], model: "model", system_prompt: "", cwd: "/tmp" };
for (const value of [null, {}, [declaration(["*"])], [{ ...declaration(null), server_label: "functions" }],
[{ ...declaration(null), server_url: "https://user:secret@example.invalid/mcp" }],
[{ ...declaration(null), server_url: "https://example.invalid/mcp?" }],
[{ ...declaration(null), server_url: "https://example.invalid/mcp#fragment" }],
[{ ...declaration(null), required: "true" }], [{ ...declaration(null), required: null }], [declaration(null), declaration([])]]) {
assert.throws(() => parseStart(JSON.stringify({ ...start, mcp_http_servers: value })));
}
assert.deepEqual(parseStart(JSON.stringify({ ...start, mcp_http_servers: [declaration(null), { ...declaration([]), server_label: "empty" }] })).mcp_http_servers,
[declaration(null), { ...declaration([]), server_label: "empty" }]);
const withQuery = { ...declaration(null), server_url: "https://example.invalid/mcp?tenant=a" };
assert.deepEqual(parseStart(JSON.stringify({ ...start, mcp_http_servers: [withQuery] })).mcp_http_servers, [withQuery]);
});

test("native tool spelling preserves original identity and rejects ambiguous aliases", () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ func TestAgentConfigurationLocalLimitsKeepCodes(t *testing.T) {
for _, tc := range []struct{ fields, message string }{
{`"tools":[{"type":"programmatic_tool_calling","enabled":false},{"type":"programmatic_tool_calling","enabled":false}]`, "Execution requires distinct tool controls."},
{`"multi_agent":{"enabled":true},"tools":[{"type":"programmatic_tool_calling","enabled":true}]`, "Harness codex does not support the requested Agent/environment configuration: programmatic_tool_calling.enabled=true is not supported with multi_agent by this Harness."},
{`"tools":[{"type":"function","name":"","description":"","parameters":{}}]`, "Function names must be nonempty, unique and at most 512 bytes."},
{`"tools":[{"type":"function","name":"","description":"","parameters":{}}]`, "Function names must be nonempty and unique."},
} {
assertConfigurationError(t, credentialRequest(h, http.MethodPost, session.path, session.body(tc.fields)), "unsupported_or_invalid_configuration", nil, tc.message)
}
Expand Down
7 changes: 2 additions & 5 deletions services/core/internal/api/function_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,10 @@ import (

func resolveFunctions(input []v1.FunctionToolInput) ([]json.RawMessage, error) {
tools := make([]json.RawMessage, 0, len(input))
if len(input) > 64 {
return nil, errors.New("This service supports at most 64 function tools.")
}
names := make(map[string]bool, len(input))
for _, tool := range input {
if strings.TrimSpace(tool.Name) == "" || len(tool.Name) > 512 || names[tool.Name] {
return nil, errors.New("Function names must be nonempty, unique and at most 512 bytes.")
if strings.TrimSpace(tool.Name) == "" || names[tool.Name] {
return nil, errors.New("Function names must be nonempty and unique.")
}
value, err := resolveFunction(tool)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions services/core/internal/api/mcp_configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ func resolveMCPTool(raw json.RawMessage, saved bool) (json.RawMessage, error) {
return nil, errors.New("Nonempty MCP request_metadata is not supported yet.")
}
u, err := url.Parse(transport.ServerURL)
if err != nil || u.Hostname() == "" || (u.Scheme != "http" && u.Scheme != "https") || u.User != nil || u.Fragment != "" || u.RawQuery != "" || u.ForceQuery {
return nil, errors.New("MCP server_url must be an absolute HTTP(S) URL without credentials, query or fragment.")
if err != nil || u.Hostname() == "" || (u.Scheme != "http" && u.Scheme != "https") || u.User != nil || u.Fragment != "" {
return nil, errors.New("MCP server_url must be an absolute HTTP(S) URL without credentials or fragment.")
}
if transport.Authorization != nil {
return nil, errors.New("Inline MCP authorization is not supported yet.")
Expand Down
Loading
Loading