Skip to content

Commit 5fc2b94

Browse files
Copilot ScribeCopilot
andcommitted
Implement milestone-2 process inspection
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent c79cc21 commit 5fc2b94

11 files changed

Lines changed: 827 additions & 28 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Ripley Decision: M2 Plan Update
2+
3+
**Date:** Session 792ebf1b
4+
**Role:** Lead
5+
6+
## Decision
7+
8+
Updated project plan to reflect current progress after Milestone 2 implementation and validation.
9+
10+
## Rationale
11+
12+
1. **M1 published** — Feature complete, branch frozen
13+
2. **Branch model aligned** — Clear separation: master (stable) → milestone-1 (frozen) → milestone-2 (dev)
14+
3. **M2 process inspection** — Implemented on milestone-2, validation confirmed
15+
4. **Next phase identified** — Integration testing with client is the logical follow-up
16+
17+
## Changes made
18+
19+
- Updated `plan.md` status section to reflect M1/M2 state
20+
- Added branch model clarity
21+
- Documented process inspection completion
22+
- Identified next work items (integration testing, diagnostics expansion, docs)
23+
24+
## Risk/Notes
25+
26+
- Ensure M2 branch remains stable and tests continue to pass
27+
- Integration testing will be first opportunity to validate M2 tools with real Azure client calls

README.md

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Linux Diagnostics MCP Server - Lecture Demo
22

3-
A Python/Linux adaptation of the original `MCPDemo` teaching repository. This repo mirrors the C# demo's public structure and documentation style while staying truthful about current scope: **Milestone 1 parity only** today.
3+
A Python/Linux adaptation of the original `MCPDemo` teaching repository. This repo now reaches **Milestone 2 parity** for the public teaching flow: compact system inspection plus Linux process listing and drill-down.
44

55
## What This Demo Shows
66

77
This is an early **MCP lecture demo** with:
8-
-**Tools**: one read-only Linux diagnostics tool, `get_system_info`
8+
-**Tools**: read-only Linux diagnostics tools for `get_system_info`, `get_process_list`, `get_process_by_id`, and `get_process_by_name`
99
-**AI Chat Client**: a Python Azure OpenAI client that launches the local stdio server and lets the model call MCP tools
1010
-**STDIO transport**
1111
-**Python 3.12 implementation** with the official MCP Python SDK
@@ -41,7 +41,8 @@ This script:
4141
2. Performs the MCP handshake
4242
3. Discovers tools
4343
4. Executes `get_system_info`
44-
5. Verifies the lecture chat client fails safely when Azure OpenAI settings are missing
44+
5. Exercises the process inspection tools
45+
6. Verifies the lecture chat client fails safely when Azure OpenAI settings are missing
4546

4647
### 3. Test with MCP Inspector
4748

@@ -53,7 +54,7 @@ mcp dev src/mcp_linux_diag_server/server.py:server --with-editable .
5354
Then in Inspector:
5455
1. Connect to the server
5556
2. Open the **Tools** tab
56-
3. Select **get_system_info**
57+
3. Select **get_system_info**, **get_process_list**, **get_process_by_id**, or **get_process_by_name**
5758
4. Call the tool and inspect the JSON response
5859

5960
### 4. Use the Lecture Chat Client
@@ -103,10 +104,18 @@ python3 -m mcp_linux_diag_server.client --prompt "What is the system information
103104
- Memory summary
104105
- WSL detection flag
105106

107+
### Process Inspection
108+
- **`get_process_list`** - Returns a lightweight list of running processes with names and PIDs
109+
- **`get_process_by_id`** - Returns detailed Linux process information for one PID
110+
- **`get_process_by_name`** - Returns paged detailed process information for a process name
111+
- Defaults to `page_number=1`
112+
- Defaults to `page_size=5`
113+
- Keeps the list-first, detail-second teaching flow from the original demo
114+
106115
## Projects
107116

108117
### `src/mcp_linux_diag_server/server.py`
109-
The stdio MCP server exposing the Milestone 1 `get_system_info` tool.
118+
The stdio MCP server exposing the Milestone 2 diagnostics and process inspection tools.
110119

