leonsimon23 commited on
Commit
c31258c
·
verified ·
1 Parent(s): 94faf3b

Update Dockerfile

Browse files
Files changed (1) hide show
  1. Dockerfile +174 -96
Dockerfile CHANGED
@@ -28,15 +28,16 @@ RUN uv pip install -e .
28
  # 安装额外的web依赖
29
  RUN uv pip install fastapi uvicorn python-multipart
30
 
31
- # 创建一个HTTP API包装器
32
  COPY <<'EOF' /app/http_wrapper.py
33
  #!/usr/bin/env python3
34
 
35
  import asyncio
36
  import json
37
  import logging
 
38
  from contextlib import asynccontextmanager
39
- from typing import Any, Dict
40
 
41
  import uvicorn
42
  from fastapi import FastAPI, HTTPException
@@ -55,91 +56,130 @@ class SearchRequest(BaseModel):
55
  class DownloadRequest(BaseModel):
56
  paper_id: str
57
 
58
- # 全局变量存储工具
59
- tools = {}
 
60
 
61
- @asynccontextmanager
62
- async def lifespan(app: FastAPI):
63
- """初始化MCP工具"""
64
- global tools
65
  try:
66
- # 导入MCP服务器的工具
67
- from paper_search_mcp.server import (
68
- SearchArxivTool,
69
- SearchPubmedTool,
70
- SearchBiorxivTool,
71
- DownloadArxivTool,
72
- DownloadPubmedTool
73
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- # 初始化工具
76
- tools = {
77
- 'search_arxiv': SearchArxivTool(),
78
- 'search_pubmed': SearchPubmedTool(),
79
- 'search_biorxiv': SearchBiorxivTool(),
80
- 'download_arxiv': DownloadArxivTool(),
81
- 'download_pubmed': DownloadPubmedTool(),
82
- }
83
 
84
- logger.info("MCP工具初始化完成")
85
- yield
86
  except Exception as e:
87
- logger.error(f"初始化失败: {e}")
88
- yield
 
 
 
 
 
 
89
 
90
  # 创建FastAPI应用
91
  app = FastAPI(
92
  title="Paper Search MCP Server",
93
- description="HTTP wrapper for Paper Search MCP Server",
94
  version="1.0.0",
95
  lifespan=lifespan
96
  )
97
 
98
  @app.get("/", response_class=HTMLResponse)
99
  async def root():
100
- return """
101
  <!DOCTYPE html>
102
  <html>
103
  <head>
104
  <title>Paper Search MCP Server</title>
105
  <style>
106
- body { font-family: Arial, sans-serif; margin: 40px; }
107
- .endpoint { background: #f5f5f5; padding: 10px; margin: 10px 0; border-radius: 5px; }
108
- .method { color: #0066cc; font-weight: bold; }
 
 
109
  </style>
110
  </head>
111
  <body>
112
  <h1>Paper Search MCP Server</h1>
113
- <p>HTTP wrapper for the Paper Search MCP Server</p>
114
 
115
- <h2>Available Endpoints:</h2>
116
-
117
- <div class="endpoint">
118
- <span class="method">GET</span> <code>/health</code> - Health check
119
  </div>
120
 
121
- <div class="endpoint">
122
- <span class="method">POST</span> <code>/search/arxiv</code> - Search ArXiv papers
123
- <br><small>Body: {"query": "machine learning", "max_results": 10}</small>
124
  </div>
125
 
 
 
126
  <div class="endpoint">
127
- <span class="method">POST</span> <code>/search/pubmed</code> - Search PubMed papers
128
- <br><small>Body: {"query": "covid-19", "max_results": 10}</small>
129
  </div>
130
 
131
  <div class="endpoint">
132
- <span class="method">POST</span> <code>/search/biorxiv</code> - Search bioRxiv papers
133
- <br><small>Body: {"query": "genomics", "max_results": 10}</small>
134
  </div>
135
 
136
  <div class="endpoint">
137
- <span class="method">POST</span> <code>/download/arxiv</code> - Download ArXiv paper
138
- <br><small>Body: {"paper_id": "2301.12345"}</small>
139
  </div>
140
 
141
  <div class="endpoint">
142
- <span class="method">GET</span> <code>/docs</code> - API documentation (Swagger)
143
  </div>
144
  </body>
145
  </html>
@@ -147,63 +187,101 @@ async def root():
147
 
148
  @app.get("/health")
149
  async def health():
150
- return {"status": "healthy", "tools_loaded": len(tools)}
 
 
 
 
151
 
152
- @app.post("/search/arxiv")
153
- async def search_arxiv(request: SearchRequest):
154
- try:
155
- if 'search_arxiv' not in tools:
156
- raise HTTPException(status_code=500, detail="ArXiv search tool not available")
157
-
158
- # 调用MCP工具
159
- tool = tools['search_arxiv']
160
- result = await tool.search_papers(request.query, request.max_results)
161
-
162
- return {"results": result, "count": len(result)}
163
- except Exception as e:
164
- logger.error(f"ArXiv search error: {e}")
165
- raise HTTPException(status_code=500, detail=str(e))
166
 
167
- @app.post("/search/pubmed")
168
- async def search_pubmed(request: SearchRequest):
169
- try:
170
- if 'search_pubmed' not in tools:
171
- raise HTTPException(status_code=500, detail="PubMed search tool not available")
172
-
173
- tool = tools['search_pubmed']
174
- result = await tool.search_papers(request.query, request.max_results)
175
-
176
- return {"results": result, "count": len(result)}
177
- except Exception as e:
178
- logger.error(f"PubMed search error: {e}")
179
- raise HTTPException(status_code=500, detail=str(e))
180
 
181
- @app.post("/search/biorxiv")
182
- async def search_biorxiv(request: SearchRequest):
183
  try:
184
- if 'search_biorxiv' not in tools:
185
- raise HTTPException(status_code=500, detail="bioRxiv search tool not available")
 
 
 
 
 
186
 
187
- tool = tools['search_biorxiv']
188
- result = await tool.search_papers(request.query, request.max_results)
189
 
190
- return {"results": result, "count": len(result)}
191
- except Exception as e:
192
- logger.error(f"bioRxiv search error: {e}")
193
- raise HTTPException(status_code=500, detail=str(e))
194
-
195
- @app.post("/download/arxiv")
196
- async def download_arxiv(request: DownloadRequest):
197
- try:
198
- if 'download_arxiv' not in tools:
199
- raise HTTPException(status_code=500, detail="ArXiv download tool not available")
200
 
201
- tool = tools['download_arxiv']
202
- result = await tool.download_paper(request.paper_id)
 
 
 
 
203
 
204
- return {"download_info": result}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  except Exception as e:
206
- logger.error(f"ArXiv download error: {e}")
207
  raise HTTPException(status_code=500, detail=str(e))
208
 
209
  if __name__ == "__main__":
 
28
  # 安装额外的web依赖
29
  RUN uv pip install fastapi uvicorn python-multipart
30
 
31
+ # 创建一个探索性的HTTP API包装器
32
  COPY <<'EOF' /app/http_wrapper.py
33
  #!/usr/bin/env python3
34
 
35
  import asyncio
36
  import json
37
  import logging
38
+ import traceback
39
  from contextlib import asynccontextmanager
40
+ from typing import Any, Dict, Optional, List
41
 
42
  import uvicorn
43
  from fastapi import FastAPI, HTTPException
 
56
  class DownloadRequest(BaseModel):
57
  paper_id: str
58
 
59
+ # 全局变量
60
+ available_functions = {}
61
+ project_info = {}
62
 
63
+ async def explore_project_structure():
64
+ """探索项目结构并找到可用的功能"""
65
+ global available_functions, project_info
66
+
67
  try:
68
+ # 尝试导入主模块
69
+ import paper_search_mcp
70
+ project_info['main_module'] = True
71
+ logger.info("主模块导入成功")
72
+
73
+ # 尝试导入服务器模块
74
+ try:
75
+ import paper_search_mcp.server as server_module
76
+ project_info['server_module'] = dir(server_module)
77
+ logger.info(f"服务器模块属性: {project_info['server_module']}")
78
+ except Exception as e:
79
+ logger.error(f"服务器模块导入失败: {e}")
80
+
81
+ # 尝试导入学术平台模块
82
+ try:
83
+ import paper_search_mcp.academic_platforms as platforms
84
+ project_info['platforms_module'] = dir(platforms)
85
+ logger.info(f"学术平台模块属性: {project_info['platforms_module']}")
86
+
87
+ # 尝试获取具体的搜索函数
88
+ for attr_name in dir(platforms):
89
+ if not attr_name.startswith('_'):
90
+ attr = getattr(platforms, attr_name)
91
+ if callable(attr):
92
+ available_functions[attr_name] = attr
93
+ logger.info(f"找到可调用函数: {attr_name}")
94
+
95
+ except Exception as e:
96
+ logger.error(f"学术平台模块导入失败: {e}")
97
+
98
+ # 尝试导入各个子模块
99
+ submodules = ['arxiv', 'pubmed', 'biorxiv', 'scihub']
100
+ for submodule in submodules:
101
+ try:
102
+ module = __import__(f'paper_search_mcp.academic_platforms.{submodule}', fromlist=[submodule])
103
+ project_info[f'{submodule}_module'] = dir(module)
104
+ logger.info(f"{submodule}模块属性: {project_info[f'{submodule}_module']}")
105
+
106
+ # 查找搜索函数
107
+ for attr_name in dir(module):
108
+ if not attr_name.startswith('_') and 'search' in attr_name.lower():
109
+ attr = getattr(module, attr_name)
110
+ if callable(attr):
111
+ available_functions[f'{submodule}_{attr_name}'] = attr
112
+ logger.info(f"找到{submodule}搜索函数: {attr_name}")
113
+
114
+ except Exception as e:
115
+ logger.warning(f"子模块 {submodule} 导入失败: {e}")
116
 
117
+ logger.info(f"总共找到 {len(available_functions)} 个可用函数")
 
 
 
 
 
 
 
118
 
 
 
119
  except Exception as e:
120
+ logger.error(f"项目探索失败: {e}")
121
+ logger.error(traceback.format_exc())
122
+
123
+ @asynccontextmanager
124
+ async def lifespan(app: FastAPI):
125
+ """应用生命周期管理"""
126
+ await explore_project_structure()
127
+ yield
128
 
129
  # 创建FastAPI应用
130
  app = FastAPI(
131
  title="Paper Search MCP Server",
132
+ description="HTTP wrapper for Paper Search MCP Server with dynamic function discovery",
133
  version="1.0.0",
134
  lifespan=lifespan
135
  )
136
 
137
  @app.get("/", response_class=HTMLResponse)
138
  async def root():
139
+ return f"""
140
  <!DOCTYPE html>
141
  <html>
142
  <head>
143
  <title>Paper Search MCP Server</title>
144
  <style>
145
+ body {{ font-family: Arial, sans-serif; margin: 40px; }}
146
+ .info {{ background: #e7f3ff; padding: 15px; margin: 15px 0; border-radius: 5px; }}
147
+ .endpoint {{ background: #f5f5f5; padding: 10px; margin: 10px 0; border-radius: 5px; }}
148
+ .method {{ color: #0066cc; font-weight: bold; }}
149
+ pre {{ background: #f8f8f8; padding: 10px; border-radius: 3px; overflow-x: auto; }}
150
  </style>
151
  </head>
152
  <body>
153
  <h1>Paper Search MCP Server</h1>
154
+ <p>HTTP wrapper with dynamic function discovery</p>
155
 
156
+ <div class="info">
157
+ <h3>发现的功能 ({len(available_functions)} 个):</h3>
158
+ <pre>{json.dumps(list(available_functions.keys()), indent=2)}</pre>
 
159
  </div>
160
 
161
+ <div class="info">
162
+ <h3>项目信息:</h3>
163
+ <pre>{json.dumps(project_info, indent=2, default=str)}</pre>
164
  </div>
165
 
166
+ <h2>可用端点:</h2>
167
+
168
  <div class="endpoint">
169
+ <span class="method">GET</span> <code>/health</code> - 健康检查
 
170
  </div>
171
 
172
  <div class="endpoint">
173
+ <span class="method">GET</span> <code>/functions</code> - 列出所有发现的功能
 
174
  </div>
175
 
176
  <div class="endpoint">
177
+ <span class="method">POST</span> <code>/search</code> - 通用搜索接口
178
+ <br><small>Body: {{"platform": "arxiv", "query": "machine learning", "max_results": 10}}</small>
179
  </div>
180
 
181
  <div class="endpoint">
182
+ <span class="method">GET</span> <code>/docs</code> - API文档 (Swagger)
183
  </div>
184
  </body>
185
  </html>
 
187
 
188
  @app.get("/health")
189
  async def health():
190
+ return {
191
+ "status": "healthy",
192
+ "functions_loaded": len(available_functions),
193
+ "available_functions": list(available_functions.keys())
194
+ }
195
 
196
+ @app.get("/functions")
197
+ async def list_functions():
198
+ return {
199
+ "available_functions": list(available_functions.keys()),
200
+ "project_info": project_info
201
+ }
 
 
 
 
 
 
 
 
202
 
203
+ class GenericSearchRequest(BaseModel):
204
+ platform: str
205
+ query: str
206
+ max_results: int = 10
 
 
 
 
 
 
 
 
 
207
 
208
+ @app.post("/search")
209
+ async def generic_search(request: GenericSearchRequest):
210
  try:
211
+ # 查找合适的搜索函数
212
+ possible_function_names = [
213
+ f"{request.platform}_search",
214
+ f"search_{request.platform}",
215
+ f"{request.platform}_search_papers",
216
+ request.platform
217
+ ]
218
 
219
+ search_function = None
220
+ used_function_name = None
221
 
222
+ for func_name in possible_function_names:
223
+ if func_name in available_functions:
224
+ search_function = available_functions[func_name]
225
+ used_function_name = func_name
226
+ break
 
 
 
 
 
227
 
228
+ if not search_function:
229
+ return {
230
+ "error": f"No search function found for platform: {request.platform}",
231
+ "available_functions": list(available_functions.keys()),
232
+ "searched_for": possible_function_names
233
+ }
234
 
235
+ # 尝试调用搜索函数
236
+ try:
237
+ # 尝试不同的参数组合
238
+ result = None
239
+ error_msgs = []
240
+
241
+ # 尝试1: (query, max_results)
242
+ try:
243
+ result = await asyncio.to_thread(search_function, request.query, request.max_results)
244
+ except Exception as e1:
245
+ error_msgs.append(f"尝试1失败: {str(e1)}")
246
+
247
+ # 尝试2: 只传query
248
+ try:
249
+ result = await asyncio.to_thread(search_function, request.query)
250
+ except Exception as e2:
251
+ error_msgs.append(f"尝试2失败: {str(e2)}")
252
+
253
+ # 尝试3: 字典参数
254
+ try:
255
+ result = await asyncio.to_thread(search_function, {
256
+ 'query': request.query,
257
+ 'max_results': request.max_results
258
+ })
259
+ except Exception as e3:
260
+ error_msgs.append(f"尝试3失败: {str(e3)}")
261
+
262
+ if result is not None:
263
+ return {
264
+ "platform": request.platform,
265
+ "function_used": used_function_name,
266
+ "results": result,
267
+ "count": len(result) if isinstance(result, (list, tuple)) else 1
268
+ }
269
+ else:
270
+ return {
271
+ "error": "所有调用尝试都失败了",
272
+ "function_used": used_function_name,
273
+ "error_messages": error_msgs
274
+ }
275
+
276
+ except Exception as e:
277
+ logger.error(f"搜索函数调用错误: {e}")
278
+ return {
279
+ "error": f"Function call failed: {str(e)}",
280
+ "function_used": used_function_name
281
+ }
282
+
283
  except Exception as e:
284
+ logger.error(f"通用搜索错误: {e}")
285
  raise HTTPException(status_code=500, detail=str(e))
286
 
287
  if __name__ == "__main__":