Marginalia MCP Server
by bmorphism
Verified
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ErrorCode,
McpError,
} from "@modelcontextprotocol/sdk/types.js";
import axios from "axios";
const API_KEY = process.env.MARGINALIA_API_KEY || 'public';
const BASE_URL = 'https://api.marginalia.nu';
interface SearchResult {
url: string;
title: string;
description: string;
}
interface MarginaliaResponse {
query: string;
license: string;
results: SearchResult[];
}
const server = new Server(
{
name: "marginalia-mcp-server",
version: "0.1.0",
},
{
capabilities: {
tools: {},
},
}
);
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "search-marginalia",
description: "Search the web using Marginalia Search",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Search query"
},
index: {
type: "number",
description: "Search index (corresponds to dropdown in main GUI)",
minimum: 0
},
count: {
type: "number",
description: "Number of results to return",
minimum: 1,
maximum: 100
}
},
required: ["query"]
}
}
]
};
});
server.setRequestHandler(CallToolRequestSchema, async (request: { params: { name: string; arguments?: any } }) => {
if (request.params.name !== "search-marginalia") {
throw new McpError(ErrorCode.MethodNotFound, "Unknown tool");
}
const { query, index = 0, count = 10 } = request.params.arguments as {
query: string;
index?: number;
count?: number;
};
try {
const url = `${BASE_URL}/${API_KEY}/search/${encodeURIComponent(query)}`;
const params = { index, count };
const response = await axios.get<MarginaliaResponse>(url, { params });
return {
content: [{
type: "text",
text: JSON.stringify({
query: response.data.query,
license: response.data.license,
results: response.data.results.map(result => ({
url: result.url,
title: result.title,
description: result.description
}))
}, null, 2)
}]
};
} catch (error) {
if (axios.isAxiosError(error)) {
if (error.response?.status === 503) {
throw new McpError(
ErrorCode.InvalidRequest,
"Rate limit exceeded. Consider requesting a dedicated API key from kontakt@marginalia.nu"
);
}
throw new McpError(
ErrorCode.InternalError,
`Search failed: ${error.response?.data?.message || error.message}`
);
}
throw error;
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Marginalia MCP server running on stdio');
}
main().catch((error) => {
console.error("Server error:", error);
process.exit(1);
});