Skip to content
Conduit

mcp server

PerplexityAgent

Security-hardened MCP server exposing the Perplexity Search + Sonar APIs (search, ask, deep_research) to AI agents. Hardened per NSA MCP guidance.

CryptoJones/PerplexityAgentcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

Before you start

  • uv (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "perplexityagent": {
      "command": "uvx",
      "args": [
        "perplexity-agent"
      ]
    }
  }
}

same format Cursor, GitHub Copilot CLI, Junie, JetBrains AI Assistant, Augment (Auggie CLI), Factory Droid, Kiro, Devin CLI +12

Values in angle brackets are placeholders. Restart Claude Desktop after editing its config.Claude Desktop docs

README

synced Oct 26, 2026

PerplexityAgent

Release CI CodeQL Python ≥ 3.11 License: MIT

A simple, security-hardened Model Context Protocol (MCP) server that gives AI agents — Claude Code, Hermes, or any MCP client — access to the Perplexity Search, Sonar, and Responses-compatible Agent APIs.

It follows a retrieval-first reference architecture (search and synthesis kept separate, citations validated against retrieval metadata) and applies the defensive controls from the NSA's Model Context Protocol (MCP): Security Design Considerations (May 2026). See SECURITY.md for the full control mapping.

Tools

ToolDescription
perplexity_searchRanked web results from the Perplexity Search API.
sonar_askA grounded answer from Sonar / Sonar Pro (OpenAI-compatible chat).
deep_researchMulti-step pipeline: decompose → search each sub-question → dedupe → synthesize (JSON schema) → validate citations → return a cited report with a validation_report.
responses_createFull Agent API: reasoning, multimodal input, built-in/custom tools, streaming events, and response chaining.
responses_retrieveRetrieve a stored Agent response snapshot by its resp_ ID.
finance_searchAgent response with Perplexity's structured finance tool enabled.
people_searchStructured results from the Search API's people index.
fetch_urlFetch a bounded set of public URLs through the SSRF-hardened page fetcher.
registered_function_callInvoke an operator-registered Python handler; present only when a registry is configured.
retrieveRetrieve a result offloaded because it exceeded the output budget.
server_metricsIn-process request, latency, and rate-limit counters.

Requirements

Setup

git clone https://codeberg.org/CryptoJones/PerplexityAgent.git
cd PerplexityAgent
uv sync                      # install (add --extra dev for tests)
cp .env.example .env         # then edit .env and set PERPLEXITY_API_KEY

The API key is read server-side only (from the environment or .env) and is never returned in any tool output. The server refuses to start without it.

Running

stdio (default — recommended)

Runs as a local subprocess of the agent with no network exposure:

uv run perplexity-agent

Register with Claude Code

claude mcp add perplexity -- uv --directory /abs/path/to/PerplexityAgent run perplexity-agent

or in your MCP client config (mcpServers):

{
  "mcpServers": {
    "perplexity": {
      "command": "uv",
      "args": ["--directory", "/abs/path/to/PerplexityAgent", "run", "perplexity-agent"],
      "env": { "PERPLEXITY_API_KEY": "pplx-..." }
    }
  }
}

Hermes

Hermes consumes MCP servers over stdio the same way — point it at the uv ... run perplexity-agent command with PERPLEXITY_API_KEY in the environment.

Optional hardened HTTP transport

Off by default. It refuses to start without a bearer token and binds to localhost. Only enable it if you understand the added attack surface (see SECURITY.md):

PERPLEXITY_HTTP_AUTH_TOKEN="$(openssl rand -hex 32)" uv run perplexity-agent --transport http

Clients must send Authorization: Bearer <token>. Terminate TLS in front of it (reverse proxy) and keep it behind a filtering egress proxy.

Calling the tools

Once the server is registered, the agent calls these tools automatically. The signatures, sample arguments, and return shapes are below.

perplexity_search

Ranked web results from the Search API.

ParamTypeDefaultBounds
querystring— (required)1–4096 chars
max_resultsint51–20
max_tokens_per_pageint1024128–4096
{ "query": "latest CRISPR base-editing clinical trials", "max_results": 8 }

Returns the raw Search API payload, e.g.:

{
  "results": [
    { "title": "…", "url": "https://…", "snippet": "…" }
  ]
}

