Rename host MCP to workspace MCP for clarity
- Rename binary from host-mcp to workspace-mcp - Rename src/bin/host_mcp.rs to src/bin/workspace_mcp.rs - Update tool prefix from host_* to workspace_* - Update MCP registration name from "host" to "workspace" - Add "Builtin" tag to UI for workspace and desktop MCPs - Update documentation (CLAUDE.md, INSTALL.md, docs-site) The "workspace MCP" name better reflects that it runs in the workspace's execution context - inside containers for container workspaces, on host for host workspaces.
This commit is contained in:
@@ -22,6 +22,21 @@ Open Agent is a managed control plane for OpenCode-based agents. The backend **d
|
||||
|
||||
MCPs can be global because and run as child processes on the host or workspace (run inside the container). It depends on the kind of MCP.
|
||||
|
||||
## Container Execution Model (Important!)
|
||||
|
||||
When a **mission runs in a container workspace**, bash commands execute **inside the container**, not on the host. Here's why:
|
||||
|
||||
1. OpenCode's built-in Bash tool is **disabled** for container workspaces
|
||||
2. Agents use `workspace_bash` from the "workspace MCP" instead
|
||||
3. The "workspace MCP" command is **wrapped in systemd-nspawn** at startup
|
||||
4. Therefore all `workspace_bash` commands run inside the container with container networking
|
||||
|
||||
The "workspace MCP" is named this way because it runs in the **workspace's execution context** - for container workspaces, that means inside the container.
|
||||
|
||||
See `src/workspace.rs` lines 590-640 (nspawn wrapping) and 714-720 (tool configuration).
|
||||
|
||||
**Contrast with workspace exec API**: The `/api/workspaces/:id/exec` endpoint also runs commands inside containers (via nspawn), but is subject to HTTP timeouts. The mission system uses SSE streaming with no timeout.
|
||||
|
||||
## Design Guardrails
|
||||
|
||||
- Do **not** reintroduce autonomous agent logic (budgeting, task splitting, verification, model selection). OpenCode handles execution.
|
||||
|
||||
@@ -79,8 +79,8 @@ name = "desktop-mcp"
|
||||
path = "src/bin/desktop_mcp.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "host-mcp"
|
||||
path = "src/bin/host_mcp.rs"
|
||||
name = "workspace-mcp"
|
||||
path = "src/bin/workspace_mcp.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio-test = "0.4"
|
||||
|
||||
12
INSTALL.md
12
INSTALL.md
@@ -349,15 +349,15 @@ cd /opt/open_agent/vaduz-v1
|
||||
source /root/.cargo/env
|
||||
|
||||
# Debug build (fast) - recommended for rapid iteration
|
||||
cargo build --bin open_agent --bin host-mcp --bin desktop-mcp
|
||||
cargo build --bin open_agent --bin workspace-mcp --bin desktop-mcp
|
||||
install -m 0755 target/debug/open_agent /usr/local/bin/open_agent
|
||||
install -m 0755 target/debug/host-mcp /usr/local/bin/host-mcp
|
||||
install -m 0755 target/debug/workspace-mcp /usr/local/bin/workspace-mcp
|
||||
install -m 0755 target/debug/desktop-mcp /usr/local/bin/desktop-mcp
|
||||
|
||||
# Or: Release build (slower compile, faster runtime)
|
||||
# cargo build --release --bin open_agent --bin host-mcp --bin desktop-mcp
|
||||
# cargo build --release --bin open_agent --bin workspace-mcp --bin desktop-mcp
|
||||
# install -m 0755 target/release/open_agent /usr/local/bin/open_agent
|
||||
# install -m 0755 target/release/host-mcp /usr/local/bin/host-mcp
|
||||
# install -m 0755 target/release/workspace-mcp /usr/local/bin/workspace-mcp
|
||||
# install -m 0755 target/release/desktop-mcp /usr/local/bin/desktop-mcp
|
||||
```
|
||||
|
||||
@@ -611,9 +611,9 @@ cd /opt/open_agent/vaduz-v1
|
||||
git fetch --tags origin
|
||||
git checkout <version-tag> # e.g., v0.2.1
|
||||
source /root/.cargo/env
|
||||
cargo build --bin open_agent --bin host-mcp --bin desktop-mcp
|
||||
cargo build --bin open_agent --bin workspace-mcp --bin desktop-mcp
|
||||
install -m 0755 target/debug/open_agent /usr/local/bin/open_agent
|
||||
install -m 0755 target/debug/host-mcp /usr/local/bin/host-mcp
|
||||
install -m 0755 target/debug/workspace-mcp /usr/local/bin/workspace-mcp
|
||||
install -m 0755 target/debug/desktop-mcp /usr/local/bin/desktop-mcp
|
||||
systemctl restart open_agent.service
|
||||
```
|
||||
|
||||
@@ -301,6 +301,9 @@ function RuntimeMcpCard({
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="font-medium text-white truncate">{mcp.name}</h3>
|
||||
<span className="tag bg-cyan-500/10 text-cyan-400 border-cyan-500/20">Runtime</span>
|
||||
{(mcp.name === 'workspace' || mcp.name === 'desktop') && (
|
||||
<span className="tag bg-violet-500/10 text-violet-400 border-violet-500/20">Builtin</span>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
'tag',
|
||||
@@ -527,6 +530,9 @@ function RuntimeMcpDetailPanel({
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-lg font-semibold text-white">{mcp.name}</h2>
|
||||
<span className="tag bg-cyan-500/10 text-cyan-400 border-cyan-500/20">Runtime</span>
|
||||
{(mcp.name === 'workspace' || mcp.name === 'desktop') && (
|
||||
<span className="tag bg-violet-500/10 text-violet-400 border-violet-500/20">Builtin</span>
|
||||
)}
|
||||
<span
|
||||
className={cn(
|
||||
'tag',
|
||||
|
||||
@@ -68,6 +68,16 @@ nav.nextra-nav-container {
|
||||
|
||||
main.nextra-content {
|
||||
background-color: rgb(var(--background));
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Ensure no rounded corners on any layout containers */
|
||||
main.nextra-content,
|
||||
.nextra-main-content,
|
||||
[class*="main-content"],
|
||||
article {
|
||||
border-radius: 0 !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
}
|
||||
|
||||
.nextra-toc {
|
||||
@@ -824,6 +834,54 @@ aside a:hover,
|
||||
background-color: rgba(255, 248, 240, 0.04);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
SIDEBAR BOTTOM-LEFT CORNER FIX
|
||||
============================================ */
|
||||
|
||||
/* Fix the rounded corner artifact in the bottom-left of the sidebar */
|
||||
.nextra-sidebar-container,
|
||||
aside.nextra-sidebar-container,
|
||||
[class*="sidebar-container"],
|
||||
aside[class*="nextra"] {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Target the theme switcher container at the bottom of the sidebar */
|
||||
.nextra-sidebar-container > div:last-child,
|
||||
aside > div:last-child,
|
||||
aside > div:last-of-type {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Ensure the sidebar footer area has no rounded corners */
|
||||
[class*="sidebar"] [class*="footer"],
|
||||
[class*="sidebar"] > :last-child,
|
||||
aside [class*="theme"],
|
||||
aside [class*="toggle"] {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Target any nested containers that might have rounded corners */
|
||||
.nextra-sidebar-container *,
|
||||
aside.nextra-sidebar-container * {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Specifically remove the bottom-left radius from the sidebar wrapper */
|
||||
.nextra-sidebar-container > div,
|
||||
aside > div {
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Fix any pseudo-elements that might create the corner effect */
|
||||
.nextra-sidebar-container::after,
|
||||
.nextra-sidebar-container::before,
|
||||
aside::after,
|
||||
aside::before {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
MOBILE SIDEBAR FIXES
|
||||
============================================ */
|
||||
|
||||
@@ -64,9 +64,9 @@ cd /opt/open_agent
|
||||
git clone https://github.com/Th0rgal/open-agent vaduz-v1
|
||||
cd vaduz-v1
|
||||
|
||||
cargo build --bin open_agent --bin host-mcp --bin desktop-mcp
|
||||
cargo build --bin open_agent --bin workspace-mcp --bin desktop-mcp
|
||||
install -m 0755 target/debug/open_agent /usr/local/bin/open_agent
|
||||
install -m 0755 target/debug/host-mcp /usr/local/bin/host-mcp
|
||||
install -m 0755 target/debug/workspace-mcp /usr/local/bin/workspace-mcp
|
||||
install -m 0755 target/debug/desktop-mcp /usr/local/bin/desktop-mcp
|
||||
```
|
||||
|
||||
|
||||
@@ -576,7 +576,7 @@ fn stream_open_agent_update() -> impl Stream<Item = Result<Event, std::convert::
|
||||
|
||||
// Source cargo env and build
|
||||
let build_result = Command::new("bash")
|
||||
.args(["-c", "source /root/.cargo/env && cargo build --bin open_agent --bin host-mcp --bin desktop-mcp"])
|
||||
.args(["-c", "source /root/.cargo/env && cargo build --bin open_agent --bin workspace-mcp --bin desktop-mcp"])
|
||||
.current_dir(OPEN_AGENT_REPO_PATH)
|
||||
.output()
|
||||
.await;
|
||||
@@ -620,7 +620,7 @@ fn stream_open_agent_update() -> impl Stream<Item = Result<Event, std::convert::
|
||||
|
||||
let binaries = [
|
||||
("open_agent", "/usr/local/bin/open_agent"),
|
||||
("host-mcp", "/usr/local/bin/host-mcp"),
|
||||
("workspace-mcp", "/usr/local/bin/workspace-mcp"),
|
||||
("desktop-mcp", "/usr/local/bin/desktop-mcp"),
|
||||
];
|
||||
|
||||
|
||||
@@ -366,11 +366,11 @@ fn debug_log(tag: &str, payload: &Value) {
|
||||
if std::env::var("OPEN_AGENT_MCP_DEBUG").ok().as_deref() != Some("1") {
|
||||
return;
|
||||
}
|
||||
let line = format!("[host-mcp] {} {}\n", tag, payload);
|
||||
let line = format!("[workspace-mcp] {} {}\n", tag, payload);
|
||||
if let Ok(mut file) = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("/tmp/host-mcp-debug.log")
|
||||
.open("/tmp/workspace-mcp-debug.log")
|
||||
{
|
||||
let _ = file.write_all(line.as_bytes());
|
||||
}
|
||||
@@ -596,7 +596,7 @@ fn handle_request(
|
||||
json!({
|
||||
"protocolVersion": "2024-11-05",
|
||||
"serverInfo": {
|
||||
"name": "host-mcp",
|
||||
"name": "workspace-mcp",
|
||||
"version": env!("CARGO_PKG_VERSION"),
|
||||
},
|
||||
"capabilities": {
|
||||
@@ -644,7 +644,7 @@ fn handle_request(
|
||||
}
|
||||
|
||||
fn main() {
|
||||
eprintln!("[host-mcp] Starting MCP server for host tools...");
|
||||
eprintln!("[workspace-mcp] Starting MCP server for workspace tools...");
|
||||
|
||||
let runtime = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
@@ -158,24 +158,24 @@ impl McpRegistry {
|
||||
);
|
||||
desktop.scope = McpScope::Workspace;
|
||||
|
||||
let host_command = {
|
||||
let release = working_dir.join("target").join("release").join("host-mcp");
|
||||
let debug = working_dir.join("target").join("debug").join("host-mcp");
|
||||
let workspace_command = {
|
||||
let release = working_dir.join("target").join("release").join("workspace-mcp");
|
||||
let debug = working_dir.join("target").join("debug").join("workspace-mcp");
|
||||
if release.exists() {
|
||||
release.to_string_lossy().to_string()
|
||||
} else if debug.exists() {
|
||||
debug.to_string_lossy().to_string()
|
||||
} else {
|
||||
"host-mcp".to_string()
|
||||
"workspace-mcp".to_string()
|
||||
}
|
||||
};
|
||||
let mut host = McpServerConfig::new_stdio(
|
||||
"host".to_string(),
|
||||
host_command,
|
||||
let mut workspace = McpServerConfig::new_stdio(
|
||||
"workspace".to_string(),
|
||||
workspace_command,
|
||||
Vec::new(),
|
||||
HashMap::new(),
|
||||
);
|
||||
host.scope = McpScope::Workspace;
|
||||
workspace.scope = McpScope::Workspace;
|
||||
// Prefer bunx (Bun) when present, but fall back to npx for compatibility.
|
||||
let js_runner = if command_exists("bunx") {
|
||||
"bunx"
|
||||
@@ -194,7 +194,7 @@ impl McpRegistry {
|
||||
);
|
||||
playwright.scope = McpScope::Workspace;
|
||||
|
||||
vec![host, desktop, playwright]
|
||||
vec![workspace, desktop, playwright]
|
||||
}
|
||||
|
||||
async fn ensure_defaults(
|
||||
@@ -274,11 +274,11 @@ impl McpRegistry {
|
||||
.await;
|
||||
}
|
||||
|
||||
// Prefer repo-local MCP binaries for host/desktop (debug or release),
|
||||
// Prefer repo-local MCP binaries for workspace/desktop (debug or release),
|
||||
// so default configs work without installing to PATH.
|
||||
for config in configs.iter_mut() {
|
||||
let binary_name = match config.name.as_str() {
|
||||
"host" => Some("host-mcp"),
|
||||
"workspace" => Some("workspace-mcp"),
|
||||
"desktop" => Some("desktop-mcp"),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
@@ -351,7 +351,7 @@ pub async fn ensure_global_config(mcp: &McpRegistry) -> anyhow::Result<()> {
|
||||
tools_obj.insert("desktop_*".to_string(), json!(true));
|
||||
tools_obj.insert("playwright_*".to_string(), json!(true));
|
||||
tools_obj.insert("browser_*".to_string(), json!(true));
|
||||
tools_obj.insert("host_*".to_string(), json!(true));
|
||||
tools_obj.insert("workspace_*".to_string(), json!(true));
|
||||
|
||||
let payload = serde_json::to_string_pretty(&root)?;
|
||||
tokio::fs::write(&config_path, payload).await?;
|
||||
|
||||
@@ -711,19 +711,22 @@ async fn write_opencode_config(
|
||||
}
|
||||
}
|
||||
|
||||
// Disable OpenCode's builtin bash tools so agents must use the host MCP's bash.
|
||||
// For container (nspawn) workspaces, the host MCP runs INSIDE the container via
|
||||
// systemd-nspawn wrapping, so its bash tool has container networking (Tailscale, etc).
|
||||
// Disable OpenCode's builtin bash tools so agents must use the workspace MCP's bash.
|
||||
//
|
||||
// The "workspace MCP" is the MCP provided by Open Agent that runs in the workspace's
|
||||
// execution context. For container (nspawn) workspaces, this MCP runs INSIDE the
|
||||
// container via systemd-nspawn wrapping (see lines 590-640), so its bash tool executes
|
||||
// commands inside the container with container networking (Tailscale, etc).
|
||||
// For host workspaces, disable bash entirely (security: no host shell access).
|
||||
let mut tools = serde_json::Map::new();
|
||||
match workspace_type {
|
||||
WorkspaceType::Chroot => {
|
||||
// Disable OpenCode built-in bash - agents must use host MCP's bash
|
||||
// Disable OpenCode built-in bash - agents must use workspace MCP's bash
|
||||
// which runs inside the container with container networking
|
||||
tools.insert("Bash".to_string(), json!(false)); // Claude Code built-in
|
||||
tools.insert("bash".to_string(), json!(false)); // lowercase variant
|
||||
// Enable MCP-provided tools (host MCP runs inside container via nspawn)
|
||||
tools.insert("host_*".to_string(), json!(true));
|
||||
// Enable MCP-provided tools (workspace MCP runs inside container via nspawn)
|
||||
tools.insert("workspace_*".to_string(), json!(true));
|
||||
tools.insert("desktop_*".to_string(), json!(true));
|
||||
tools.insert("playwright_*".to_string(), json!(true));
|
||||
tools.insert("browser_*".to_string(), json!(true));
|
||||
@@ -735,8 +738,8 @@ async fn write_opencode_config(
|
||||
tools.insert("desktop_*".to_string(), json!(false));
|
||||
tools.insert("playwright_*".to_string(), json!(false));
|
||||
tools.insert("browser_*".to_string(), json!(false));
|
||||
// Only allow host MCP tools (files, etc)
|
||||
tools.insert("host_*".to_string(), json!(true));
|
||||
// Only allow workspace MCP tools (files, etc)
|
||||
tools.insert("workspace_*".to_string(), json!(true));
|
||||
}
|
||||
}
|
||||
config_json.insert("tools".to_string(), serde_json::Value::Object(tools));
|
||||
@@ -1648,7 +1651,7 @@ async fn sync_workspace_mcp_binaries(
|
||||
working_dir: &Path,
|
||||
container_root: &Path,
|
||||
) -> anyhow::Result<()> {
|
||||
for binary in ["host-mcp", "desktop-mcp"] {
|
||||
for binary in ["workspace-mcp", "desktop-mcp"] {
|
||||
copy_binary_into_container(working_dir, container_root, binary).await?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user