/**
* agent_mcp_bootstrap_if_needed.
*
* @effects: [host, mcp]
* @errors: []
* @api_stability: experimental
*/
pub fn agent_mcp_bootstrap_if_needed(session, opts) {
let specs = opts?.mcp_servers
if specs == nil || len(specs) == 0 {
return opts
}
return __mcp_bootstrap_and_admit(session, opts, specs)
}
/**
* agent_mcp_mount_additional.
*
* Mount MCP servers declared by a skill that activated mid-conversation.
* Only servers whose name is not already mounted (tracked via the
* `_mcp_server_info` running list the bootstrap path appends to) are
* bootstrapped, so a live server is never re-connected and its tools are
* never duplicated in the catalog or ceiling. `__host_mcp_bootstrap`
* replaces the session's MCP client map on every call, so re-bootstrapping
* the full set would drop live handles — mounting ONLY the delta keeps
* every previously mounted server intact.
*
* The delta bootstrap result is stashed on the returned opts as
* `_mcp_delta_bootstrap` (nil when nothing new mounted) so the caller can
* admit the same tools into the current turn's options with
* `agent_mcp_admit_bootstrap` — no second host round-trip.
*
* @effects: [host, mcp]
* @errors: []
* @api_stability: experimental
*/
pub fn agent_mcp_mount_additional(session, opts, requested_specs) {
let delta = __mcp_delta_specs(opts, requested_specs ?? [])
if len(delta) == 0 {
return opts + {_mcp_delta_bootstrap: nil}
}
let mounted = __mcp_bootstrap_and_admit(session, opts, delta)
return mounted + {_mcp_delta_bootstrap: mounted?._mcp_bootstrap}
}
/**
* agent_mcp_admit_bootstrap.
*
* Admit an already-computed `__host_mcp_bootstrap` result's tools into a
* second opts view (catalog merge + ceiling extension) without a fresh
* host call. Lets a mid-conversation mount thread the new tools into the
* current turn's options as well as the persistent loop options.
*
* @effects: []
* @errors: []
* @api_stability: experimental
*/
pub fn agent_mcp_admit_bootstrap(opts, result) {
if type_of(result) != "dict" {
return opts
}
return __mcp_admit_tools(opts, result)
}
/** Bootstrap `specs` and admit their tools into `opts` (catalog + ceiling). */
fn __mcp_bootstrap_and_admit(session, opts, specs) {
let result = __host_mcp_bootstrap(session.session_id, specs)
let prior_info = opts?._mcp_server_info ?? []
let out = opts + {_mcp_bootstrap: result, _mcp_server_info: prior_info + (result?.server_info ?? [])}
return __mcp_admit_tools(out, result)
}
/**
* Merge a bootstrap result's `tools_added` into the catalog (deduping by
* dispatch name) and extend the execution-policy tool ceiling so the tools
* are visible AND callable. Idempotent: a tool already present is skipped.
*/
fn __mcp_admit_tools(opts, result) {
let tools_added = result?.tools_added ?? []
if len(tools_added) == 0 {
return opts
}
let existing = opts?.tools
let existing_tools = if existing?._type == "tool_registry" {
existing?.tools ?? []
} else {
[]
}
let reg = {_type: "tool_registry", tools: __mcp_merge_tool_lists(existing_tools, tools_added)}
return __with_mcp_tool_ceiling(opts + {tools: reg}, tools_added)
}
/** Append `added` tools to `existing`, skipping any whose name is present. */
fn __mcp_merge_tool_lists(existing, added) {
var out = existing
var names = []
for entry in existing {
let name = __mcp_tool_ceiling_name(entry)
if name != "" {
names = names + [name]
}
}
for entry in added {
let name = __mcp_tool_ceiling_name(entry)
if name != "" && names.contains(name) {
continue
}
out = out + [entry]
if name != "" {
names = names + [name]
}
}
return out
}
/** Spec entries whose server name is neither already mounted nor a dup. */
fn __mcp_delta_specs(opts, specs) {
let mounted = __mcp_mounted_server_ids(opts)
var delta = []
var seen = []
for spec in specs {
let name = to_string(spec?.name ?? "")
if name == "" || mounted.contains(name) || seen.contains(name) {
continue
}
delta = delta + [spec]
seen = seen + [name]
}
return delta
}
/** Server names already mounted this run, from the running `_mcp_server_info`. */
fn __mcp_mounted_server_ids(opts) {
var ids = []
for info in opts?._mcp_server_info ?? [] {
let name = to_string(info?.name ?? "")
if name != "" && !ids.contains(name) {
ids = ids + [name]
}
}
return ids
}
/**
* Admit the just-bootstrapped MCP tools into the execution-policy tool
* ceiling so the agent that can SEE them in its catalog can also CALL them.
*
* The catalog merge above adds the `server__tool` entries to `opts.tools`,
* which is what makes the model aware of them; without a matching entry in
* `opts.policy.tools` (the ceiling enforced by
* `enforce_current_policy_for_tool`) every dispatch is denied with
* "exceeds tool ceiling". An MCP tool is a runtime projection — its name is
* not known until `tools/list` returns — so the ceiling can only learn it
* here, at boot time, not from the host's static surface declaration.
*
* An EMPTY `policy.tools` means "no ceiling" (allow any tool), so we only
* extend a non-empty ceiling: adding names to an empty list would flip an
* open policy into a closed one and silently forbid the host's own tools.
* Idempotent: never double-adds a name already present.
*/
fn __with_mcp_tool_ceiling(opts, tools_added) {
let policy = opts?.policy
if type_of(policy) != "dict" {
return opts
}
let ceiling = policy?.tools
if type_of(ceiling) != "list" || len(ceiling) == 0 {
return opts
}
var names = []
for entry in tools_added {
let name = __mcp_tool_ceiling_name(entry)
if name != "" && !ceiling.contains(name) && !names.contains(name) {
names = names + [name]
}
}
if len(names) == 0 {
return opts
}
return opts + {policy: policy + {tools: ceiling + names}}
}
/** Resolve a bootstrapped tool entry's dispatch name (flat or OpenAI-shape). */
fn __mcp_tool_ceiling_name(entry) {
if type_of(entry) != "dict" {
return ""
}
let flat = to_string(entry?.name ?? "")
if flat != "" {
return flat
}
return to_string(entry?.function?.name ?? "")
}