@perplexity-ai/mcp-server 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/README.md +3 -1
- package/dist/server.js +91 -28
- package/package.json +1 -1
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
},
|
|
7
7
|
"metadata": {
|
|
8
8
|
"description": "Official Perplexity AI plugin providing real-time web search, reasoning, and research capabilities",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.7.0"
|
|
10
10
|
},
|
|
11
11
|
"plugins": [
|
|
12
12
|
{
|
|
13
13
|
"name": "perplexity",
|
|
14
14
|
"source": "./",
|
|
15
15
|
"description": "Real-time web search, reasoning, and research through Perplexity's API",
|
|
16
|
-
"version": "0.
|
|
16
|
+
"version": "0.7.0",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Perplexity AI",
|
|
19
19
|
"email": "api@perplexity.ai"
|
package/README.md
CHANGED
|
@@ -34,7 +34,8 @@ Advanced reasoning and problem-solving using the `sonar-reasoning-pro` model. Pe
|
|
|
34
34
|
1. Get your Perplexity API Key from the [API Portal](https://www.perplexity.ai/account/api/group)
|
|
35
35
|
2. Replace `your_key_here` in the configurations below with your API key
|
|
36
36
|
3. (Optional) Set timeout: `PERPLEXITY_TIMEOUT_MS=600000` (default: 5 minutes)
|
|
37
|
-
4. (Optional) Set
|
|
37
|
+
4. (Optional) Set custom base URL: `PERPLEXITY_BASE_URL=https://your-custom-url.com` (default: https://api.perplexity.ai)
|
|
38
|
+
5. (Optional) Set log level: `PERPLEXITY_LOG_LEVEL=DEBUG|INFO|WARN|ERROR` (default: ERROR)
|
|
38
39
|
|
|
39
40
|
### Claude Code
|
|
40
41
|
|
|
@@ -145,6 +146,7 @@ For cloud or shared deployments, run the server in HTTP mode.
|
|
|
145
146
|
| Variable | Description | Default |
|
|
146
147
|
|----------|-------------|---------|
|
|
147
148
|
| `PERPLEXITY_API_KEY` | Your Perplexity API key | *Required* |
|
|
149
|
+
| `PERPLEXITY_BASE_URL` | Custom base URL for API requests | `https://api.perplexity.ai` |
|
|
148
150
|
| `PORT` | HTTP server port | `8080` |
|
|
149
151
|
| `BIND_ADDRESS` | Network interface to bind to | `0.0.0.0` |
|
|
150
152
|
| `ALLOWED_ORIGINS` | CORS origins (comma-separated) | `*` |
|
package/dist/server.js
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import { fetch as undiciFetch, ProxyAgent } from "undici";
|
|
4
4
|
import { ChatCompletionResponseSchema, SearchResponseSchema } from "./validation.js";
|
|
5
5
|
const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY;
|
|
6
|
+
const PERPLEXITY_BASE_URL = process.env.PERPLEXITY_BASE_URL || "https://api.perplexity.ai";
|
|
6
7
|
export function getProxyUrl() {
|
|
7
8
|
return process.env.PERPLEXITY_PROXY ||
|
|
8
9
|
process.env.HTTPS_PROXY ||
|
|
@@ -42,16 +43,20 @@ export function validateMessages(messages, toolName) {
|
|
|
42
43
|
export function stripThinkingTokens(content) {
|
|
43
44
|
return content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
44
45
|
}
|
|
45
|
-
export async function performChatCompletion(messages, model = "sonar-pro", stripThinking = false, serviceOrigin) {
|
|
46
|
+
export async function performChatCompletion(messages, model = "sonar-pro", stripThinking = false, serviceOrigin, options) {
|
|
46
47
|
if (!PERPLEXITY_API_KEY) {
|
|
47
48
|
throw new Error("PERPLEXITY_API_KEY environment variable is required");
|
|
48
49
|
}
|
|
49
50
|
// Read timeout fresh each time to respect env var changes
|
|
50
51
|
const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10);
|
|
51
|
-
const url = new URL(
|
|
52
|
+
const url = new URL(`${PERPLEXITY_BASE_URL}/chat/completions`);
|
|
52
53
|
const body = {
|
|
53
54
|
model: model,
|
|
54
55
|
messages: messages,
|
|
56
|
+
...(options?.search_recency_filter && { search_recency_filter: options.search_recency_filter }),
|
|
57
|
+
...(options?.search_domain_filter && { search_domain_filter: options.search_domain_filter }),
|
|
58
|
+
...(options?.search_context_size && { web_search_options: { search_context_size: options.search_context_size } }),
|
|
59
|
+
...(options?.reasoning_effort && { reasoning_effort: options.reasoning_effort }),
|
|
55
60
|
};
|
|
56
61
|
const controller = new AbortController();
|
|
57
62
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
@@ -143,7 +148,7 @@ export async function performSearch(query, maxResults = 10, maxTokensPerPage = 1
|
|
|
143
148
|
}
|
|
144
149
|
// Read timeout fresh each time to respect env var changes
|
|
145
150
|
const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10);
|
|
146
|
-
const url = new URL(
|
|
151
|
+
const url = new URL(`${PERPLEXITY_BASE_URL}/search`);
|
|
147
152
|
const body = {
|
|
148
153
|
query: query,
|
|
149
154
|
max_results: maxResults,
|
|
@@ -199,36 +204,76 @@ export async function performSearch(query, maxResults = 10, maxTokensPerPage = 1
|
|
|
199
204
|
export function createPerplexityServer(serviceOrigin) {
|
|
200
205
|
const server = new McpServer({
|
|
201
206
|
name: "io.github.perplexityai/mcp-server",
|
|
202
|
-
version: "0.
|
|
207
|
+
version: "0.7.0",
|
|
208
|
+
}, {
|
|
209
|
+
instructions: "Perplexity AI server for web-grounded search, research, and reasoning. " +
|
|
210
|
+
"Use perplexity_search for finding URLs, facts, and recent news. " +
|
|
211
|
+
"Use perplexity_ask for quick AI-answered questions with citations. Supports recency filters, domain restrictions, and search context size control. " +
|
|
212
|
+
"Use perplexity_research for in-depth multi-source investigation (slow, 30s+). Supports reasoning_effort parameter to control depth. " +
|
|
213
|
+
"Use perplexity_reason for complex analysis requiring step-by-step logic. Supports recency filters, domain restrictions, and search context size control. " +
|
|
214
|
+
"All tools are read-only and access live web data.",
|
|
203
215
|
});
|
|
204
216
|
const messageSchema = z.object({
|
|
205
|
-
role: z.
|
|
217
|
+
role: z.enum(["system", "user", "assistant"]).describe("Role of the message sender"),
|
|
206
218
|
content: z.string().describe("The content of the message"),
|
|
207
219
|
});
|
|
208
220
|
const messagesField = z.array(messageSchema).describe("Array of conversation messages");
|
|
209
221
|
const stripThinkingField = z.boolean().optional()
|
|
210
222
|
.describe("If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false.");
|
|
223
|
+
const searchRecencyFilterField = z.enum(["hour", "day", "week", "month", "year"]).optional()
|
|
224
|
+
.describe("Filter search results by recency. Use 'hour' for very recent news, 'day' for today's updates, 'week' for this week, etc.");
|
|
225
|
+
const searchDomainFilterField = z.array(z.string()).optional()
|
|
226
|
+
.describe("Restrict search results to specific domains (e.g., ['wikipedia.org', 'arxiv.org']). Use '-' prefix for exclusion (e.g., ['-reddit.com']).");
|
|
227
|
+
const searchContextSizeField = z.enum(["low", "medium", "high"]).optional()
|
|
228
|
+
.describe("Controls how much web context is retrieved. 'low' (default) is fastest, 'high' provides more comprehensive results.");
|
|
229
|
+
const reasoningEffortField = z.enum(["minimal", "low", "medium", "high"]).optional()
|
|
230
|
+
.describe("Controls depth of deep research reasoning. Higher values produce more thorough analysis.");
|
|
211
231
|
const responseOutputSchema = {
|
|
212
|
-
response: z.string().describe("
|
|
232
|
+
response: z.string().describe("AI-generated text response with numbered citation references"),
|
|
213
233
|
};
|
|
214
234
|
// Input schemas
|
|
215
|
-
const messagesOnlyInputSchema = {
|
|
216
|
-
|
|
235
|
+
const messagesOnlyInputSchema = {
|
|
236
|
+
messages: messagesField,
|
|
237
|
+
search_recency_filter: searchRecencyFilterField,
|
|
238
|
+
search_domain_filter: searchDomainFilterField,
|
|
239
|
+
search_context_size: searchContextSizeField,
|
|
240
|
+
};
|
|
241
|
+
const messagesWithStripThinkingInputSchema = {
|
|
242
|
+
messages: messagesField,
|
|
243
|
+
strip_thinking: stripThinkingField,
|
|
244
|
+
search_recency_filter: searchRecencyFilterField,
|
|
245
|
+
search_domain_filter: searchDomainFilterField,
|
|
246
|
+
search_context_size: searchContextSizeField,
|
|
247
|
+
};
|
|
248
|
+
const researchInputSchema = {
|
|
249
|
+
messages: messagesField,
|
|
250
|
+
strip_thinking: stripThinkingField,
|
|
251
|
+
reasoning_effort: reasoningEffortField,
|
|
252
|
+
};
|
|
217
253
|
server.registerTool("perplexity_ask", {
|
|
218
254
|
title: "Ask Perplexity",
|
|
219
|
-
description: "
|
|
220
|
-
"
|
|
221
|
-
"
|
|
255
|
+
description: "Answer a question using web-grounded AI (Sonar Pro model). " +
|
|
256
|
+
"Best for: quick factual questions, summaries, explanations, and general Q&A. " +
|
|
257
|
+
"Returns a text response with numbered citations. Fastest and cheapest option. " +
|
|
258
|
+
"Supports filtering by recency (hour/day/week/month/year), domain restrictions, and search context size. " +
|
|
259
|
+
"For in-depth multi-source research, use perplexity_research instead. " +
|
|
260
|
+
"For step-by-step reasoning and analysis, use perplexity_reason instead.",
|
|
222
261
|
inputSchema: messagesOnlyInputSchema,
|
|
223
262
|
outputSchema: responseOutputSchema,
|
|
224
263
|
annotations: {
|
|
225
264
|
readOnlyHint: true,
|
|
226
265
|
openWorldHint: true,
|
|
266
|
+
idempotentHint: true,
|
|
227
267
|
},
|
|
228
268
|
}, async (args) => {
|
|
229
|
-
const { messages } = args;
|
|
269
|
+
const { messages, search_recency_filter, search_domain_filter, search_context_size } = args;
|
|
230
270
|
validateMessages(messages, "perplexity_ask");
|
|
231
|
-
const
|
|
271
|
+
const options = {
|
|
272
|
+
...(search_recency_filter && { search_recency_filter }),
|
|
273
|
+
...(search_domain_filter && { search_domain_filter }),
|
|
274
|
+
...(search_context_size && { search_context_size }),
|
|
275
|
+
};
|
|
276
|
+
const result = await performChatCompletion(messages, "sonar-pro", false, serviceOrigin, Object.keys(options).length > 0 ? options : undefined);
|
|
232
277
|
return {
|
|
233
278
|
content: [{ type: "text", text: result }],
|
|
234
279
|
structuredContent: { response: result },
|
|
@@ -236,20 +281,27 @@ export function createPerplexityServer(serviceOrigin) {
|
|
|
236
281
|
});
|
|
237
282
|
server.registerTool("perplexity_research", {
|
|
238
283
|
title: "Deep Research",
|
|
239
|
-
description: "
|
|
240
|
-
"
|
|
241
|
-
"
|
|
242
|
-
|
|
284
|
+
description: "Conduct deep, multi-source research on a topic (Sonar Deep Research model). " +
|
|
285
|
+
"Best for: literature reviews, comprehensive overviews, investigative queries needing " +
|
|
286
|
+
"many sources. Returns a detailed response with numbered citations. " +
|
|
287
|
+
"Significantly slower than other tools (30+ seconds). " +
|
|
288
|
+
"For quick factual questions, use perplexity_ask instead. " +
|
|
289
|
+
"For logical analysis and reasoning, use perplexity_reason instead.",
|
|
290
|
+
inputSchema: researchInputSchema,
|
|
243
291
|
outputSchema: responseOutputSchema,
|
|
244
292
|
annotations: {
|
|
245
293
|
readOnlyHint: true,
|
|
246
294
|
openWorldHint: true,
|
|
295
|
+
idempotentHint: true,
|
|
247
296
|
},
|
|
248
297
|
}, async (args) => {
|
|
249
|
-
const { messages, strip_thinking } = args;
|
|
298
|
+
const { messages, strip_thinking, reasoning_effort } = args;
|
|
250
299
|
validateMessages(messages, "perplexity_research");
|
|
251
300
|
const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false;
|
|
252
|
-
const
|
|
301
|
+
const options = {
|
|
302
|
+
...(reasoning_effort && { reasoning_effort }),
|
|
303
|
+
};
|
|
304
|
+
const result = await performChatCompletion(messages, "sonar-deep-research", stripThinking, serviceOrigin, Object.keys(options).length > 0 ? options : undefined);
|
|
253
305
|
return {
|
|
254
306
|
content: [{ type: "text", text: result }],
|
|
255
307
|
structuredContent: { response: result },
|
|
@@ -257,20 +309,29 @@ export function createPerplexityServer(serviceOrigin) {
|
|
|
257
309
|
});
|
|
258
310
|
server.registerTool("perplexity_reason", {
|
|
259
311
|
title: "Advanced Reasoning",
|
|
260
|
-
description: "
|
|
261
|
-
"
|
|
262
|
-
"
|
|
312
|
+
description: "Analyze a question using step-by-step reasoning with web grounding (Sonar Reasoning Pro model). " +
|
|
313
|
+
"Best for: math, logic, comparisons, complex arguments, and tasks requiring chain-of-thought. " +
|
|
314
|
+
"Returns a reasoned response with numbered citations. " +
|
|
315
|
+
"Supports filtering by recency (hour/day/week/month/year), domain restrictions, and search context size. " +
|
|
316
|
+
"For quick factual questions, use perplexity_ask instead. " +
|
|
317
|
+
"For comprehensive multi-source research, use perplexity_research instead.",
|
|
263
318
|
inputSchema: messagesWithStripThinkingInputSchema,
|
|
264
319
|
outputSchema: responseOutputSchema,
|
|
265
320
|
annotations: {
|
|
266
321
|
readOnlyHint: true,
|
|
267
322
|
openWorldHint: true,
|
|
323
|
+
idempotentHint: true,
|
|
268
324
|
},
|
|
269
325
|
}, async (args) => {
|
|
270
|
-
const { messages, strip_thinking } = args;
|
|
326
|
+
const { messages, strip_thinking, search_recency_filter, search_domain_filter, search_context_size } = args;
|
|
271
327
|
validateMessages(messages, "perplexity_reason");
|
|
272
328
|
const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false;
|
|
273
|
-
const
|
|
329
|
+
const options = {
|
|
330
|
+
...(search_recency_filter && { search_recency_filter }),
|
|
331
|
+
...(search_domain_filter && { search_domain_filter }),
|
|
332
|
+
...(search_context_size && { search_context_size }),
|
|
333
|
+
};
|
|
334
|
+
const result = await performChatCompletion(messages, "sonar-reasoning-pro", stripThinking, serviceOrigin, Object.keys(options).length > 0 ? options : undefined);
|
|
274
335
|
return {
|
|
275
336
|
content: [{ type: "text", text: result }],
|
|
276
337
|
structuredContent: { response: result },
|
|
@@ -286,18 +347,20 @@ export function createPerplexityServer(serviceOrigin) {
|
|
|
286
347
|
.describe("ISO 3166-1 alpha-2 country code for regional results (e.g., 'US', 'GB')"),
|
|
287
348
|
};
|
|
288
349
|
const searchOutputSchema = {
|
|
289
|
-
results: z.string().describe("Formatted search results"),
|
|
350
|
+
results: z.string().describe("Formatted search results, each with title, URL, snippet, and date"),
|
|
290
351
|
};
|
|
291
352
|
server.registerTool("perplexity_search", {
|
|
292
353
|
title: "Search the Web",
|
|
293
|
-
description: "
|
|
294
|
-
"
|
|
295
|
-
"
|
|
354
|
+
description: "Search the web and return a ranked list of results with titles, URLs, snippets, and dates. " +
|
|
355
|
+
"Best for: finding specific URLs, checking recent news, verifying facts, discovering sources. " +
|
|
356
|
+
"Returns formatted results (title, URL, snippet, date) — no AI synthesis. " +
|
|
357
|
+
"For AI-generated answers with citations, use perplexity_ask instead.",
|
|
296
358
|
inputSchema: searchInputSchema,
|
|
297
359
|
outputSchema: searchOutputSchema,
|
|
298
360
|
annotations: {
|
|
299
361
|
readOnlyHint: true,
|
|
300
362
|
openWorldHint: true,
|
|
363
|
+
idempotentHint: true,
|
|
301
364
|
},
|
|
302
365
|
}, async (args) => {
|
|
303
366
|
const { query, max_results, max_tokens_per_page, country } = args;
|
package/package.json
CHANGED