Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions examples/pdf-server/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
pathToFileUrl,
fileUrlToPath,
allowedLocalFiles,
allowedRemoteOrigins,
DEFAULT_PDF,
} from "./server.js";

Expand Down Expand Up @@ -132,9 +131,6 @@ async function main() {
}

console.error(`[pdf-server] Ready (${urls.length} URL(s) configured)`);
console.error(
`[pdf-server] Allowed origins: ${[...allowedRemoteOrigins].join(", ")}`,
);

if (stdio) {
await startStdioServer(createServer);
Expand Down
43 changes: 7 additions & 36 deletions examples/pdf-server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* PDF MCP Server
*
* An MCP server that displays PDFs in an interactive viewer.
* Supports local files and remote URLs from academic sources (arxiv, biorxiv, etc).
* Supports local files and remote HTTPS URLs.
*
* Tools:
* - list_pdfs: List available PDFs
Expand Down Expand Up @@ -44,26 +44,6 @@ export const CACHE_MAX_LIFETIME_MS = 60_000; // 60 seconds
/** Max size for cached PDFs (defensive limit to prevent memory exhaustion) */
export const CACHE_MAX_PDF_SIZE_BYTES = 50 * 1024 * 1024; // 50MB

/** Allowed remote origins (security allowlist) */
export const allowedRemoteOrigins = new Set([
"https://agrirxiv.org",
"https://arxiv.org",
"https://chemrxiv.org",
"https://edarxiv.org",
"https://engrxiv.org",
"https://hal.science",
"https://osf.io",
"https://psyarxiv.com",
"https://ssrn.com",
"https://www.biorxiv.org",
"https://www.eartharxiv.org",
"https://www.medrxiv.org",
"https://www.preprints.org",
"https://www.researchsquare.com",
"https://www.sportarxiv.org",
"https://zenodo.org",
]);

/** Allowed local file paths (populated from CLI args) */
export const allowedLocalFiles = new Set<string>();

Expand Down Expand Up @@ -134,14 +114,11 @@ export function validateUrl(url: string): { valid: boolean; error?: string } {
return { valid: true };
}

// Remote URL - check against allowed origins
// Remote URL - require HTTPS
try {
const parsed = new URL(url);
const origin = `${parsed.protocol}//${parsed.hostname}`;
if (
![...allowedRemoteOrigins].some((allowed) => origin.startsWith(allowed))
) {
return { valid: false, error: `Origin not allowed: ${origin}` };
if (parsed.protocol !== "https:") {
return { valid: false, error: `Only HTTPS URLs are allowed: ${url}` };
}
return { valid: true };
} catch {
Expand Down Expand Up @@ -417,7 +394,7 @@ export function createServer(): McpServer {
// Create session-local cache (isolated per server instance)
const { readPdfRange } = createPdfCache();

// Tool: list_pdfs - List available PDFs (local files + allowed origins)
// Tool: list_pdfs - List available PDFs
server.tool(
"list_pdfs",
"List available PDFs that can be displayed",
Expand All @@ -443,15 +420,14 @@ export function createServer(): McpServer {
);
}
parts.push(
`Remote PDFs from ${[...allowedRemoteOrigins].join(", ")} can also be loaded dynamically.`,
`Any remote PDF accessible via HTTPS can also be loaded dynamically.`,
);

return {
content: [{ type: "text", text: parts.join("\n\n") }],
structuredContent: {
localFiles: pdfs.filter((p) => p.type === "local").map((p) => p.url),
allowedDirectories: [...allowedLocalDirs],
allowedOrigins: [...allowedRemoteOrigins],
},
};
},
Expand Down Expand Up @@ -531,11 +507,6 @@ export function createServer(): McpServer {
},
);

// Build allowed domains list for tool description (strip https:// and www.)
const allowedDomains = [...allowedRemoteOrigins]
.map((origin) => origin.replace(/^https?:\/\/(www\.)?/, ""))
.join(", ");

// Tool: display_pdf - Show interactive viewer
registerAppTool(
server,
Expand All @@ -547,7 +518,7 @@ export function createServer(): McpServer {
Accepts:
- Local files explicitly added to the server (use list_pdfs to see available files)
- Local files under directories provided by the client as MCP roots
- Remote PDFs from: ${allowedDomains}`,
- Any remote PDF accessible via HTTPS`,
inputSchema: {
url: z.string().default(DEFAULT_PDF).describe("PDF URL"),
page: z.number().min(1).default(1).describe("Initial page"),
Expand Down
Loading