Skip to content

feat: add MCP server for Perplexica integration#1044

Open
willtwilson wants to merge 2 commits intoItzCrazyKns:masterfrom
willtwilson:feat/mcp-server
Open

feat: add MCP server for Perplexica integration#1044
willtwilson wants to merge 2 commits intoItzCrazyKns:masterfrom
willtwilson:feat/mcp-server

Conversation

@willtwilson
Copy link

@willtwilson willtwilson commented Mar 8, 2026

What

Adds an MCP (Model Context Protocol) server that exposes Perplexica's web search as a tool for AI agents.

Why

MCP is becoming the standard protocol for AI tool integration. With this server, Perplexica can be used as a search tool in:

  • Claude Desktop — add to \claude_desktop_config.json\
  • LibreChat — add to \librechat.yaml\ mcpServers
  • Any MCP-compatible client — Continue, Cursor, etc.

There's no official MCP server for Perplexica today, despite growing demand (related: #996).

What's included

File Description
\integrations/mcp-server/src/server.ts\ MCP SSE server with \perplexica_search\ tool
\integrations/mcp-server/Dockerfile\ Multi-stage Docker build, non-root user
\integrations/mcp-server/README.md\ Setup guide for Docker, manual install, LibreChat, Claude Desktop
\integrations/mcp-server/.env.example\ Environment variable reference

Technical details

  • Uses the documented \POST /api/search\ endpoint with \sources\ parameter (v1.12.1+ API)
  • Appends source citations as markdown links to tool responses
  • No \�xpress.json()\ middleware — the MCP SDK reads raw request streams; body-parsing middleware breaks the SSE transport
  • Non-root Docker user for security
  • Provider UUIDs discovered via \GET /api/providers\

Testing

Tested against Perplexica v1.12.1 with:

  • SearXNG backend
  • Multiple LLM providers (OpenAI, Ollama, OpenAI-compatible proxies)
  • LibreChat MCP integration (production use)

Summary by cubic

Adds an MCP SSE server that exposes Perplexica’s search as a tool for clients like Claude Desktop and LibreChat. Provides cited results via perplexica_search with Docker support.

  • New Features

    • New package at integrations/mcp-server (@perplexica/mcp-server) providing an SSE MCP server.
    • perplexica_search backed by Perplexica POST /api/search (v1.12.1+), with optional sources.
    • Returns answers with source citations as markdown links.
    • HTTP routes: /sse, /messages, /health; avoids express.json() to keep raw SSE streams.
    • Dockerfile (multi-stage, non-root) using npm ci for reproducible builds; .env.example and README with LibreChat and Claude Desktop examples.
    • Env vars for provider/model IDs; discover provider UUIDs via /api/providers.
  • Bug Fixes

    • Added try/catch in async Express route handlers to prevent unhandled rejections.
    • Moved res.on('close') before server.connect() to avoid transport leaks on connect errors.
    • Fail-fast startup validation for required env vars with clear error output.
    • Tightened sources validation to z.enum(['web', 'academic', 'discussions']) with default ['web'].

Written for commit 216a67a. Summary will update on new commits.

Add integrations/mcp-server/ — an MCP SSE server that exposes
Perplexica web search as a tool for Claude Desktop, LibreChat,
and other MCP-compatible clients.

Features:
- perplexica_search tool with configurable sources
- Uses documented /api/search endpoint (v1.12.1+ API)
- Source citations appended as markdown links
- Docker support with multi-stage build and non-root user
- Client configuration examples for LibreChat and Claude Desktop

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings March 8, 2026 22:52
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="integrations/mcp-server/Dockerfile">

<violation number="1" location="integrations/mcp-server/Dockerfile:11">
P2: Final image includes devDependencies because node_modules is copied from builder without pruning. Run `npm ci --omit=dev` in the final stage to install only production dependencies.</violation>
</file>

<file name="integrations/mcp-server/src/server.ts">