Optional Search API filters include search_domain_filter (up to 20 domains), search_language_filter, search_recency_filter, and published/last-updated before/after dates in M/D/YYYY form.

responses_create

Calls POST /v1/agent using Perplexity's OpenAI Responses-compatible request shape. It accepts text or message-array input, provider-qualified model (or a fallback models list / preset), reasoning, response_format, tools, max_output_tokens, and max_steps.

Set store: true, use responses_retrieve to read the returned ID later, and pass that ID as previous_response_id to continue the response. Snapshots are kept in the local SQLite store and scoped to the originating MCP session in addition to Perplexity's upstream persistence. Retention defaults to the newest 100 snapshots per session and is configurable with PERPLEXITY_MAX_RESPONSES_PER_SESSION.

Function tools support both client-driven and automatic chaining. Pass their JSON schemas in tools; client-driven callers can submit a function_call_output with the returned call_id. Server operators can instead pass an allowlisted function_registry to build_server() and set auto_execute_functions: true. Only registered handlers execute, arguments and outputs are bounded, errors become tool outputs, and continuation rounds are capped. Registered handlers are also available to MCP clients through registered_function_call.

For multimodal input, use message content parts:

{
  "input": [{
    "type": "message",
    "role": "user",
    "content": [
      {"type": "input_text", "text": "Describe this image"},
      {"type": "input_image", "image_url": "https://example.com/image.png"}
    ]
  }],
  "model": "openai/gpt-5.5"
}

When stream: true, the API's SSE events (response.created, text deltas, response.completed, and other item events) are parsed and returned in an events array. MCP tool calls are request/response, so this surface collects the events rather than emitting incremental MCP messages.

Specialized search and fetch tools

  • finance_search takes a natural-language query plus optional categories and tickers hints, and enables the Agent API's finance_search tool.
  • people_search takes query, max_results, and max_tokens_per_page, and routes the standard Search API through search_type="people".
  • fetch_url accepts either url or a urls list plus an explicit max_urls cap (1–10), and returns extracted contents with final URLs, titles, text, and prompt-injection flags. It validates and pins every redirect hop to prevent SSRF and DNS rebinding.

Finance caching is disabled by default because prices change rapidly. Set PERPLEXITY_FINANCE_CACHE_TTL_S to a small value (maximum 300 seconds) to enable a bounded per-client cache for identical category/ticker queries.

sonar_ask

A grounded answer from Sonar (OpenAI-compatible chat completion).

ParamTypeDefaultNotes
questionstring— (required)1–4096 chars
modelstring"sonar"legacy sonar/sonar-pro or an Agent provider/model ID
system_promptstringnulloptional, ≤ 4096 chars
reasoningobjectnulloptional Agent reasoning effort (low through max)
response_formatobjectnulloptional text/JSON/JSON-schema output format
max_output_tokensintnullrequired for Anthropic Agent models
{
  "question": "What changed in the EU AI Act's 2026 enforcement timeline?",
  "model": "sonar-pro",
  "system_prompt": "Answer concisely and cite sources."
}

Returns the chat-completion payload; the answer is at choices[0].message.content, with citations in the response metadata.

deep_research

The full pipeline: decompose → search each sub-question → dedupe → synthesize (JSON schema) → validate citations.

ParamTypeDefaultBounds
questionstring— (required)1–4096 chars
num_subquestionsint41–8
modelstring"sonar-pro""sonar" or "sonar-pro"
max_results_per_subquestionint51–10
use_model_decompositionboolfalseask Sonar to derive the sub-questions (one extra call; falls back to the deterministic angles on any failure)
{ "question": "Is small modular nuclear cost-competitive with grid-scale solar?", "num_subquestions": 5 }

Returns a structured, citation-validated report:

{
  "question": "…",
  "subquestions": ["…", "…"],
  "sources": [{ "title": "…", "url": "https://…", "snippet": "…" }],
  "report": {
    "answer": "…",
    "key_findings": ["…"],
    "open_questions": ["…"],
    "claims": [
      { "claim": "…", "supporting_urls": ["https://…"], "confidence": "high" }
    ]
  },
  "validation_report": {
    "total_claims": 6,
    "all_claims_supported": true,
    "all_urls_known": true,
    "passed": true,
    "flagged": []
  },
  "security_flags": { "possible_prompt_injection_patterns": [] },
  "usage": { "prompt_tokens": 1234, "completion_tokens": 567 }
}

