Support per-server MCP OAuth callback ports (#38448)

## What changed

- Add `oauth.callback_port` to MCP server configuration and preserve it when
  serializing configuration edits.
- Accept `oauth.callbackPort` from plugin MCP declarations and skill dependency
  metadata.
- Prefer the server-specific callback port over `mcp_oauth_callback_port` for
  CLI login, app-server, plugin installation, executor, and skill dependency
  OAuth flows.

## Testing

- Cover configuration parsing, serialization, and fallback behavior.
- Verify plugin, executor, and skill OAuth registrations use their configured
  callback ports instead of the global port.

GitOrigin-RevId: 7f65e5e7869358307e49779f1b75e8672b607736
This commit is contained in:
willwang-openai authored and copyberry committed 2026-08-13 22:42:12 +00:00
1 parent ca83f7908c
commit 1da59ad257
22 files changed
+205 -37

No files matched your search

@@ -76,6 +76,7 @@ fn connector_dependencies_make_generic_skills_discoverable() {
transport: None,
command: None,
url: None,
oauth_callback_port: None,
}],
};
let documents = [
@@ -14,6 +14,7 @@ fn dependency_names_outrank_description_only_matches() {
transport: None,
command: None,
url: None,
oauth_callback_port: None,
}],
};
let documents = [
@@ -80,6 +81,7 @@ fn dependency_records_are_bounded() {
transport: None,
command: None,
url: None,
oauth_callback_port: None,
})
.collect::<Vec<_>>();
tools.push(SkillToolDependency {
@@ -89,6 +91,7 @@ fn dependency_records_are_bounded() {
transport: None,
command: None,
url: None,
oauth_callback_port: None,
});
let dependencies = SkillDependencies { tools };
let document = SkillSelectionDocument {
@@ -68,6 +68,7 @@ policy:
transport: Some("stdio".to_string()),
command: Some("deploy-mcp".to_string()),
url: None,
oauth_callback_port: None,
}],
}),
policy: Some(SkillPolicy {
@@ -117,6 +117,8 @@ async fn loads_host_frontmatter_dependencies_and_policy() {
- type: mcp
value: demo-tool
description: Demo tool
oauth:
callbackPort: 3118
policy:
allow_implicit_invocation: false
products: [codex, CHATGPT, atlas]
@@ -141,6 +143,7 @@ policy:
transport: None,
command: None,
url: None,
oauth_callback_port: Some(3118),
}],
}),
policy: Some(SkillPolicy {
@@ -62,6 +62,14 @@ struct DependencyTool {
transport: Option<String>,
command: Option<String>,
url: Option<String>,
oauth: Option<DependencyOAuth>,
}
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct DependencyOAuth {
#[serde(alias = "callback_port")]
callback_port: Option<u16>,
}
pub(super) async fn load_host_skill_metadata(
@@ -186,6 +194,7 @@ fn resolve_dependency_tool(tool: DependencyTool) -> Option<SkillToolDependency>
"dependencies.tools.command",
);
let url = resolve_str(tool.url, MAX_DEPENDENCY_URL_LEN, "dependencies.tools.url");
let oauth_callback_port = tool.oauth.and_then(|oauth| oauth.callback_port);
Some(SkillToolDependency {
r#type,
@@ -194,6 +203,7 @@ fn resolve_dependency_tool(tool: DependencyTool) -> Option<SkillToolDependency>
transport,
command,
url,
oauth_callback_port,
})
}