<violation number="1" location="integrations/mcp-server/src/server.ts:109">
P2: Async Express route handler lacks error handling. In Express 4, rejected promises from async handlers are not automatically caught by error middleware and will cause unhandled promise rejections.</violation>

<violation number="2" location="integrations/mcp-server/src/server.ts:118">
P2: Async Express route handler lacks error handling. In Express 4, rejected promises from async handlers are not automatically caught by error middleware and will cause unhandled promise rejections.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an MCP (Model Context Protocol) server integration that exposes Perplexica's web search as a tool (perplexica_search) for AI agents via SSE transport. It enables usage from MCP-compatible clients like Claude Desktop, LibreChat, Continue, and Cursor. The server forwards queries to Perplexica's existing POST /api/search endpoint and returns synthesized answers with source citations.

Changes:

  • New Express-based MCP SSE server (server.ts) that wraps Perplexica's search API as an MCP tool, with a health endpoint and session management
  • Multi-stage Docker build with non-root user, plus environment variable configuration for provider/model settings
  • Documentation (README) covering setup, client configuration, and environment variables

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
integrations/mcp-server/src/server.ts Core MCP server implementation: SSE transport, perplexica_search tool definition, session management, and health check endpoint
integrations/mcp-server/package.json Package definition with MCP SDK, Express, and Zod dependencies
integrations/mcp-server/Dockerfile Multi-stage Docker build with non-root user for production security
integrations/mcp-server/README.md Setup guide covering Docker, manual install, client configuration (LibreChat, Claude Desktop), and environment variable reference
integrations/mcp-server/.env.example Template for required environment variables (provider UUIDs, model keys)
integrations/mcp-server/tsconfig.json TypeScript configuration targeting ES2022 with NodeNext module resolution

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +113 to +115
const server = buildMcpServer();
await server.connect(transport);
res.on("close", () => transports.delete(id));
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If server.connect(transport) throws, the res.on("close") cleanup handler is never registered, but the transport has already been added to the transports map (line 112). This creates a potential memory leak where orphaned entries accumulate. Move the res.on("close", ...) handler before await server.connect(transport), or wrap the connect call in a try/catch that cleans up the transport on failure.

Suggested change
const server = buildMcpServer();
await server.connect(transport);
res.on("close", () => transports.delete(id));
res.on("close", () => transports.delete(id));
const server = buildMcpServer();
await server.connect(transport);

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in follow-up commit (216a67a) — moved res.on('close', ...)) cleanup registration to before server.connect(). If connect throws, the transport is now cleaned from the Map correctly. Good catch on the ordering.

Comment on lines +3 to +4
COPY package.json tsconfig.json ./
RUN npm install
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no package-lock.json included in the PR, and the Dockerfile uses npm install without a lockfile. This means Docker builds are non-reproducible — different builds may resolve different dependency versions. The main Perplexica project uses yarn install --frozen-lockfile for reproducible builds (see Dockerfile.slim:8). Consider committing a package-lock.json (or yarn.lock) and using npm ci (or yarn install --frozen-lockfile) in the Dockerfile for deterministic builds.

Suggested change
COPY package.json tsconfig.json ./
RUN npm install
COPY package.json package-lock.json tsconfig.json ./
RUN npm ci

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in follow-up commit (216a67a) — committed a package-lock.json and updated the Dockerfile to use npm ci in the builder stage for deterministic, reproducible builds. You're right that following the same pattern as the main Perplexica project (frozen lockfile installs) is the right approach.

