@landicefu/web-to-markdown-mcp-server
Version:
MCP server for retrieving web content as markdown using Jina AI API
136 lines • 5.55 kB
JavaScript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
import axios from 'axios';
// Environment variables
const JINA_API_TOKEN = process.env.JINA_API_TOKEN;
// Validate the URL input
const isValidUrl = (url) => {
try {
new URL(url);
return true;
}
catch (error) {
return false;
}
};
// Validate the tool arguments
const isValidGetWebContentArgs = (args) => typeof args === 'object' &&
args !== null &&
typeof args.url === 'string' &&
isValidUrl(args.url);
class WebToMarkdownServer {
constructor() {
this.server = new Server({
name: '@landicefu/web-to-markdown-mcp-server',
version: '1.0.0',
}, {
capabilities: {
tools: {},
},
});
// Validate Jina API token
if (!JINA_API_TOKEN) {
console.warn('JINA_API_TOKEN environment variable is not set. Please obtain a token from https://jina.ai/ for reliable operation.');
}
this.axiosInstance = axios.create({
timeout: 30000, // 30 seconds timeout
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; @landicefu/web-to-markdown-mcp-server/1.0.0)',
...(JINA_API_TOKEN ? { 'Authorization': `Bearer ${JINA_API_TOKEN}` } : {}),
},
});
this.setupToolHandlers();
// Error handling - removed console.error to prevent interference with MCP protocol
this.server.onerror = (error) => {
// Errors are handled through the MCP protocol, no need for console.error
};
process.on('SIGINT', async () => {
await this.server.close();
process.exit(0);
});
}
setupToolHandlers() {
// Define the available tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'get_web_content_as_markdown',
description: 'Retrieve web content as markdown using Jina AI API. Uses the JINA_API_TOKEN environment variable for authentication, or falls back to a default token.',
inputSchema: {
type: 'object',
properties: {
url: {
type: 'string',
description: 'The URL of the web page to convert to markdown',
},
},
required: ['url'],
},
},
],
}));
// Implement the tool handler
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== 'get_web_content_as_markdown') {
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
}
// Ensure arguments exist with default values
if (!request.params.arguments) {
request.params.arguments = { url: '' };
}
if (!isValidGetWebContentArgs(request.params.arguments)) {
throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments: url must be a valid URL string');
}
const targetUrl = request.params.arguments.url;
const jinaUrl = `https://r.jina.ai/${targetUrl}`;
try {
const response = await this.axiosInstance.get(jinaUrl);
const content = response.data;
return {
content: [
{
type: 'text',
text: content,
},
],
};
}
catch (error) {
if (axios.isAxiosError(error)) {
// Check for authentication errors (401 Unauthorized)
if (error.response?.status === 401) {
return {
content: [
{
type: 'text',
text: `Authentication error: Invalid or expired Jina API token. Please obtain a valid token from https://jina.ai/ and set it as the JINA_API_TOKEN environment variable.`,
},
],
isError: true,
};
}
return {
content: [
{
type: 'text',
text: `Error retrieving web content: ${error.response?.statusText || error.message}`,
},
],
isError: true,
};
}
throw error;
}
});
}
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
// Removed console.error logs to prevent interference with MCP protocol
}
}
const server = new WebToMarkdownServer();
server.run().catch(console.error);
//# sourceMappingURL=index.js.map