jarvis-ai-assistant 0.1.193__py3-none-any.whl → 0.1.195__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.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_agent/__init__.py +45 -41
- jarvis/jarvis_agent/builtin_input_handler.py +26 -4
- jarvis/jarvis_agent/jarvis.py +30 -19
- jarvis/jarvis_agent/main.py +20 -12
- jarvis/jarvis_agent/output_handler.py +7 -7
- jarvis/jarvis_agent/shell_input_handler.py +14 -11
- jarvis/jarvis_code_agent/code_agent.py +81 -79
- jarvis/jarvis_code_agent/lint.py +92 -105
- jarvis/jarvis_code_analysis/checklists/__init__.py +1 -1
- jarvis/jarvis_code_analysis/checklists/c_cpp.py +1 -1
- jarvis/jarvis_code_analysis/checklists/csharp.py +1 -1
- jarvis/jarvis_code_analysis/checklists/data_format.py +1 -1
- jarvis/jarvis_code_analysis/checklists/devops.py +1 -1
- jarvis/jarvis_code_analysis/checklists/docs.py +1 -1
- jarvis/jarvis_code_analysis/checklists/go.py +1 -1
- jarvis/jarvis_code_analysis/checklists/infrastructure.py +1 -1
- jarvis/jarvis_code_analysis/checklists/java.py +1 -1
- jarvis/jarvis_code_analysis/checklists/javascript.py +1 -1
- jarvis/jarvis_code_analysis/checklists/kotlin.py +1 -1
- jarvis/jarvis_code_analysis/checklists/loader.py +31 -29
- jarvis/jarvis_code_analysis/checklists/php.py +1 -1
- jarvis/jarvis_code_analysis/checklists/python.py +1 -1
- jarvis/jarvis_code_analysis/checklists/ruby.py +1 -1
- jarvis/jarvis_code_analysis/checklists/rust.py +1 -1
- jarvis/jarvis_code_analysis/checklists/shell.py +1 -1
- jarvis/jarvis_code_analysis/checklists/sql.py +1 -1
- jarvis/jarvis_code_analysis/checklists/swift.py +1 -1
- jarvis/jarvis_code_analysis/checklists/web.py +1 -1
- jarvis/jarvis_code_analysis/code_review.py +292 -190
- jarvis/jarvis_dev/main.py +73 -56
- jarvis/jarvis_git_details/main.py +29 -33
- jarvis/jarvis_git_squash/main.py +13 -11
- jarvis/jarvis_git_utils/git_commiter.py +15 -5
- jarvis/jarvis_mcp/__init__.py +8 -10
- jarvis/jarvis_mcp/sse_mcp_client.py +182 -205
- jarvis/jarvis_mcp/stdio_mcp_client.py +93 -120
- jarvis/jarvis_mcp/streamable_mcp_client.py +117 -142
- jarvis/jarvis_methodology/main.py +71 -39
- jarvis/jarvis_multi_agent/__init__.py +24 -16
- jarvis/jarvis_multi_agent/main.py +10 -4
- jarvis/jarvis_platform/__init__.py +1 -1
- jarvis/jarvis_platform/base.py +44 -18
- jarvis/jarvis_platform/human.py +15 -3
- jarvis/jarvis_platform/kimi.py +117 -81
- jarvis/jarvis_platform/openai.py +23 -28
- jarvis/jarvis_platform/registry.py +43 -29
- jarvis/jarvis_platform/tongyi.py +16 -10
- jarvis/jarvis_platform/yuanbao.py +197 -144
- jarvis/jarvis_platform_manager/main.py +4 -2
- jarvis/jarvis_smart_shell/main.py +35 -30
- jarvis/jarvis_tools/ask_user.py +8 -16
- jarvis/jarvis_tools/base.py +3 -2
- jarvis/jarvis_tools/chdir.py +7 -19
- jarvis/jarvis_tools/cli/main.py +14 -10
- jarvis/jarvis_tools/code_plan.py +10 -31
- jarvis/jarvis_tools/create_code_agent.py +6 -11
- jarvis/jarvis_tools/create_sub_agent.py +10 -22
- jarvis/jarvis_tools/edit_file.py +98 -76
- jarvis/jarvis_tools/execute_script.py +46 -46
- jarvis/jarvis_tools/file_analyzer.py +22 -34
- jarvis/jarvis_tools/file_operation.py +69 -62
- jarvis/jarvis_tools/generate_new_tool.py +0 -2
- jarvis/jarvis_tools/methodology.py +19 -23
- jarvis/jarvis_tools/read_code.py +35 -35
- jarvis/jarvis_tools/read_webpage.py +7 -16
- jarvis/jarvis_tools/registry.py +63 -30
- jarvis/jarvis_tools/rewrite_file.py +26 -29
- jarvis/jarvis_tools/search_web.py +5 -8
- jarvis/jarvis_tools/virtual_tty.py +133 -122
- jarvis/jarvis_utils/__init__.py +0 -1
- jarvis/jarvis_utils/builtin_replace_map.py +9 -9
- jarvis/jarvis_utils/config.py +60 -37
- jarvis/jarvis_utils/embedding.py +24 -19
- jarvis/jarvis_utils/file_processors.py +16 -9
- jarvis/jarvis_utils/git_utils.py +157 -107
- jarvis/jarvis_utils/globals.py +1 -1
- jarvis/jarvis_utils/input.py +85 -52
- jarvis/jarvis_utils/jarvis_history.py +43 -0
- jarvis/jarvis_utils/methodology.py +31 -24
- jarvis/jarvis_utils/output.py +164 -80
- jarvis/jarvis_utils/tag.py +2 -1
- jarvis/jarvis_utils/utils.py +84 -52
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/METADATA +362 -230
- jarvis_ai_assistant-0.1.195.dist-info/RECORD +98 -0
- jarvis/jarvis_agent/file_input_handler.py +0 -112
- jarvis/jarvis_event/__init__.py +0 -0
- jarvis_ai_assistant-0.1.193.dist-info/RECORD +0 -99
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/WHEEL +0 -0
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/licenses/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.193.dist-info → jarvis_ai_assistant-0.1.195.dist-info}/top_level.txt +0 -0
@@ -13,46 +13,49 @@ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
13
13
|
|
14
14
|
class SSEMcpClient(McpClient):
|
15
15
|
"""远程MCP客户端实现
|
16
|
-
|
16
|
+
|
17
17
|
参数:
|
18
18
|
config: 配置字典,包含以下字段:
|
19
19
|
- base_url: str - MCP服务器的基础URL
|
20
20
|
- auth_token: str - 认证令牌(可选)
|
21
21
|
- headers: Dict[str, str] - 额外的HTTP头(可选)
|
22
22
|
"""
|
23
|
+
|
23
24
|
def __init__(self, config: Dict[str, Any]):
|
24
25
|
self.config = config
|
25
|
-
self.base_url = config.get(
|
26
|
+
self.base_url = config.get("base_url", "")
|
26
27
|
if not self.base_url:
|
27
|
-
raise ValueError(
|
28
|
-
|
28
|
+
raise ValueError("No base_url specified in config")
|
29
|
+
|
29
30
|
# 设置HTTP客户端
|
30
31
|
self.session = requests.Session()
|
31
|
-
self.session.headers.update(
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
self.session.headers.update(
|
33
|
+
{
|
34
|
+
"Content-Type": "application/json",
|
35
|
+
"Accept": "application/json",
|
36
|
+
}
|
37
|
+
)
|
38
|
+
|
36
39
|
# 添加认证令牌(如果提供)
|
37
|
-
auth_token = config.get(
|
40
|
+
auth_token = config.get("auth_token")
|
38
41
|
if auth_token:
|
39
|
-
self.session.headers[
|
40
|
-
|
42
|
+
self.session.headers["Authorization"] = f"Bearer {auth_token}"
|
43
|
+
|
41
44
|
# 添加额外的HTTP头
|
42
|
-
extra_headers = config.get(
|
45
|
+
extra_headers = config.get("headers", {})
|
43
46
|
self.session.headers.update(extra_headers)
|
44
|
-
|
47
|
+
|
45
48
|
# SSE相关属性
|
46
49
|
self.sse_response = None
|
47
50
|
self.sse_thread = None
|
48
51
|
self.messages_endpoint = None
|
49
|
-
self.session_id = None
|
52
|
+
self.session_id = None # 从SSE连接获取的会话ID
|
50
53
|
self.pending_requests = {} # 存储等待响应的请求 {id: Event}
|
51
|
-
self.request_results = {}
|
54
|
+
self.request_results = {} # 存储请求结果 {id: result}
|
52
55
|
self.notification_handlers = {}
|
53
56
|
self.event_lock = threading.Lock()
|
54
57
|
self.request_id_counter = 0
|
55
|
-
|
58
|
+
|
56
59
|
# 初始化连接
|
57
60
|
self._initialize()
|
58
61
|
|
@@ -61,36 +64,43 @@ class SSEMcpClient(McpClient):
|
|
61
64
|
try:
|
62
65
|
# 启动SSE连接
|
63
66
|
self._start_sse_connection()
|
64
|
-
|
67
|
+
|
65
68
|
# 等待获取消息端点和会话ID
|
66
69
|
start_time = time.time()
|
67
|
-
while (
|
70
|
+
while (
|
71
|
+
not self.messages_endpoint or not self.session_id
|
72
|
+
) and time.time() - start_time < 5:
|
68
73
|
time.sleep(0.1)
|
69
|
-
|
74
|
+
|
70
75
|
if not self.messages_endpoint:
|
71
76
|
self.messages_endpoint = "/messages" # 默认端点
|
72
|
-
PrettyOutput.print(
|
73
|
-
|
77
|
+
PrettyOutput.print(
|
78
|
+
f"未获取到消息端点,使用默认值: {self.messages_endpoint}",
|
79
|
+
OutputType.WARNING,
|
80
|
+
)
|
81
|
+
|
74
82
|
if not self.session_id:
|
75
83
|
PrettyOutput.print("未获取到会话ID", OutputType.WARNING)
|
76
|
-
|
84
|
+
|
77
85
|
# 发送初始化请求
|
78
|
-
response = self._send_request(
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
86
|
+
response = self._send_request(
|
87
|
+
"initialize",
|
88
|
+
{
|
89
|
+
"processId": None, # 远程客户端不需要进程ID
|
90
|
+
"clientInfo": {"name": "jarvis", "version": "1.0.0"},
|
91
|
+
"capabilities": {},
|
92
|
+
"protocolVersion": "2025-03-26",
|
83
93
|
},
|
84
|
-
|
85
|
-
'protocolVersion': "2025-03-26"
|
86
|
-
})
|
94
|
+
)
|
87
95
|
|
88
96
|
# 验证服务器响应
|
89
|
-
if
|
90
|
-
raise RuntimeError(
|
97
|
+
if "result" not in response:
|
98
|
+
raise RuntimeError(
|
99
|
+
f"初始化失败: {response.get('error', 'Unknown error')}"
|
100
|
+
)
|
91
101
|
|
92
102
|
# 发送initialized通知
|
93
|
-
self._send_notification(
|
103
|
+
self._send_notification("notifications/initialized", {})
|
94
104
|
|
95
105
|
except Exception as e:
|
96
106
|
PrettyOutput.print(f"MCP初始化失败: {str(e)}", OutputType.ERROR)
|
@@ -101,55 +111,58 @@ class SSEMcpClient(McpClient):
|
|
101
111
|
try:
|
102
112
|
# 设置SSE请求头
|
103
113
|
sse_headers = dict(self.session.headers)
|
104
|
-
sse_headers.update(
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
114
|
+
sse_headers.update(
|
115
|
+
{
|
116
|
+
"Accept": "text/event-stream",
|
117
|
+
"Cache-Control": "no-cache",
|
118
|
+
}
|
119
|
+
)
|
120
|
+
|
109
121
|
# 建立SSE连接
|
110
|
-
sse_url = urljoin(self.base_url,
|
122
|
+
sse_url = urljoin(self.base_url, "sse")
|
111
123
|
self.sse_response = self.session.get(
|
112
|
-
sse_url,
|
113
|
-
stream=True,
|
114
|
-
headers=sse_headers,
|
115
|
-
timeout=30
|
124
|
+
sse_url, stream=True, headers=sse_headers, timeout=30
|
116
125
|
)
|
117
126
|
self.sse_response.raise_for_status()
|
118
|
-
|
127
|
+
|
119
128
|
# 启动事件处理线程
|
120
|
-
self.sse_thread = threading.Thread(
|
129
|
+
self.sse_thread = threading.Thread(
|
130
|
+
target=self._process_sse_events, daemon=True
|
131
|
+
)
|
121
132
|
self.sse_thread.start()
|
122
|
-
|
133
|
+
|
123
134
|
except Exception as e:
|
124
135
|
PrettyOutput.print(f"SSE连接失败: {str(e)}", OutputType.ERROR)
|
125
136
|
raise
|
126
|
-
|
137
|
+
|
127
138
|
def _process_sse_events(self) -> None:
|
128
139
|
"""处理SSE事件流"""
|
129
140
|
if not self.sse_response:
|
130
141
|
return
|
131
|
-
|
142
|
+
|
132
143
|
buffer = ""
|
133
144
|
for line in self.sse_response.iter_lines(decode_unicode=True):
|
134
145
|
if line:
|
135
146
|
if line.startswith("data:"):
|
136
147
|
data = line[5:].strip()
|
137
148
|
# 检查是否包含消息端点信息
|
138
|
-
if data.startswith(
|
149
|
+
if data.startswith("/"):
|
139
150
|
# 这是消息端点信息,例如 "/messages/?session_id=xyz"
|
140
151
|
try:
|
141
152
|
# 提取消息端点路径和会话ID
|
142
|
-
url_parts = data.split(
|
153
|
+
url_parts = data.split("?")
|
143
154
|
self.messages_endpoint = url_parts[0]
|
144
|
-
|
155
|
+
|
145
156
|
# 如果有查询参数,尝试提取session_id
|
146
157
|
if len(url_parts) > 1:
|
147
158
|
query_string = url_parts[1]
|
148
159
|
query_params = parse_qs(query_string)
|
149
|
-
if
|
150
|
-
self.session_id = query_params[
|
160
|
+
if "session_id" in query_params:
|
161
|
+
self.session_id = query_params["session_id"][0]
|
151
162
|
except Exception as e:
|
152
|
-
PrettyOutput.print(
|
163
|
+
PrettyOutput.print(
|
164
|
+
f"解析消息端点或会话ID失败: {e}", OutputType.WARNING
|
165
|
+
)
|
153
166
|
else:
|
154
167
|
buffer += data
|
155
168
|
elif line.startswith(":"): # 忽略注释行
|
@@ -167,28 +180,28 @@ class SSEMcpClient(McpClient):
|
|
167
180
|
except Exception as e:
|
168
181
|
PrettyOutput.print(f"处理SSE事件出错: {e}", OutputType.ERROR)
|
169
182
|
buffer = ""
|
170
|
-
|
183
|
+
|
171
184
|
PrettyOutput.print("SSE连接已关闭", OutputType.WARNING)
|
172
185
|
|
173
186
|
def _handle_sse_event(self, data: str) -> None:
|
174
187
|
"""处理单个SSE事件数据"""
|
175
188
|
try:
|
176
189
|
event_data = json.loads(data)
|
177
|
-
|
190
|
+
|
178
191
|
# 检查是请求响应还是通知
|
179
|
-
if
|
192
|
+
if "id" in event_data:
|
180
193
|
# 这是一个请求的响应
|
181
|
-
req_id = event_data[
|
194
|
+
req_id = event_data["id"]
|
182
195
|
with self.event_lock:
|
183
196
|
self.request_results[req_id] = event_data
|
184
197
|
if req_id in self.pending_requests:
|
185
198
|
# 通知等待线程响应已到达
|
186
199
|
self.pending_requests[req_id].set()
|
187
|
-
elif
|
200
|
+
elif "method" in event_data:
|
188
201
|
# 这是一个通知
|
189
|
-
method = event_data.get(
|
190
|
-
params = event_data.get(
|
191
|
-
|
202
|
+
method = event_data.get("method", "")
|
203
|
+
params = event_data.get("params", {})
|
204
|
+
|
192
205
|
# 调用已注册的处理器
|
193
206
|
if method in self.notification_handlers:
|
194
207
|
for handler in self.notification_handlers[method]:
|
@@ -196,8 +209,7 @@ class SSEMcpClient(McpClient):
|
|
196
209
|
handler(params)
|
197
210
|
except Exception as e:
|
198
211
|
PrettyOutput.print(
|
199
|
-
f"处理通知时出错 ({method}): {e}",
|
200
|
-
OutputType.ERROR
|
212
|
+
f"处理通知时出错 ({method}): {e}", OutputType.ERROR
|
201
213
|
)
|
202
214
|
except json.JSONDecodeError:
|
203
215
|
PrettyOutput.print(f"无法解析SSE事件: {data}", OutputType.WARNING)
|
@@ -206,7 +218,7 @@ class SSEMcpClient(McpClient):
|
|
206
218
|
|
207
219
|
def register_notification_handler(self, method: str, handler: Callable) -> None:
|
208
220
|
"""注册通知处理器
|
209
|
-
|
221
|
+
|
210
222
|
参数:
|
211
223
|
method: 通知方法名
|
212
224
|
handler: 处理通知的回调函数,接收params参数
|
@@ -218,7 +230,7 @@ class SSEMcpClient(McpClient):
|
|
218
230
|
|
219
231
|
def unregister_notification_handler(self, method: str, handler: Callable) -> None:
|
220
232
|
"""注销通知处理器
|
221
|
-
|
233
|
+
|
222
234
|
参数:
|
223
235
|
method: 通知方法名
|
224
236
|
handler: 要注销的处理器函数
|
@@ -238,88 +250,82 @@ class SSEMcpClient(McpClient):
|
|
238
250
|
|
239
251
|
def _send_request(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
240
252
|
"""发送请求到MCP服务器
|
241
|
-
|
253
|
+
|
242
254
|
参数:
|
243
255
|
method: 请求方法
|
244
256
|
params: 请求参数
|
245
|
-
|
257
|
+
|
246
258
|
返回:
|
247
259
|
Dict[str, Any]: 响应结果
|
248
260
|
"""
|
249
261
|
# 生成唯一请求ID
|
250
262
|
req_id = self._get_next_request_id()
|
251
|
-
|
263
|
+
|
252
264
|
# 创建事件标志,用于等待响应
|
253
265
|
event = threading.Event()
|
254
|
-
|
266
|
+
|
255
267
|
with self.event_lock:
|
256
268
|
self.pending_requests[req_id] = event
|
257
|
-
|
269
|
+
|
258
270
|
try:
|
259
271
|
# 构建请求
|
260
272
|
request = {
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
273
|
+
"jsonrpc": "2.0",
|
274
|
+
"method": method,
|
275
|
+
"params": params,
|
276
|
+
"id": req_id,
|
265
277
|
}
|
266
278
|
|
267
279
|
# 尝试不同的请求发送方式
|
268
280
|
if self.session_id:
|
269
281
|
# 方法1: 使用查询参数中的session_id
|
270
|
-
query_params = {
|
282
|
+
query_params = {"session_id": self.session_id}
|
271
283
|
messages_url = urljoin(self.base_url, self.messages_endpoint)
|
272
|
-
|
284
|
+
|
273
285
|
# 尝试直接使用原始URL(不追加查询参数)
|
274
286
|
try:
|
275
|
-
post_response = self.session.post(
|
276
|
-
messages_url,
|
277
|
-
json=request
|
278
|
-
)
|
287
|
+
post_response = self.session.post(messages_url, json=request)
|
279
288
|
post_response.raise_for_status()
|
280
289
|
except requests.HTTPError:
|
281
290
|
# 如果失败,尝试添加会话ID到查询参数
|
282
|
-
messages_url_with_session =
|
291
|
+
messages_url_with_session = (
|
292
|
+
f"{messages_url}?{urlencode(query_params)}"
|
293
|
+
)
|
283
294
|
post_response = self.session.post(
|
284
|
-
messages_url_with_session,
|
285
|
-
json=request
|
295
|
+
messages_url_with_session, json=request
|
286
296
|
)
|
287
297
|
post_response.raise_for_status()
|
288
298
|
else:
|
289
299
|
# 方法2: 不使用session_id
|
290
300
|
if not self.messages_endpoint:
|
291
301
|
self.messages_endpoint = "/messages"
|
292
|
-
|
302
|
+
|
293
303
|
messages_url = urljoin(self.base_url, self.messages_endpoint)
|
294
|
-
|
304
|
+
|
295
305
|
# 尝试直接使用messages端点而不带任何查询参数
|
296
306
|
try:
|
297
307
|
# 尝试1: 标准JSON-RPC格式
|
298
|
-
post_response = self.session.post(
|
299
|
-
messages_url,
|
300
|
-
json=request
|
301
|
-
)
|
308
|
+
post_response = self.session.post(messages_url, json=request)
|
302
309
|
post_response.raise_for_status()
|
303
310
|
except requests.HTTPError:
|
304
311
|
# 尝试2: JSON字符串作为请求参数
|
305
312
|
post_response = self.session.post(
|
306
|
-
messages_url,
|
307
|
-
params={'request': json.dumps(request)}
|
313
|
+
messages_url, params={"request": json.dumps(request)}
|
308
314
|
)
|
309
315
|
post_response.raise_for_status()
|
310
|
-
|
316
|
+
|
311
317
|
# 等待SSE通道返回响应(最多30秒)
|
312
318
|
if not event.wait(timeout=30):
|
313
319
|
raise TimeoutError(f"等待响应超时: {method}")
|
314
|
-
|
320
|
+
|
315
321
|
# 获取响应结果
|
316
322
|
with self.event_lock:
|
317
323
|
result = self.request_results.pop(req_id, None)
|
318
324
|
self.pending_requests.pop(req_id, None)
|
319
|
-
|
325
|
+
|
320
326
|
if result is None:
|
321
327
|
raise RuntimeError(f"未收到响应: {method}")
|
322
|
-
|
328
|
+
|
323
329
|
return result
|
324
330
|
|
325
331
|
except Exception as e:
|
@@ -327,66 +333,58 @@ class SSEMcpClient(McpClient):
|
|
327
333
|
with self.event_lock:
|
328
334
|
self.pending_requests.pop(req_id, None)
|
329
335
|
self.request_results.pop(req_id, None)
|
330
|
-
|
336
|
+
|
331
337
|
PrettyOutput.print(f"发送请求失败: {str(e)}", OutputType.ERROR)
|
332
338
|
raise
|
333
339
|
|
334
340
|
def _send_notification(self, method: str, params: Dict[str, Any]) -> None:
|
335
341
|
"""发送通知到MCP服务器(不需要响应)
|
336
|
-
|
342
|
+
|
337
343
|
参数:
|
338
344
|
method: 通知方法
|
339
345
|
params: 通知参数
|
340
346
|
"""
|
341
347
|
try:
|
342
348
|
# 构建通知
|
343
|
-
notification = {
|
344
|
-
'jsonrpc': '2.0',
|
345
|
-
'method': method,
|
346
|
-
'params': params
|
347
|
-
}
|
349
|
+
notification = {"jsonrpc": "2.0", "method": method, "params": params}
|
348
350
|
|
349
351
|
# 尝试不同的请求发送方式,与_send_request保持一致
|
350
352
|
if self.session_id:
|
351
353
|
# 方法1: 使用查询参数中的session_id
|
352
|
-
query_params = {
|
353
|
-
messages_url = urljoin(
|
354
|
-
|
354
|
+
query_params = {"session_id": self.session_id}
|
355
|
+
messages_url = urljoin(
|
356
|
+
self.base_url, self.messages_endpoint or "/messages"
|
357
|
+
)
|
358
|
+
|
355
359
|
# 尝试直接使用原始URL(不追加查询参数)
|
356
360
|
try:
|
357
|
-
post_response = self.session.post(
|
358
|
-
messages_url,
|
359
|
-
json=notification
|
360
|
-
)
|
361
|
+
post_response = self.session.post(messages_url, json=notification)
|
361
362
|
post_response.raise_for_status()
|
362
363
|
except requests.HTTPError:
|
363
364
|
# 如果失败,尝试添加会话ID到查询参数
|
364
|
-
messages_url_with_session =
|
365
|
+
messages_url_with_session = (
|
366
|
+
f"{messages_url}?{urlencode(query_params)}"
|
367
|
+
)
|
365
368
|
post_response = self.session.post(
|
366
|
-
messages_url_with_session,
|
367
|
-
json=notification
|
369
|
+
messages_url_with_session, json=notification
|
368
370
|
)
|
369
371
|
post_response.raise_for_status()
|
370
372
|
else:
|
371
373
|
# 方法2: 不使用session_id
|
372
374
|
if not self.messages_endpoint:
|
373
375
|
self.messages_endpoint = "/messages"
|
374
|
-
|
376
|
+
|
375
377
|
messages_url = urljoin(self.base_url, self.messages_endpoint)
|
376
|
-
|
378
|
+
|
377
379
|
# 尝试直接使用messages端点而不带任何查询参数
|
378
380
|
try:
|
379
381
|
# 尝试1: 标准JSON-RPC格式
|
380
|
-
post_response = self.session.post(
|
381
|
-
messages_url,
|
382
|
-
json=notification
|
383
|
-
)
|
382
|
+
post_response = self.session.post(messages_url, json=notification)
|
384
383
|
post_response.raise_for_status()
|
385
384
|
except requests.HTTPError:
|
386
385
|
# 尝试2: JSON字符串作为请求参数
|
387
386
|
post_response = self.session.post(
|
388
|
-
messages_url,
|
389
|
-
params={'request': json.dumps(notification)}
|
387
|
+
messages_url, params={"request": json.dumps(notification)}
|
390
388
|
)
|
391
389
|
post_response.raise_for_status()
|
392
390
|
|
@@ -396,7 +394,7 @@ class SSEMcpClient(McpClient):
|
|
396
394
|
|
397
395
|
def get_tool_list(self) -> List[Dict[str, Any]]:
|
398
396
|
"""获取工具列表
|
399
|
-
|
397
|
+
|
400
398
|
返回:
|
401
399
|
List[Dict[str, Any]]: 工具列表,每个工具包含以下字段:
|
402
400
|
- name: str - 工具名称
|
@@ -404,34 +402,36 @@ class SSEMcpClient(McpClient):
|
|
404
402
|
- parameters: Dict - 工具参数
|
405
403
|
"""
|
406
404
|
try:
|
407
|
-
response = self._send_request(
|
408
|
-
if
|
405
|
+
response = self._send_request("tools/list", {})
|
406
|
+
if "result" in response and "tools" in response["result"]:
|
409
407
|
# 注意这里: 响应结构是 response['result']['tools']
|
410
|
-
tools = response[
|
408
|
+
tools = response["result"]["tools"]
|
411
409
|
# 将MCP协议字段转换为内部格式
|
412
410
|
formatted_tools = []
|
413
411
|
for tool in tools:
|
414
412
|
# 从inputSchema中提取参数定义
|
415
|
-
input_schema = tool.get(
|
413
|
+
input_schema = tool.get("inputSchema", {})
|
416
414
|
parameters = {}
|
417
|
-
if
|
418
|
-
parameters = input_schema[
|
419
|
-
|
420
|
-
formatted_tools.append(
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
415
|
+
if "properties" in input_schema:
|
416
|
+
parameters = input_schema["properties"]
|
417
|
+
|
418
|
+
formatted_tools.append(
|
419
|
+
{
|
420
|
+
"name": tool.get("name", ""),
|
421
|
+
"description": tool.get("description", ""),
|
422
|
+
"parameters": parameters,
|
423
|
+
}
|
424
|
+
)
|
425
425
|
return formatted_tools
|
426
426
|
else:
|
427
427
|
error_msg = "获取工具列表失败"
|
428
|
-
if
|
428
|
+
if "error" in response:
|
429
429
|
error_msg += f": {response['error']}"
|
430
|
-
elif
|
430
|
+
elif "result" in response:
|
431
431
|
error_msg += f": 响应格式不正确 - {response['result']}"
|
432
432
|
else:
|
433
433
|
error_msg += ": 未知错误"
|
434
|
-
|
434
|
+
|
435
435
|
PrettyOutput.print(error_msg, OutputType.ERROR)
|
436
436
|
return []
|
437
437
|
except Exception as e:
|
@@ -440,11 +440,11 @@ class SSEMcpClient(McpClient):
|
|
440
440
|
|
441
441
|
def execute(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
442
442
|
"""执行工具
|
443
|
-
|
443
|
+
|
444
444
|
参数:
|
445
445
|
tool_name: 工具名称
|
446
446
|
arguments: 参数字典,包含工具执行所需的参数
|
447
|
-
|
447
|
+
|
448
448
|
返回:
|
449
449
|
Dict[str, Any]: 执行结果,包含以下字段:
|
450
450
|
- success: bool - 是否执行成功
|
@@ -452,43 +452,34 @@ class SSEMcpClient(McpClient):
|
|
452
452
|
- stderr: str - 标准错误
|
453
453
|
"""
|
454
454
|
try:
|
455
|
-
response = self._send_request(
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
result = response['result']
|
455
|
+
response = self._send_request(
|
456
|
+
"tools/call", {"name": tool_name, "arguments": arguments}
|
457
|
+
)
|
458
|
+
if "result" in response:
|
459
|
+
result = response["result"]
|
461
460
|
# 从content中提取输出信息
|
462
|
-
stdout =
|
463
|
-
stderr =
|
464
|
-
for content in result.get(
|
465
|
-
if content.get(
|
466
|
-
stdout += content.get(
|
467
|
-
elif content.get(
|
468
|
-
stderr += content.get(
|
469
|
-
|
470
|
-
return {
|
471
|
-
'success': True,
|
472
|
-
'stdout': stdout,
|
473
|
-
'stderr': stderr
|
474
|
-
}
|
461
|
+
stdout = ""
|
462
|
+
stderr = ""
|
463
|
+
for content in result.get("content", []):
|
464
|
+
if content.get("type") == "text":
|
465
|
+
stdout += content.get("text", "")
|
466
|
+
elif content.get("type") == "error":
|
467
|
+
stderr += content.get("text", "")
|
468
|
+
|
469
|
+
return {"success": True, "stdout": stdout, "stderr": stderr}
|
475
470
|
else:
|
476
471
|
return {
|
477
|
-
|
478
|
-
|
479
|
-
|
472
|
+
"success": False,
|
473
|
+
"stdout": "",
|
474
|
+
"stderr": response.get("error", "Unknown error"),
|
480
475
|
}
|
481
476
|
except Exception as e:
|
482
477
|
PrettyOutput.print(f"执行工具失败: {str(e)}", OutputType.ERROR)
|
483
|
-
return {
|
484
|
-
'success': False,
|
485
|
-
'stdout': '',
|
486
|
-
'stderr': str(e)
|
487
|
-
}
|
478
|
+
return {"success": False, "stdout": "", "stderr": str(e)}
|
488
479
|
|
489
480
|
def get_resource_list(self) -> List[Dict[str, Any]]:
|
490
481
|
"""获取资源列表
|
491
|
-
|
482
|
+
|
492
483
|
返回:
|
493
484
|
List[Dict[str, Any]]: 资源列表,每个资源包含以下字段:
|
494
485
|
- uri: str - 资源的唯一标识符
|
@@ -497,12 +488,12 @@ class SSEMcpClient(McpClient):
|
|
497
488
|
- mimeType: str - 资源的MIME类型(可选)
|
498
489
|
"""
|
499
490
|
try:
|
500
|
-
response = self._send_request(
|
501
|
-
if
|
502
|
-
return response[
|
491
|
+
response = self._send_request("resources/list", {})
|
492
|
+
if "result" in response and "resources" in response["result"]:
|
493
|
+
return response["result"]["resources"]
|
503
494
|
else:
|
504
495
|
error_msg = "获取资源列表失败"
|
505
|
-
if
|
496
|
+
if "error" in response:
|
506
497
|
error_msg += f": {response['error']}"
|
507
498
|
else:
|
508
499
|
error_msg += ": 未知错误"
|
@@ -514,10 +505,10 @@ class SSEMcpClient(McpClient):
|
|
514
505
|
|
515
506
|
def get_resource(self, uri: str) -> Dict[str, Any]:
|
516
507
|
"""获取指定资源的内容
|
517
|
-
|
508
|
+
|
518
509
|
参数:
|
519
510
|
uri: str - 资源的URI标识符
|
520
|
-
|
511
|
+
|
521
512
|
返回:
|
522
513
|
Dict[str, Any]: 执行结果,包含以下字段:
|
523
514
|
- success: bool - 是否执行成功
|
@@ -525,51 +516,37 @@ class SSEMcpClient(McpClient):
|
|
525
516
|
- stderr: str - 错误信息
|
526
517
|
"""
|
527
518
|
try:
|
528
|
-
response = self._send_request(
|
529
|
-
|
530
|
-
|
531
|
-
if 'result' in response and 'contents' in response['result']:
|
532
|
-
contents = response['result']['contents']
|
519
|
+
response = self._send_request("resources/read", {"uri": uri})
|
520
|
+
if "result" in response and "contents" in response["result"]:
|
521
|
+
contents = response["result"]["contents"]
|
533
522
|
if contents:
|
534
523
|
content = contents[0] # 获取第一个资源内容
|
535
524
|
# 根据资源类型返回内容
|
536
|
-
if
|
525
|
+
if "text" in content:
|
537
526
|
return {
|
538
|
-
|
539
|
-
|
540
|
-
|
527
|
+
"success": True,
|
528
|
+
"stdout": content["text"],
|
529
|
+
"stderr": "",
|
541
530
|
}
|
542
|
-
elif
|
531
|
+
elif "blob" in content:
|
543
532
|
return {
|
544
|
-
|
545
|
-
|
546
|
-
|
533
|
+
"success": True,
|
534
|
+
"stdout": content["blob"],
|
535
|
+
"stderr": "",
|
547
536
|
}
|
548
|
-
return {
|
549
|
-
'success': False,
|
550
|
-
'stdout': '',
|
551
|
-
'stderr': '资源内容为空'
|
552
|
-
}
|
537
|
+
return {"success": False, "stdout": "", "stderr": "资源内容为空"}
|
553
538
|
else:
|
554
539
|
error_msg = "获取资源内容失败"
|
555
|
-
if
|
540
|
+
if "error" in response:
|
556
541
|
error_msg += f": {response['error']}"
|
557
542
|
else:
|
558
543
|
error_msg += ": 未知错误"
|
559
544
|
PrettyOutput.print(error_msg, OutputType.ERROR)
|
560
|
-
return {
|
561
|
-
'success': False,
|
562
|
-
'stdout': '',
|
563
|
-
'stderr': error_msg
|
564
|
-
}
|
545
|
+
return {"success": False, "stdout": "", "stderr": error_msg}
|
565
546
|
except Exception as e:
|
566
547
|
error_msg = f"获取资源内容失败: {str(e)}"
|
567
548
|
PrettyOutput.print(error_msg, OutputType.ERROR)
|
568
|
-
return {
|
569
|
-
'success': False,
|
570
|
-
'stdout': '',
|
571
|
-
'stderr': error_msg
|
572
|
-
}
|
549
|
+
return {"success": False, "stdout": "", "stderr": error_msg}
|
573
550
|
|
574
551
|
def __del__(self):
|
575
552
|
"""清理资源"""
|
@@ -579,14 +556,14 @@ class SSEMcpClient(McpClient):
|
|
579
556
|
event.set() # 释放所有等待的请求
|
580
557
|
self.pending_requests.clear()
|
581
558
|
self.request_results.clear()
|
582
|
-
|
559
|
+
|
583
560
|
# 关闭SSE响应
|
584
561
|
if self.sse_response:
|
585
562
|
try:
|
586
563
|
self.sse_response.close()
|
587
564
|
except:
|
588
565
|
pass
|
589
|
-
|
566
|
+
|
590
567
|
# 关闭HTTP会话
|
591
568
|
if self.session:
|
592
569
|
self.session.close()
|