Skip to content
Draft
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
5 changes: 2 additions & 3 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
},
"metadata": {
"description": "Official Perplexity AI plugin providing real-time web search, reasoning, and research capabilities",
"version": "0.9.0"
"version": "0.10.0"
},
"plugins": [
{
"name": "perplexity",
"source": "./",
"description": "Real-time web search, reasoning, and research through Perplexity's API",
"version": "0.9.0",
"version": "0.10.0",
"author": {
"name": "Perplexity AI",
"email": "[email protected]"
Expand Down Expand Up @@ -46,4 +46,3 @@
}
]
}

10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@
 
[![npm version](https://img.shields.io/npm/v/%40perplexity-ai%2Fmcp-server?style=for-the-badge&logo=npm&logoColor=white&color=CB3837)](https://www.npmjs.com/package/@perplexity-ai/mcp-server)

The official MCP server implementation for the Perplexity API Platform, providing AI assistants with real-time web search, reasoning, and research capabilities through Sonar models and the Search API.
The official MCP server implementation for the Perplexity API Platform, providing AI assistants with real-time web search, reasoning, and research capabilities through the Agent API and Search API.

## Available Tools

### **perplexity_search**
Direct web search using the Perplexity Search API. Returns ranked search results with metadata, perfect for finding current information.

### **perplexity_ask**
General-purpose conversational AI with real-time web search using the `sonar-pro` model. Great for quick questions and everyday searches.
General-purpose conversational AI with real-time web search using the Agent API `fast` preset by default. Great for quick questions and everyday searches.

### **perplexity_research**
Deep, comprehensive research using the `sonar-deep-research` model. Ideal for thorough analysis and detailed reports.
Deep, comprehensive research using the Agent API `medium` preset by default. Ideal for thorough analysis and detailed reports.

### **perplexity_reason**
Advanced reasoning and problem-solving using the `sonar-reasoning-pro` model. Perfect for complex analytical tasks.
Advanced reasoning and problem-solving using the Agent API `low` preset by default. Perfect for complex analytical tasks.

The three Agent API tools accept optional `model` and `preset` parameters. Use `model` for a provider/model ID such as `perplexity/sonar`, or `preset` for any current Agent API preset. If both are provided, the model overrides the preset's model while retaining its other configuration.

> [!TIP]
> Available as an optional parameter for **perplexity_reason** and **perplexity_research**: `strip_thinking`
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@perplexity-ai/mcp-server",
"version": "0.9.0",
"version": "0.10.0",
"mcpName": "ai.perplexity/mcp-server",
"description": "Real-time web search, reasoning, and research through Perplexity's API",
"keywords": [
Expand Down
5 changes: 2 additions & 3 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
"name": "ai.perplexity/mcp-server",
"title": "Perplexity API Platform",
"description": "Real-time web search, reasoning, and research through Perplexity's API",
"version": "0.9.0",
"version": "0.10.0",
"packages": [
{
"registryType": "npm",
"identifier": "@perplexity-ai/mcp-server",
"version": "0.9.0",
"version": "0.10.0",
"transport": {
"type": "stdio"
}
}
]
}

170 changes: 170 additions & 0 deletions src/agent.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { performAgentResponse } from "./server.js";

function createSseResponse(events: unknown[]): Response {
const body = events
.map((event) => `data: ${JSON.stringify(event)}\n\n`)
.concat("data: [DONE]\n\n")
.join("");
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode(body));
controller.close();
},
});

return { ok: true, body: stream } as Response;
}

describe("performAgentResponse", () => {
let originalFetch: typeof global.fetch;

beforeEach(() => {
originalFetch = global.fetch;
});

afterEach(() => {
global.fetch = originalFetch;
vi.restoreAllMocks();
});

it("streams an Agent API response using a selected model", async () => {
global.fetch = vi.fn().mockResolvedValue(createSseResponse([
{ type: "response.output_text.delta", delta: "A grounded " },
{ type: "response.output_text.delta", delta: "answer[1]." },
{
type: "response.completed",
response: {
id: "resp_123",
object: "response",
created_at: 1,
model: "perplexity/sonar",
status: "completed",
output: [
{
type: "search_results",
results: [
{
id: 1,
title: "Primary source",
url: "https://example.com/source",
},
],
},
{
type: "message",
id: "msg_123",
role: "assistant",
content: [{ type: "output_text", text: "A grounded answer[1]." }],
},
],
},
},
]));

const messages = [{ role: "user", content: "test question" }];
const result = await performAgentResponse(messages, { model: "perplexity/sonar" });

expect(result).toBe(
"A grounded answer[1].\n\nCitations:\n[1] https://example.com/source\n",
);
expect(global.fetch).toHaveBeenCalledWith(
"https://api.perplexity.ai/v1/agent",
expect.objectContaining({
method: "POST",
headers: expect.objectContaining({
"Content-Type": "application/json",
Authorization: "Bearer test-api-key",
"X-Source": "pplx-mcp-server",
}),
body: JSON.stringify({
input: messages,
model: "perplexity/sonar",
stream: true,
}),
}),
);
});

it("applies preset search and reasoning options to the Agent API request", async () => {
global.fetch = vi.fn().mockResolvedValue(createSseResponse([
{
type: "response.reasoning.search_results",
results: [
{ id: 1, title: "First source", url: "https://example.com/first" },
],
},
{
type: "response.reasoning.search_results",
results: [
{ id: 2, title: "Second source", url: "https://example.com/second" },
],
},
{
type: "response.output_text.delta",
delta: "<think>hidden</think>The result[1][2].",
},
]));

const messages = [{ role: "user", content: "research this" }];
const result = await performAgentResponse(messages, {
preset: "medium",
strip_thinking: true,
search_recency_filter: "week",
search_domain_filter: ["example.com"],
search_context_size: "high",
reasoning_effort: "high",
});

expect(result).toBe(
"The result[1][2].\n\nCitations:\n" +
"[1] https://example.com/first\n" +
"[2] https://example.com/second\n",
);
expect(global.fetch).toHaveBeenCalledWith(
"https://api.perplexity.ai/v1/agent",
expect.objectContaining({
body: JSON.stringify({
input: messages,
preset: "medium",
stream: true,
tools: [{
type: "web_search",
search_context_size: "high",
filters: {
search_recency_filter: "week",
search_domain_filter: ["example.com"],
},
}],
reasoning: { effort: "high" },
}),
}),
);
});

it("surfaces failed Agent API stream responses", async () => {
global.fetch = vi.fn().mockResolvedValue(createSseResponse([
{
type: "response.failed",
response: {
id: "resp_failed",
object: "response",
created_at: 1,
model: "perplexity/sonar",
status: "failed",
output: [],
error: {
type: "server_error",
code: "upstream_error",
message: "The upstream model failed",
},
},
},
]));

await expect(performAgentResponse(
[{ role: "user", content: "test question" }],
{ model: "perplexity/sonar" },
)).rejects.toThrow("Agent API response failed: The upstream model failed");
});
});
Loading
Loading