MCP

MCP connects A3S Code sessions to external tool servers. A stdio server can be added to a live session, inspected, called through its registered tools, and removed. Tools from a server are registered into the session and named mcp__<server>__<tool>.

Add A Server

Use the object-shaped addMcp(...) API for new code. It maps directly to the core McpServerConfig shape and avoids the positional overload's argument ordering.

Rust
Node.js
Python
Go
Rust
use a3s_code_core::mcp::{McpServerConfig, McpTransportConfig};
use std::collections::HashMap;
let count = session
.add_mcp_server(McpServerConfig {
name: "echo".into(),
transport: McpTransportConfig::Stdio {
command: "node".into(),
args: vec![
"tools/mcp_echo_server.mjs".into(),
"example-value".into(),
],
},
enabled: true,
env: HashMap::new(),
oauth: None,
tool_timeout_secs: 30,
})
.await?;
println!("registered tools: {count}");

Python exposes the object-shaped API as session.add_mcp({...}). addMcpServer(...) / add_mcp_server(...) and addMcpServerConfig(...) / add_mcp_server_config(...) remain compatibility aliases.

For remote servers, use a nested transport object with type: 'http' or type: 'streamable-http'. Supply credentials from the host environment or a secret manager, and validate the server before relying on it in production docs or release notes.

Inspect And Remove

Rust
Node.js
Python
Go
Rust
use serde_json::json;
println!("{:#?}", session.mcp_status().await);
println!(
"{:#?}",
session
.tool_names()
.into_iter()
.filter(|name| name.starts_with("mcp__"))
.collect::<Vec<_>>()
);
let result = session
.tool("mcp__echo__echo", json!({ "message": "docs mcp ok" }))
.await?;
println!("{}", result.output);
session.remove_mcp_server("echo").await?;

Ownership And Session Isolation

MCP capability discovery and live mutation have different owners:

  • The agent-global manager owns servers loaded from global configuration.
  • A manager supplied through Rust SessionOptions is an inherited, read-only capability source for that session.
  • Every built session owns a new private manager for live addMcp and removeMcpServer operations.

Session assembly reads tool definitions from inherited sources without merging session configuration back into those managers. A locally added server can override an inherited server with the same fully qualified tool names, but only inside that session. Removing the local server reveals the inherited tools again. It does not unregister, disconnect, or otherwise mutate the global or host-owned source, and sibling sessions remain unchanged.

Delegated child agents receive the ordered capability sources so they can call the same MCP tools without taking ownership. session.close() disconnects only the private manager's servers. agent.close() and disconnectIdleMcp(...) remain responsible for the agent-global manager.

For Rust, a host-supplied MCP source requires async session construction so discovery happens without blocking:

Rust
let session = agent
.session_builder("/repo")
.options(SessionOptions::new().with_mcp(manager))
.build()
.await?;

Idle Disconnect

A connected stdio MCP server holds resources — file descriptors and a background worker — even while it sits idle. In a long-lived cluster session those quiet servers accumulate. disconnectIdleMcp reaps them on demand: it disconnects every global MCP server whose last activity is older than the threshold (or that has no recorded activity), releasing the FDs and workers, while keeping each server's registered config.

This is an agent-level method (it operates only on the agent's global MCP manager, backed by McpManager::disconnect_idle(threshold_ms)). It returns the list of disconnected server names.

TypeScript
// Reap servers idle longer than 5 minutes. Returns disconnected names.
const dropped = await agent.disconnectIdleMcp(5 * 60 * 1000);
console.log('disconnected:', dropped);
Python
# Reap servers idle longer than 5 minutes. Returns disconnected names.
dropped = agent.disconnect_idle_mcp(5 * 60 * 1000)
print('disconnected:', dropped)
Go
// Reap servers idle longer than 5 minutes. Returns disconnected names.
dropped, err := agent.DisconnectIdleMCP(ctx, 5*60*1000)
if err != nil {
return err
}
fmt.Println("disconnected:", dropped)

Rust hosts call Agent::disconnect_idle_mcp(threshold_ms) directly.

Hosts running thousands of long-lived sessions should call this periodically from a sweeper (e.g. every 60s with a 5-min threshold). Disconnect does not reconnect lazily: a later tool call on a disconnected server fails with MCP server not connected: <name> until the host reconnects it. Calling syncGlobalMcpServers(...) / sync_global_mcp_servers(...) / SyncGlobalMCPServers(...) with the full server list reconnects every enabled server that is not connected; entries omitted from the list are removed.

Disconnect also purges orphan timestamps: a touch()-without-connect entry (recorded for a server that was never actually connected) is swept on each disconnect_idle call so the activity map cannot grow unbounded over a long-running manager's lifetime.

Security

Treat external tool servers as privileged integrations and keep secrets in environment variables.