111120
### `src/mcp_linux_diag_server/client.py`
112121
The lecture chat client that:
@@ -119,11 +128,11 @@ The lecture chat client that:
119128

120129
| Method | Visual | Interactive | LLM | Best For |
121130
|--------|--------|-------------|-----|----------|
122-
| `python3 scripts/smoke_test.py` | ❌ No | ❌ No | ❌ No | quick verification |
131+
| `python3 scripts/smoke_test.py` | ❌ No | ❌ No | ❌ No | quick verification of Milestone 1 + 2 tools |
123132
| MCP Inspector | ✅ Yes | ✅ Yes | ❌ No | development, debugging, teaching |
124133
| `python3 -m mcp_linux_diag_server.client` | ❌ No | ✅ Yes | ✅ Yes | lecture demo flow |
125134

126-
For a parity-focused Milestone 1 checklist, see [M1_VALIDATION_GUIDE.md](M1_VALIDATION_GUIDE.md).
135+
For the Milestone 1 validation checklist that still underpins the base lecture flow, see [M1_VALIDATION_GUIDE.md](M1_VALIDATION_GUIDE.md).
127136

128137
## Project Structure
129138

@@ -141,10 +150,13 @@ MCPPythonDemo/
141150
│ ├── client.py
142151
│ ├── server.py
143152
│ └── tools/
153+
│ ├── processes.py
144154
│ └── system_info.py
145155
├── tests/
146156
│ ├── test_client.py
147157
│ ├── test_m1_smoke.py
158+
│ ├── test_m2_smoke.py
159+
│ ├── test_processes.py
148160
│ └── test_system_info.py
149161
```
150162

@@ -157,12 +169,12 @@ MCPPythonDemo/
157169
## Milestones
158170

159171
**Milestone 1** - Minimal diagnostics tool over stdio plus lecture chat client
160-
**Milestone 2** - Process inspection
172+
**Milestone 2** - Process inspection
161173
**Milestone 3** - Resources and prompts
162174
**Milestone 4** - HTTP transport and security
163175
**Milestone 5+** - Elicitation, sampling, and roots
164176

165-
This repo does **not** claim feature parity with the later C# milestones yet.
177+
This repo does **not** claim feature parity with Milestone 3 and beyond yet.
166178

167179
## License
168180

docs/MCPDemoRoadmap.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ This roadmap mirrors the teaching arc of the original C# `MCPDemo` repository, b
77
| Milestone | Original C# Theme | Python/Linux Plan | Status |
88
|----------|--------------------|-------------------|--------|
99
| 1 | Minimal stdio diagnostics tool | `get_system_info` + lecture chat client | ✅ Complete |
10-
| 2 | Process inspection | Linux process listing and per-PID detail | ⏳ Planned |
10+
| 2 | Process inspection | Linux process listing, per-PID detail, and by-name paging | ✅ Complete |
1111
| 3 | Resources and prompts | Linux snapshot resources + prompts | ⏳ Planned |
1212
| 4 | HTTP transport and security | Python HTTP MCP transport + auth | ⏳ Planned |
1313
| 5 | Elicitation | Confirmation flow for risky operations | ⏳ Planned |
@@ -26,14 +26,15 @@ Implemented today:
2626
- `/proc/meminfo`
2727
- Python lecture chat client that launches the server and supports Azure OpenAI tool-calling
2828

29-
This is the only milestone currently implemented in this public Python repo.
29+
Milestone 1 remains the base teaching path and stays fully supported.
3030

31-
## Milestone 2 – Process inspection
31+
## Milestone 2 – Process inspection
3232

33-
Planned scope:
34-
- list running processes
35-
- inspect one process by PID
36-
- keep responses small and lecture-friendly
33+
Implemented scope:
34+
- list running processes with lightweight summaries
35+
- inspect one process by PID with Linux `/proc` detail
36+
- inspect matching processes by name with default paging (`page_size=5`)
37+
- keep the summary-first, detail-second teaching flow
3738

3839
## Milestone 3 – Resources and prompts ⏳
3940

@@ -68,4 +69,4 @@ Planned scope:
6869

6970
## Important Parity Note
7071

71-
This repo intentionally follows the **same milestone progression** as the original C# demo, but it does **not** claim feature parity with the later milestones yet. Public documentation should stay aligned with the real Python implementation state.
72+
This repo intentionally follows the **same milestone progression** as the original C# demo, but it does **not** claim feature parity with Milestone 3 and later yet. Public documentation should stay aligned with the real Python implementation state.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
55
[project]
66
name = "mcp-linux-diag-server"
77
version = "0.1.0"
8-
description = "Milestone 1 Linux diagnostics MCP server plus lecture chat client for the Season of AI teaching arc."
8+
description = "Milestone 2 Linux diagnostics MCP server plus lecture chat client for the Season of AI teaching arc."
99
readme = "README.md"
1010
license = { file = "LICENSE.txt" }
1111
requires-python = ">=3.12"

scripts/smoke_test.py

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,56 @@ async def run_server_smoke() -> dict[str, object]:
3535

3636
tools = await session.list_tools()
3737
tool_names = [tool.name for tool in tools.tools]
38-
if "get_system_info" not in tool_names:
39-
raise RuntimeError("get_system_info tool was not advertised by the server")
38+
expected_tools = {
39+
"get_system_info",
40+
"get_process_list",
41+
"get_process_by_id",
42+
"get_process_by_name",
43+
}
44+
if not expected_tools.issubset(tool_names):
45+
missing_tools = sorted(expected_tools - set(tool_names))
46+
raise RuntimeError(f"Expected tools were not advertised by the server: {missing_tools}")
47+
48+
system_result = await session.call_tool("get_system_info", {})
49+
if system_result.isError:
50+
raise RuntimeError(f"Tool call failed: {system_result.content}")
51+
52+
process_list_result = await session.call_tool("get_process_list", {})
53+
if process_list_result.isError:
54+
raise RuntimeError(f"Process list tool failed: {process_list_result.content}")
55+
56+
process_list = process_list_result.structuredContent
57+
if isinstance(process_list, dict) and "result" in process_list:
58+
process_list = process_list["result"]
59+
if not isinstance(process_list, list) or not process_list:
60+
raise RuntimeError("Expected get_process_list to return at least one process")
61+
62+
process_detail = None
63+
process_page = None
64+
for process_entry in process_list[:10]:
65+
process_id = process_entry["process_id"]
66+
process_name = process_entry["process_name"]
67+
68+
process_detail_result = await session.call_tool("get_process_by_id", {"process_id": process_id})
69+
if process_detail_result.isError:
70+
continue
71+
72+
process_page_result = await session.call_tool("get_process_by_name", {"process_name": process_name})
73+
if process_page_result.isError:
74+
continue
75+
76+
process_detail = process_detail_result.structuredContent
77+
process_page = process_page_result.structuredContent
78+
break
4079

41-
result = await session.call_tool("get_system_info", {})
42-
if result.isError:
43-
raise RuntimeError(f"Tool call failed: {result.content}")
80+
if process_detail is None or process_page is None:
81+
raise RuntimeError("Unable to complete live process detail smoke calls")
4482

4583
return {
4684
"tools": tool_names,
47-
"system_info": result.structuredContent,
85+
"system_info": system_result.structuredContent,
86+
"process_sample": process_detail,
87+
"process_page": process_page,
4888
}
4989

5090

src/mcp_linux_diag_server/client.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
"You are a helpful Linux diagnostics assistant. "
2323
"You can call MCP tools that inspect the current Linux or WSL machine. "
2424
"When the user asks about the system, use get_system_info before answering. "
25+
"When the user asks about processes, list them first and then use get_process_by_id "
26+
"or get_process_by_name for detail. "
2527
"Keep answers concise, practical, and grounded in tool results."
2628
)
2729
AZURE_OPENAI_SCOPE = "https://cognitiveservices.azure.com/.default"

src/mcp_linux_diag_server/server.py

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,26 @@
1-
"""Milestone 1 MCP server entrypoint."""
1+
"""Milestone 2 MCP server entrypoint."""
2+
3+
from typing import Annotated
24

35
from mcp.server.fastmcp import FastMCP
6+
from pydantic import Field
47

5-
from mcp_linux_diag_server.tools.system_info import SystemInfoResult, collect_system_info
8+
from mcp_linux_diag_server.tools import (
9+
BasicProcessInfo,
10+
ProcessDetailResult,
11+
ProcessQueryResult,
12+
SystemInfoResult,
13+
collect_system_info,
14+
get_process_by_id as collect_process_by_id,
15+
get_processes_by_name as collect_processes_by_name,
16+
list_processes as collect_process_list,
17+
)
618

719
server = FastMCP(
820
name="Linux Diagnostics Demo",
921
instructions=(
10-
"Milestone 1 teaching server. Exposes one read-only Linux diagnostics tool "
11-
"over stdio and keeps the rest of the MCP surface for later milestones."
22+
"Milestone 2 teaching server. Start with compact system or process summaries, "
23+
"then drill into one Linux process with the detail tools."
1224
),
1325
)
1426

@@ -23,6 +35,42 @@ def get_system_info() -> SystemInfoResult:
2335
return collect_system_info()
2436

2537

38+
@server.tool(
39+
name="get_process_list",
40+
title="Get Process List",
41+
description="Return a lightweight Linux process list with process names and PIDs. Use this before requesting detail.",
42+
)
43+
def get_process_list() -> list[BasicProcessInfo]:
44+
"""Return a lightweight Linux process list."""
45+
return collect_process_list()
46+
47+
48+
@server.tool(
49+
name="get_process_by_id",
50+
title="Get Process By ID",
51+
description="Return detailed Linux process information for one PID.",
52+
)
53+
def get_process_by_id(
54+
process_id: Annotated[int, Field(description="Linux process ID to inspect.")],
55+
) -> ProcessDetailResult:
56+
"""Return detailed Linux process information for one PID."""
57+
return collect_process_by_id(process_id)
58+
59+
60+
@server.tool(
61+
name="get_process_by_name",
62+
title="Get Process By Name",
63+
description="Return paged detailed Linux process information for a process name. Defaults to page 1 and 5 results per page.",
64+
)
65+
def get_process_by_name(
66+
process_name: Annotated[str, Field(description="Process name to match, such as python or python3.")],
67+
page_number: Annotated[int | None, Field(description="Optional page number. Defaults to 1.")] = None,
68+
page_size: Annotated[int | None, Field(description="Optional page size. Defaults to 5.")] = None,
69+
) -> ProcessQueryResult:
70+
"""Return paged detailed Linux process information for a process name."""
71+
return collect_processes_by_name(process_name, page_number=page_number, page_size=page_size)
72+
73+
2674
def main() -> None:
2775
"""Run the MCP server over stdio."""
2876
server.run(transport="stdio")
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,33 @@
11
"""Tool implementations for the Linux diagnostics server."""
2+
3+
from .processes import (
4+
BasicProcessInfo,
5+
ProcessDetailResult,
6+
ProcessMemorySnapshot,
7+
ProcessQueryResult,
8+
get_process_by_id,
9+
get_processes_by_name,
10+
list_processes,
11+
)
12+
from .system_info import SystemInfoResult, collect_system_info
13+
14+
ProcessInfoResult = ProcessDetailResult
15+
ProcessMemoryUsage = ProcessMemorySnapshot
16+
get_process_by_name = get_processes_by_name
17+
get_process_list = list_processes
18+
19+
__all__ = [
20+
"BasicProcessInfo",
21+
"ProcessDetailResult",
22+
"ProcessInfoResult",
23+
"ProcessMemorySnapshot",
24+
"ProcessMemoryUsage",
25+
"ProcessQueryResult",
26+
"SystemInfoResult",
27+
"collect_system_info",
28+
"get_process_by_id",
29+
"get_processes_by_name",
30+
"get_process_by_name",
31+
"list_processes",
32+
"get_process_list",
33+
]

0 commit comments

Comments
 (0)