screenforge 0.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/__init__.py +0 -0
- cli/_version.py +1 -0
- cli/dispatch.py +266 -0
- cli/doctor.py +487 -0
- cli/modes/__init__.py +0 -0
- cli/modes/action.py +262 -0
- cli/modes/default.py +248 -0
- cli/modes/demo.py +162 -0
- cli/modes/dry_run.py +237 -0
- cli/modes/init.py +133 -0
- cli/modes/plan.py +148 -0
- cli/modes/workflow.py +354 -0
- cli/parser.py +305 -0
- cli/reporter.py +207 -0
- cli/session.py +146 -0
- cli/shared.py +427 -0
- cli/shorthand.py +90 -0
- cli/tool_protocol_handlers.py +446 -0
- common/__init__.py +0 -0
- common/adapters/__init__.py +21 -0
- common/adapters/android_adapter.py +273 -0
- common/adapters/base_adapter.py +24 -0
- common/adapters/ios_adapter.py +278 -0
- common/adapters/web_adapter.py +271 -0
- common/ai.py +277 -0
- common/ai_autonomous.py +273 -0
- common/ai_heal.py +222 -0
- common/cache/__init__.py +15 -0
- common/cache/cache_hash.py +57 -0
- common/cache/cache_manager.py +300 -0
- common/cache/cache_stats.py +133 -0
- common/cache/cache_storage.py +79 -0
- common/cache/embedding_loader.py +150 -0
- common/capabilities.py +121 -0
- common/case_memory.py +327 -0
- common/error_codes.py +61 -0
- common/exceptions.py +18 -0
- common/executor.py +1504 -0
- common/failure_diagnosis.py +138 -0
- common/history_manager.py +75 -0
- common/logs.py +168 -0
- common/mcp_server.py +467 -0
- common/preflight.py +496 -0
- common/progress.py +37 -0
- common/run_reporter.py +415 -0
- common/run_resume.py +149 -0
- common/runtime_modes.py +35 -0
- common/tool_protocol.py +196 -0
- common/visual_fallback.py +71 -0
- common/workflow_schema.py +150 -0
- config/__init__.py +0 -0
- config/config.py +167 -0
- config/env_loader.py +76 -0
- screenforge-0.4.0.dist-info/METADATA +43 -0
- screenforge-0.4.0.dist-info/RECORD +64 -0
- screenforge-0.4.0.dist-info/WHEEL +5 -0
- screenforge-0.4.0.dist-info/entry_points.txt +2 -0
- screenforge-0.4.0.dist-info/licenses/LICENSE +21 -0
- screenforge-0.4.0.dist-info/top_level.txt +4 -0
- utils/__init__.py +0 -0
- utils/screenshot_annotator.py +60 -0
- utils/utils_ios.py +195 -0
- utils/utils_web.py +304 -0
- utils/utils_xml.py +218 -0
common/mcp_server.py
ADDED
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Any, Callable
|
|
4
|
+
|
|
5
|
+
from pydantic import ValidationError
|
|
6
|
+
|
|
7
|
+
from cli._version import __version__
|
|
8
|
+
from common.capabilities import (
|
|
9
|
+
ACTIONS_REQUIRING_EXTRA_VALUE,
|
|
10
|
+
CONTROL_PLANES,
|
|
11
|
+
EXECUTION_MODES,
|
|
12
|
+
SUPPORTED_ACTIONS,
|
|
13
|
+
SUPPORTED_PLATFORMS,
|
|
14
|
+
)
|
|
15
|
+
from common.tool_protocol import ToolRequest
|
|
16
|
+
|
|
17
|
+
JSONRPC_VERSION = "2.0"
|
|
18
|
+
MCP_SERVER_NAME = "screenforge"
|
|
19
|
+
MCP_SERVER_VERSION = __version__
|
|
20
|
+
MCP_TOOL_CAPABILITIES = "ui_agent_capabilities"
|
|
21
|
+
MCP_TOOL_EXECUTE = "ui_agent_execute"
|
|
22
|
+
MCP_TOOL_LOAD_RUN = "ui_agent_load_run"
|
|
23
|
+
MCP_TOOL_INSPECT_UI = "ui_agent_inspect_ui"
|
|
24
|
+
MCP_TOOL_LOAD_CASE_MEMORY = "ui_agent_load_case_memory"
|
|
25
|
+
SUPPORTED_MCP_PROTOCOL_VERSIONS = (
|
|
26
|
+
"2025-11-25",
|
|
27
|
+
"2025-06-18",
|
|
28
|
+
"2025-03-26",
|
|
29
|
+
"2024-11-05",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _jsonrpc_response(request_id: Any, result: dict) -> dict:
|
|
34
|
+
return {
|
|
35
|
+
"jsonrpc": JSONRPC_VERSION,
|
|
36
|
+
"id": request_id,
|
|
37
|
+
"result": result,
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _jsonrpc_error(request_id: Any, code: int, message: str, data: Any | None = None) -> dict:
|
|
42
|
+
error = {
|
|
43
|
+
"code": code,
|
|
44
|
+
"message": message,
|
|
45
|
+
}
|
|
46
|
+
if data is not None:
|
|
47
|
+
error["data"] = data
|
|
48
|
+
return {
|
|
49
|
+
"jsonrpc": JSONRPC_VERSION,
|
|
50
|
+
"id": request_id,
|
|
51
|
+
"error": error,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _negotiate_protocol_version(requested_version: str) -> str:
|
|
56
|
+
requested = str(requested_version).strip()
|
|
57
|
+
if requested in SUPPORTED_MCP_PROTOCOL_VERSIONS:
|
|
58
|
+
return requested
|
|
59
|
+
return SUPPORTED_MCP_PROTOCOL_VERSIONS[0]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _build_capabilities_tool_schema() -> dict:
|
|
63
|
+
return {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"properties": {},
|
|
66
|
+
"additionalProperties": False,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _build_execute_tool_schema() -> dict:
|
|
71
|
+
return {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"properties": {
|
|
74
|
+
"mode": {
|
|
75
|
+
"type": "string",
|
|
76
|
+
"enum": list(EXECUTION_MODES),
|
|
77
|
+
"description": "执行模式:run / doctor / plan_only / dry_run",
|
|
78
|
+
},
|
|
79
|
+
"platform": {
|
|
80
|
+
"type": "string",
|
|
81
|
+
"enum": list(SUPPORTED_PLATFORMS),
|
|
82
|
+
"description": "目标平台",
|
|
83
|
+
},
|
|
84
|
+
"env": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"description": "环境名称,默认 dev",
|
|
87
|
+
},
|
|
88
|
+
"vision": {
|
|
89
|
+
"type": "boolean",
|
|
90
|
+
"description": "是否启用视觉模式",
|
|
91
|
+
},
|
|
92
|
+
"context": {
|
|
93
|
+
"type": "string",
|
|
94
|
+
"description": "上下文文件路径",
|
|
95
|
+
},
|
|
96
|
+
"output": {
|
|
97
|
+
"type": "string",
|
|
98
|
+
"description": "输出脚本路径",
|
|
99
|
+
},
|
|
100
|
+
"resume_run_id": {
|
|
101
|
+
"type": "string",
|
|
102
|
+
"description": "恢复上下文的 run_id",
|
|
103
|
+
},
|
|
104
|
+
"workflow": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"properties": {
|
|
107
|
+
"path": {
|
|
108
|
+
"type": "string",
|
|
109
|
+
"description": "workflow YAML 路径",
|
|
110
|
+
},
|
|
111
|
+
"vars": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"description": "workflow 变量覆盖",
|
|
114
|
+
"additionalProperties": {"type": "string"},
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
"required": ["path"],
|
|
118
|
+
"additionalProperties": False,
|
|
119
|
+
},
|
|
120
|
+
"action": {
|
|
121
|
+
"type": "object",
|
|
122
|
+
"properties": {
|
|
123
|
+
"action": {
|
|
124
|
+
"type": "string",
|
|
125
|
+
"enum": list(SUPPORTED_ACTIONS),
|
|
126
|
+
"description": "即时动作类型",
|
|
127
|
+
},
|
|
128
|
+
"action_name": {
|
|
129
|
+
"type": "string",
|
|
130
|
+
"description": "即时动作名称",
|
|
131
|
+
},
|
|
132
|
+
"locator_type": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"description": "定位器类型",
|
|
135
|
+
},
|
|
136
|
+
"locator_value": {
|
|
137
|
+
"type": "string",
|
|
138
|
+
"description": "定位器值",
|
|
139
|
+
},
|
|
140
|
+
"extra_value": {
|
|
141
|
+
"type": "string",
|
|
142
|
+
"description": "动作附加值",
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
"required": ["action"],
|
|
146
|
+
"additionalProperties": False,
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
"additionalProperties": False,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _build_inspect_ui_tool_schema() -> dict:
|
|
154
|
+
return {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"properties": {
|
|
157
|
+
"platform": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"enum": list(SUPPORTED_PLATFORMS),
|
|
160
|
+
"description": "目标平台",
|
|
161
|
+
},
|
|
162
|
+
"env": {
|
|
163
|
+
"type": "string",
|
|
164
|
+
"description": "环境名称,默认 dev",
|
|
165
|
+
},
|
|
166
|
+
"vision": {
|
|
167
|
+
"type": "boolean",
|
|
168
|
+
"description": "是否同时返回截图 base64",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
"additionalProperties": False,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _build_load_case_memory_tool_schema() -> dict:
|
|
176
|
+
return {
|
|
177
|
+
"type": "object",
|
|
178
|
+
"properties": {
|
|
179
|
+
"platform": {
|
|
180
|
+
"type": "string",
|
|
181
|
+
"enum": list(SUPPORTED_PLATFORMS),
|
|
182
|
+
"description": "可选的平台过滤条件",
|
|
183
|
+
},
|
|
184
|
+
"control_kind": {
|
|
185
|
+
"type": "string",
|
|
186
|
+
"enum": ["goal", "workflow", "action", "doctor"],
|
|
187
|
+
"description": "可选的控制面过滤条件",
|
|
188
|
+
},
|
|
189
|
+
"source_ref": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"description": "可选的来源引用过滤条件",
|
|
192
|
+
},
|
|
193
|
+
"query": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"description": "按 control_label / source_ref / successful_actions 模糊查询",
|
|
196
|
+
},
|
|
197
|
+
"limit": {
|
|
198
|
+
"type": "integer",
|
|
199
|
+
"description": "最多返回条数,默认 20",
|
|
200
|
+
"minimum": 1,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
"additionalProperties": False,
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _build_load_run_tool_schema() -> dict:
|
|
208
|
+
return {
|
|
209
|
+
"type": "object",
|
|
210
|
+
"properties": {
|
|
211
|
+
"run_id": {
|
|
212
|
+
"type": "string",
|
|
213
|
+
"description": "需要加载的历史运行 run_id",
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
"required": ["run_id"],
|
|
217
|
+
"additionalProperties": False,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def build_mcp_tools() -> list[dict]:
|
|
222
|
+
return [
|
|
223
|
+
{
|
|
224
|
+
"name": MCP_TOOL_CAPABILITIES,
|
|
225
|
+
"title": "ScreenForge Capabilities",
|
|
226
|
+
"description": "Returns a capability snapshot of supported platforms, modes, control planes, and actions.",
|
|
227
|
+
"inputSchema": _build_capabilities_tool_schema(),
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"name": MCP_TOOL_EXECUTE,
|
|
231
|
+
"title": "ScreenForge Execute",
|
|
232
|
+
"description": (
|
|
233
|
+
"Execute or dry-run a ScreenForge request. Agent entry supports workflow, action, and doctor control planes. "
|
|
234
|
+
f"Execution modes: {', '.join(EXECUTION_MODES)}. "
|
|
235
|
+
f"Platforms: {', '.join(SUPPORTED_PLATFORMS)}. "
|
|
236
|
+
f"Control planes: {', '.join(CONTROL_PLANES)}. "
|
|
237
|
+
f"Actions requiring extra_value: {', '.join(sorted(ACTIONS_REQUIRING_EXTRA_VALUE))}."
|
|
238
|
+
),
|
|
239
|
+
"inputSchema": _build_execute_tool_schema(),
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
"name": MCP_TOOL_INSPECT_UI,
|
|
243
|
+
"title": "ScreenForge Inspect UI",
|
|
244
|
+
"description": "Connect to the target platform and return a cleaned XML/DOM tree for the Agent to analyze and locate elements.",
|
|
245
|
+
"inputSchema": _build_inspect_ui_tool_schema(),
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
"name": MCP_TOOL_LOAD_CASE_MEMORY,
|
|
249
|
+
"title": "ScreenForge Load Case Memory",
|
|
250
|
+
"description": "Load cross-run test memory for the Agent to reuse successful actions, locator strategies, and pytest assets.",
|
|
251
|
+
"inputSchema": _build_load_case_memory_tool_schema(),
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"name": MCP_TOOL_LOAD_RUN,
|
|
255
|
+
"title": "ScreenForge Load Run",
|
|
256
|
+
"description": "Load a historical run by run_id, returning its summary, resume_context, and replay assets.",
|
|
257
|
+
"inputSchema": _build_load_run_tool_schema(),
|
|
258
|
+
},
|
|
259
|
+
]
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _build_initialize_result(protocol_version: str) -> dict:
|
|
263
|
+
return {
|
|
264
|
+
"protocolVersion": _negotiate_protocol_version(protocol_version),
|
|
265
|
+
"capabilities": {
|
|
266
|
+
"tools": {
|
|
267
|
+
"listChanged": False,
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
"serverInfo": {
|
|
271
|
+
"name": MCP_SERVER_NAME,
|
|
272
|
+
"version": MCP_SERVER_VERSION,
|
|
273
|
+
},
|
|
274
|
+
"instructions": (
|
|
275
|
+
"Use ui_agent_capabilities to inspect ScreenForge support, ui_agent_inspect_ui "
|
|
276
|
+
"to capture the current UI tree, ui_agent_load_case_memory to reuse existing test knowledge, "
|
|
277
|
+
"and ui_agent_execute to run workflow/action/doctor requests against the execution engine."
|
|
278
|
+
),
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _build_tool_result(payload: dict) -> dict:
|
|
283
|
+
return {
|
|
284
|
+
"content": [
|
|
285
|
+
{
|
|
286
|
+
"type": "text",
|
|
287
|
+
"text": json.dumps(payload, ensure_ascii=False, indent=2),
|
|
288
|
+
}
|
|
289
|
+
],
|
|
290
|
+
"structuredContent": payload,
|
|
291
|
+
"isError": not bool(payload.get("ok", False)),
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class McpServerSession:
|
|
296
|
+
def __init__(
|
|
297
|
+
self,
|
|
298
|
+
execute_tool_request: Callable[[ToolRequest], dict],
|
|
299
|
+
load_run_payload: Callable[[str], dict] | None = None,
|
|
300
|
+
inspect_ui_payload: Callable[[ToolRequest], dict] | None = None,
|
|
301
|
+
load_case_memory_payload: Callable[[ToolRequest], dict] | None = None,
|
|
302
|
+
):
|
|
303
|
+
self._execute_tool_request = execute_tool_request
|
|
304
|
+
self._load_run_payload = load_run_payload or (
|
|
305
|
+
lambda run_id: {
|
|
306
|
+
"ok": False,
|
|
307
|
+
"operation": "load_run",
|
|
308
|
+
"exit_code": 2,
|
|
309
|
+
"error": f"未配置 load_run 处理器: {run_id}",
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
self._inspect_ui_payload = inspect_ui_payload or (
|
|
313
|
+
lambda request: {
|
|
314
|
+
"ok": False,
|
|
315
|
+
"operation": "inspect_ui",
|
|
316
|
+
"exit_code": 2,
|
|
317
|
+
"error": f"未配置 inspect_ui 处理器: {request.platform}",
|
|
318
|
+
}
|
|
319
|
+
)
|
|
320
|
+
self._load_case_memory_payload = load_case_memory_payload or (
|
|
321
|
+
lambda request: {
|
|
322
|
+
"ok": False,
|
|
323
|
+
"operation": "load_case_memory",
|
|
324
|
+
"exit_code": 2,
|
|
325
|
+
"error": "load_case_memory handler not configured",
|
|
326
|
+
}
|
|
327
|
+
)
|
|
328
|
+
self._initialized = False
|
|
329
|
+
|
|
330
|
+
def handle_message(self, message: dict) -> dict | None:
|
|
331
|
+
if not isinstance(message, dict):
|
|
332
|
+
return _jsonrpc_error(None, -32600, "Invalid JSON-RPC request")
|
|
333
|
+
|
|
334
|
+
method = str(message.get("method", "")).strip()
|
|
335
|
+
request_id = message.get("id")
|
|
336
|
+
params = message.get("params", {}) or {}
|
|
337
|
+
|
|
338
|
+
if method == "notifications/initialized":
|
|
339
|
+
self._initialized = True
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
if method == "initialize":
|
|
343
|
+
self._initialized = True
|
|
344
|
+
return _jsonrpc_response(
|
|
345
|
+
request_id,
|
|
346
|
+
_build_initialize_result(params.get("protocolVersion", "")),
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
if method == "ping":
|
|
350
|
+
return _jsonrpc_response(request_id, {})
|
|
351
|
+
|
|
352
|
+
if method == "tools/list":
|
|
353
|
+
return _jsonrpc_response(request_id, {"tools": build_mcp_tools()})
|
|
354
|
+
|
|
355
|
+
if method == "tools/call":
|
|
356
|
+
return self._handle_tool_call(request_id, params)
|
|
357
|
+
|
|
358
|
+
if request_id is None:
|
|
359
|
+
return None
|
|
360
|
+
|
|
361
|
+
return _jsonrpc_error(request_id, -32601, f"不支持的方法: {method}")
|
|
362
|
+
|
|
363
|
+
def _handle_tool_call(self, request_id: Any, params: dict) -> dict:
|
|
364
|
+
tool_name = str(params.get("name", "")).strip()
|
|
365
|
+
arguments = params.get("arguments", {}) or {}
|
|
366
|
+
|
|
367
|
+
if tool_name == MCP_TOOL_CAPABILITIES:
|
|
368
|
+
request = ToolRequest(operation="capabilities")
|
|
369
|
+
elif tool_name == MCP_TOOL_INSPECT_UI:
|
|
370
|
+
try:
|
|
371
|
+
payload = self._inspect_ui_payload(
|
|
372
|
+
ToolRequest.model_validate(
|
|
373
|
+
{
|
|
374
|
+
"operation": "inspect_ui",
|
|
375
|
+
**arguments,
|
|
376
|
+
}
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
except ValidationError as exc:
|
|
380
|
+
return _jsonrpc_error(request_id, -32602, "tool 参数校验失败", str(exc))
|
|
381
|
+
except Exception as exc:
|
|
382
|
+
return _jsonrpc_error(request_id, -32603, "工具执行失败", str(exc))
|
|
383
|
+
return _jsonrpc_response(request_id, _build_tool_result(payload))
|
|
384
|
+
elif tool_name == MCP_TOOL_LOAD_CASE_MEMORY:
|
|
385
|
+
try:
|
|
386
|
+
payload = self._load_case_memory_payload(
|
|
387
|
+
ToolRequest.model_validate(
|
|
388
|
+
{
|
|
389
|
+
"operation": "load_case_memory",
|
|
390
|
+
**arguments,
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
)
|
|
394
|
+
except ValidationError as exc:
|
|
395
|
+
return _jsonrpc_error(request_id, -32602, "tool 参数校验失败", str(exc))
|
|
396
|
+
except Exception as exc:
|
|
397
|
+
return _jsonrpc_error(request_id, -32603, "工具执行失败", str(exc))
|
|
398
|
+
return _jsonrpc_response(request_id, _build_tool_result(payload))
|
|
399
|
+
elif tool_name == MCP_TOOL_LOAD_RUN:
|
|
400
|
+
run_id = str(arguments.get("run_id", "")).strip()
|
|
401
|
+
if not run_id:
|
|
402
|
+
return _jsonrpc_error(request_id, -32602, "tool 参数校验失败", "run_id 不能为空")
|
|
403
|
+
try:
|
|
404
|
+
payload = self._load_run_payload(run_id)
|
|
405
|
+
except Exception as exc:
|
|
406
|
+
return _jsonrpc_error(request_id, -32603, "工具执行失败", str(exc))
|
|
407
|
+
return _jsonrpc_response(request_id, _build_tool_result(payload))
|
|
408
|
+
elif tool_name == MCP_TOOL_EXECUTE:
|
|
409
|
+
try:
|
|
410
|
+
request = ToolRequest.model_validate(
|
|
411
|
+
{
|
|
412
|
+
"operation": "execute",
|
|
413
|
+
**arguments,
|
|
414
|
+
}
|
|
415
|
+
)
|
|
416
|
+
except ValidationError as exc:
|
|
417
|
+
return _jsonrpc_error(request_id, -32602, "tool 参数校验失败", str(exc))
|
|
418
|
+
else:
|
|
419
|
+
return _jsonrpc_error(request_id, -32602, f"不支持的工具: {tool_name}")
|
|
420
|
+
|
|
421
|
+
try:
|
|
422
|
+
payload = self._execute_tool_request(request)
|
|
423
|
+
except Exception as exc:
|
|
424
|
+
return _jsonrpc_error(request_id, -32603, "工具执行失败", str(exc))
|
|
425
|
+
|
|
426
|
+
return _jsonrpc_response(request_id, _build_tool_result(payload))
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def run_stdio_mcp_server(
|
|
430
|
+
execute_tool_request: Callable[[ToolRequest], dict],
|
|
431
|
+
load_run_payload: Callable[[str], dict],
|
|
432
|
+
inspect_ui_payload: Callable[[ToolRequest], dict],
|
|
433
|
+
load_case_memory_payload: Callable[[ToolRequest], dict],
|
|
434
|
+
stdin=None,
|
|
435
|
+
stdout=None,
|
|
436
|
+
) -> int:
|
|
437
|
+
input_stream = stdin or sys.stdin
|
|
438
|
+
output_stream = stdout or sys.stdout
|
|
439
|
+
session = McpServerSession(
|
|
440
|
+
execute_tool_request=execute_tool_request,
|
|
441
|
+
load_run_payload=load_run_payload,
|
|
442
|
+
inspect_ui_payload=inspect_ui_payload,
|
|
443
|
+
load_case_memory_payload=load_case_memory_payload,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
for raw_line in input_stream:
|
|
447
|
+
line = str(raw_line).strip()
|
|
448
|
+
if not line:
|
|
449
|
+
continue
|
|
450
|
+
|
|
451
|
+
try:
|
|
452
|
+
message = json.loads(line)
|
|
453
|
+
except json.JSONDecodeError as exc:
|
|
454
|
+
response = _jsonrpc_error(None, -32700, "JSON parse error", str(exc))
|
|
455
|
+
else:
|
|
456
|
+
if isinstance(message, list):
|
|
457
|
+
response = _jsonrpc_error(None, -32600, "Batch requests are not supported")
|
|
458
|
+
else:
|
|
459
|
+
response = session.handle_message(message)
|
|
460
|
+
|
|
461
|
+
if response is None:
|
|
462
|
+
continue
|
|
463
|
+
|
|
464
|
+
output_stream.write(json.dumps(response, ensure_ascii=False) + "\n")
|
|
465
|
+
output_stream.flush()
|
|
466
|
+
|
|
467
|
+
return 0
|