Comment on lines +76 to +81
sources: z
.array(z.string())
.optional()
.describe(
'Sources to search. Defaults to ["web"]. Options: "web", "academic", "discussions"',
),
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sources parameter accepts any string[] without validation, but the Perplexica API only accepts 'web' | 'discussions' | 'academic' (defined in src/lib/agents/search/types.ts:7). Invalid values passed through could cause unexpected behavior or confusing errors. Consider constraining the zod schema to use z.enum(["web", "academic", "discussions"]) instead of z.string() to catch invalid values at the MCP tool level and provide clear feedback to the AI agent.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in follow-up commit (216a67a) — changed z.string() to z.enum(['web', 'academic', 'discussions']) to match the actual Perplexica API types. Also added .default(['web']) so the tool works out-of-the-box without the AI agent needing to specify sources explicitly.

const PERPLEXICA_CHAT_MODEL = process.env.PERPLEXICA_CHAT_MODEL ?? "";
const PERPLEXICA_EMBED_PROVIDER = process.env.PERPLEXICA_EMBED_PROVIDER ?? "";
const PERPLEXICA_EMBED_MODEL = process.env.PERPLEXICA_EMBED_MODEL ?? "";

Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The required environment variables PERPLEXICA_CHAT_PROVIDER, PERPLEXICA_CHAT_MODEL, PERPLEXICA_EMBED_PROVIDER, and PERPLEXICA_EMBED_MODEL default to empty strings, and the server starts without any validation. Every search request will fail at the Perplexica API with "Invalid provider id" when these are not set. Consider adding startup validation that logs a clear error (or exits) if these required variables are missing, so operators get immediate feedback instead of opaque runtime errors on every request.

Suggested change
// Validate required Perplexica configuration at startup to avoid opaque runtime errors.
const missingPerplexicaEnv: string[] = [];
if (!PERPLEXICA_CHAT_PROVIDER) missingPerplexicaEnv.push("PERPLEXICA_CHAT_PROVIDER");
if (!PERPLEXICA_CHAT_MODEL) missingPerplexicaEnv.push("PERPLEXICA_CHAT_MODEL");
if (!PERPLEXICA_EMBED_PROVIDER) missingPerplexicaEnv.push("PERPLEXICA_EMBED_PROVIDER");
if (!PERPLEXICA_EMBED_MODEL) missingPerplexicaEnv.push("PERPLEXICA_EMBED_MODEL");
if (missingPerplexicaEnv.length > 0) {
console.error(
"Perplexica MCP server misconfiguration: missing required environment variable(s):",
missingPerplexicaEnv.join(", "),
);
console.error(
"Please set these environment variables before starting the server to avoid 'Invalid provider id' errors from Perplexica.",
);
process.exit(1);
}

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great suggestion — added a startup validation block that checks all four required env vars and calls process.exit(1) with a clear message listing which are missing. This turns an opaque runtime failure on every search into an immediate, actionable error at startup. Fixed in follow-up commit (216a67a).

Comment on lines +11 to +13
COPY --from=builder /app/dist dist/
COPY --from=builder /app/node_modules node_modules/
COPY package.json ./
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Dockerfile copies the full node_modules (including devDependencies like tsx, typescript, and @types/*) into the production image. Since only runtime dependencies are needed, you should either run a separate npm install --omit=dev for the production stage or prune devDependencies before copying. This would reduce the final image size noticeably.

Suggested change
COPY --from=builder /app/dist dist/
COPY --from=builder /app/node_modules node_modules/
COPY package.json ./
COPY package.json ./
RUN npm install --omit=dev
COPY --from=builder /app/dist dist/

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the same commit (216a67a) — the final stage now runs npm ci --omit=dev independently rather than copying node_modules from the builder. This removes tsx, typescript, @types/* and all other devDependencies from the production image.

… Docker build

- Move res.on('close') before server.connect() to prevent transport leak on connect errors
- Add try/catch to both async Express route handlers (Express 4 doesn't propagate async rejections)
- Add startup validation for required env vars with process.exit(1) on missing vars
- Change sources to z.enum(['web', 'academic', 'discussions']) with .default(['web'])
- Add package-lock.json and update Dockerfile to use npm ci (deterministic builds)
- Final Docker stage uses npm ci --omit=dev for production-only dependencies

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants