You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/architecture.md
+152Lines changed: 152 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,3 +54,155 @@ graph TB
54
54
style Row1 fill:none,stroke:none
55
55
style Row2 fill:none,stroke:none
56
56
```
57
+
58
+
## Where Each Component Runs
59
+
60
+
Understanding where the **client**, **MCP server**, and **target Linux system** run is helpful for deployment and development alike.
61
+
62
+
| Component | Location | Description |
63
+
|-----------|----------|-------------|
64
+
|**Client**| User's machine | The MCP client (Cursor, Claude Desktop, Goose, etc.) runs on the machine where the user works. It spawns the Linux MCP Server as a **subprocess** and communicates over **stdio** (stdin/stdout) using the MCP protocol (JSON-RPC). |
65
+
|**MCP Server**| Same host as client, or in a container | The server process is started by the client (e.g. `linux-mcp-server` or `podman run ... linux-mcp-server`). With a native install it runs on the **same machine as the client**. With a container deploy it runs **inside the container** on that same machine. In both cases the server receives tool calls over stdio and performs command execution. |
66
+
|**Target Linux system**| Same host as client, or any host reachable via SSH | Commands are executed either **locally** on the same host where the MCP server is running (subprocess or container), or **remotely** on another machine. Remote execution is done by the server opening an **SSH connection from the server host to the target host** and running commands there. The client never talks to the target directly. |
67
+
68
+
Some important things to consider:
69
+
70
+
-**Client ↔ Server**: Communication is **always** over stdio, using the MCP JSON-RPC protocol. The client does not connect to the target directly.
71
+
-**Server ↔ Target**: If `host` is omitted, execution is **local** (same host as the server). If `host` is set, the server **SSHs from its own host** to that host to run commands.
72
+
-**Containers**: When the server runs in a container, “local” means inside the container. Local execution can be disabled via the `disallow_local_execution_in_containers` decorator (tools then require a `host` parameter for remote SSH).
-**Native**: Server runs on the user's machine; it can run commands locally (same machine) or via SSH to remote hosts.
102
+
-**Container**: Server runs inside a container on the user's machine; it typically only runs commands on **remote** hosts (SSH). Local execution from inside the container is blocked by design.
103
+
104
+
## Detailed Tool Call Flow
105
+
106
+
End-to-end flow of a single MCP tool call: from the client request through the server to command execution on the target, and back.
107
+
108
+
### Sequence Overview
109
+
110
+
```mermaid
111
+
sequenceDiagram
112
+
participant User
113
+
participant Client as MCP Client<br/>(e.g. Cursor)
114
+
participant LLM as LLM Service
115
+
participant Server as MCP Server Process<br/>(same host or container)
116
+
participant FastMCP as FastMCP
117
+
participant Tool as Tool (e.g. get_system_information)
118
+
participant Cmd as CommandSpec / COMMANDS
119
+
participant Exec as execute_command
120
+
participant Target as Target Linux System<br/>(local or remote)
subgraph ServerHost["Where: Same machine as client (or container)"]
166
+
subgraph MCPProcess["MCP Server process"]
167
+
B[FastMCP]
168
+
C[Tool e.g. get_system_information]
169
+
D[commands.get_command / CommandSpec.run]
170
+
E[connection.ssh.execute_command]
171
+
end
172
+
end
173
+
174
+
subgraph LocalTarget["Where: Same host as MCP server"]
175
+
F[_execute_local: subprocess]
176
+
end
177
+
178
+
subgraph RemoteTarget["Where: Remote host"]
179
+
G[SSHConnectionManager]
180
+
H[asyncssh: SSH to host]
181
+
end
182
+
183
+
A -->|"stdio (MCP)"| B
184
+
B --> C
185
+
C --> D
186
+
D --> E
187
+
E -->|host is None| F
188
+
E -->|host set| G
189
+
G --> H
190
+
F --> I[(stdout/stderr)]
191
+
H --> I
192
+
I --> D
193
+
D --> C
194
+
C --> B
195
+
B --> A
196
+
```
197
+
198
+
### Summary of the flow
199
+
200
+
1.**Client** (on user's machine): Sends `tools/call` with tool name and arguments over **stdio** to the server process.
201
+
2.**MCP Server** (same machine as client, or in container): FastMCP receives the call and invokes the registered **tool** (e.g. `get_system_information`).
202
+
3.**Tool**: Uses **CommandSpec** from `commands.COMMANDS`, calls `cmd.run(host=host)`, which calls `execute_with_fallback` → **execute_command**.
203
+
4.**execute_command** (in `connection/ssh.py`): If `host` is `None`, runs **local** via `_execute_local` (subprocess on the server host). If `host` is set, uses **SSHConnectionManager** to get an SSH connection and runs the command on the **remote** host.
204
+
5.**Target Linux system**: Either the same host as the server (local) or the remote host (SSH). Output (stdout/stderr) is returned along the same path.
205
+
6.**Tool**: Parses and formats the output (parsers, formatters), then returns a string to FastMCP.
206
+
7.**Client**: Receives the MCP response and presents the result to the user.
207
+
208
+
The **client** never connects directly to the target; all execution is performed by the **MCP server** on behalf of the client, either locally or via SSH from the server host.
0 commit comments