auto-coder 0.1.215__py3-none-any.whl → 0.1.218__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 auto-coder might be problematic. Click here for more details.
- {auto_coder-0.1.215.dist-info → auto_coder-0.1.218.dist-info}/METADATA +2 -1
- {auto_coder-0.1.215.dist-info → auto_coder-0.1.218.dist-info}/RECORD +24 -21
- autocoder/auto_coder.py +6 -2
- autocoder/auto_coder_rag.py +16 -0
- autocoder/chat_auto_coder.py +213 -98
- autocoder/command_args.py +5 -5
- autocoder/common/anything2img.py +196 -0
- autocoder/common/code_auto_generate.py +4 -1
- autocoder/common/code_auto_generate_diff.py +8 -2
- autocoder/common/code_auto_generate_editblock.py +8 -2
- autocoder/common/code_auto_generate_strict_diff.py +8 -2
- autocoder/common/command_completer.py +7 -0
- autocoder/common/mcp_hub.py +83 -7
- autocoder/common/mcp_server.py +249 -18
- autocoder/common/mcp_servers/__init__.py +0 -0
- autocoder/common/mcp_servers/mcp_server_perplexity.py +135 -0
- autocoder/common/mcp_tools.py +27 -379
- autocoder/index/index.py +1 -1
- autocoder/rag/types.py +77 -0
- autocoder/version.py +1 -1
- {auto_coder-0.1.215.dist-info → auto_coder-0.1.218.dist-info}/LICENSE +0 -0
- {auto_coder-0.1.215.dist-info → auto_coder-0.1.218.dist-info}/WHEEL +0 -0
- {auto_coder-0.1.215.dist-info → auto_coder-0.1.218.dist-info}/entry_points.txt +0 -0
- {auto_coder-0.1.215.dist-info → auto_coder-0.1.218.dist-info}/top_level.txt +0 -0
autocoder/command_args.py
CHANGED
|
@@ -171,7 +171,7 @@ def parse_args(input_args: Optional[List[str]] = None) -> AutoCoderArgs:
|
|
|
171
171
|
parser.add_argument("--rag_url", default="", help="")
|
|
172
172
|
parser.add_argument("--rag_params_max_tokens", default=4096, help="")
|
|
173
173
|
parser.add_argument(
|
|
174
|
-
"--rag_type", default="
|
|
174
|
+
"--rag_type", default="simple", help="RAG type, default is simple"
|
|
175
175
|
)
|
|
176
176
|
|
|
177
177
|
parser.add_argument(
|
|
@@ -502,8 +502,8 @@ def parse_args(input_args: Optional[List[str]] = None) -> AutoCoderArgs:
|
|
|
502
502
|
chat_parser.add_argument("--rag_params_max_tokens", default=4096, help="")
|
|
503
503
|
chat_parser.add_argument(
|
|
504
504
|
"--rag_type",
|
|
505
|
-
default="
|
|
506
|
-
help="RAG type (simple/storage), default is
|
|
505
|
+
default="simple",
|
|
506
|
+
help="RAG type (simple/storage), default is simple",
|
|
507
507
|
)
|
|
508
508
|
chat_parser.add_argument("--target_file", default="./output.txt", help="")
|
|
509
509
|
chat_parser.add_argument(
|
|
@@ -661,7 +661,7 @@ def parse_args(input_args: Optional[List[str]] = None) -> AutoCoderArgs:
|
|
|
661
661
|
auto_tool_parser.add_argument(
|
|
662
662
|
"--rag_params_max_tokens", default=4096, help="")
|
|
663
663
|
auto_tool_parser.add_argument(
|
|
664
|
-
"--rag_type", default="
|
|
664
|
+
"--rag_type", default="simple", help="RAG type, default is simple"
|
|
665
665
|
)
|
|
666
666
|
auto_tool_parser.add_argument(
|
|
667
667
|
"--target_file", default="./output.txt", help="")
|
|
@@ -728,7 +728,7 @@ def parse_args(input_args: Optional[List[str]] = None) -> AutoCoderArgs:
|
|
|
728
728
|
planner_parser.add_argument(
|
|
729
729
|
"--rag_params_max_tokens", default=4096, help="")
|
|
730
730
|
planner_parser.add_argument(
|
|
731
|
-
"--rag_type", default="
|
|
731
|
+
"--rag_type", default="simple", help="RAG type, default is simple"
|
|
732
732
|
)
|
|
733
733
|
planner_parser.add_argument(
|
|
734
734
|
"--target_file", default="./output.txt", help="")
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List, Optional, Dict, Any, Tuple
|
|
3
|
+
from PIL import Image
|
|
4
|
+
import fitz # PyMuPDF
|
|
5
|
+
import byzerllm
|
|
6
|
+
from autocoder.common import AutoCoderArgs
|
|
7
|
+
from loguru import logger
|
|
8
|
+
import pydantic
|
|
9
|
+
from docx import Document
|
|
10
|
+
from spire.doc import Document
|
|
11
|
+
from spire.doc import ImageType
|
|
12
|
+
from PIL import Image
|
|
13
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
14
|
+
class ImageInfo(pydantic.BaseModel):
|
|
15
|
+
"""
|
|
16
|
+
图片信息
|
|
17
|
+
"""
|
|
18
|
+
coordinates: List[float] = pydantic.Field(..., description="图片坐标 [x1,y1,x2,y2]")
|
|
19
|
+
text: Optional[str] = pydantic.Field(None, description="图片描述")
|
|
20
|
+
|
|
21
|
+
class Page(pydantic.BaseModel):
|
|
22
|
+
"""
|
|
23
|
+
页面信息,包含文本和图片
|
|
24
|
+
"""
|
|
25
|
+
text: str = pydantic.Field(..., description="页面文本内容")
|
|
26
|
+
images: List[ImageInfo] = pydantic.Field(default_factory=list, description="页面中的图片信息")
|
|
27
|
+
width: int = pydantic.Field(..., description="页面宽度")
|
|
28
|
+
height: int = pydantic.Field(..., description="页面高度")
|
|
29
|
+
|
|
30
|
+
class Anything2Img:
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
llm: byzerllm.ByzerLLM,
|
|
34
|
+
args: AutoCoderArgs,
|
|
35
|
+
keep_conversion: bool = False,
|
|
36
|
+
):
|
|
37
|
+
self.llm = llm
|
|
38
|
+
self.vl_model = llm.get_sub_client("vl_model")
|
|
39
|
+
self.args = args
|
|
40
|
+
self.output_dir = args.output
|
|
41
|
+
os.makedirs(self.output_dir, exist_ok=True)
|
|
42
|
+
self.keep_conversion = keep_conversion
|
|
43
|
+
|
|
44
|
+
@byzerllm.prompt()
|
|
45
|
+
def analyze_image(self, image_path: str) -> str:
|
|
46
|
+
"""
|
|
47
|
+
{{ image }}
|
|
48
|
+
图片中一般包含文字,图片,图表。分析图片,返回该图片包含的文本内容以及图片位置信息。
|
|
49
|
+
请遵循以下格式返回:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"text": "页面的文本内容",
|
|
54
|
+
"images": [
|
|
55
|
+
{
|
|
56
|
+
"coordinates": [x1, y1, x2, y2],
|
|
57
|
+
"text": "对图片的描述"
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"width": 页面宽度,
|
|
61
|
+
"height": 页面高度
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
注意:
|
|
66
|
+
1. 其中x1,y1是左上角坐标,x2,y2是右下角坐标,使用绝对坐标,也就是图片的像素坐标。
|
|
67
|
+
2. 文本内容应保持原有的段落格式
|
|
68
|
+
3. width和height是页面宽度,高度,要求整数类型
|
|
69
|
+
4. 格局图片中文本和图片的位置关系,在文本中使用 <image_placeholder> 来表示图片。
|
|
70
|
+
"""
|
|
71
|
+
image = byzerllm.Image.load_image_from_path(image_path)
|
|
72
|
+
return {"image": image}
|
|
73
|
+
|
|
74
|
+
def convert_pdf(self, file_path: str) -> List[str]:
|
|
75
|
+
"""转换PDF文件为图片列表"""
|
|
76
|
+
pdf_document = fitz.open(file_path)
|
|
77
|
+
image_paths = []
|
|
78
|
+
try:
|
|
79
|
+
# 分别保存每一页
|
|
80
|
+
for page_num in range(len(pdf_document)):
|
|
81
|
+
page = pdf_document[page_num]
|
|
82
|
+
pix = page.get_pixmap()
|
|
83
|
+
basename = os.path.basename(file_path).replace(" ", "_")
|
|
84
|
+
image_path = os.path.join(self.output_dir, f"{basename}_page{page_num + 1}.png")
|
|
85
|
+
pix.save(image_path)
|
|
86
|
+
image_paths.append(image_path)
|
|
87
|
+
finally:
|
|
88
|
+
# 确保PDF文档关闭
|
|
89
|
+
pdf_document.close()
|
|
90
|
+
return image_paths
|
|
91
|
+
|
|
92
|
+
def convert_docx(self, file_path: str) -> List[str]:
|
|
93
|
+
"""使用 Spire.Doc 将 Word 文档直接转换为图片"""
|
|
94
|
+
# 创建 Spire.Doc 文档对象
|
|
95
|
+
doc = Document()
|
|
96
|
+
doc.LoadFromFile(file_path)
|
|
97
|
+
|
|
98
|
+
# 设置图片保存选项
|
|
99
|
+
image_paths = []
|
|
100
|
+
try:
|
|
101
|
+
# 将每一页保存为图片
|
|
102
|
+
for i in range(doc.GetPageCount()):
|
|
103
|
+
imageStream = doc.SaveImageToStreams(i, ImageType.Bitmap)
|
|
104
|
+
basename = os.path.basename(file_path).replace(" ", "_")
|
|
105
|
+
image_path = os.path.join(self.output_dir, f"{basename}_page{i + 1}.png")
|
|
106
|
+
with open(image_path, 'wb') as imageFile:
|
|
107
|
+
imageFile.write(imageStream.ToArray())
|
|
108
|
+
image_paths.append(image_path)
|
|
109
|
+
finally:
|
|
110
|
+
# 确保文档关闭
|
|
111
|
+
doc.Close()
|
|
112
|
+
|
|
113
|
+
return image_paths
|
|
114
|
+
|
|
115
|
+
def convert(self, file_path: str) -> List[str]:
|
|
116
|
+
"""根据文件类型选择合适的转换方法"""
|
|
117
|
+
file_path = os.path.abspath(file_path)
|
|
118
|
+
if file_path.lower().endswith('.pdf'):
|
|
119
|
+
return self.convert_pdf(file_path)
|
|
120
|
+
elif file_path.lower().endswith('.docx'):
|
|
121
|
+
return self.convert_docx(file_path)
|
|
122
|
+
else:
|
|
123
|
+
raise ValueError(f"Unsupported file format: {file_path}")
|
|
124
|
+
|
|
125
|
+
def to_markdown(self, file_path: str, size: int = -1, max_workers: int = 10) -> str:
|
|
126
|
+
"""
|
|
127
|
+
将文档转换为Markdown格式
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
file_path: 文件路径
|
|
131
|
+
size: 转换的页数,-1表示全部
|
|
132
|
+
max_workers: 并行度,控制同时分析图片的线程数
|
|
133
|
+
"""
|
|
134
|
+
# 创建 _images 目录
|
|
135
|
+
images_dir = os.path.join(self.output_dir, "_images")
|
|
136
|
+
os.makedirs(images_dir, exist_ok=True)
|
|
137
|
+
|
|
138
|
+
# 转换文档为图片
|
|
139
|
+
if size == -1:
|
|
140
|
+
image_paths = self.convert(file_path)
|
|
141
|
+
else:
|
|
142
|
+
image_paths = self.convert(file_path)[0:size]
|
|
143
|
+
|
|
144
|
+
pages: List[Page] = []
|
|
145
|
+
# 使用线程池并行分析图片
|
|
146
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
147
|
+
futures = {
|
|
148
|
+
executor.submit(
|
|
149
|
+
self.analyze_image.with_llm(self.vl_model).with_return_type(Page).run,
|
|
150
|
+
image_path
|
|
151
|
+
): image_path for image_path in image_paths
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for future in as_completed(futures):
|
|
155
|
+
image_path = futures[future]
|
|
156
|
+
try:
|
|
157
|
+
result = future.result()
|
|
158
|
+
pages.append(result)
|
|
159
|
+
logger.info(f"Analyzed {image_path}")
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.error(f"Failed to analyze {image_path}: {str(e)}")
|
|
162
|
+
|
|
163
|
+
# 生成Markdown内容
|
|
164
|
+
markdown_content = []
|
|
165
|
+
|
|
166
|
+
# 遍历每个页面和对应的图片路径
|
|
167
|
+
for page, image_path in zip(pages, image_paths):
|
|
168
|
+
# 处理页面中的每个图片
|
|
169
|
+
for img in page.images:
|
|
170
|
+
# 打开原始图片
|
|
171
|
+
original_image = Image.open(image_path)
|
|
172
|
+
|
|
173
|
+
# 获得坐标
|
|
174
|
+
x1 = img.coordinates[0]
|
|
175
|
+
y1 = img.coordinates[1]
|
|
176
|
+
x2 = img.coordinates[2]
|
|
177
|
+
y2 = img.coordinates[3]
|
|
178
|
+
|
|
179
|
+
# 截取图片
|
|
180
|
+
cropped_image = original_image.crop((x1, y1, x2, y2))
|
|
181
|
+
|
|
182
|
+
# 保存截取后的图片
|
|
183
|
+
cropped_image_path = os.path.join(images_dir, f"cropped_{os.path.basename(image_path)}")
|
|
184
|
+
cropped_image.save(cropped_image_path)
|
|
185
|
+
|
|
186
|
+
# 将图片路径转换为Markdown格式
|
|
187
|
+
image_markdown = f""
|
|
188
|
+
|
|
189
|
+
# 替换文本中的<image_placeholder>为实际的图片Markdown
|
|
190
|
+
page.text = page.text.replace("<image_placeholder>", image_markdown, 1)
|
|
191
|
+
|
|
192
|
+
# 将处理后的页面文本添加到Markdown内容中
|
|
193
|
+
markdown_content.append(page.text)
|
|
194
|
+
|
|
195
|
+
# 将所有页面内容合并为一个Markdown文档
|
|
196
|
+
return '\n\n'.join(markdown_content)
|
|
@@ -52,12 +52,15 @@ class CodeAutoGenerate:
|
|
|
52
52
|
|
|
53
53
|
{%- if content %}
|
|
54
54
|
下面是一些文件路径以及每个文件对应的源码:
|
|
55
|
-
|
|
55
|
+
<files>
|
|
56
56
|
{{ content }}
|
|
57
|
+
</files>
|
|
57
58
|
{%- endif %}
|
|
58
59
|
|
|
59
60
|
{%- if context %}
|
|
61
|
+
<extra_context>
|
|
60
62
|
{{ context }}
|
|
63
|
+
</extra_context>
|
|
61
64
|
{%- endif %}
|
|
62
65
|
|
|
63
66
|
下面是用户的需求:
|
|
@@ -131,12 +131,15 @@ class CodeAutoGenerateDiff:
|
|
|
131
131
|
|
|
132
132
|
{%- if content %}
|
|
133
133
|
下面是一些文件路径以及每个文件对应的源码:
|
|
134
|
-
|
|
134
|
+
<files>
|
|
135
135
|
{{ content }}
|
|
136
|
+
</files>
|
|
136
137
|
{%- endif %}
|
|
137
138
|
|
|
138
139
|
{%- if context %}
|
|
140
|
+
<extra_context>
|
|
139
141
|
{{ context }}
|
|
142
|
+
</extra_context>
|
|
140
143
|
{%- endif %}
|
|
141
144
|
|
|
142
145
|
下面是用户的需求:
|
|
@@ -265,12 +268,15 @@ class CodeAutoGenerateDiff:
|
|
|
265
268
|
|
|
266
269
|
{%- if content %}
|
|
267
270
|
下面是一些文件路径以及每个文件对应的源码:
|
|
268
|
-
|
|
271
|
+
<files>
|
|
269
272
|
{{ content }}
|
|
273
|
+
</files>
|
|
270
274
|
{%- endif %}
|
|
271
275
|
|
|
272
276
|
{%- if context %}
|
|
277
|
+
<extra_context>
|
|
273
278
|
{{ context }}
|
|
279
|
+
</extra_context>
|
|
274
280
|
{%- endif %}
|
|
275
281
|
|
|
276
282
|
下面是用户的需求:
|
|
@@ -182,12 +182,15 @@ class CodeAutoGenerateEditBlock:
|
|
|
182
182
|
|
|
183
183
|
{%- if content %}
|
|
184
184
|
下面是一些文件路径以及每个文件对应的源码:
|
|
185
|
-
|
|
185
|
+
<files>
|
|
186
186
|
{{ content }}
|
|
187
|
+
</files>
|
|
187
188
|
{%- endif %}
|
|
188
189
|
|
|
189
190
|
{%- if context %}
|
|
191
|
+
<extra_context>
|
|
190
192
|
{{ context }}
|
|
193
|
+
</extra_context>
|
|
191
194
|
{%- endif %}
|
|
192
195
|
|
|
193
196
|
下面是用户的需求:
|
|
@@ -343,12 +346,15 @@ class CodeAutoGenerateEditBlock:
|
|
|
343
346
|
|
|
344
347
|
{%- if content %}
|
|
345
348
|
下面是一些文件路径以及每个文件对应的源码:
|
|
346
|
-
|
|
349
|
+
<files>
|
|
347
350
|
{{ content }}
|
|
351
|
+
</files>
|
|
348
352
|
{%- endif %}
|
|
349
353
|
|
|
350
354
|
{%- if context %}
|
|
355
|
+
<extra_context>
|
|
351
356
|
{{ context }}
|
|
357
|
+
</extra_context>
|
|
352
358
|
{%- endif %}
|
|
353
359
|
|
|
354
360
|
下面是用户的需求:
|
|
@@ -114,12 +114,15 @@ class CodeAutoGenerateStrictDiff:
|
|
|
114
114
|
|
|
115
115
|
{%- if content %}
|
|
116
116
|
下面是一些文件路径以及每个文件对应的源码:
|
|
117
|
-
|
|
117
|
+
<files>
|
|
118
118
|
{{ content }}
|
|
119
|
+
</files>
|
|
119
120
|
{%- endif %}
|
|
120
121
|
|
|
121
122
|
{%- if context %}
|
|
123
|
+
<extra_context>
|
|
122
124
|
{{ context }}
|
|
125
|
+
</extra_context>
|
|
123
126
|
{%- endif %}
|
|
124
127
|
|
|
125
128
|
下面是用户的需求:
|
|
@@ -235,12 +238,15 @@ class CodeAutoGenerateStrictDiff:
|
|
|
235
238
|
|
|
236
239
|
{%- if content %}
|
|
237
240
|
下面是一些文件路径以及每个文件对应的源码:
|
|
238
|
-
|
|
241
|
+
<files>
|
|
239
242
|
{{ content }}
|
|
243
|
+
</files>
|
|
240
244
|
{%- endif %}
|
|
241
245
|
|
|
242
246
|
{%- if context %}
|
|
247
|
+
<extra_context>
|
|
243
248
|
{{ context }}
|
|
249
|
+
</extra_context>
|
|
244
250
|
{%- endif %}
|
|
245
251
|
|
|
246
252
|
下面是用户的需求:
|
|
@@ -14,6 +14,13 @@ COMMANDS = {
|
|
|
14
14
|
},
|
|
15
15
|
"/coding": {"/apply": {}, "/next": {}},
|
|
16
16
|
"/chat": {"/new": {}, "/review": {}, "/no_context": {}},
|
|
17
|
+
"/mcp": {
|
|
18
|
+
"/add": "",
|
|
19
|
+
"/remove": "",
|
|
20
|
+
"/list": "",
|
|
21
|
+
"/list_running": "",
|
|
22
|
+
"/refresh": ""
|
|
23
|
+
},
|
|
17
24
|
"/lib": {
|
|
18
25
|
"/add": "",
|
|
19
26
|
"/remove": "",
|
autocoder/common/mcp_hub.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
3
|
import asyncio
|
|
4
|
+
import aiohttp
|
|
4
5
|
from datetime import datetime
|
|
5
6
|
from typing import Dict, List, Optional, Any, Set, Optional
|
|
6
7
|
from pathlib import Path
|
|
@@ -10,6 +11,8 @@ from mcp import ClientSession
|
|
|
10
11
|
from mcp.client.stdio import stdio_client, StdioServerParameters
|
|
11
12
|
import mcp.types as mcp_types
|
|
12
13
|
from loguru import logger
|
|
14
|
+
import time
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class McpTool(BaseModel):
|
|
15
18
|
"""Represents an MCP tool configuration"""
|
|
@@ -60,6 +63,24 @@ class McpConnection:
|
|
|
60
63
|
)
|
|
61
64
|
|
|
62
65
|
|
|
66
|
+
MCP_PERPLEXITY_SERVER = '''
|
|
67
|
+
{
|
|
68
|
+
"perplexity": {
|
|
69
|
+
"command": "python",
|
|
70
|
+
"args": [
|
|
71
|
+
"-m", "autocoder.common.mcp_servers.mcp_server_perplexity"
|
|
72
|
+
],
|
|
73
|
+
"env": {
|
|
74
|
+
"PERPLEXITY_API_KEY": "{{PERPLEXITY_API_KEY}}"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
'''
|
|
79
|
+
|
|
80
|
+
MCP_BUILD_IN_SERVERS = {
|
|
81
|
+
"perplexity": json.loads(MCP_PERPLEXITY_SERVER)["perplexity"]
|
|
82
|
+
}
|
|
83
|
+
|
|
63
84
|
class McpHub:
|
|
64
85
|
"""
|
|
65
86
|
Manages MCP server connections and interactions.
|
|
@@ -98,6 +119,45 @@ class McpHub:
|
|
|
98
119
|
with open(self.settings_path, "w") as f:
|
|
99
120
|
json.dump(default_settings, f, indent=2)
|
|
100
121
|
|
|
122
|
+
async def add_server_config(self, name: str, config:Dict[str,Any]) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Add or update MCP server configuration in settings file.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
server_name_or_config: Name of the server or server configuration dictionary
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
settings = self._read_settings()
|
|
131
|
+
settings["mcpServers"][name] = config
|
|
132
|
+
with open(self.settings_path, "w") as f:
|
|
133
|
+
json.dump(settings, f, indent=2, ensure_ascii=False)
|
|
134
|
+
await self.initialize()
|
|
135
|
+
logger.info(f"Added/updated MCP server config: {name}")
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"Failed to add MCP server config: {e}")
|
|
138
|
+
raise
|
|
139
|
+
|
|
140
|
+
async def remove_server_config(self, name: str) -> None:
|
|
141
|
+
"""
|
|
142
|
+
Remove MCP server configuration from settings file.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
name: Name of the server to remove
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
settings = self._read_settings()
|
|
149
|
+
if name in settings["mcpServers"]:
|
|
150
|
+
del settings["mcpServers"][name]
|
|
151
|
+
with open(self.settings_path, "w") as f:
|
|
152
|
+
json.dump(settings, f, indent=2, ensure_ascii=False)
|
|
153
|
+
logger.info(f"Removed MCP server config: {name}")
|
|
154
|
+
await self.initialize()
|
|
155
|
+
else:
|
|
156
|
+
logger.warning(f"MCP server {name} not found in settings")
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.error(f"Failed to remove MCP server config: {e}")
|
|
159
|
+
raise
|
|
160
|
+
|
|
101
161
|
async def initialize(self):
|
|
102
162
|
"""Initialize MCP server connections from settings"""
|
|
103
163
|
try:
|
|
@@ -128,14 +188,15 @@ class McpHub:
|
|
|
128
188
|
server_params = StdioServerParameters(
|
|
129
189
|
command=config["command"],
|
|
130
190
|
args=config.get("args", []),
|
|
131
|
-
env={**config.get("env", {}),
|
|
191
|
+
env={**config.get("env", {}),
|
|
192
|
+
"PATH": os.environ.get("PATH", "")},
|
|
132
193
|
)
|
|
133
194
|
|
|
134
195
|
# Create transport using context manager
|
|
135
196
|
transport_manager = stdio_client(server_params)
|
|
136
197
|
transport = await transport_manager.__aenter__()
|
|
137
198
|
try:
|
|
138
|
-
session = await ClientSession(transport[0], transport[1]).__aenter__()
|
|
199
|
+
session = await ClientSession(transport[0], transport[1]).__aenter__()
|
|
139
200
|
await session.initialize()
|
|
140
201
|
|
|
141
202
|
# Store connection with transport manager
|
|
@@ -148,9 +209,9 @@ class McpHub:
|
|
|
148
209
|
server.resources = await self._fetch_resources(name)
|
|
149
210
|
server.resource_templates = await self._fetch_resource_templates(name)
|
|
150
211
|
|
|
151
|
-
except Exception as e:
|
|
212
|
+
except Exception as e:
|
|
152
213
|
# Clean up transport if session initialization fails
|
|
153
|
-
|
|
214
|
+
|
|
154
215
|
await transport_manager.__aexit__(None, None, None)
|
|
155
216
|
raise
|
|
156
217
|
|
|
@@ -179,6 +240,19 @@ class McpHub:
|
|
|
179
240
|
if name in self.connections:
|
|
180
241
|
del self.connections[name]
|
|
181
242
|
|
|
243
|
+
async def refresh_server_connection(self, name: str) -> None:
|
|
244
|
+
"""
|
|
245
|
+
Refresh a server connection
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
config = self._read_settings()
|
|
249
|
+
await self.delete_connection(name)
|
|
250
|
+
await self.connect_to_server(name, config.get("mcpServers", {}).get(name, {}))
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.error(f"Failed to refresh MCP server {name}: {e}")
|
|
253
|
+
raise
|
|
254
|
+
|
|
255
|
+
|
|
182
256
|
async def update_server_connections(self, new_servers: Dict[str, Any]) -> None:
|
|
183
257
|
"""
|
|
184
258
|
Update server connections based on new configuration
|
|
@@ -204,7 +278,8 @@ class McpHub:
|
|
|
204
278
|
elif current_conn.server.config != json.dumps(config):
|
|
205
279
|
# Updated configuration
|
|
206
280
|
await self.connect_to_server(name, config)
|
|
207
|
-
logger.info(
|
|
281
|
+
logger.info(
|
|
282
|
+
f"Reconnected MCP server with updated config: {name}")
|
|
208
283
|
|
|
209
284
|
finally:
|
|
210
285
|
self.is_connecting = False
|
|
@@ -247,7 +322,7 @@ class McpHub:
|
|
|
247
322
|
for resource in response.resources
|
|
248
323
|
]
|
|
249
324
|
except Exception as e:
|
|
250
|
-
logger.
|
|
325
|
+
logger.warning(f"Failed to fetch resources for {server_name}: {e}")
|
|
251
326
|
return []
|
|
252
327
|
|
|
253
328
|
async def _fetch_resource_templates(
|
|
@@ -284,7 +359,8 @@ class McpHub:
|
|
|
284
359
|
for template in response.resourceTemplates
|
|
285
360
|
]
|
|
286
361
|
except Exception as e:
|
|
287
|
-
logger.
|
|
362
|
+
logger.warning(
|
|
363
|
+
f"Failed to fetch resource templates for {server_name}: {e}")
|
|
288
364
|
return []
|
|
289
365
|
|
|
290
366
|
def _read_settings(self) -> dict:
|