use papers_core::{DiskCache, OpenAlexClient};
use std::time::Duration;
use rmcp::handler::server::tool::ToolRouter;
use rmcp::handler::server::wrapper::Parameters;
use rmcp::model::{ServerCapabilities, ServerInfo};
use rmcp::{ServerHandler, tool, tool_handler, tool_router};
use serde::Serialize;
use crate::params::{
AutocompleteToolParams, AuthorListToolParams, DomainListToolParams, FieldListToolParams,
FindWorksToolParams, FunderListToolParams, GetToolParams, InstitutionListToolParams,
PublisherListToolParams, SourceListToolParams, SubfieldListToolParams, TopicListToolParams,
WorkListToolParams,
};
#[derive(Clone)]
pub struct PapersMcp {
client: OpenAlexClient,
tool_router: ToolRouter<Self>,
}
impl Default for PapersMcp {
fn default() -> Self {
Self::new()
}
}
impl PapersMcp {
pub fn new() -> Self {
let mut client = OpenAlexClient::new();
if let Ok(cache) = DiskCache::default_location(Duration::from_secs(600)) {
client = client.with_cache(cache);
}
Self {
client,
tool_router: Self::tool_router(),
}
}
pub fn with_client(client: OpenAlexClient) -> Self {
Self {
client,
tool_router: Self::tool_router(),
}
}
}
fn json_result<T: Serialize, E: std::fmt::Display>(result: Result<T, E>) -> Result<String, String> {
match result {
Ok(response) => {
serde_json::to_string_pretty(&response).map_err(|e| format!("JSON serialization error: {e}"))
}
Err(e) => Err(e.to_string()),
}
}
#[tool_router(vis = "pub")]
impl PapersMcp {
#[tool]
pub async fn work_list(&self, Parameters(params): Parameters<WorkListToolParams>) -> Result<String, String> {
json_result(papers_core::api::work_list(&self.client, ¶ms.into_work_list_params()).await)
}
#[tool]
pub async fn author_list(&self, Parameters(params): Parameters<AuthorListToolParams>) -> Result<String, String> {
json_result(papers_core::api::author_list(&self.client, ¶ms.into_entity_params()).await)
}
#[tool]
pub async fn source_list(&self, Parameters(params): Parameters<SourceListToolParams>) -> Result<String, String> {
json_result(papers_core::api::source_list(&self.client, ¶ms.into_entity_params()).await)
}
#[tool]
pub async fn institution_list(&self, Parameters(params): Parameters<InstitutionListToolParams>) -> Result<String, String> {
json_result(papers_core::api::institution_list(&self.client, ¶ms.into_entity_params()).await)
}
#[tool]
pub async fn topic_list(&self, Parameters(params): Parameters<TopicListToolParams>) -> Result<String, String> {
json_result(papers_core::api::topic_list(&self.client, ¶ms.into_entity_params()).await)
}
#[tool]
pub async fn publisher_list(&self, Parameters(params): Parameters<PublisherListToolParams>) -> Result<String, String> {
json_result(papers_core::api::publisher_list(&self.client, ¶ms.into_entity_params()).await)
}
#[tool]
pub async fn funder_list(&self, Parameters(params): Parameters<FunderListToolParams>) -> Result<String, String> {
json_result(papers_core::api::funder_list(&self.client, ¶ms.into_entity_params()).await)
}
#[tool]
pub async fn domain_list(&self, Parameters(params): Parameters<DomainListToolParams>) -> Result<String, String> {
json_result(papers_core::api::domain_list(&self.client, ¶ms.into_entity_params()).await)
}
#[tool]
pub async fn field_list(&self, Parameters(params): Parameters<FieldListToolParams>) -> Result<String, String> {
json_result(papers_core::api::field_list(&self.client, ¶ms.into_entity_params()).await)
}
#[tool]
pub async fn subfield_list(&self, Parameters(params): Parameters<SubfieldListToolParams>) -> Result<String, String> {
json_result(papers_core::api::subfield_list(&self.client, ¶ms.into_entity_params()).await)
}
#[tool]
pub async fn work_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::work_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn author_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::author_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn source_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::source_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn institution_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::institution_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn topic_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::topic_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn publisher_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::publisher_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn funder_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::funder_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn domain_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::domain_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn field_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::field_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn subfield_get(&self, Parameters(params): Parameters<GetToolParams>) -> Result<String, String> {
json_result(papers_core::api::subfield_get(&self.client, ¶ms.id, ¶ms.into_get_params()).await)
}
#[tool]
pub async fn work_autocomplete(&self, Parameters(params): Parameters<AutocompleteToolParams>) -> Result<String, String> {
json_result(papers_core::api::work_autocomplete(&self.client, ¶ms.q).await)
}
#[tool]
pub async fn author_autocomplete(&self, Parameters(params): Parameters<AutocompleteToolParams>) -> Result<String, String> {
json_result(papers_core::api::author_autocomplete(&self.client, ¶ms.q).await)
}
#[tool]
pub async fn source_autocomplete(&self, Parameters(params): Parameters<AutocompleteToolParams>) -> Result<String, String> {
json_result(papers_core::api::source_autocomplete(&self.client, ¶ms.q).await)
}
#[tool]
pub async fn institution_autocomplete(&self, Parameters(params): Parameters<AutocompleteToolParams>) -> Result<String, String> {
json_result(papers_core::api::institution_autocomplete(&self.client, ¶ms.q).await)
}
#[tool]
pub async fn publisher_autocomplete(&self, Parameters(params): Parameters<AutocompleteToolParams>) -> Result<String, String> {
json_result(papers_core::api::publisher_autocomplete(&self.client, ¶ms.q).await)
}
#[tool]
pub async fn funder_autocomplete(&self, Parameters(params): Parameters<AutocompleteToolParams>) -> Result<String, String> {
json_result(papers_core::api::funder_autocomplete(&self.client, ¶ms.q).await)
}
#[tool]
pub async fn subfield_autocomplete(&self, Parameters(params): Parameters<AutocompleteToolParams>) -> Result<String, String> {
json_result(papers_core::api::subfield_autocomplete(&self.client, ¶ms.q).await)
}
#[tool]
pub async fn work_find(&self, Parameters(params): Parameters<FindWorksToolParams>) -> Result<String, String> {
json_result(papers_core::api::work_find(&self.client, ¶ms.into_find_params()).await)
}
}
#[tool_handler]
impl ServerHandler for PapersMcp {
fn get_info(&self) -> ServerInfo {
ServerInfo {
protocol_version: Default::default(),
capabilities: ServerCapabilities::builder()
.enable_tools()
.build(),
server_info: rmcp::model::Implementation {
name: "papers-mcp".into(),
version: env!("CARGO_PKG_VERSION").into(),
title: None,
description: None,
icons: None,
website_url: None,
},
instructions: Some(
"MCP server for querying the OpenAlex academic research database. \
Provides tools to search, filter, and retrieve scholarly works, \
authors, sources, institutions, topics, publishers, and funders."
.into(),
),
}
}
}