Skip to content

Commit 778624a

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 778624a

1 file changed

Lines changed: 79 additions & 0 deletions

File tree

docs/architecture.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,82 @@ 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+
This means:
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+
## Detailed Tool Call Flow
75+
76+
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.
77+
78+
```mermaid
79+
sequenceDiagram
80+
participant User
81+
participant Client as MCP Client<br/>(e.g. Cursor)
82+
participant LLM as LLM Service
83+
participant Server as MCP Server Process<br/>(same host or container)
84+
participant FastMCP as FastMCP
85+
participant Tool as Tool (e.g. get_system_information)
86+
participant Cmd as CommandSpec / COMMANDS
87+
participant Exec as execute_command
88+
participant Target as Target Linux System<br/>(local or remote)
89+
90+
User->>Client: "What's my system info?"
91+
Client->>LLM: Send prompt request
92+
LLM->>Client: Decide to call tool
93+
Client->>Server: MCP JSON-RPC: tools/call get_system_information
94+
Note over Client,Server: stdio (stdin/stdout)
95+
96+
Server->>FastMCP: Dispatch tool call
97+
FastMCP->>Tool: get_system_information(host=...)
98+
Tool->>Tool: @log_tool_call, @disallow_local_execution_in_containers
99+
Tool->>Cmd: get_command_group("system_info") etc.
100+
Tool->>Cmd: cmd.run(host=host) for each subcommand
101+
102+
Cmd->>Exec: execute_command(command, host=host)
103+
104+
alt host is None (local)
105+
Exec->>Target: asyncio.create_subprocess_exec (local)
106+
Note over Server,Target: Target = same host as server
107+
else host is set (remote)
108+
Exec->>Server: SSHConnectionManager.get_connection(host)
109+
Exec->>Target: conn.run(cmd) over SSH
110+
Note over Server,Target: Target = remote host
111+
end
112+
113+
Target-->>Exec: return_code, stdout, stderr
114+
Exec-->>Cmd: return code, stdout, stderr
115+
Cmd-->>Tool: results
116+
Tool->>Tool: parse_*, format_*
117+
Tool-->>FastMCP: formatted string
118+
FastMCP-->>Server: MCP response
119+
Server-->>Client: JSON-RPC result
120+
Client->>LLM: Tool call result
121+
LLM->>Client: Inference completion
122+
Client->>User: Answer with system info
123+
```
124+
125+
1. User asks a question in the MCP client (e.g. Cursor).
126+
2. Client sends the prompt to the LLM; the LLM decides to call a tool.
127+
3. Client sends MCP JSON-RPC `tools/call` (e.g. `get_system_information`) over **stdio** to the MCP server (same host or container).
128+
4. FastMCP dispatches the call to the tool.
129+
5. Tool runs decorators (`@log_tool_call`, `@disallow_local_execution_in_containers`), resolves commands via `CommandSpec`/`COMMANDS`, and invokes `cmd.run(host=host)` for each subcommand.
130+
6. `execute_command` runs on the target: **local** (`asyncio.create_subprocess_exec` when `host` is omitted) or **remote** (SSH via `SSHConnectionManager` when `host` is set).
131+
7. Target returns return code, stdout, and stderr to `execute_command`.
132+
8. Results flow back: Exec → Cmd → Tool; tool parses and formats (e.g. `parse_*`, `format_*`).
133+
9. Tool returns formatted string to FastMCP; server sends MCP response over stdio to the client.
134+
10. Client passes the tool result to the LLM; LLM produces the answer; client presents it to the user.
135+

0 commit comments

Comments
 (0)