IACR MCP Server
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ErrorCode,
ListToolsRequestSchema,
McpError,
CallToolRequest,
ListToolsRequest
} from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
import { parseStringPromise } from 'xml2js';
import { z } from 'zod';
const IACR_RSS_URL = 'https://eprint.iacr.org/rss/rss.xml?order=recent';
// Zod schemas for input validation
const SearchPapersSchema = z.object({
query: z.string(),
year: z.number().optional(),
category: z.string().optional(),
max_results: z.number().optional().default(20)
});
const GetPaperDetailsSchema = z.object({
paper_id: z.string()
});
const DownloadPaperSchema = z.object({
paper_id: z.string(),
format: z.enum(['pdf', 'txt']).optional().default('pdf')
});
interface IACRPaper {
id: string;
title: string;
authors: string;
link: string;
description: string;
pubDate: string;
}
class IACRServer {
private server: Server;
constructor() {
this.server = new Server(
{
name: 'iacr-mcp-server',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupToolHandlers();
}
private setupToolHandlers() {
// List available tools
this.server.setRequestHandler(ListToolsRequestSchema, async (_request: ListToolsRequest) => ({
tools: [
{
name: 'search_papers',
description: 'Search for papers in the IACR Cryptology ePrint Archive',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string' },
year: { type: 'number' },
category: { type: 'string' },
max_results: { type: 'number', default: 20 }
},
required: ['query']
}
},
{
name: 'get_paper_details',
description: 'Retrieve details of a specific paper by its ID',
inputSchema: {
type: 'object',
properties: {
paper_id: { type: 'string' }
},
required: ['paper_id']
}
},
{
name: 'download_paper',
description: 'Download a paper in PDF or TXT format',
inputSchema: {
type: 'object',
properties: {
paper_id: { type: 'string' },
format: {
type: 'string',
enum: ['pdf', 'txt'],
default: 'pdf'
}
},
required: ['paper_id']
}
}
]
}));
// Tool call handler
this.server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {
switch (request.params.name) {
case 'search_papers':
return this.searchPapers(request.params.arguments);
case 'get_paper_details':
return this.getPaperDetails(request.params.arguments);
case 'download_paper':
return this.downloadPaper(request.params.arguments);
default:
throw new McpError(
ErrorCode.MethodNotFound,
`Unknown tool: ${request.params.name}`
);
}
});
}
private async searchPapers(args: unknown) {
const validatedArgs = SearchPapersSchema.parse(args);
try {
// Fetch RSS feed with comprehensive headers
const response = await axios.get(IACR_RSS_URL, {
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'application/xml,text/xml,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9'
},
timeout: 15000
});
// Parse XML feed
const parsedFeed = await parseStringPromise(response.data);
// Ensure items exist
if (!parsedFeed.rss?.channel?.[0]?.item) {
console.error('No items found in RSS feed');
return { content: [{ type: 'text', text: '[]' }] };
}
// Current year for filtering
const currentYear = new Date().getFullYear();
// Extract and filter papers
const papers = parsedFeed.rss.channel[0].item
.map((item: any) => ({
id: item.link[0].split('/').pop(),
title: item.title[0],
authors: item['dc:creator'] ? item['dc:creator'][0] : 'Unknown',
link: item.link[0],
description: item.description[0],
pubDate: item.pubDate[0]
}))
.filter((paper: IACRPaper) => {
const title = paper.title.toLowerCase();
const description = paper.description.toLowerCase();
const query = validatedArgs.query.toLowerCase();
// Query matching
const matchesQuery = title.includes(query) || description.includes(query);
// Year filtering
const paperYear = new Date(paper.pubDate).getFullYear();
const matchesYear = !validatedArgs.year ||
(validatedArgs.year && paperYear === validatedArgs.year) ||
(paperYear >= currentYear - 10);
return matchesQuery && matchesYear;
})
.slice(0, validatedArgs.max_results)
.map((paper: IACRPaper) => ({
id: paper.id,
title: paper.title,
authors: paper.authors,
year: new Date(paper.pubDate).getFullYear(),
link: paper.link,
abstract: paper.description
}));
// Log results for debugging
console.error('Search Results:', JSON.stringify(papers, null, 2));
return {
content: [{
type: 'text',
text: JSON.stringify(papers, null, 2)
}]
};
} catch (error) {
// Comprehensive error logging
console.error('Search Error:', error);
throw new McpError(
ErrorCode.InternalError,
`Search failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
private async getPaperDetails(args: unknown) {
const validatedArgs = GetPaperDetailsSchema.parse(args);
try {
// Fetch RSS feed to get paper details
const response = await axios.get(IACR_RSS_URL);
const parsedFeed = await parseStringPromise(response.data);
const paperItem = parsedFeed.rss.channel[0].item.find((item: any) =>
item.link[0].split('/').pop() === validatedArgs.paper_id
);
if (!paperItem) {
throw new Error('Paper not found');
}
const paperDetails = {
id: validatedArgs.paper_id,
title: paperItem.title[0],
authors: paperItem['dc:creator'] ? paperItem['dc:creator'][0] : 'Unknown',
abstract: paperItem.description[0],
link: paperItem.link[0],
date: paperItem.pubDate[0]
};
return {
content: [{
type: 'text',
text: JSON.stringify(paperDetails, null, 2)
}]
};
} catch (error) {
console.error('Paper Details Error:', error);
throw new McpError(
ErrorCode.InternalError,
`Paper details retrieval failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
private async downloadPaper(args: unknown) {
const validatedArgs = DownloadPaperSchema.parse(args);
try {
const response = await axios.get(`https://eprint.iacr.org/${validatedArgs.paper_id}.${validatedArgs.format}`, {
responseType: 'arraybuffer'
});
return {
content: [{
type: 'file',
name: `${validatedArgs.paper_id}.${validatedArgs.format}`,
data: response.data.toString('base64')
}]
};
} catch (error) {
console.error('Paper Download Error:', error);
throw new McpError(
ErrorCode.InternalError,
`Paper download failed: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('IACR MCP server running on stdio');
}
}
const server = new IACRServer();
server.run().catch(console.error);