mirror of
https://github.com/blazickjp/arxiv-mcp-server.git
synced 2025-07-25 20:38:49 +03:00
Updates for Prompt deep-paper-analysis workflow.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -20,6 +20,7 @@ wheels/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
*.coverage
|
||||
*.DS_Store
|
||||
|
||||
# Virtual Environment
|
||||
venv/
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
"""Deep research analysis prompt for the arXiv MCP server."""
|
||||
|
||||
# Consolidated comprehensive paper analysis prompt
|
||||
PAPER_ANALYSIS_PROMPT = """
|
||||
You are an AI research assistant tasked with analyzing academic papers from arXiv. You have access to several tools to help with this analysis:
|
||||
|
||||
AVAILABLE TOOLS:
|
||||
1. read_paper: Use this tool to retrieve the full content of the paper with the provided arXiv ID
|
||||
2. download_paper: If the paper is not already available locally, use this tool to download it first
|
||||
3. search_papers: Find related papers on the same topic to provide context
|
||||
4. list_papers: Check which papers are already downloaded and available for reading
|
||||
|
||||
WORKFLOW FOR PAPER ANALYSIS:
|
||||
1. PREPARATION:
|
||||
- First, use the list_papers tool to check if the paper is already downloaded
|
||||
- If not found, use the download_paper tool to retrieve it
|
||||
- Then use the read_paper tool with the paper_id to get the full content
|
||||
|
||||
2. COMPREHENSIVE ANALYSIS (after reading the paper):
|
||||
- Executive Summary (2-3 sentences):
|
||||
* Core contribution and significance at a glance
|
||||
|
||||
- Research Context (1 paragraph):
|
||||
* Research area and specific problem addressed
|
||||
* Key prior approaches and their limitations
|
||||
* How this paper aims to advance the field
|
||||
|
||||
- Methodology Analysis:
|
||||
* Step-by-step breakdown of the approach
|
||||
* Key innovations in the methodology
|
||||
* Theoretical foundations and assumptions
|
||||
* Technical implementation details
|
||||
* Algorithmic complexity and performance characteristics
|
||||
|
||||
- Results Analysis:
|
||||
* Experimental setup (datasets, benchmarks, metrics)
|
||||
* Main experimental results and their significance
|
||||
* Statistical validity and robustness of results
|
||||
* How results support or challenge the paper's claims
|
||||
* Comparison to state-of-the-art approaches
|
||||
|
||||
- Practical Implications:
|
||||
* How could this be implemented or applied?
|
||||
* Required resources and potential challenges
|
||||
* Available code, datasets, or resources
|
||||
|
||||
- Theoretical Implications:
|
||||
* How this work advances fundamental understanding
|
||||
* New concepts or paradigms introduced
|
||||
* Challenges to existing theories or assumptions
|
||||
* Open questions raised
|
||||
|
||||
- Future Directions:
|
||||
* Limitations that future work could address
|
||||
* Promising follow-up research questions
|
||||
* Potential for integration with other approaches
|
||||
* Long-term research agenda this work enables
|
||||
|
||||
- Broader Impact (when relevant):
|
||||
* Societal, ethical, or policy implications
|
||||
* Environmental or economic considerations
|
||||
* Potential real-world applications and timeframe
|
||||
|
||||
3. OPTIONAL EXTENSIONS:
|
||||
- Use the search_papers tool to find related work or papers building on this work
|
||||
- Cross-reference findings with other papers you've analyzed
|
||||
- Create diagrams or pseudocode to illustrate key concepts
|
||||
- Summarize key results in tables for easy reference
|
||||
|
||||
Structure your analysis with clear headings, maintain technical accuracy while being accessible, and include your critical assessment where appropriate. Your analysis should be comprehensive but concise. Be sure to critically evaluate the statistical significance and reproducibility of any reported results.
|
||||
"""
|
||||
|
||||
# For backward compatibility
|
||||
CONCISE_PAPER_ANALYSIS = PAPER_ANALYSIS_PROMPT
|
||||
DEEP_PAPER_ANALYSIS = PAPER_ANALYSIS_PROMPT
|
||||
METHODOLOGY_FOCUS = PAPER_ANALYSIS_PROMPT
|
||||
RESULTS_FOCUS = PAPER_ANALYSIS_PROMPT
|
||||
IMPLICATIONS_FOCUS = PAPER_ANALYSIS_PROMPT
|
||||
@@ -1,23 +1,80 @@
|
||||
"""Handlers for prompt-related requests."""
|
||||
"""Handlers for prompt-related requests with paper analysis functionality."""
|
||||
|
||||
from typing import List, Dict
|
||||
from mcp.types import (
|
||||
Prompt,
|
||||
PromptMessage,
|
||||
TextContent,
|
||||
GetPromptResult
|
||||
)
|
||||
from typing import List, Dict, Optional
|
||||
from mcp.types import Prompt, PromptMessage, TextContent, GetPromptResult
|
||||
from .prompts import PROMPTS
|
||||
from .deep_research_analysis_prompt import PAPER_ANALYSIS_PROMPT
|
||||
from .prompt_manager import (
|
||||
get_research_session,
|
||||
create_research_session,
|
||||
update_session_from_prompt,
|
||||
)
|
||||
|
||||
|
||||
# Legacy global research context - used as fallback when no session_id is provided
|
||||
class ResearchContext:
|
||||
"""Maintains context throughout a research session."""
|
||||
|
||||
def __init__(self):
|
||||
self.expertise_level = "intermediate" # default
|
||||
self.explored_papers = {} # paper_id -> basic metadata
|
||||
self.paper_analyses = {} # paper_id -> analysis focus and summary
|
||||
|
||||
def update_from_arguments(self, args: Dict[str, str]) -> None:
|
||||
"""Update context based on new arguments."""
|
||||
if "expertise_level" in args:
|
||||
self.expertise_level = args["expertise_level"]
|
||||
if "paper_id" in args and args["paper_id"] not in self.explored_papers:
|
||||
self.explored_papers[args["paper_id"]] = {"id": args["paper_id"]}
|
||||
|
||||
|
||||
# Global research context for backward compatibility
|
||||
_research_context = ResearchContext()
|
||||
|
||||
# Citation and evidence standards by domain
|
||||
CITATION_STANDARDS = {
|
||||
"computer_science": "Include specific section numbers and algorithm references",
|
||||
"physics": "Reference equations and experimental results by number",
|
||||
"biology": "Include methodology details and statistical significance",
|
||||
"default": "Reference specific findings and methodologies from the papers",
|
||||
}
|
||||
|
||||
# Output structure for deep paper analysis
|
||||
OUTPUT_STRUCTURE = """
|
||||
Present your analysis with the following structure:
|
||||
1. Executive Summary: 3-5 sentence overview of key contributions
|
||||
2. Detailed Analysis: Following the specific focus requested
|
||||
3. Visual Breakdown: Describe key figures/tables and their significance
|
||||
4. Related Work Map: Position this paper within the research landscape
|
||||
5. Implementation Notes: Practical considerations for applying these findings
|
||||
"""
|
||||
|
||||
|
||||
async def list_prompts() -> List[Prompt]:
|
||||
"""Handle prompts/list request."""
|
||||
return list(PROMPTS.values())
|
||||
# Filter to only include deep-paper-analysis
|
||||
return [PROMPTS["deep-paper-analysis"]] if "deep-paper-analysis" in PROMPTS else []
|
||||
|
||||
async def get_prompt(name: str, arguments: Dict[str, str] | None = None) -> GetPromptResult:
|
||||
"""Handle prompts/get request."""
|
||||
if name not in PROMPTS:
|
||||
|
||||
async def get_prompt(
|
||||
name: str, arguments: Dict[str, str] | None = None, session_id: Optional[str] = None
|
||||
) -> GetPromptResult:
|
||||
"""Handle prompts/get request for paper analysis.
|
||||
|
||||
Args:
|
||||
name: The name of the prompt to get
|
||||
arguments: The arguments to use with the prompt
|
||||
session_id: Optional user session ID for context persistence
|
||||
|
||||
Returns:
|
||||
GetPromptResult: The resulting prompt with messages
|
||||
|
||||
Raises:
|
||||
ValueError: If prompt not found or arguments invalid
|
||||
"""
|
||||
if name != "deep-paper-analysis":
|
||||
raise ValueError(f"Prompt not found: {name}")
|
||||
|
||||
|
||||
prompt = PROMPTS[name]
|
||||
if arguments is None:
|
||||
raise ValueError(f"No arguments provided for prompt: {name}")
|
||||
@@ -26,66 +83,67 @@ async def get_prompt(name: str, arguments: Dict[str, str] | None = None) -> GetP
|
||||
for arg in prompt.arguments:
|
||||
if arg.required and (arg.name not in arguments or not arguments.get(arg.name)):
|
||||
raise ValueError(f"Missing required argument: {arg.name}")
|
||||
|
||||
if name == "research-discovery":
|
||||
topic = arguments.get("topic", "")
|
||||
expertise = arguments.get("expertise_level", "intermediate")
|
||||
time_period = arguments.get("time_period", "")
|
||||
|
||||
guide = {
|
||||
"beginner": "I'll explain key concepts and methodologies.",
|
||||
"intermediate": "We'll focus on recent developments.",
|
||||
"expert": "We'll dive deep into technical details."
|
||||
}.get(expertise, "We'll focus on recent developments.")
|
||||
|
||||
return GetPromptResult(
|
||||
messages=[
|
||||
PromptMessage(
|
||||
role="user",
|
||||
content=TextContent(
|
||||
type="text",
|
||||
text=f"Help me explore research papers on {topic}. "
|
||||
f"{f'Time period: {time_period}. ' if time_period else ''}"
|
||||
f"{guide}\n\nWhat specific aspects interest you most?"
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
elif name == "paper-analysis":
|
||||
paper_id = arguments.get("paper_id", "")
|
||||
focus = arguments.get("focus_area", "complete")
|
||||
|
||||
return GetPromptResult(
|
||||
messages=[
|
||||
PromptMessage(
|
||||
role="user",
|
||||
content=TextContent(
|
||||
type="text",
|
||||
text=f"Analyze paper {paper_id} with a focus on {focus}. "
|
||||
f"Please provide a detailed breakdown of the paper's content, "
|
||||
f"methodology, and key findings."
|
||||
)
|
||||
)
|
||||
# Get research context - either from session or fallback to global
|
||||
context = None
|
||||
if session_id:
|
||||
try:
|
||||
# Try to get existing session
|
||||
session_data = get_research_session(session_id)
|
||||
context = session_data
|
||||
except ValueError:
|
||||
# Create new session if it doesn't exist
|
||||
create_research_session(session_id, arguments)
|
||||
context = get_research_session(session_id)
|
||||
|
||||
# Update session with current prompt info
|
||||
update_session_from_prompt(session_id, name, arguments)
|
||||
else:
|
||||
# Fallback to global context for backward compatibility
|
||||
_research_context.update_from_arguments(arguments)
|
||||
context = _research_context
|
||||
|
||||
# Determine domain-specific guidance
|
||||
domain = "default" # Default domain since it's no longer required
|
||||
citation_guidance = CITATION_STANDARDS.get(domain, CITATION_STANDARDS["default"])
|
||||
|
||||
# Process deep-paper-analysis prompt
|
||||
paper_id = arguments.get("paper_id", "")
|
||||
|
||||
# Add context from previous papers if available
|
||||
previous_papers_context = ""
|
||||
|
||||
if session_id:
|
||||
# Get papers from session
|
||||
papers = context.get("papers", {})
|
||||
if len(papers) > 1:
|
||||
previous_ids = [pid for pid in papers.keys() if pid != paper_id]
|
||||
if previous_ids:
|
||||
previous_papers_context = f"\nI've previously analyzed papers: {', '.join(previous_ids)}. If relevant, note connections to these works."
|
||||
else:
|
||||
# Use global context
|
||||
if len(_research_context.explored_papers) > 1:
|
||||
previous_ids = [
|
||||
pid
|
||||
for pid in _research_context.explored_papers.keys()
|
||||
if pid != paper_id
|
||||
]
|
||||
)
|
||||
|
||||
elif name == "literature-synthesis":
|
||||
paper_ids = arguments.get("paper_ids", "")
|
||||
synthesis_type = arguments.get("synthesis_type", "comprehensive")
|
||||
|
||||
return GetPromptResult(
|
||||
messages=[
|
||||
PromptMessage(
|
||||
role="user",
|
||||
content=TextContent(
|
||||
type="text",
|
||||
text=f"Synthesize the findings from these papers: {paper_ids}. "
|
||||
f"Focus on creating a {synthesis_type} analysis that highlights "
|
||||
f"key themes, methodological approaches, and research implications."
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
raise ValueError("Prompt implementation not found")
|
||||
if previous_ids:
|
||||
previous_papers_context = f"\nI've previously analyzed papers: {', '.join(previous_ids)}. If relevant, note connections to these works."
|
||||
|
||||
# Track this analysis in context (for global context only, session is updated above)
|
||||
if not session_id:
|
||||
_research_context.paper_analyses[paper_id] = {"analysis": "complete"}
|
||||
|
||||
return GetPromptResult(
|
||||
messages=[
|
||||
PromptMessage(
|
||||
role="user",
|
||||
content=TextContent(
|
||||
type="text",
|
||||
text=f"Analyze paper {paper_id}.{previous_papers_context}\n\n"
|
||||
f"{citation_guidance}\n\n{OUTPUT_STRUCTURE}\n\n{PAPER_ANALYSIS_PROMPT}",
|
||||
),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1,29 +1,240 @@
|
||||
"""Prompt management for the arXiv MCP server."""
|
||||
"""Research journey prompt management for the arXiv MCP server."""
|
||||
|
||||
from typing import Optional
|
||||
from .base import PromptManager
|
||||
from .templates.discovery import research_discovery_prompts
|
||||
from .templates.analysis import paper_analysis_prompts
|
||||
from .templates.synthesis import knowledge_synthesis_prompts
|
||||
from .templates.workflow import research_workflow_prompts
|
||||
from typing import Dict, Optional, List, Any
|
||||
from mcp.types import Prompt, PromptMessage, TextContent
|
||||
from .prompts import PROMPTS
|
||||
|
||||
# Global prompt manager instance
|
||||
_prompt_manager: Optional[PromptManager] = None
|
||||
_prompt_manager: Optional[Dict[str, Prompt]] = None
|
||||
|
||||
# Research session tracking for associating prompts with a user session
|
||||
_research_sessions: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
|
||||
def get_prompt_manager() -> PromptManager:
|
||||
"""Get or create the global PromptManager instance."""
|
||||
def get_prompt_manager() -> Dict[str, Prompt]:
|
||||
"""Get or create the global prompt manager dictionary.
|
||||
|
||||
Returns:
|
||||
Dict[str, Prompt]: Dictionary of available prompts
|
||||
"""
|
||||
global _prompt_manager
|
||||
if _prompt_manager is None:
|
||||
_prompt_manager = PromptManager()
|
||||
# Register all prompts
|
||||
for prompt in research_discovery_prompts:
|
||||
_prompt_manager.register_template(prompt)
|
||||
for prompt in paper_analysis_prompts:
|
||||
_prompt_manager.register_template(prompt)
|
||||
for prompt in knowledge_synthesis_prompts:
|
||||
_prompt_manager.register_template(prompt)
|
||||
for prompt in research_workflow_prompts:
|
||||
_prompt_manager.register_template(prompt)
|
||||
_prompt_manager = PROMPTS
|
||||
|
||||
return _prompt_manager
|
||||
|
||||
|
||||
def register_prompt(prompt: Prompt) -> None:
|
||||
"""Register a new prompt in the prompt manager.
|
||||
|
||||
Args:
|
||||
prompt (Prompt): The prompt to register
|
||||
"""
|
||||
manager = get_prompt_manager()
|
||||
manager[prompt.name] = prompt
|
||||
|
||||
|
||||
def create_research_session(
|
||||
session_id: str, initial_metadata: Optional[Dict[str, Any]] = None
|
||||
) -> None:
|
||||
"""Create a new research session to track context across prompts.
|
||||
|
||||
Args:
|
||||
session_id: Unique identifier for the user session
|
||||
initial_metadata: Optional initial metadata for the session
|
||||
"""
|
||||
global _research_sessions
|
||||
if session_id in _research_sessions:
|
||||
return # Session already exists
|
||||
|
||||
_research_sessions[session_id] = {
|
||||
"domain": initial_metadata.get("domain") if initial_metadata else None,
|
||||
"expertise_level": (
|
||||
initial_metadata.get("expertise_level", "intermediate")
|
||||
if initial_metadata
|
||||
else "intermediate"
|
||||
),
|
||||
"topics": [],
|
||||
"papers": {}, # paper_id -> metadata
|
||||
"analyses": {}, # paper_id -> analysis info
|
||||
"research_questions": [],
|
||||
"prompt_history": [], # List of prompts used in this session
|
||||
}
|
||||
|
||||
|
||||
def get_research_session(session_id: str) -> Dict[str, Any]:
|
||||
"""Get research session data for a given session ID.
|
||||
|
||||
Args:
|
||||
session_id: Unique identifier for the user session
|
||||
|
||||
Returns:
|
||||
Session data dictionary
|
||||
|
||||
Raises:
|
||||
ValueError: If session doesn't exist
|
||||
"""
|
||||
if session_id not in _research_sessions:
|
||||
raise ValueError(f"Research session not found: {session_id}")
|
||||
|
||||
return _research_sessions[session_id]
|
||||
|
||||
|
||||
def update_session_from_prompt(
|
||||
session_id: str, prompt_name: str, arguments: Dict[str, str]
|
||||
) -> None:
|
||||
"""Update research session with data from a prompt.
|
||||
|
||||
Args:
|
||||
session_id: Unique identifier for the user session
|
||||
prompt_name: Name of the prompt being used
|
||||
arguments: Arguments provided to the prompt
|
||||
|
||||
Raises:
|
||||
ValueError: If session doesn't exist
|
||||
"""
|
||||
if session_id not in _research_sessions:
|
||||
create_research_session(session_id)
|
||||
|
||||
session = _research_sessions[session_id]
|
||||
|
||||
# Track prompt usage
|
||||
session["prompt_history"].append(
|
||||
{
|
||||
"name": prompt_name,
|
||||
"arguments": arguments,
|
||||
"timestamp": __import__("datetime").datetime.now().isoformat(),
|
||||
}
|
||||
)
|
||||
|
||||
# Update research metadata based on prompt type and arguments
|
||||
if "expertise_level" in arguments:
|
||||
session["expertise_level"] = arguments["expertise_level"]
|
||||
|
||||
if "domain" in arguments:
|
||||
session["domain"] = arguments["domain"]
|
||||
|
||||
if prompt_name == "research-discovery" and "topic" in arguments:
|
||||
topic = arguments["topic"]
|
||||
if topic and topic not in session["topics"]:
|
||||
session["topics"].append(topic)
|
||||
|
||||
elif prompt_name == "deep-paper-analysis" and "paper_id" in arguments:
|
||||
paper_id = arguments["paper_id"]
|
||||
focus = arguments.get("focus_area", "complete")
|
||||
|
||||
if paper_id not in session["papers"]:
|
||||
session["papers"][paper_id] = {"id": paper_id}
|
||||
|
||||
if paper_id not in session["analyses"]:
|
||||
session["analyses"][paper_id] = {"focus": focus}
|
||||
|
||||
elif prompt_name == "literature-synthesis" and "paper_ids" in arguments:
|
||||
paper_ids = arguments["paper_ids"].split(",")
|
||||
for paper_id in paper_ids:
|
||||
paper_id = paper_id.strip()
|
||||
if paper_id and paper_id not in session["papers"]:
|
||||
session["papers"][paper_id] = {"id": paper_id}
|
||||
|
||||
elif prompt_name == "research-question" and "topic" in arguments:
|
||||
topic = arguments["topic"]
|
||||
if topic and topic not in session["topics"]:
|
||||
session["topics"].append(topic)
|
||||
|
||||
|
||||
def update_session_with_research_questions(
|
||||
session_id: str, questions: List[str]
|
||||
) -> None:
|
||||
"""Update a research session with newly generated research questions.
|
||||
|
||||
Args:
|
||||
session_id: Unique identifier for the user session
|
||||
questions: List of research questions to add
|
||||
|
||||
Raises:
|
||||
ValueError: If session doesn't exist
|
||||
"""
|
||||
if session_id not in _research_sessions:
|
||||
raise ValueError(f"Research session not found: {session_id}")
|
||||
|
||||
session = _research_sessions[session_id]
|
||||
|
||||
# Add new questions, avoiding duplicates
|
||||
for question in questions:
|
||||
if question not in session["research_questions"]:
|
||||
session["research_questions"].append(question)
|
||||
|
||||
|
||||
def suggest_next_prompts(session_id: str) -> List[Dict[str, Any]]:
|
||||
"""Suggest relevant next prompts based on session context.
|
||||
|
||||
Args:
|
||||
session_id: Unique identifier for the user session
|
||||
|
||||
Returns:
|
||||
List of suggested prompts with prefilled arguments
|
||||
|
||||
Raises:
|
||||
ValueError: If session doesn't exist
|
||||
"""
|
||||
if session_id not in _research_sessions:
|
||||
raise ValueError(f"Research session not found: {session_id}")
|
||||
|
||||
session = _research_sessions[session_id]
|
||||
suggestions = []
|
||||
|
||||
# If user has explored topics but no papers, suggest paper analysis
|
||||
if session["topics"] and not session["papers"]:
|
||||
suggestions.append(
|
||||
{
|
||||
"prompt": "deep-paper-analysis",
|
||||
"message": "Analyze a specific paper on one of your topics of interest",
|
||||
"prefill": {
|
||||
"domain": session["domain"],
|
||||
"expertise_level": session["expertise_level"],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# If user has analyzed at least one paper, suggest literature synthesis
|
||||
if len(session["papers"]) >= 1:
|
||||
suggestions.append(
|
||||
{
|
||||
"prompt": "literature-synthesis",
|
||||
"message": "Synthesize findings across the papers you've explored",
|
||||
"prefill": {
|
||||
"paper_ids": ",".join(session["papers"].keys()),
|
||||
"domain": session["domain"],
|
||||
"expertise_level": session["expertise_level"],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# If user has analyzed multiple papers, suggest research questions
|
||||
if len(session["papers"]) >= 2:
|
||||
suggestions.append(
|
||||
{
|
||||
"prompt": "research-question",
|
||||
"message": "Generate research questions based on your explored papers",
|
||||
"prefill": {
|
||||
"paper_ids": ",".join(session["papers"].keys()),
|
||||
"topic": session["topics"][-1] if session["topics"] else "",
|
||||
"domain": session["domain"],
|
||||
"expertise_level": session["expertise_level"],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Always suggest exploring a new topic
|
||||
suggestions.append(
|
||||
{
|
||||
"prompt": "research-discovery",
|
||||
"message": "Explore a new research topic",
|
||||
"prefill": {
|
||||
"domain": session["domain"],
|
||||
"expertise_level": session["expertise_level"],
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return suggestions
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
"""Prompt definitions for arXiv MCP server."""
|
||||
"""Prompt definitions for arXiv MCP server with research journey support."""
|
||||
|
||||
from mcp.types import (
|
||||
Prompt,
|
||||
Prompt,
|
||||
PromptArgument,
|
||||
PromptMessage,
|
||||
TextContent,
|
||||
GetPromptResult
|
||||
)
|
||||
|
||||
# Define all prompts
|
||||
@@ -14,25 +11,69 @@ PROMPTS = {
|
||||
name="research-discovery",
|
||||
description="Begin research exploration on a specific topic",
|
||||
arguments=[
|
||||
PromptArgument(name="topic", description="Research topic or question", required=True),
|
||||
PromptArgument(name="expertise_level", description="User's familiarity (beginner/intermediate/expert)", required=False),
|
||||
PromptArgument(name="time_period", description="Time period for search (e.g., '2023-present')", required=False)
|
||||
]
|
||||
PromptArgument(
|
||||
name="topic", description="Research topic or question", required=True
|
||||
),
|
||||
PromptArgument(
|
||||
name="expertise_level",
|
||||
description="User's familiarity (beginner/intermediate/expert)",
|
||||
required=False,
|
||||
),
|
||||
PromptArgument(
|
||||
name="time_period",
|
||||
description="Time period for search (e.g., '2023-present')",
|
||||
required=False,
|
||||
),
|
||||
PromptArgument(
|
||||
name="domain",
|
||||
description="Academic domain (e.g., computer_science/physics/biology)",
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
),
|
||||
"paper-analysis": Prompt(
|
||||
name="paper-analysis",
|
||||
description="Analyze a specific paper",
|
||||
"deep-paper-analysis": Prompt(
|
||||
name="deep-paper-analysis",
|
||||
description="Analyze a specific paper in detail",
|
||||
arguments=[
|
||||
PromptArgument(name="paper_id", description="arXiv paper ID", required=True),
|
||||
PromptArgument(name="focus_area", description="Focus area (methodology/results/implications)", required=False)
|
||||
]
|
||||
PromptArgument(
|
||||
name="paper_id", description="arXiv paper ID", required=True
|
||||
),
|
||||
],
|
||||
),
|
||||
"literature-synthesis": Prompt(
|
||||
name="literature-synthesis",
|
||||
description="Synthesize findings across papers",
|
||||
description="Synthesize findings across multiple papers",
|
||||
arguments=[
|
||||
PromptArgument(name="paper_ids", description="List of arXiv paper IDs", required=True),
|
||||
PromptArgument(name="synthesis_type", description="Synthesis type (themes/methods/timeline/gaps)", required=False)
|
||||
]
|
||||
)
|
||||
}
|
||||
PromptArgument(
|
||||
name="paper_ids", description="Comma-separated list of arXiv paper IDs", required=True
|
||||
),
|
||||
PromptArgument(
|
||||
name="synthesis_type",
|
||||
description="Synthesis type (themes/methods/timeline/gaps/comprehensive)",
|
||||
required=False,
|
||||
),
|
||||
PromptArgument(
|
||||
name="domain",
|
||||
description="Academic domain (e.g., computer_science/physics/biology)",
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
),
|
||||
"research-question": Prompt(
|
||||
name="research-question",
|
||||
description="Formulate research questions based on literature",
|
||||
arguments=[
|
||||
PromptArgument(
|
||||
name="paper_ids", description="Comma-separated list of arXiv paper IDs", required=True
|
||||
),
|
||||
PromptArgument(
|
||||
name="topic", description="Research topic or question", required=True
|
||||
),
|
||||
PromptArgument(
|
||||
name="domain",
|
||||
description="Academic domain (e.g., computer_science/physics/biology)",
|
||||
required=False,
|
||||
),
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""Integration tests for prompt functionality."""
|
||||
|
||||
import pytest
|
||||
from arxiv_mcp_server.server import server
|
||||
from arxiv_mcp_server.prompts.handlers import list_prompts, get_prompt
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_server_list_prompts():
|
||||
"""Test server list_prompts endpoint."""
|
||||
prompts = await server.list_prompts()
|
||||
assert len(prompts) == 3
|
||||
prompts = await list_prompts()
|
||||
assert len(prompts) == 1
|
||||
|
||||
# Check that all prompts have required fields
|
||||
for prompt in prompts:
|
||||
@@ -15,57 +15,27 @@ async def test_server_list_prompts():
|
||||
assert prompt.description
|
||||
assert prompt.arguments is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_server_get_research_prompt():
|
||||
"""Test server get_prompt endpoint with research prompt."""
|
||||
result = await server.get_prompt(
|
||||
name="research-discovery",
|
||||
arguments={"topic": "machine learning", "expertise_level": "expert"}
|
||||
)
|
||||
|
||||
assert len(result.messages) == 1
|
||||
message = result.messages[0]
|
||||
assert message.role == "user"
|
||||
assert "machine learning" in message.content.text
|
||||
assert "dive deep" in message.content.text.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_server_get_analysis_prompt():
|
||||
"""Test server get_prompt endpoint with analysis prompt."""
|
||||
result = await server.get_prompt(
|
||||
name="paper-analysis",
|
||||
arguments={"paper_id": "2401.00123", "focus_area": "implications"}
|
||||
result = await get_prompt(
|
||||
"deep-paper-analysis",
|
||||
{"paper_id": "2401.00123"}
|
||||
)
|
||||
|
||||
assert len(result.messages) == 1
|
||||
message = result.messages[0]
|
||||
assert message.role == "user"
|
||||
assert "2401.00123" in message.content.text
|
||||
assert "implications" in message.content.text.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_server_get_synthesis_prompt():
|
||||
"""Test server get_prompt endpoint with synthesis prompt."""
|
||||
paper_ids = "2401.00123, 2401.00124"
|
||||
result = await server.get_prompt(
|
||||
name="literature-synthesis",
|
||||
arguments={"paper_ids": paper_ids, "synthesis_type": "timeline"}
|
||||
)
|
||||
|
||||
assert len(result.messages) == 1
|
||||
message = result.messages[0]
|
||||
assert message.role == "user"
|
||||
assert paper_ids in message.content.text
|
||||
assert "timeline" in message.content.text.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_server_get_prompt_invalid_name():
|
||||
"""Test server get_prompt endpoint with invalid prompt name."""
|
||||
with pytest.raises(ValueError, match="Prompt not found"):
|
||||
await server.get_prompt(name="invalid-prompt", arguments={})
|
||||
await get_prompt("invalid-prompt", {})
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_server_get_prompt_missing_args():
|
||||
"""Test server get_prompt endpoint with missing required arguments."""
|
||||
with pytest.raises(ValueError, match="Missing required argument"):
|
||||
await server.get_prompt(name="research-discovery", arguments={})
|
||||
await get_prompt("deep-paper-analysis", {})
|
||||
@@ -9,36 +9,18 @@ from mcp.types import GetPromptResult, PromptMessage, TextContent
|
||||
async def test_list_prompts():
|
||||
"""Test listing available prompts."""
|
||||
prompts = await list_prompts()
|
||||
assert len(prompts) == 3
|
||||
assert len(prompts) == 1
|
||||
|
||||
prompt_names = {p.name for p in prompts}
|
||||
expected_names = {"research-discovery", "paper-analysis", "literature-synthesis"}
|
||||
expected_names = {"deep-paper-analysis"}
|
||||
assert prompt_names == expected_names
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_research_discovery_prompt():
|
||||
"""Test getting research discovery prompt."""
|
||||
result = await get_prompt(
|
||||
"research-discovery",
|
||||
{"topic": "quantum computing", "expertise_level": "beginner"}
|
||||
)
|
||||
|
||||
assert isinstance(result, GetPromptResult)
|
||||
assert len(result.messages) == 1
|
||||
message = result.messages[0]
|
||||
|
||||
assert isinstance(message, PromptMessage)
|
||||
assert message.role == "user"
|
||||
assert isinstance(message.content, TextContent)
|
||||
assert "quantum computing" in message.content.text
|
||||
assert "explain key concepts" in message.content.text.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_paper_analysis_prompt():
|
||||
"""Test getting paper analysis prompt."""
|
||||
result = await get_prompt(
|
||||
"paper-analysis",
|
||||
{"paper_id": "2401.00123", "focus_area": "methodology"}
|
||||
"deep-paper-analysis",
|
||||
{"paper_id": "2401.00123"}
|
||||
)
|
||||
|
||||
assert isinstance(result, GetPromptResult)
|
||||
@@ -49,26 +31,6 @@ async def test_get_paper_analysis_prompt():
|
||||
assert message.role == "user"
|
||||
assert isinstance(message.content, TextContent)
|
||||
assert "2401.00123" in message.content.text
|
||||
assert "methodology" in message.content.text.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_literature_synthesis_prompt():
|
||||
"""Test getting literature synthesis prompt."""
|
||||
paper_ids = "2401.00123, 2401.00124"
|
||||
result = await get_prompt(
|
||||
"literature-synthesis",
|
||||
{"paper_ids": paper_ids, "synthesis_type": "themes"}
|
||||
)
|
||||
|
||||
assert isinstance(result, GetPromptResult)
|
||||
assert len(result.messages) == 1
|
||||
message = result.messages[0]
|
||||
|
||||
assert isinstance(message, PromptMessage)
|
||||
assert message.role == "user"
|
||||
assert isinstance(message.content, TextContent)
|
||||
assert paper_ids in message.content.text
|
||||
assert "themes" in message.content.text.lower()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_prompt_with_invalid_name():
|
||||
@@ -80,10 +42,10 @@ async def test_get_prompt_with_invalid_name():
|
||||
async def test_get_prompt_with_no_arguments():
|
||||
"""Test getting prompt with no arguments."""
|
||||
with pytest.raises(ValueError, match="No arguments provided"):
|
||||
await get_prompt("research-discovery", None)
|
||||
await get_prompt("deep-paper-analysis", None)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_prompt_with_missing_required_argument():
|
||||
"""Test getting prompt with missing required argument."""
|
||||
with pytest.raises(ValueError, match="Missing required argument"):
|
||||
await get_prompt("research-discovery", {})
|
||||
await get_prompt("deep-paper-analysis", {})
|
||||
4
uv.lock
generated
4
uv.lock
generated
@@ -144,7 +144,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "arxiv-mcp-server"
|
||||
version = "0.2.5"
|
||||
version = "0.2.6"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "aiofiles" },
|
||||
@@ -270,7 +270,7 @@ name = "click"
|
||||
version = "8.1.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 }
|
||||
wheels = [
|
||||
|
||||
Reference in New Issue
Block a user