jarvis-ai-assistant 0.1.150__py3-none-any.whl → 0.1.151__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.
Potentially problematic release.
This version of jarvis-ai-assistant might be problematic. Click here for more details.
- jarvis/__init__.py +1 -1
- jarvis/jarvis_git_utils/git_commiter.py +7 -0
- jarvis/jarvis_mcp/__init__.py +29 -0
- jarvis/jarvis_mcp/local_mcp_client.py +85 -0
- jarvis/jarvis_mcp/remote_mcp_client.py +389 -29
- jarvis/jarvis_tools/ask_codebase.py +3 -10
- jarvis/jarvis_tools/registry.py +125 -83
- jarvis/jarvis_utils/config.py +1 -1
- jarvis/jarvis_utils/methodology.py +2 -2
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.151.dist-info}/METADATA +32 -3
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.151.dist-info}/RECORD +15 -15
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.151.dist-info}/WHEEL +1 -1
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.151.dist-info}/entry_points.txt +0 -0
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.151.dist-info/licenses}/LICENSE +0 -0
- {jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.151.dist-info}/top_level.txt +0 -0
jarvis/__init__.py
CHANGED
|
@@ -177,6 +177,13 @@ class GitCommitTool:
|
|
|
177
177
|
# 如果成功提取,就跳出循环
|
|
178
178
|
if commit_message:
|
|
179
179
|
break
|
|
180
|
+
prompt = f"""格式错误,请按照以下格式重新生成提交信息:
|
|
181
|
+
{ot("COMMIT_MESSAGE")}
|
|
182
|
+
<类型>(<范围>): <主题>
|
|
183
|
+
|
|
184
|
+
[可选] 详细描述变更内容和原因
|
|
185
|
+
{ct("COMMIT_MESSAGE")}
|
|
186
|
+
"""
|
|
180
187
|
spinner.write("✅ 生成提交消息")
|
|
181
188
|
|
|
182
189
|
# 执行提交
|
jarvis/jarvis_mcp/__init__.py
CHANGED
|
@@ -33,4 +33,33 @@ class McpClient(ABC):
|
|
|
33
33
|
"""
|
|
34
34
|
pass
|
|
35
35
|
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def get_resource_list(self) -> List[Dict[str, Any]]:
|
|
38
|
+
"""获取资源列表
|
|
39
|
+
|
|
40
|
+
返回:
|
|
41
|
+
List[Dict[str, Any]]: 资源列表,每个资源包含以下字段:
|
|
42
|
+
- uri: str - 资源的唯一标识符
|
|
43
|
+
- name: str - 资源的名称
|
|
44
|
+
- description: str - 资源的描述(可选)
|
|
45
|
+
- mimeType: str - 资源的MIME类型(可选)
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def get_resource(self, uri: str) -> Dict[str, Any]:
|
|
51
|
+
"""获取指定资源的内容
|
|
52
|
+
|
|
53
|
+
参数:
|
|
54
|
+
uri: str - 资源的URI标识符
|
|
55
|
+
|
|
56
|
+
返回:
|
|
57
|
+
Dict[str, Any]: 资源内容,包含以下字段:
|
|
58
|
+
- uri: str - 资源的URI
|
|
59
|
+
- mimeType: str - 资源的MIME类型(可选)
|
|
60
|
+
- text: str - 文本内容(如果是文本资源)
|
|
61
|
+
- blob: str - 二进制内容(如果是二进制资源,base64编码)
|
|
62
|
+
"""
|
|
63
|
+
pass
|
|
64
|
+
|
|
36
65
|
|
|
@@ -228,6 +228,91 @@ class LocalMcpClient(McpClient):
|
|
|
228
228
|
'stderr': str(e)
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
+
def get_resource_list(self) -> List[Dict[str, Any]]:
|
|
232
|
+
"""获取资源列表
|
|
233
|
+
|
|
234
|
+
返回:
|
|
235
|
+
List[Dict[str, Any]]: 资源列表,每个资源包含以下字段:
|
|
236
|
+
- uri: str - 资源的唯一标识符
|
|
237
|
+
- name: str - 资源的名称
|
|
238
|
+
- description: str - 资源的描述(可选)
|
|
239
|
+
- mimeType: str - 资源的MIME类型(可选)
|
|
240
|
+
"""
|
|
241
|
+
try:
|
|
242
|
+
response = self._send_request('resources/list', {})
|
|
243
|
+
if 'result' in response and 'resources' in response['result']:
|
|
244
|
+
return response['result']['resources']
|
|
245
|
+
else:
|
|
246
|
+
error_msg = "获取资源列表失败"
|
|
247
|
+
if 'error' in response:
|
|
248
|
+
error_msg += f": {response['error']}"
|
|
249
|
+
else:
|
|
250
|
+
error_msg += ": 未知错误"
|
|
251
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
252
|
+
return []
|
|
253
|
+
except Exception as e:
|
|
254
|
+
PrettyOutput.print(f"获取资源列表失败: {str(e)}", OutputType.ERROR)
|
|
255
|
+
return []
|
|
256
|
+
|
|
257
|
+
def get_resource(self, uri: str) -> Dict[str, Any]:
|
|
258
|
+
"""获取指定资源的内容
|
|
259
|
+
|
|
260
|
+
参数:
|
|
261
|
+
uri: str - 资源的URI标识符
|
|
262
|
+
|
|
263
|
+
返回:
|
|
264
|
+
Dict[str, Any]: 执行结果,包含以下字段:
|
|
265
|
+
- success: bool - 是否执行成功
|
|
266
|
+
- stdout: str - 资源内容(文本或base64编码的二进制内容)
|
|
267
|
+
- stderr: str - 错误信息
|
|
268
|
+
"""
|
|
269
|
+
try:
|
|
270
|
+
response = self._send_request('resources/read', {
|
|
271
|
+
'uri': uri
|
|
272
|
+
})
|
|
273
|
+
if 'result' in response and 'contents' in response['result']:
|
|
274
|
+
contents = response['result']['contents']
|
|
275
|
+
if contents:
|
|
276
|
+
content = contents[0] # 获取第一个资源内容
|
|
277
|
+
# 根据资源类型返回内容
|
|
278
|
+
if 'text' in content:
|
|
279
|
+
return {
|
|
280
|
+
'success': True,
|
|
281
|
+
'stdout': content['text'],
|
|
282
|
+
'stderr': ''
|
|
283
|
+
}
|
|
284
|
+
elif 'blob' in content:
|
|
285
|
+
return {
|
|
286
|
+
'success': True,
|
|
287
|
+
'stdout': content['blob'],
|
|
288
|
+
'stderr': ''
|
|
289
|
+
}
|
|
290
|
+
return {
|
|
291
|
+
'success': False,
|
|
292
|
+
'stdout': '',
|
|
293
|
+
'stderr': '资源内容为空'
|
|
294
|
+
}
|
|
295
|
+
else:
|
|
296
|
+
error_msg = "获取资源内容失败"
|
|
297
|
+
if 'error' in response:
|
|
298
|
+
error_msg += f": {response['error']}"
|
|
299
|
+
else:
|
|
300
|
+
error_msg += ": 未知错误"
|
|
301
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
302
|
+
return {
|
|
303
|
+
'success': False,
|
|
304
|
+
'stdout': '',
|
|
305
|
+
'stderr': error_msg
|
|
306
|
+
}
|
|
307
|
+
except Exception as e:
|
|
308
|
+
error_msg = f"获取资源内容失败: {str(e)}"
|
|
309
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
310
|
+
return {
|
|
311
|
+
'success': False,
|
|
312
|
+
'stdout': '',
|
|
313
|
+
'stderr': error_msg
|
|
314
|
+
}
|
|
315
|
+
|
|
231
316
|
def __del__(self):
|
|
232
317
|
"""清理资源"""
|
|
233
318
|
if self.process:
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
from typing import Any, Dict, List
|
|
1
|
+
from typing import Any, Dict, List, Optional, Iterator, Callable
|
|
2
2
|
import requests
|
|
3
|
-
import
|
|
4
|
-
|
|
3
|
+
import json
|
|
4
|
+
import threading
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
from urllib.parse import urljoin, urlencode, parse_qs
|
|
5
8
|
from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
6
9
|
from . import McpClient
|
|
7
10
|
|
|
@@ -37,13 +40,38 @@ class RemoteMcpClient(McpClient):
|
|
|
37
40
|
extra_headers = config.get('headers', {})
|
|
38
41
|
self.session.headers.update(extra_headers)
|
|
39
42
|
|
|
40
|
-
#
|
|
41
|
-
self.
|
|
43
|
+
# SSE相关属性
|
|
44
|
+
self.sse_response = None
|
|
45
|
+
self.sse_thread = None
|
|
46
|
+
self.messages_endpoint = None
|
|
47
|
+
self.session_id = None # 从SSE连接获取的会话ID
|
|
48
|
+
self.pending_requests = {} # 存储等待响应的请求 {id: Event}
|
|
49
|
+
self.request_results = {} # 存储请求结果 {id: result}
|
|
50
|
+
self.notification_handlers = {}
|
|
51
|
+
self.event_lock = threading.Lock()
|
|
52
|
+
self.request_id_counter = 0
|
|
53
|
+
|
|
54
|
+
# 初始化连接
|
|
42
55
|
self._initialize()
|
|
43
56
|
|
|
44
57
|
def _initialize(self) -> None:
|
|
45
58
|
"""初始化MCP连接"""
|
|
46
59
|
try:
|
|
60
|
+
# 启动SSE连接
|
|
61
|
+
self._start_sse_connection()
|
|
62
|
+
|
|
63
|
+
# 等待获取消息端点和会话ID
|
|
64
|
+
start_time = time.time()
|
|
65
|
+
while (not self.messages_endpoint or not self.session_id) and time.time() - start_time < 5:
|
|
66
|
+
time.sleep(0.1)
|
|
67
|
+
|
|
68
|
+
if not self.messages_endpoint:
|
|
69
|
+
self.messages_endpoint = "/messages" # 默认端点
|
|
70
|
+
PrettyOutput.print(f"未获取到消息端点,使用默认值: {self.messages_endpoint}", OutputType.WARNING)
|
|
71
|
+
|
|
72
|
+
if not self.session_id:
|
|
73
|
+
PrettyOutput.print("未获取到会话ID", OutputType.WARNING)
|
|
74
|
+
|
|
47
75
|
# 发送初始化请求
|
|
48
76
|
response = self._send_request('initialize', {
|
|
49
77
|
'processId': None, # 远程客户端不需要进程ID
|
|
@@ -59,20 +87,153 @@ class RemoteMcpClient(McpClient):
|
|
|
59
87
|
if 'result' not in response:
|
|
60
88
|
raise RuntimeError(f"初始化失败: {response.get('error', 'Unknown error')}")
|
|
61
89
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
# 发送initialized通知 - 使用正确的方法名格式
|
|
90
|
+
# 发送initialized通知
|
|
65
91
|
self._send_notification('notifications/initialized', {})
|
|
66
92
|
|
|
67
|
-
# 建立SSE连接
|
|
68
|
-
sse_url = urljoin(self.base_url, 'events')
|
|
69
|
-
response = self.session.get(sse_url, stream=True)
|
|
70
|
-
self.sse_client = sseclient.SSEClient(response)
|
|
71
|
-
|
|
72
93
|
except Exception as e:
|
|
73
94
|
PrettyOutput.print(f"MCP初始化失败: {str(e)}", OutputType.ERROR)
|
|
74
95
|
raise
|
|
75
96
|
|
|
97
|
+
def _start_sse_connection(self) -> None:
|
|
98
|
+
"""建立SSE连接并启动处理线程"""
|
|
99
|
+
try:
|
|
100
|
+
# 设置SSE请求头
|
|
101
|
+
sse_headers = dict(self.session.headers)
|
|
102
|
+
sse_headers.update({
|
|
103
|
+
'Accept': 'text/event-stream',
|
|
104
|
+
'Cache-Control': 'no-cache',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
# 建立SSE连接
|
|
108
|
+
sse_url = urljoin(self.base_url, 'sse')
|
|
109
|
+
self.sse_response = self.session.get(
|
|
110
|
+
sse_url,
|
|
111
|
+
stream=True,
|
|
112
|
+
headers=sse_headers,
|
|
113
|
+
timeout=30
|
|
114
|
+
)
|
|
115
|
+
self.sse_response.raise_for_status()
|
|
116
|
+
|
|
117
|
+
# 启动事件处理线程
|
|
118
|
+
self.sse_thread = threading.Thread(target=self._process_sse_events, daemon=True)
|
|
119
|
+
self.sse_thread.start()
|
|
120
|
+
|
|
121
|
+
except Exception as e:
|
|
122
|
+
PrettyOutput.print(f"SSE连接失败: {str(e)}", OutputType.ERROR)
|
|
123
|
+
raise
|
|
124
|
+
|
|
125
|
+
def _process_sse_events(self) -> None:
|
|
126
|
+
"""处理SSE事件流"""
|
|
127
|
+
if not self.sse_response:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
buffer = ""
|
|
131
|
+
for line in self.sse_response.iter_lines(decode_unicode=True):
|
|
132
|
+
if line:
|
|
133
|
+
if line.startswith("data:"):
|
|
134
|
+
data = line[5:].strip()
|
|
135
|
+
# 检查是否包含消息端点信息
|
|
136
|
+
if data.startswith('/'):
|
|
137
|
+
# 这是消息端点信息,例如 "/messages/?session_id=xyz"
|
|
138
|
+
try:
|
|
139
|
+
# 提取消息端点路径和会话ID
|
|
140
|
+
url_parts = data.split('?')
|
|
141
|
+
self.messages_endpoint = url_parts[0]
|
|
142
|
+
|
|
143
|
+
# 如果有查询参数,尝试提取session_id
|
|
144
|
+
if len(url_parts) > 1:
|
|
145
|
+
query_string = url_parts[1]
|
|
146
|
+
query_params = parse_qs(query_string)
|
|
147
|
+
if 'session_id' in query_params:
|
|
148
|
+
self.session_id = query_params['session_id'][0]
|
|
149
|
+
except Exception as e:
|
|
150
|
+
PrettyOutput.print(f"解析消息端点或会话ID失败: {e}", OutputType.WARNING)
|
|
151
|
+
else:
|
|
152
|
+
buffer += data
|
|
153
|
+
elif line.startswith(":"): # 忽略注释行
|
|
154
|
+
continue
|
|
155
|
+
elif line.startswith("event:"): # 事件类型
|
|
156
|
+
continue # 我们不使用事件类型
|
|
157
|
+
elif line.startswith("id:"): # 事件ID
|
|
158
|
+
continue # 我们不使用事件ID
|
|
159
|
+
elif line.startswith("retry:"): # 重连时间
|
|
160
|
+
continue # 我们自己管理重连
|
|
161
|
+
else: # 空行表示事件结束
|
|
162
|
+
if buffer:
|
|
163
|
+
try:
|
|
164
|
+
self._handle_sse_event(buffer)
|
|
165
|
+
except Exception as e:
|
|
166
|
+
PrettyOutput.print(f"处理SSE事件出错: {e}", OutputType.ERROR)
|
|
167
|
+
buffer = ""
|
|
168
|
+
|
|
169
|
+
PrettyOutput.print("SSE连接已关闭", OutputType.WARNING)
|
|
170
|
+
|
|
171
|
+
def _handle_sse_event(self, data: str) -> None:
|
|
172
|
+
"""处理单个SSE事件数据"""
|
|
173
|
+
try:
|
|
174
|
+
event_data = json.loads(data)
|
|
175
|
+
|
|
176
|
+
# 检查是请求响应还是通知
|
|
177
|
+
if 'id' in event_data:
|
|
178
|
+
# 这是一个请求的响应
|
|
179
|
+
req_id = event_data['id']
|
|
180
|
+
with self.event_lock:
|
|
181
|
+
self.request_results[req_id] = event_data
|
|
182
|
+
if req_id in self.pending_requests:
|
|
183
|
+
# 通知等待线程响应已到达
|
|
184
|
+
self.pending_requests[req_id].set()
|
|
185
|
+
elif 'method' in event_data:
|
|
186
|
+
# 这是一个通知
|
|
187
|
+
method = event_data.get('method', '')
|
|
188
|
+
params = event_data.get('params', {})
|
|
189
|
+
|
|
190
|
+
# 调用已注册的处理器
|
|
191
|
+
if method in self.notification_handlers:
|
|
192
|
+
for handler in self.notification_handlers[method]:
|
|
193
|
+
try:
|
|
194
|
+
handler(params)
|
|
195
|
+
except Exception as e:
|
|
196
|
+
PrettyOutput.print(
|
|
197
|
+
f"处理通知时出错 ({method}): {e}",
|
|
198
|
+
OutputType.ERROR
|
|
199
|
+
)
|
|
200
|
+
except json.JSONDecodeError:
|
|
201
|
+
PrettyOutput.print(f"无法解析SSE事件: {data}", OutputType.WARNING)
|
|
202
|
+
except Exception as e:
|
|
203
|
+
PrettyOutput.print(f"处理SSE事件时出错: {e}", OutputType.ERROR)
|
|
204
|
+
|
|
205
|
+
def register_notification_handler(self, method: str, handler: Callable) -> None:
|
|
206
|
+
"""注册通知处理器
|
|
207
|
+
|
|
208
|
+
参数:
|
|
209
|
+
method: 通知方法名
|
|
210
|
+
handler: 处理通知的回调函数,接收params参数
|
|
211
|
+
"""
|
|
212
|
+
with self.event_lock:
|
|
213
|
+
if method not in self.notification_handlers:
|
|
214
|
+
self.notification_handlers[method] = []
|
|
215
|
+
self.notification_handlers[method].append(handler)
|
|
216
|
+
|
|
217
|
+
def unregister_notification_handler(self, method: str, handler: Callable) -> None:
|
|
218
|
+
"""注销通知处理器
|
|
219
|
+
|
|
220
|
+
参数:
|
|
221
|
+
method: 通知方法名
|
|
222
|
+
handler: 要注销的处理器函数
|
|
223
|
+
"""
|
|
224
|
+
with self.event_lock:
|
|
225
|
+
if method in self.notification_handlers:
|
|
226
|
+
if handler in self.notification_handlers[method]:
|
|
227
|
+
self.notification_handlers[method].remove(handler)
|
|
228
|
+
if not self.notification_handlers[method]:
|
|
229
|
+
del self.notification_handlers[method]
|
|
230
|
+
|
|
231
|
+
def _get_next_request_id(self) -> str:
|
|
232
|
+
"""获取下一个请求ID"""
|
|
233
|
+
with self.event_lock:
|
|
234
|
+
self.request_id_counter += 1
|
|
235
|
+
return str(self.request_id_counter)
|
|
236
|
+
|
|
76
237
|
def _send_request(self, method: str, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
77
238
|
"""发送请求到MCP服务器
|
|
78
239
|
|
|
@@ -83,24 +244,88 @@ class RemoteMcpClient(McpClient):
|
|
|
83
244
|
返回:
|
|
84
245
|
Dict[str, Any]: 响应结果
|
|
85
246
|
"""
|
|
247
|
+
# 生成唯一请求ID
|
|
248
|
+
req_id = self._get_next_request_id()
|
|
249
|
+
|
|
250
|
+
# 创建事件标志,用于等待响应
|
|
251
|
+
event = threading.Event()
|
|
252
|
+
|
|
253
|
+
with self.event_lock:
|
|
254
|
+
self.pending_requests[req_id] = event
|
|
255
|
+
|
|
86
256
|
try:
|
|
87
257
|
# 构建请求
|
|
88
258
|
request = {
|
|
89
259
|
'jsonrpc': '2.0',
|
|
90
260
|
'method': method,
|
|
91
261
|
'params': params,
|
|
92
|
-
'id':
|
|
262
|
+
'id': req_id
|
|
93
263
|
}
|
|
94
264
|
|
|
95
|
-
#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
265
|
+
# 尝试不同的请求发送方式
|
|
266
|
+
if self.session_id:
|
|
267
|
+
# 方法1: 使用查询参数中的session_id
|
|
268
|
+
query_params = {'session_id': self.session_id}
|
|
269
|
+
messages_url = urljoin(self.base_url, self.messages_endpoint)
|
|
270
|
+
|
|
271
|
+
# 尝试直接使用原始URL(不追加查询参数)
|
|
272
|
+
try:
|
|
273
|
+
post_response = self.session.post(
|
|
274
|
+
messages_url,
|
|
275
|
+
json=request
|
|
276
|
+
)
|
|
277
|
+
post_response.raise_for_status()
|
|
278
|
+
except requests.HTTPError:
|
|
279
|
+
# 如果失败,尝试添加会话ID到查询参数
|
|
280
|
+
messages_url_with_session = f"{messages_url}?{urlencode(query_params)}"
|
|
281
|
+
post_response = self.session.post(
|
|
282
|
+
messages_url_with_session,
|
|
283
|
+
json=request
|
|
284
|
+
)
|
|
285
|
+
post_response.raise_for_status()
|
|
286
|
+
else:
|
|
287
|
+
# 方法2: 不使用session_id
|
|
288
|
+
if not self.messages_endpoint:
|
|
289
|
+
self.messages_endpoint = "/messages"
|
|
290
|
+
|
|
291
|
+
messages_url = urljoin(self.base_url, self.messages_endpoint)
|
|
292
|
+
|
|
293
|
+
# 尝试直接使用messages端点而不带任何查询参数
|
|
294
|
+
try:
|
|
295
|
+
# 尝试1: 标准JSON-RPC格式
|
|
296
|
+
post_response = self.session.post(
|
|
297
|
+
messages_url,
|
|
298
|
+
json=request
|
|
299
|
+
)
|
|
300
|
+
post_response.raise_for_status()
|
|
301
|
+
except requests.HTTPError:
|
|
302
|
+
# 尝试2: JSON字符串作为请求参数
|
|
303
|
+
post_response = self.session.post(
|
|
304
|
+
messages_url,
|
|
305
|
+
params={'request': json.dumps(request)}
|
|
306
|
+
)
|
|
307
|
+
post_response.raise_for_status()
|
|
308
|
+
|
|
309
|
+
# 等待SSE通道返回响应(最多30秒)
|
|
310
|
+
if not event.wait(timeout=30):
|
|
311
|
+
raise TimeoutError(f"等待响应超时: {method}")
|
|
312
|
+
|
|
313
|
+
# 获取响应结果
|
|
314
|
+
with self.event_lock:
|
|
315
|
+
result = self.request_results.pop(req_id, None)
|
|
316
|
+
self.pending_requests.pop(req_id, None)
|
|
317
|
+
|
|
318
|
+
if result is None:
|
|
319
|
+
raise RuntimeError(f"未收到响应: {method}")
|
|
320
|
+
|
|
321
|
+
return result
|
|
102
322
|
|
|
103
323
|
except Exception as e:
|
|
324
|
+
# 清理请求状态
|
|
325
|
+
with self.event_lock:
|
|
326
|
+
self.pending_requests.pop(req_id, None)
|
|
327
|
+
self.request_results.pop(req_id, None)
|
|
328
|
+
|
|
104
329
|
PrettyOutput.print(f"发送请求失败: {str(e)}", OutputType.ERROR)
|
|
105
330
|
raise
|
|
106
331
|
|
|
@@ -119,12 +344,49 @@ class RemoteMcpClient(McpClient):
|
|
|
119
344
|
'params': params
|
|
120
345
|
}
|
|
121
346
|
|
|
122
|
-
#
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
347
|
+
# 尝试不同的请求发送方式,与_send_request保持一致
|
|
348
|
+
if self.session_id:
|
|
349
|
+
# 方法1: 使用查询参数中的session_id
|
|
350
|
+
query_params = {'session_id': self.session_id}
|
|
351
|
+
messages_url = urljoin(self.base_url, self.messages_endpoint or '/messages')
|
|
352
|
+
|
|
353
|
+
# 尝试直接使用原始URL(不追加查询参数)
|
|
354
|
+
try:
|
|
355
|
+
post_response = self.session.post(
|
|
356
|
+
messages_url,
|
|
357
|
+
json=notification
|
|
358
|
+
)
|
|
359
|
+
post_response.raise_for_status()
|
|
360
|
+
except requests.HTTPError:
|
|
361
|
+
# 如果失败,尝试添加会话ID到查询参数
|
|
362
|
+
messages_url_with_session = f"{messages_url}?{urlencode(query_params)}"
|
|
363
|
+
post_response = self.session.post(
|
|
364
|
+
messages_url_with_session,
|
|
365
|
+
json=notification
|
|
366
|
+
)
|
|
367
|
+
post_response.raise_for_status()
|
|
368
|
+
else:
|
|
369
|
+
# 方法2: 不使用session_id
|
|
370
|
+
if not self.messages_endpoint:
|
|
371
|
+
self.messages_endpoint = "/messages"
|
|
372
|
+
|
|
373
|
+
messages_url = urljoin(self.base_url, self.messages_endpoint)
|
|
374
|
+
|
|
375
|
+
# 尝试直接使用messages端点而不带任何查询参数
|
|
376
|
+
try:
|
|
377
|
+
# 尝试1: 标准JSON-RPC格式
|
|
378
|
+
post_response = self.session.post(
|
|
379
|
+
messages_url,
|
|
380
|
+
json=notification
|
|
381
|
+
)
|
|
382
|
+
post_response.raise_for_status()
|
|
383
|
+
except requests.HTTPError:
|
|
384
|
+
# 尝试2: JSON字符串作为请求参数
|
|
385
|
+
post_response = self.session.post(
|
|
386
|
+
messages_url,
|
|
387
|
+
params={'request': json.dumps(notification)}
|
|
388
|
+
)
|
|
389
|
+
post_response.raise_for_status()
|
|
128
390
|
|
|
129
391
|
except Exception as e:
|
|
130
392
|
PrettyOutput.print(f"发送通知失败: {str(e)}", OutputType.ERROR)
|
|
@@ -222,9 +484,107 @@ class RemoteMcpClient(McpClient):
|
|
|
222
484
|
'stderr': str(e)
|
|
223
485
|
}
|
|
224
486
|
|
|
487
|
+
def get_resource_list(self) -> List[Dict[str, Any]]:
|
|
488
|
+
"""获取资源列表
|
|
489
|
+
|
|
490
|
+
返回:
|
|
491
|
+
List[Dict[str, Any]]: 资源列表,每个资源包含以下字段:
|
|
492
|
+
- uri: str - 资源的唯一标识符
|
|
493
|
+
- name: str - 资源的名称
|
|
494
|
+
- description: str - 资源的描述(可选)
|
|
495
|
+
- mimeType: str - 资源的MIME类型(可选)
|
|
496
|
+
"""
|
|
497
|
+
try:
|
|
498
|
+
response = self._send_request('resources/list', {})
|
|
499
|
+
if 'result' in response and 'resources' in response['result']:
|
|
500
|
+
return response['result']['resources']
|
|
501
|
+
else:
|
|
502
|
+
error_msg = "获取资源列表失败"
|
|
503
|
+
if 'error' in response:
|
|
504
|
+
error_msg += f": {response['error']}"
|
|
505
|
+
else:
|
|
506
|
+
error_msg += ": 未知错误"
|
|
507
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
508
|
+
return []
|
|
509
|
+
except Exception as e:
|
|
510
|
+
PrettyOutput.print(f"获取资源列表失败: {str(e)}", OutputType.ERROR)
|
|
511
|
+
return []
|
|
512
|
+
|
|
513
|
+
def get_resource(self, uri: str) -> Dict[str, Any]:
|
|
514
|
+
"""获取指定资源的内容
|
|
515
|
+
|
|
516
|
+
参数:
|
|
517
|
+
uri: str - 资源的URI标识符
|
|
518
|
+
|
|
519
|
+
返回:
|
|
520
|
+
Dict[str, Any]: 执行结果,包含以下字段:
|
|
521
|
+
- success: bool - 是否执行成功
|
|
522
|
+
- stdout: str - 资源内容(文本或base64编码的二进制内容)
|
|
523
|
+
- stderr: str - 错误信息
|
|
524
|
+
"""
|
|
525
|
+
try:
|
|
526
|
+
response = self._send_request('resources/read', {
|
|
527
|
+
'uri': uri
|
|
528
|
+
})
|
|
529
|
+
if 'result' in response and 'contents' in response['result']:
|
|
530
|
+
contents = response['result']['contents']
|
|
531
|
+
if contents:
|
|
532
|
+
content = contents[0] # 获取第一个资源内容
|
|
533
|
+
# 根据资源类型返回内容
|
|
534
|
+
if 'text' in content:
|
|
535
|
+
return {
|
|
536
|
+
'success': True,
|
|
537
|
+
'stdout': content['text'],
|
|
538
|
+
'stderr': ''
|
|
539
|
+
}
|
|
540
|
+
elif 'blob' in content:
|
|
541
|
+
return {
|
|
542
|
+
'success': True,
|
|
543
|
+
'stdout': content['blob'],
|
|
544
|
+
'stderr': ''
|
|
545
|
+
}
|
|
546
|
+
return {
|
|
547
|
+
'success': False,
|
|
548
|
+
'stdout': '',
|
|
549
|
+
'stderr': '资源内容为空'
|
|
550
|
+
}
|
|
551
|
+
else:
|
|
552
|
+
error_msg = "获取资源内容失败"
|
|
553
|
+
if 'error' in response:
|
|
554
|
+
error_msg += f": {response['error']}"
|
|
555
|
+
else:
|
|
556
|
+
error_msg += ": 未知错误"
|
|
557
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
558
|
+
return {
|
|
559
|
+
'success': False,
|
|
560
|
+
'stdout': '',
|
|
561
|
+
'stderr': error_msg
|
|
562
|
+
}
|
|
563
|
+
except Exception as e:
|
|
564
|
+
error_msg = f"获取资源内容失败: {str(e)}"
|
|
565
|
+
PrettyOutput.print(error_msg, OutputType.ERROR)
|
|
566
|
+
return {
|
|
567
|
+
'success': False,
|
|
568
|
+
'stdout': '',
|
|
569
|
+
'stderr': error_msg
|
|
570
|
+
}
|
|
571
|
+
|
|
225
572
|
def __del__(self):
|
|
226
573
|
"""清理资源"""
|
|
227
|
-
|
|
228
|
-
|
|
574
|
+
# 清理请求状态
|
|
575
|
+
with self.event_lock:
|
|
576
|
+
for event in self.pending_requests.values():
|
|
577
|
+
event.set() # 释放所有等待的请求
|
|
578
|
+
self.pending_requests.clear()
|
|
579
|
+
self.request_results.clear()
|
|
580
|
+
|
|
581
|
+
# 关闭SSE响应
|
|
582
|
+
if self.sse_response:
|
|
583
|
+
try:
|
|
584
|
+
self.sse_response.close()
|
|
585
|
+
except:
|
|
586
|
+
pass
|
|
587
|
+
|
|
588
|
+
# 关闭HTTP会话
|
|
229
589
|
if self.session:
|
|
230
590
|
self.session.close()
|
|
@@ -156,10 +156,9 @@ class AskCodebaseTool:
|
|
|
156
156
|
2. 使用fd命令查找可能相关的文件
|
|
157
157
|
3. 使用rg命令搜索关键词和代码模式
|
|
158
158
|
4. 使用read_code工具直接读取和分析相关文件内容
|
|
159
|
-
5.
|
|
160
|
-
6.
|
|
161
|
-
7.
|
|
162
|
-
8. 优先查阅README文件、文档目录和项目文档
|
|
159
|
+
5. 根据文件内容提供具体、准确的回答
|
|
160
|
+
6. 确保分析的完整性,收集充分的信息后再得出结论,不要在只掌握部分信息就得出结论
|
|
161
|
+
7. 优先查阅README文件、文档目录和项目文档
|
|
163
162
|
|
|
164
163
|
## 分析步骤
|
|
165
164
|
1. **确定项目的编程语言**:
|
|
@@ -186,12 +185,6 @@ class AskCodebaseTool:
|
|
|
186
185
|
- 提供基于直接分析代码的具体回答
|
|
187
186
|
- 引用具体文件和代码片段作为依据
|
|
188
187
|
|
|
189
|
-
## 关于RAG工具使用
|
|
190
|
-
- RAG工具应作为最后选择,仅在fd、rg和read_code都无法解决问题时使用
|
|
191
|
-
- 必须通过查看实际代码文件验证RAG返回的每条重要信息
|
|
192
|
-
- 对于关键发现,始终使用`read_code`工具查看原始文件内容进行求证
|
|
193
|
-
- 如发现RAG结果与实际代码不符,以实际代码为准
|
|
194
|
-
|
|
195
188
|
## 输出要求
|
|
196
189
|
- 提供准确、具体的回答,避免模糊不清的描述
|
|
197
190
|
- 引用具体文件路径和代码片段作为依据
|
jarvis/jarvis_tools/registry.py
CHANGED
|
@@ -16,6 +16,7 @@ from jarvis.jarvis_utils.output import OutputType, PrettyOutput
|
|
|
16
16
|
from jarvis.jarvis_utils.utils import ct, ot, init_env
|
|
17
17
|
from jarvis.jarvis_mcp.local_mcp_client import LocalMcpClient
|
|
18
18
|
from jarvis.jarvis_mcp.remote_mcp_client import RemoteMcpClient
|
|
19
|
+
from jarvis.jarvis_mcp import McpClient
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
|
|
@@ -138,7 +139,7 @@ class ToolRegistry(OutputHandler):
|
|
|
138
139
|
|
|
139
140
|
def use_tools(self, name: List[str]) -> None:
|
|
140
141
|
"""使用指定工具
|
|
141
|
-
|
|
142
|
+
|
|
142
143
|
参数:
|
|
143
144
|
name: 要使用的工具名称列表
|
|
144
145
|
"""
|
|
@@ -149,7 +150,7 @@ class ToolRegistry(OutputHandler):
|
|
|
149
150
|
|
|
150
151
|
def dont_use_tools(self, names: List[str]) -> None:
|
|
151
152
|
"""从注册表中移除指定工具
|
|
152
|
-
|
|
153
|
+
|
|
153
154
|
参数:
|
|
154
155
|
names: 要移除的工具名称列表
|
|
155
156
|
"""
|
|
@@ -193,7 +194,7 @@ class ToolRegistry(OutputHandler):
|
|
|
193
194
|
|
|
194
195
|
def register_mcp_tool_by_file(self, file_path: str) -> bool:
|
|
195
196
|
"""从指定文件加载并注册工具
|
|
196
|
-
|
|
197
|
+
|
|
197
198
|
参数:
|
|
198
199
|
file_path: 工具文件的路径
|
|
199
200
|
|
|
@@ -205,84 +206,125 @@ class ToolRegistry(OutputHandler):
|
|
|
205
206
|
if 'type' not in config:
|
|
206
207
|
PrettyOutput.print(f"文件 {file_path} 缺少type字段", OutputType.WARNING)
|
|
207
208
|
return False
|
|
209
|
+
|
|
210
|
+
# 检查enable标志
|
|
211
|
+
if not config.get('enable', True):
|
|
212
|
+
PrettyOutput.print(f"文件 {file_path} 已禁用(enable=false),跳过注册", OutputType.INFO)
|
|
213
|
+
return False
|
|
214
|
+
|
|
215
|
+
name = config.get('name', Path(file_path).stem)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
# 注册资源工具
|
|
219
|
+
def create_resource_list_func(client: McpClient):
|
|
220
|
+
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
221
|
+
args = arguments.copy()
|
|
222
|
+
args.pop('agent', None)
|
|
223
|
+
args.pop('want', None)
|
|
224
|
+
ret = client.get_resource_list()
|
|
225
|
+
PrettyOutput.print(f"MCP {name} 资源列表:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
|
|
226
|
+
return {
|
|
227
|
+
'success': True,
|
|
228
|
+
'stdout': yaml.safe_dump(ret),
|
|
229
|
+
'stderr': ''
|
|
230
|
+
}
|
|
231
|
+
return execute
|
|
232
|
+
|
|
233
|
+
def create_resource_get_func(client: McpClient):
|
|
234
|
+
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
235
|
+
args = arguments.copy()
|
|
236
|
+
args.pop('agent', None)
|
|
237
|
+
args.pop('want', None)
|
|
238
|
+
if 'uri' not in args:
|
|
239
|
+
return {
|
|
240
|
+
'success': False,
|
|
241
|
+
'stdout': '',
|
|
242
|
+
'stderr': '缺少必需的uri参数'
|
|
243
|
+
}
|
|
244
|
+
ret = client.get_resource(args['uri'])
|
|
245
|
+
PrettyOutput.print(f"MCP {name} 获取资源:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
|
|
246
|
+
return ret
|
|
247
|
+
return execute
|
|
248
|
+
|
|
249
|
+
def create_mcp_execute_func(tool_name: str, client: McpClient):
|
|
250
|
+
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
251
|
+
args = arguments.copy()
|
|
252
|
+
args.pop('agent', None)
|
|
253
|
+
args.pop('want', None)
|
|
254
|
+
ret = client.execute(tool_name, args)
|
|
255
|
+
PrettyOutput.print(f"MCP {name} {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
|
|
256
|
+
return ret
|
|
257
|
+
return execute
|
|
258
|
+
|
|
208
259
|
if config['type'] == 'local':
|
|
209
260
|
if 'command' not in config:
|
|
210
261
|
PrettyOutput.print(f"文件 {file_path} 缺少command字段", OutputType.WARNING)
|
|
211
262
|
return False
|
|
212
|
-
|
|
213
|
-
# 创建本地MCP客户端
|
|
214
|
-
mcp_client = LocalMcpClient(config)
|
|
215
|
-
|
|
216
|
-
# 获取工具信息
|
|
217
|
-
tools = mcp_client.get_tool_list()
|
|
218
|
-
if not tools:
|
|
219
|
-
PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
|
|
220
|
-
return False
|
|
221
|
-
|
|
222
|
-
# 注册每个工具
|
|
223
|
-
for tool in tools:
|
|
224
|
-
def create_local_execute_func(tool_name: str, client: LocalMcpClient):
|
|
225
|
-
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
226
|
-
args = arguments.copy()
|
|
227
|
-
args.pop('agent', None)
|
|
228
|
-
args.pop('want', None)
|
|
229
|
-
ret = client.execute(tool_name, args)
|
|
230
|
-
PrettyOutput.print(f"MCP {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
|
|
231
|
-
return ret
|
|
232
|
-
return execute
|
|
233
|
-
|
|
234
|
-
# 注册工具
|
|
235
|
-
self.register_tool(
|
|
236
|
-
name=tool['name'],
|
|
237
|
-
description=tool['description'],
|
|
238
|
-
parameters=tool['parameters'],
|
|
239
|
-
func=create_local_execute_func(tool['name'], mcp_client)
|
|
240
|
-
)
|
|
241
|
-
|
|
242
|
-
return True
|
|
243
|
-
|
|
244
263
|
elif config['type'] == 'remote':
|
|
245
264
|
if 'base_url' not in config:
|
|
246
265
|
PrettyOutput.print(f"文件 {file_path} 缺少base_url字段", OutputType.WARNING)
|
|
247
266
|
return False
|
|
248
|
-
|
|
249
|
-
# 创建远程MCP客户端
|
|
250
|
-
mcp_client = RemoteMcpClient(config)
|
|
251
|
-
|
|
252
|
-
# 获取工具信息
|
|
253
|
-
tools = mcp_client.get_tool_list()
|
|
254
|
-
if not tools:
|
|
255
|
-
PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
|
|
256
|
-
return False
|
|
257
|
-
|
|
258
|
-
# 注册每个工具
|
|
259
|
-
for tool in tools:
|
|
260
|
-
def create_remote_execute_func(tool_name: str, client: RemoteMcpClient):
|
|
261
|
-
def execute(arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
262
|
-
args = arguments.copy()
|
|
263
|
-
args.pop('agent', None)
|
|
264
|
-
args.pop('want', None)
|
|
265
|
-
ret = client.execute(tool_name, args)
|
|
266
|
-
PrettyOutput.print(f"MCP {tool_name} 执行结果:\n{yaml.safe_dump(ret)}", OutputType.TOOL)
|
|
267
|
-
return ret
|
|
268
|
-
return execute
|
|
269
|
-
|
|
270
|
-
# 注册工具
|
|
271
|
-
self.register_tool(
|
|
272
|
-
name=tool['name'],
|
|
273
|
-
description=tool['description'],
|
|
274
|
-
parameters=tool['parameters'],
|
|
275
|
-
func=create_remote_execute_func(tool['name'], mcp_client)
|
|
276
|
-
)
|
|
277
|
-
|
|
278
|
-
return True
|
|
279
267
|
else:
|
|
280
268
|
PrettyOutput.print(f"文件 {file_path} 类型错误: {config['type']}", OutputType.WARNING)
|
|
281
269
|
return False
|
|
270
|
+
|
|
271
|
+
# 创建MCP客户端
|
|
272
|
+
mcp_client: McpClient = LocalMcpClient(config) if config['type'] == 'local' else RemoteMcpClient(config)
|
|
273
|
+
|
|
274
|
+
# 获取工具信息
|
|
275
|
+
tools = mcp_client.get_tool_list()
|
|
276
|
+
if not tools:
|
|
277
|
+
PrettyOutput.print(f"从 {file_path} 获取工具列表失败", OutputType.WARNING)
|
|
278
|
+
return False
|
|
279
|
+
|
|
280
|
+
# 注册每个工具
|
|
281
|
+
for tool in tools:
|
|
282
|
+
|
|
283
|
+
# 注册工具
|
|
284
|
+
self.register_tool(
|
|
285
|
+
name=f"{name}.tool_call.{tool['name']}",
|
|
286
|
+
description=tool['description'],
|
|
287
|
+
parameters=tool['parameters'],
|
|
288
|
+
func=create_mcp_execute_func(tool['name'], mcp_client)
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# 注册资源列表工具
|
|
293
|
+
self.register_tool(
|
|
294
|
+
name=f"{name}.resource.get_resource_list",
|
|
295
|
+
description=f"获取{name}MCP服务器上的资源列表",
|
|
296
|
+
parameters={
|
|
297
|
+
'type': 'object',
|
|
298
|
+
'properties': {},
|
|
299
|
+
'required': []
|
|
300
|
+
},
|
|
301
|
+
func=create_resource_list_func(mcp_client)
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# 注册获取资源工具
|
|
305
|
+
self.register_tool(
|
|
306
|
+
name=f"{name}.resource.get_resource",
|
|
307
|
+
description=f"获取{name}MCP服务器上的指定资源",
|
|
308
|
+
parameters={
|
|
309
|
+
'type': 'object',
|
|
310
|
+
'properties': {
|
|
311
|
+
'uri': {
|
|
312
|
+
'type': 'string',
|
|
313
|
+
'description': '资源的URI标识符'
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
'required': ['uri']
|
|
317
|
+
},
|
|
318
|
+
func=create_resource_get_func(mcp_client)
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
return True
|
|
322
|
+
|
|
323
|
+
|
|
282
324
|
except Exception as e:
|
|
283
325
|
PrettyOutput.print(f"文件 {file_path} 加载失败: {str(e)}", OutputType.WARNING)
|
|
284
326
|
return False
|
|
285
|
-
|
|
327
|
+
|
|
286
328
|
def register_tool_by_file(self, file_path: str) -> bool:
|
|
287
329
|
"""从指定文件加载并注册工具
|
|
288
330
|
|
|
@@ -348,12 +390,12 @@ class ToolRegistry(OutputHandler):
|
|
|
348
390
|
except Exception as e:
|
|
349
391
|
PrettyOutput.print(f"从 {Path(file_path).name} 加载工具失败: {str(e)}", OutputType.ERROR)
|
|
350
392
|
return False
|
|
351
|
-
|
|
393
|
+
|
|
352
394
|
@staticmethod
|
|
353
395
|
def _has_tool_calls_block(content: str) -> bool:
|
|
354
396
|
"""从内容中提取工具调用块"""
|
|
355
397
|
return re.search(ot("TOOL_CALL")+r'(.*?)'+ct("TOOL_CALL"), content, re.DOTALL) is not None
|
|
356
|
-
|
|
398
|
+
|
|
357
399
|
@staticmethod
|
|
358
400
|
def _extract_tool_calls(content: str) -> Tuple[Dict[str, Dict[str, Any]], str]:
|
|
359
401
|
"""从内容中提取工具调用。
|
|
@@ -376,20 +418,20 @@ class ToolRegistry(OutputHandler):
|
|
|
376
418
|
if 'name' in msg and 'arguments' in msg and 'want' in msg:
|
|
377
419
|
ret.append(msg)
|
|
378
420
|
else:
|
|
379
|
-
return {}, f"""工具调用格式错误,请检查工具调用格式。
|
|
380
|
-
|
|
381
|
-
{tool_call_help}"""
|
|
421
|
+
return {}, f"""工具调用格式错误,请检查工具调用格式。
|
|
422
|
+
|
|
423
|
+
{tool_call_help}"""
|
|
382
424
|
except Exception as e:
|
|
383
|
-
return {}, f"""工具调用格式错误,请检查工具调用格式。
|
|
384
|
-
|
|
385
|
-
{tool_call_help}"""
|
|
425
|
+
return {}, f"""工具调用格式错误,请检查工具调用格式。
|
|
426
|
+
|
|
427
|
+
{tool_call_help}"""
|
|
386
428
|
if len(ret) > 1:
|
|
387
429
|
return {}, "检测到多个工具调用,请一次只处理一个工具调用。"
|
|
388
430
|
return ret[0] if ret else {}, ""
|
|
389
431
|
|
|
390
|
-
def register_tool(self, name: str, description: str, parameters:
|
|
432
|
+
def register_tool(self, name: str, description: str, parameters: Any, func: Callable[..., Dict[str, Any]]) -> None:
|
|
391
433
|
"""注册新工具
|
|
392
|
-
|
|
434
|
+
|
|
393
435
|
参数:
|
|
394
436
|
name: 工具名称
|
|
395
437
|
description: 工具描述
|
|
@@ -400,10 +442,10 @@ class ToolRegistry(OutputHandler):
|
|
|
400
442
|
|
|
401
443
|
def get_tool(self, name: str) -> Optional[Tool]:
|
|
402
444
|
"""获取工具
|
|
403
|
-
|
|
445
|
+
|
|
404
446
|
参数:
|
|
405
447
|
name: 工具名称
|
|
406
|
-
|
|
448
|
+
|
|
407
449
|
返回:
|
|
408
450
|
Optional[Tool]: 找到的工具实例,如果不存在则返回None
|
|
409
451
|
"""
|
|
@@ -411,7 +453,7 @@ class ToolRegistry(OutputHandler):
|
|
|
411
453
|
|
|
412
454
|
def get_all_tools(self) -> List[Dict[str, Any]]:
|
|
413
455
|
"""获取所有工具(Ollama格式定义)
|
|
414
|
-
|
|
456
|
+
|
|
415
457
|
返回:
|
|
416
458
|
List[Dict[str, Any]]: 包含所有工具信息的列表
|
|
417
459
|
"""
|
|
@@ -419,11 +461,11 @@ class ToolRegistry(OutputHandler):
|
|
|
419
461
|
|
|
420
462
|
def execute_tool(self, name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
421
463
|
"""执行指定工具
|
|
422
|
-
|
|
464
|
+
|
|
423
465
|
参数:
|
|
424
466
|
name: 工具名称
|
|
425
467
|
arguments: 工具参数
|
|
426
|
-
|
|
468
|
+
|
|
427
469
|
返回:
|
|
428
470
|
Dict[str, Any]: 包含执行结果的字典,包含success、stdout和stderr字段
|
|
429
471
|
"""
|
|
@@ -485,7 +527,7 @@ class ToolRegistry(OutputHandler):
|
|
|
485
527
|
return f"""工具调用原始输出过长,以下是根据输出提出的信息:
|
|
486
528
|
|
|
487
529
|
{platform.chat_until_success(prompt)}"""
|
|
488
|
-
|
|
530
|
+
|
|
489
531
|
return output
|
|
490
532
|
|
|
491
533
|
except Exception as e:
|
jarvis/jarvis_utils/config.py
CHANGED
|
@@ -144,10 +144,10 @@ def load_methodology(user_input: str) -> str:
|
|
|
144
144
|
prompt = f"""根据用户需求: {user_input}
|
|
145
145
|
|
|
146
146
|
请按以下格式回复:
|
|
147
|
-
###
|
|
147
|
+
### 与该任务/需求相关的方法论
|
|
148
148
|
1. [方法论名字]
|
|
149
149
|
2. [方法论名字]
|
|
150
|
-
###
|
|
150
|
+
### 根据以上方法论,总结出方法论内容
|
|
151
151
|
[总结的方法论内容]
|
|
152
152
|
|
|
153
153
|
如果没有匹配的方法论,请输出:没有历史方法论可参考
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: jarvis-ai-assistant
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.151
|
|
4
4
|
Summary: Jarvis: An AI assistant that uses tools to interact with the system
|
|
5
5
|
Home-page: https://github.com/skyfireitdiy/Jarvis
|
|
6
6
|
Author: skyfire
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
License-File: LICENSE
|
|
43
43
|
Requires-Dist: requests==2.32.3
|
|
44
44
|
Requires-Dist: colorama==0.4.6
|
|
45
|
-
Requires-Dist:
|
|
45
|
+
Requires-Dist: prompt_toolkit==3.0.50
|
|
46
46
|
Requires-Dist: yaspin==2.4.0
|
|
47
47
|
Requires-Dist: pygments==2.19.1
|
|
48
48
|
Requires-Dist: fuzzywuzzy==0.18.0
|
|
@@ -54,11 +54,16 @@ Requires-Dist: transformers==4.46.3
|
|
|
54
54
|
Requires-Dist: torch==2.4.1
|
|
55
55
|
Requires-Dist: python-Levenshtein==0.25.1
|
|
56
56
|
Requires-Dist: sseclient==0.0.27
|
|
57
|
+
Requires-Dist: pillow==10.2.1
|
|
57
58
|
Provides-Extra: dev
|
|
58
59
|
Requires-Dist: pytest; extra == "dev"
|
|
59
60
|
Requires-Dist: black; extra == "dev"
|
|
60
61
|
Requires-Dist: isort; extra == "dev"
|
|
61
62
|
Requires-Dist: mypy; extra == "dev"
|
|
63
|
+
Dynamic: author
|
|
64
|
+
Dynamic: home-page
|
|
65
|
+
Dynamic: license-file
|
|
66
|
+
Dynamic: requires-python
|
|
62
67
|
|
|
63
68
|
# 🤖 Jarvis AI 助手
|
|
64
69
|
<p align="center">
|
|
@@ -276,6 +281,30 @@ class CustomTool:
|
|
|
276
281
|
```
|
|
277
282
|
|
|
278
283
|
|
|
284
|
+
### 添加MCP
|
|
285
|
+
MCP(模型上下文协议)。在`~/.jarvis/tools/mcp/`中创建YAML配置文件:
|
|
286
|
+
|
|
287
|
+
#### 本地MCP配置(`stdio`模式)
|
|
288
|
+
```yaml
|
|
289
|
+
type: local
|
|
290
|
+
name: MCP名称
|
|
291
|
+
command: 可执行命令
|
|
292
|
+
args: [参数列表] # 可选
|
|
293
|
+
env: # 可选环境变量
|
|
294
|
+
KEY: VALUE
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### 远程MCP配置(`sse`模式)
|
|
298
|
+
```yaml
|
|
299
|
+
type: remote
|
|
300
|
+
name: MCP名称
|
|
301
|
+
base_url: http://example.com/api
|
|
302
|
+
auth_token: 认证令牌 # 可选
|
|
303
|
+
headers: # 可选HTTP头
|
|
304
|
+
X-Custom-Header: value
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
|
|
279
308
|
### 添加新大模型平台
|
|
280
309
|
在 `~/.jarvis/platforms/` 中创建新的 Python 文件:
|
|
281
310
|
```python
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
jarvis/__init__.py,sha256=
|
|
1
|
+
jarvis/__init__.py,sha256=RgZgU-h6b2rFUONprNXY6qN0TgybOI3uDfFqF21sQzA,50
|
|
2
2
|
jarvis/jarvis_agent/__init__.py,sha256=QtWu2kh6o5IB_XtGLoxHi5K9lA1t8XoqNUXtX-OqujY,23796
|
|
3
3
|
jarvis/jarvis_agent/builtin_input_handler.py,sha256=0SjlBYnBWKNi3eVdZ7c2NuP82tQej7DEWLAqG6bY1Rc,4357
|
|
4
4
|
jarvis/jarvis_agent/file_input_handler.py,sha256=6R68cSjLBnSJjTKJrSO2IqziRDFnxazU_Jq2t1W1ndo,3695
|
|
@@ -35,16 +35,16 @@ jarvis/jarvis_git_details/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJW
|
|
|
35
35
|
jarvis/jarvis_git_details/main.py,sha256=YowncVxYyJ3y2EvGrZhAJeR4yizXp6aB3dqvoYTepFY,6117
|
|
36
36
|
jarvis/jarvis_git_squash/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
37
|
jarvis/jarvis_git_squash/main.py,sha256=xBNkAl7_8_pQC-C6RcUImA1mEU4KTqhjtA57rG_mMJ8,2179
|
|
38
|
-
jarvis/jarvis_git_utils/git_commiter.py,sha256=
|
|
38
|
+
jarvis/jarvis_git_utils/git_commiter.py,sha256=pnUiX-KkAMLDBRMXot93T9zTdoYsIJpqVMozMwQo6R0,11096
|
|
39
39
|
jarvis/jarvis_lsp/base.py,sha256=f-76xgNijfQ4G3Q0t8IfOGtCu-q2TSQ7a_in6XwDb_8,2030
|
|
40
40
|
jarvis/jarvis_lsp/cpp.py,sha256=ekci2M9_UtkCSEe9__72h26Gat93r9_knL2VmFr8X5M,3141
|
|
41
41
|
jarvis/jarvis_lsp/go.py,sha256=sSypuQSP5X2YtrVMC8XCc5nXkgfG93SO7sC89lHzoR8,3458
|
|
42
42
|
jarvis/jarvis_lsp/python.py,sha256=OJuYHLHI1aYNNWcAFayy_5GxogwyMC3A7KOYGjxN1yg,1843
|
|
43
43
|
jarvis/jarvis_lsp/registry.py,sha256=-b7lAfZ6SNp3O0ifRiFSLxH0xJlPQhkq4DATDDjJb1U,6491
|
|
44
44
|
jarvis/jarvis_lsp/rust.py,sha256=ICmQs5UVdMZwn5KjaF1YRXBCLUMtGF8Z9IwE5rqWkrU,3686
|
|
45
|
-
jarvis/jarvis_mcp/__init__.py,sha256=
|
|
46
|
-
jarvis/jarvis_mcp/local_mcp_client.py,sha256=
|
|
47
|
-
jarvis/jarvis_mcp/remote_mcp_client.py,sha256=
|
|
45
|
+
jarvis/jarvis_mcp/__init__.py,sha256=gi74_Yz5nsEFhrAyCg1Ovxsj-hLweLjMGoOaceL2yx4,2090
|
|
46
|
+
jarvis/jarvis_mcp/local_mcp_client.py,sha256=JKmGJG0sOJxs4Sk-k4SfZq01RiZCGY6oYslvcK7lxJg,11790
|
|
47
|
+
jarvis/jarvis_mcp/remote_mcp_client.py,sha256=J9Y2-9VBQb0uJKglev-U3AQg1AfLDmghRbCvNDPSzdI,23478
|
|
48
48
|
jarvis/jarvis_methodology/main.py,sha256=IBv87UOmdCailgooMtWEcqZcQHmNLhZD-kkGw5jOcVg,3375
|
|
49
49
|
jarvis/jarvis_multi_agent/__init__.py,sha256=SX8lBErhltKyYRM-rymrMz3sJ0Zl3hBXrpsPdFgzkQc,4399
|
|
50
50
|
jarvis/jarvis_multi_agent/main.py,sha256=aGuUC3YQmahabqwDwZXJjfQLYsZ3KIZdf8DZDlVNMe4,1543
|
|
@@ -58,7 +58,7 @@ jarvis/jarvis_platform_manager/main.py,sha256=o7UDrcCkLf9dTh2LOO-_bQVHjWf2X6RuSY
|
|
|
58
58
|
jarvis/jarvis_smart_shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
59
|
jarvis/jarvis_smart_shell/main.py,sha256=slP_8CwpfMjWFZis0At1ANRlPb3gx1KteAg1B7R7dl4,4546
|
|
60
60
|
jarvis/jarvis_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
|
-
jarvis/jarvis_tools/ask_codebase.py,sha256=
|
|
61
|
+
jarvis/jarvis_tools/ask_codebase.py,sha256=S6NArvKZyK8WEbsEgeGCljjg4D9mteWrq9m352V58jU,9635
|
|
62
62
|
jarvis/jarvis_tools/ask_user.py,sha256=NjxTCHGKo4nthbEQD-isvPCW4PQhTcekEferjnukX70,2143
|
|
63
63
|
jarvis/jarvis_tools/base.py,sha256=vskI4czVdlhbo38ODuF9rFrnWBYQIhJSPAqAkLVcyTs,1165
|
|
64
64
|
jarvis/jarvis_tools/chdir.py,sha256=do_OdtabiH3lZcT_ynjSAX66XgH2gPl9mYiS7dMMDa8,2682
|
|
@@ -73,22 +73,22 @@ jarvis/jarvis_tools/lsp_get_diagnostics.py,sha256=IYqv8jQwSK71sZpDBRolSDnYii8t0M
|
|
|
73
73
|
jarvis/jarvis_tools/methodology.py,sha256=gnlJojY4Dg5v9AAB5xcpKqpPIHs0tOYVtzTHkwOrWk0,5214
|
|
74
74
|
jarvis/jarvis_tools/read_code.py,sha256=_X6D3AIgRD9YplSDnFhXOm8wQAZMA3pkkXy31SG33l0,6041
|
|
75
75
|
jarvis/jarvis_tools/read_webpage.py,sha256=syduSZK4kXRRTPzeZ2W9Q6YH5umKiMJZyjA0cCpSF4g,2198
|
|
76
|
-
jarvis/jarvis_tools/registry.py,sha256
|
|
76
|
+
jarvis/jarvis_tools/registry.py,sha256=ELlX1ZqCSDQc5D9RwI5xVXgBgN0IeIjWG_lD6P9T5o0,24100
|
|
77
77
|
jarvis/jarvis_tools/search_web.py,sha256=kWW9K2QUR2AxPq6gcyx4Bgy-0Y4gzcdErq1DNT1EYM4,1333
|
|
78
78
|
jarvis/jarvis_tools/virtual_tty.py,sha256=Rpn9VXUG17LQsY87F_O6UCjN_opXB05mpwozxYf-xVI,16372
|
|
79
79
|
jarvis/jarvis_utils/__init__.py,sha256=KMg-KY5rZIhGTeOD5e2Xo5CU7DX1DUz4ULWAaTQ-ZNw,825
|
|
80
|
-
jarvis/jarvis_utils/config.py,sha256=
|
|
80
|
+
jarvis/jarvis_utils/config.py,sha256=UXIwt1TknkYIuNW15TNiyZ1HJFdjjAzmt_D7_cd6xxQ,3317
|
|
81
81
|
jarvis/jarvis_utils/embedding.py,sha256=_Q-VurYHQZSsyISClTFjadDaNqNPBMqJe58lMM6bsVs,6991
|
|
82
82
|
jarvis/jarvis_utils/file_processors.py,sha256=oNtVlz2JHcQ60NS6sgI-VsvYXOnsQgFUEVenznCXHC4,2952
|
|
83
83
|
jarvis/jarvis_utils/git_utils.py,sha256=j_Jw6h7JD91XhMf0WD3MAH4URkLUBrrYCLnuLm1GeN4,5630
|
|
84
84
|
jarvis/jarvis_utils/globals.py,sha256=Ed2d6diWXCgI74HVV_tI4qW7yXxLpNvQKN2yG0IH9hc,3388
|
|
85
85
|
jarvis/jarvis_utils/input.py,sha256=QhqZEF4BpOGDgNEBrTBabA5n8DnlU0GaHqbKUEZ13Ls,6953
|
|
86
|
-
jarvis/jarvis_utils/methodology.py,sha256=
|
|
86
|
+
jarvis/jarvis_utils/methodology.py,sha256=E_FQOSLA9o8bKfmgORNMlkDtqPiqlCkQoIkPuxPQol4,6324
|
|
87
87
|
jarvis/jarvis_utils/output.py,sha256=BmWdB1bmizv0xfU4Z___9p_xQodorriIcEgADVq9fk0,8416
|
|
88
88
|
jarvis/jarvis_utils/utils.py,sha256=j-YZap58avAzSb9ZuB2I71trVqVxIpFxxZDoh8_7a_o,4653
|
|
89
|
-
jarvis_ai_assistant-0.1.
|
|
90
|
-
jarvis_ai_assistant-0.1.
|
|
91
|
-
jarvis_ai_assistant-0.1.
|
|
92
|
-
jarvis_ai_assistant-0.1.
|
|
93
|
-
jarvis_ai_assistant-0.1.
|
|
94
|
-
jarvis_ai_assistant-0.1.
|
|
89
|
+
jarvis_ai_assistant-0.1.151.dist-info/licenses/LICENSE,sha256=AGgVgQmTqFvaztRtCAXsAMryUymB18gZif7_l2e1XOg,1063
|
|
90
|
+
jarvis_ai_assistant-0.1.151.dist-info/METADATA,sha256=_QXsf2ezynGKHG9ZD-_qbOYk6mD0hQPwrW5bMrrPjHI,11562
|
|
91
|
+
jarvis_ai_assistant-0.1.151.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
92
|
+
jarvis_ai_assistant-0.1.151.dist-info/entry_points.txt,sha256=4ZS8kq6jahnmfDyXFSx39HRi-Tkbp0uFc6cTXt3QIHA,929
|
|
93
|
+
jarvis_ai_assistant-0.1.151.dist-info/top_level.txt,sha256=1BOxyWfzOP_ZXj8rVTDnNCJ92bBGB0rwq8N1PCpoMIs,7
|
|
94
|
+
jarvis_ai_assistant-0.1.151.dist-info/RECORD,,
|
{jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.151.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.151.dist-info/licenses}/LICENSE
RENAMED
|
File without changes
|
{jarvis_ai_assistant-0.1.150.dist-info → jarvis_ai_assistant-0.1.151.dist-info}/top_level.txt
RENAMED
|
File without changes
|