#!/usr/bin/env python3
"""Article MCP CLI入口点
从main.py迁移的核心功能,保持完全兼容
"""
import argparse
import asyncio
import logging
import os
import re
import sys
from typing import TYPE_CHECKING
# 设置编码环境,确保emoji字符正确处理
os.environ["PYTHONIOENCODING"] = "utf-8"
if TYPE_CHECKING:
from fastmcp import FastMCP
def safe_print(text: str) -> None:
"""安全打印函数,处理编码问题"""
try:
print(text)
except UnicodeEncodeError:
# 移除或替换非ASCII字符
clean_text = re.sub(r"[^\x00-\x7F]+", "", text)
print(clean_text)
except UnicodeDecodeError:
# 处理解码错误
clean_text = text.encode("ascii", "ignore").decode("ascii")
print(clean_text)
def create_mcp_server() -> "FastMCP":
"""创建MCP服务器 - 集成新的6工具架构"""
from fastmcp import FastMCP
from .services.arxiv_search import create_arxiv_service
from .services.crossref_service import CrossRefService
# 导入新架构服务(使用新的包结构)
from .services.easyscholar_service import create_easyscholar_service
from .services.europe_pmc import create_europe_pmc_service
# from .services.literature_relation_service import create_literature_relation_service
from .services.openalex_metrics_service import create_openalex_metrics_service
from .services.openalex_service import OpenAlexService
from .services.pubmed_search import create_pubmed_service
from .services.reference_service import create_unified_reference_service
from .tools.core.article_tools import register_article_tools
from .tools.core.quality_tools import register_quality_tools
from .tools.core.reference_tools import register_reference_tools
from .tools.core.relation_tools import register_relation_tools
# 导入核心工具模块(使用新的包结构)
from .tools.core.search_tools import register_search_tools
# 创建 MCP 服务器实例
mcp = FastMCP("Article MCP Server", version="0.2.2")
# 创建服务实例
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
# 添加中间件
from .middleware import LoggingMiddleware, MCPErrorHandlingMiddleware, TimingMiddleware
mcp.add_middleware(MCPErrorHandlingMiddleware(logger))
mcp.add_middleware(LoggingMiddleware(logger))
mcp.add_middleware(TimingMiddleware())
# 注册资源
from .resources import register_config_resources, register_journal_resources
register_config_resources(mcp)
register_journal_resources(mcp)
# 核心服务依赖注入
pubmed_service = create_pubmed_service(logger)
europe_pmc_service = create_europe_pmc_service(logger, pubmed_service)
crossref_service = CrossRefService(logger)
openalex_service = OpenAlexService(logger)
arxiv_service = create_arxiv_service(logger)
reference_service = create_unified_reference_service(logger)
easyscholar_service = create_easyscholar_service(logger)
openalex_metrics_service = create_openalex_metrics_service(logger)
# literature_relation_service 在关系工具中使用,不需要单独创建
# 注册新架构核心工具
# 工具1: 统一搜索工具
search_services = {
"europe_pmc": europe_pmc_service,
"pubmed": pubmed_service,
"arxiv": arxiv_service,
"crossref": crossref_service,
"openalex": openalex_service,
}
register_search_tools(mcp, search_services, logger)
# 工具2: 统一文章详情工具
article_services = {
"europe_pmc": europe_pmc_service,
"crossref": crossref_service,
"openalex": openalex_service,
"arxiv": arxiv_service,
"pubmed": pubmed_service,
}
register_article_tools(mcp, article_services, logger)
# 工具3: 参考文献工具
reference_services = {
"europe_pmc": europe_pmc_service,
"crossref": crossref_service,
"pubmed": pubmed_service,
"reference": reference_service,
}
register_reference_tools(mcp, reference_services, logger)
# 工具4: 文献关系分析工具
relation_services = {
"europe_pmc": europe_pmc_service,
"pubmed": pubmed_service,
"crossref": crossref_service,
"openalex": openalex_service,
}
register_relation_tools(mcp, relation_services, logger)
# 工具5: 期刊质量评估工具
quality_services = {
"easyscholar": easyscholar_service,
"openalex": openalex_metrics_service,
}
register_quality_tools(mcp, quality_services, logger)
return mcp
def start_server(
transport: str = "stdio", host: str = "localhost", port: int = 9000, path: str = "/mcp"
) -> None:
"""启动MCP服务器"""
safe_print("启动 Article MCP 服务器 v2.0 (5个核心工具)")
safe_print(f"传输模式: {transport}")
safe_print("[新架构] 核心工具 (5个核心工具):")
safe_print("")
safe_print("[工具1] search_literature")
safe_print(" - 统一多源文献搜索工具")
safe_print(" - 支持数据源: Europe PMC, PubMed, arXiv, CrossRef, OpenAlex")
safe_print(" - 特点: 自动去重、智能排序、透明数据源标识")
safe_print(" - 参数: keyword, sources, max_results, search_type")
safe_print("")
safe_print("[工具2] get_article_details")
safe_print(" - 统一文献详情获取工具")
safe_print(" - 支持标识符: DOI, PMID, PMCID, arXiv ID")
safe_print(" - 特点: 多源数据合并、自动类型识别、可选质量指标")
safe_print(" - 参数: identifier, id_type, sources, include_quality_metrics")
safe_print("")
safe_print("[工具3] get_references")
safe_print(" - 参考文献获取工具")
safe_print(" - 支持从文献标识符获取完整参考文献列表")
safe_print(" - 特点: 多源查询、参考文献完整性检查")
safe_print(" - 参数: identifier, id_type, sources, max_results")
safe_print("")
safe_print("[工具4] get_literature_relations")
safe_print(" - 文献关系分析工具")
safe_print(" - 支持分析: 参考文献、相似文献、引用文献、合作网络")
safe_print(" - 特点: 网络分析、社区检测、可视化数据")
safe_print(" - 参数: identifier, relation_types, max_depth")
safe_print("")
safe_print("[工具5] get_journal_quality")
safe_print(" - 期刊质量评估工具")
safe_print(" - 支持指标: 影响因子、JCI、分区、排名")
safe_print(" - 特点: EasyScholar集成、本地缓存、批量评估")
safe_print(" - 参数: journal_name, include_metrics, evaluation_criteria")
safe_print("")
safe_print("[技术特性]:")
safe_print(" - FastMCP 2.13.0 框架")
safe_print(" - 依赖注入架构模式")
safe_print(" - 智能缓存机制")
safe_print(" - 并发控制优化")
safe_print(" - 多API集成")
safe_print(" - MCP配置集成")
mcp = create_mcp_server()
if transport == "stdio":
print("使用 stdio 传输模式 (推荐用于 Claude Desktop)")
mcp.run(transport="stdio")
elif transport == "sse":
print("使用 SSE 传输模式")
print(f"服务器地址: http://{host}:{port}/sse")
mcp.run(transport="sse", host=host, port=port)
elif transport == "streamable-http":
print("使用 Streamable HTTP 传输模式")
print(f"服务器地址: http://{host}:{port}{path}")
mcp.run(transport="streamable-http", host=host, port=port, path=path, stateless_http=True)
else:
print(f"不支持的传输模式: {transport}")
sys.exit(1)
async def run_test() -> bool:
"""运行测试"""
print("Europe PMC MCP 服务器测试")
print("=" * 50)
try:
# 简单测试:验证MCP服务器创建和工具注册
create_mcp_server()
print("✓ MCP 服务器创建成功")
# 测试工具函数直接调用
print("✓ 开始测试搜索功能...")
# 这里我们不能直接调用工具,因为需要MCP客户端
# 但我们可以测试服务器是否正确创建
print("✓ 测试参数准备完成")
print("✓ MCP 服务器工具注册正常")
print("\n测试结果:")
print("- MCP 服务器创建: 成功")
print("- 工具注册: 成功")
print("- 配置验证: 成功")
print("\n注意: 完整的功能测试需要在MCP客户端环境中进行")
print("建议使用 Claude Desktop 或其他 MCP 客户端进行实际测试")
return True
except Exception as e:
print(f"测试失败: {e}")
import traceback
traceback.print_exc()
return False
def show_info() -> None:
"""显示项目信息"""
safe_print("Article MCP 文献搜索服务器 (基于 BioMCP 设计模式)")
safe_print("=" * 70)
safe_print("基于 FastMCP 框架和 BioMCP 设计模式开发的文献搜索工具")
safe_print("支持搜索 Europe PMC、arXiv 等多个文献数据库")
safe_print("\n[核心功能]:")
safe_print("- [搜索] 搜索 Europe PMC 文献数据库 (同步 & 异步版本)")
safe_print("- [详情] 获取文献详细信息 (同步 & 异步版本)")
safe_print("- [文献] 获取参考文献列表 (通过DOI, 同步 & 异步版本)")
safe_print("- [性能] 异步并行优化版本(提升6.2倍性能)")
safe_print("- [标识] 支持多种标识符 (PMID, PMCID, DOI)")
safe_print("- [过滤] 支持日期范围过滤")
safe_print("- [去重] 参考文献信息补全和去重")
safe_print("- [缓存] 智能缓存机制(24小时)")
safe_print("- [传输] 支持多种传输模式")
safe_print("- [统计] 详细性能统计信息")
safe_print("\n[技术优化]:")
safe_print("- [架构] 模块化架构设计 (基于 BioMCP 模式)")
safe_print("- [并发] 并发控制 (信号量限制并发请求)")
safe_print("- [重试] 重试机制 (3次重试,指数退避)")
safe_print("- [限速] 速率限制 (遵循官方API速率限制)")
safe_print("- [异常] 完整的异常处理和日志记录")
safe_print("- [接口] 统一的工具接口 (类似 BioMCP 的 search/fetch)")
safe_print("\n[性能数据]:")
safe_print("- 同步版本: 67.79秒 (112条参考文献)")
safe_print("- 异步版本: 10.99秒 (112条参考文献)")
safe_print("- 性能提升: 6.2倍更快,节省83.8%时间")
safe_print("\n[MCP 工具详情(5个核心工具)]:")
print("1. search_literature")
print(" 功能:统一多源文献搜索工具")
print(" 参数:keyword, sources, max_results, search_type")
print(" 数据源:Europe PMC, PubMed, arXiv, CrossRef, OpenAlex")
print(" 特点:自动去重、智能排序、透明数据源标识")
print(" 适用:文献检索、复杂查询、高性能需求")
print("2. get_article_details")
print(" 功能:获取文献全文内容(支持参数容错自动修正)")
print(" 参数:pmcid, sections, format")
print(" 标识符:PMCID(支持字符串化数组自动解析)")
print(" 特点:自动修正参数格式、sections 自动转数组")
print(" 适用:文献全文获取、指定章节提取")
print("3. get_references")
print(" 功能:参考文献获取工具")
print(" 参数:identifier, id_type, sources, max_results, include_metadata")
print(" 标识符:DOI, PMID, PMCID, arXiv ID")
print(" 特点:多源查询、参考文献完整性检查、智能去重")
print(" 适用:参考文献获取、文献数据库构建")
print("4. get_literature_relations")
print(" 功能:文献关系分析工具")
print(" 参数:identifier, relation_types, max_results")
print(" 关系类型:参考文献、相似文献、引用文献、合作网络")
print(" 特点:网络分析、社区检测、可视化数据")
print(" 适用:文献关联分析、学术研究综述、文献网络构建")
print("5. get_journal_quality")
print(" 功能:期刊质量评估工具")
print(" 参数:journal_name, include_metrics")
print(" 数据源:EasyScholar, OpenAlex")
print(" 特点:EasyScholar 集成、OpenAlex 指标、本地缓存")
print(" 适用:期刊质量评估、投稿期刊选择、文献质量筛选")
safe_print("")
safe_print("[参数容错特性]:")
safe_print('- pmcid 支持字符串化数组自动解析:\'["PMC1", "PMC2"]\' -> ["PMC1", "PMC2"]')
safe_print("- sections 支持字符串自动转数组:'methods' -> ['methods']")
safe_print("- 提供友好的参数格式错误提示")
print("\n使用 'python -m article_mcp --help' 查看更多选项")
def main() -> None:
"""主函数"""
parser = argparse.ArgumentParser(
description="Article MCP 文献搜索服务器",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
使用示例:
python -m article_mcp # 启动服务器 (默认, stdio模式)
python -m article_mcp server # 启动服务器 (stdio模式)
python -m article_mcp server --transport sse # 启动SSE服务器
python -m article_mcp server --transport streamable-http # 启动Streamable HTTP服务器
python -m article_mcp test # 运行测试
python -m article_mcp info # 显示项目信息
""",
)
subparsers = parser.add_subparsers(dest="command", help="可用命令")
# 服务器命令
server_parser = subparsers.add_parser("server", help="启动MCP服务器")
server_parser.add_argument(
"--transport",
choices=["stdio", "sse", "streamable-http"],
default="stdio",
help="传输模式 (默认: stdio)",
)
server_parser.add_argument(
"--host", default="localhost", help="服务器主机地址 (默认: localhost)"
)
server_parser.add_argument("--port", type=int, default=9000, help="服务器端口 (默认: 9000)")
server_parser.add_argument(
"--path", default="/mcp", help="HTTP 路径 (仅用于 streamable-http 模式, 默认: /mcp)"
)
# 测试命令
subparsers.add_parser("test", help="运行测试")
# 信息命令
subparsers.add_parser("info", help="显示项目信息")
# 解析参数(如果没有参数,默认使用 server 命令)
args = parser.parse_args(args=None if len(sys.argv) > 1 else ["server"])
# 默认启动服务器(当没有指定命令或指定了 server 命令)
if args.command == "server" or args.command is None:
# 处理默认参数
transport = getattr(args, "transport", "stdio")
host = getattr(args, "host", "localhost")
port = getattr(args, "port", 9000)
path = getattr(args, "path", "/mcp")
try:
start_server(transport=transport, host=host, port=port, path=path)
except KeyboardInterrupt:
print("\n服务器已停止")
sys.exit(0)
except Exception as e:
print(f"启动失败: {e}")
sys.exit(1)
elif args.command == "test":
try:
asyncio.run(run_test())
except Exception as e:
print(f"测试失败: {e}")
sys.exit(1)
elif args.command == "info":
show_info()
else:
# 默认显示帮助信息
parser.print_help()
if __name__ == "__main__":
main()