Skip to content

Commit b5ddf6d

Browse files
committed
mcp: complete runbook summary metadata
1 parent cafb56b commit b5ddf6d

5 files changed

Lines changed: 61 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
1919
- MCP now exposes configurable read-only resources for runbook and Skill
2020
summaries without returning command strings, full guidance bodies, or
2121
execution handles.
22+
- MCP runbook summaries now include explicit step counts and summary safety
23+
posture while keeping command strings redacted.
2224
- Build verification now installs the built wheel and checks packaged MCP/Skill
2325
defaults plus importability of the new MCP and Skill modules.
2426
- Workspace tool activity now includes concise evidence snippets from completed

docs/design/mcp-server.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,19 @@ configured audit path.
7171

7272
| Resource | Behavior | State mutation |
7373
|---|---|---|
74-
| `linuxagent://runbooks/summary` | Returns runbook ids, titles, and step purpose/read-only flags | None |
74+
| `linuxagent://runbooks/summary` | Returns runbook ids, titles, step counts, safety posture, and step purpose/read-only flags | None |
7575
| `linuxagent://skills/summary` | Returns Skill name/version/description/permissions/guidance presence/runbook ids | None |
7676

7777
Resources intentionally return summaries. They do not expose command strings,
7878
planner guidance bodies, execution results, raw audit logs, config secrets, or
7979
filesystem content.
8080

81+
Runbook safety posture is summary-level metadata:
82+
83+
- `read_only`: every exposed step is declared read-only.
84+
- `policy_gated`: one or more steps can have side effects and must still go
85+
through planner, policy, HITL, sandbox metadata, and audit before execution.
86+
8187
## Non-Exposed Capabilities
8288

8389
These remain intentionally unavailable over MCP:

docs/zh/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ LinuxAgent 的重要变更记录在这里。
88

99
### Changed
1010

11+
- MCP runbook 摘要现在包含显式 step count 和摘要级 safety posture,同时继续
12+
隐藏原始命令字符串。
1113
- Workspace tool 活动现在会展示来自 `read_file``list_dir`
1214
`search_files` 完成结果的简短 evidence 摘要。文件修改流程返回“无需修改”时,
1315
最终回答也会附带引用到的依据,方便操作员看到模型基于哪些文件行或搜索结果做判断。

src/linuxagent/mcp_server.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ def _runbook_summary(runbooks: tuple[Runbook, ...]) -> JsonObject:
252252
{
253253
"id": runbook.id,
254254
"title": runbook.title,
255+
"step_count": len(runbook.steps),
256+
"safety_posture": _runbook_safety_posture(runbook),
255257
"steps": [
256258
{
257259
"purpose": step.purpose,
@@ -265,6 +267,12 @@ def _runbook_summary(runbooks: tuple[Runbook, ...]) -> JsonObject:
265267
}
266268

267269

270+
def _runbook_safety_posture(runbook: Runbook) -> str:
271+
if all(step.read_only for step in runbook.steps):
272+
return "read_only"
273+
return "policy_gated"
274+
275+
268276
def _skill_summary(skills: tuple[SkillManifest, ...]) -> JsonObject:
269277
return {
270278
"enabled": bool(skills),

tests/unit/test_mcp_server.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,54 @@ def test_mcp_runbook_summary_resource_is_read_only(tmp_path: Path) -> None:
9494
{
9595
"id": "skill.disk.quick",
9696
"title": "Disk quick check",
97+
"step_count": 1,
98+
"safety_posture": "read_only",
9799
"steps": [{"purpose": "Show filesystem usage", "read_only": True}],
98100
}
99101
]
100102
assert "df -h" not in response["result"]["contents"][0]["text"]
101103

102104

105+
def test_mcp_runbook_summary_reports_policy_gated_posture(tmp_path: Path) -> None:
106+
server = McpServer(
107+
DEFAULT_POLICY_ENGINE,
108+
tmp_path / "audit.log",
109+
runbooks=(
110+
Runbook(
111+
id="skill.service.restart",
112+
title="Restart service",
113+
steps=(
114+
RunbookStep(
115+
command="systemctl status nginx",
116+
purpose="Inspect service status",
117+
read_only=True,
118+
),
119+
RunbookStep(
120+
command="systemctl restart nginx",
121+
purpose="Restart service",
122+
read_only=False,
123+
),
124+
),
125+
),
126+
),
127+
)
128+
129+
response = server.handle(
130+
{
131+
"jsonrpc": "2.0",
132+
"id": 12,
133+
"method": "resources/read",
134+
"params": {"uri": "linuxagent://runbooks/summary"},
135+
}
136+
)
137+
138+
assert response is not None
139+
content = json.loads(response["result"]["contents"][0]["text"])
140+
assert content["runbooks"][0]["step_count"] == 2
141+
assert content["runbooks"][0]["safety_posture"] == "policy_gated"
142+
assert "systemctl restart nginx" not in response["result"]["contents"][0]["text"]
143+
144+
103145
def test_mcp_skill_summary_resource_reports_manifest_metadata(tmp_path: Path) -> None:
104146
server = McpServer(
105147
DEFAULT_POLICY_ENGINE,

0 commit comments

Comments
 (0)