Skip to content

Commit d826448

Browse files
committed
Add architecture details
Add more details about how tools are called and where the various components typically run.
1 parent d2e65e4 commit d826448

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

docs/architecture.md

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,155 @@ graph TB
5454
style Row1 fill:none,stroke:none
5555
style Row2 fill:none,stroke:none
5656
```
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).
73+
74+
```mermaid
75+
flowchart LR
76+
subgraph UserMachine["User workstation"]
77+
C1[MCP Client]
78+
subgraph Native["Native install (pip/uv)"]
79+
S1[MCP Server]
80+
C1 -->|stdio| S1
81+
end
82+
subgraph Container["Container install (Podman/Docker)"]
83+
CNTR[Container]
84+
S2[MCP Server]
85+
C1 -->|stdio| CNTR
86+
CNTR --> S2
87+
end
88+
end
89+
90+
subgraph Targets["Target systems"]
91+
Local[Same host as server<br/>local subprocess]
92+
Remote[Remote host<br/>SSH from server]
93+
end
94+
95+
S1 --> Local
96+
S1 --> Remote
97+
S2 --> Remote
98+
S2 -.->|"local disallowed in container"| Local
99+
```
100+
101+
- **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)
121+
122+
User->>Client: "What's my system info?"
123+
Client->>LLM: Send prompt request
124+
LLM->>Client: Decide to call tool
125+
Client->>Server: MCP JSON-RPC: tools/call get_system_information
126+
Note over Client,Server: stdio (stdin/stdout)
127+
128+
Server->>FastMCP: Dispatch tool call
129+
FastMCP->>Tool: get_system_information(host=...)
130+
Tool->>Tool: @log_tool_call, @disallow_local_execution_in_containers
131+
Tool->>Cmd: get_command_group("system_info") etc.
132+
Tool->>Cmd: cmd.run(host=host) for each subcommand
133+
134+
Cmd->>Exec: execute_command(command, host=host)
135+
136+
alt host is None (local)
137+
Exec->>Target: asyncio.create_subprocess_exec (local)
138+
Note over Server,Target: Target = same host as server
139+
else host is set (remote)
140+
Exec->>Server: SSHConnectionManager.get_connection(host)
141+
Exec->>Target: conn.run(cmd) over SSH
142+
Note over Server,Target: Target = remote host
143+
end
144+
145+
Target-->>Exec: return_code, stdout, stderr
146+
Exec-->>Cmd: return code, stdout, stderr
147+
Cmd-->>Tool: results
148+
Tool->>Tool: parse_*, format_*
149+
Tool-->>FastMCP: formatted string
150+
FastMCP-->>Server: MCP response
151+
Server-->>Client: JSON-RPC result
152+
Client->>LLM: Tool call result
153+
LLM->>Client: Inference completion
154+
Client->>User: Answer with system info
155+
```
156+
157+
### Data Flow Within the Server
158+
159+
```mermaid
160+
flowchart TB
161+
subgraph Client["Where: User's machine"]
162+
A[MCP Client]
163+
end
164+
165+
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

Comments
 (0)