A claim whose URL was never seen in retrieval is downgraded to low confidence and listed in validation_report.flagged — passed is false if any claim is unsupported or cites an unknown URL.

From a Claude Code / agent prompt

You don't construct the JSON yourself — just ask, and the model picks the tool:

> Use deep_research to assess whether small modular reactors are cost-competitive
  with grid-scale solar, then summarize only the high-confidence claims.

From a raw MCP client (stdio, for testing)

Any MCP client works. Using the Python SDK that ships with this project:

import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client


async def main():
    params = StdioServerParameters(
        command="uv",
        args=["--directory", "/abs/path/to/PerplexityAgent", "run", "perplexity-agent"],
        env={"PERPLEXITY_API_KEY": "pplx-..."},
    )
    async with stdio_client(params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            print([t.name for t in (await session.list_tools()).tools])
            result = await session.call_tool(
                "perplexity_search", {"query": "what is MCP", "max_results": 3}
            )
            print(result.content)


asyncio.run(main())

You can also explore the tools interactively with the MCP Inspector:

npx @modelcontextprotocol/inspector uv run perplexity-agent

Over the HTTP transport

With the server started via --transport http, point any streamable-HTTP MCP client at http://127.0.0.1:8080/mcp and send the bearer token:

Authorization: Bearer <PERPLEXITY_HTTP_AUTH_TOKEN>

Comet-style TUI (perplexity-agent tui)

An optional interactive terminal app that brings the spirit of Perplexity's Comet browser to the terminal, backed by the same Search / Sonar / deep-research client. A terminal can't render web pages, drive a real browser (clicking, booking, buying), or do voice — those are out of scope by physics. Everything else maps onto terminal-feasible equivalents:

Comet featureIn the TUI
Assistant sidebarA persistent chat pane (Sonar) that answers with your open "tabs" as context
Answer-first search/search — ranked results plus a grounded, cited answer
Open / summarize a page/open <url> — SSRF-guarded fetch → readable text → one-click summary
Ask about / translate a page/ask <q>, /translate <lang> on the current page
Chat with your tabs / synthesis/summary across all open tabs; bare chat is tab-aware
AI tab grouping/group clusters open tabs into named groups
Deep research/research <q> runs the full validated, cited pipeline
Memory & SpacesLocal SQLite store (owner-only 0600; tabs dedupe per space+URL, recent-tab cap and optional history retention are configurable); /space [name] switches workspaces
Background / scheduled tasks/task search|fetch <seconds> <target> monitors and alerts on change; /untask <id> stops it
Agentic task planningResearch-only planning (decompose a goal); no real web actions

Install the extra and launch it (needs PERPLEXITY_API_KEY, same as the server):

uv sync --extra tui
uv run perplexity-agent tui

The page fetcher (/open) is the only egress path other than the Perplexity API and is reachable only from the TUI, never via the MCP tools. It is SSRF-hardened (scheme allowlist, private/loopback/link-local IPs rejected on every redirect hop, connections pinned to the validated IP, a streaming size cap, and a text-only content-type allowlist) and flags fetched text for indirect prompt injection before it reaches Sonar. See SECURITY.md. The MCP tool surface is unchanged.

Configuration

All optional knobs are environment variables (see .env.example): timeouts, response-size cap, retry count, rate limits, an optional JSON audit-log path, and (for the TUI) the fetch User-Agent, PERPLEXITY_FETCH_ALLOW_PRIVATE, PERPLEXITY_STORE_PATH, and the per-Space store retention caps (PERPLEXITY_MAX_TABS_PER_SPACE, default 50; PERPLEXITY_MAX_HISTORY_PER_SPACE, default unbounded — set a positive integer to keep only the N most-recent rows per Space, or leave blank to keep everything).

Development

uv sync --extra dev --extra tui   # add --extra tui to exercise the TUI tests
uv run pytest          # unit tests (no live API needed; httpx is mocked)
uv run ruff check .    # lint
uv run mypy src        # type check (strict)
uv run pip-audit       # dependency vulnerability scan

License

MIT — see LICENSE.

Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/

↑↓ navigate openesc close