@perplexity-ai/mcp-server 0.6.2 → 0.8.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/dist/server.js +164 -28
- package/package.json +2 -2
|
@@ -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.8.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.8.0",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Perplexity AI",
|
|
19
19
|
"email": "api@perplexity.ai"
|
package/dist/server.js
CHANGED
|
@@ -43,16 +43,89 @@ export function validateMessages(messages, toolName) {
|
|
|
43
43
|
export function stripThinkingTokens(content) {
|
|
44
44
|
return content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
45
45
|
}
|
|
46
|
-
export async function
|
|
46
|
+
export async function consumeSSEStream(response) {
|
|
47
|
+
const body = response.body;
|
|
48
|
+
if (!body) {
|
|
49
|
+
throw new Error("Response body is null");
|
|
50
|
+
}
|
|
51
|
+
const reader = body.getReader();
|
|
52
|
+
const decoder = new TextDecoder();
|
|
53
|
+
let contentParts = [];
|
|
54
|
+
let citations;
|
|
55
|
+
let usage;
|
|
56
|
+
let id;
|
|
57
|
+
let model;
|
|
58
|
+
let created;
|
|
59
|
+
let buffer = "";
|
|
60
|
+
while (true) {
|
|
61
|
+
const { done, value } = await reader.read();
|
|
62
|
+
if (done)
|
|
63
|
+
break;
|
|
64
|
+
buffer += decoder.decode(value, { stream: true });
|
|
65
|
+
const lines = buffer.split("\n");
|
|
66
|
+
// Keep the last potentially incomplete line in the buffer
|
|
67
|
+
buffer = lines.pop() || "";
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
const trimmed = line.trim();
|
|
70
|
+
if (!trimmed || !trimmed.startsWith("data:"))
|
|
71
|
+
continue;
|
|
72
|
+
const data = trimmed.slice("data:".length).trim();
|
|
73
|
+
if (data === "[DONE]")
|
|
74
|
+
continue;
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(data);
|
|
77
|
+
if (parsed.id)
|
|
78
|
+
id = parsed.id;
|
|
79
|
+
if (parsed.model)
|
|
80
|
+
model = parsed.model;
|
|
81
|
+
if (parsed.created)
|
|
82
|
+
created = parsed.created;
|
|
83
|
+
if (parsed.citations)
|
|
84
|
+
citations = parsed.citations;
|
|
85
|
+
if (parsed.usage)
|
|
86
|
+
usage = parsed.usage;
|
|
87
|
+
const delta = parsed.choices?.[0]?.delta;
|
|
88
|
+
if (delta?.content) {
|
|
89
|
+
contentParts.push(delta.content);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Skip malformed JSON chunks (e.g. keep-alive pings)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const assembled = {
|
|
98
|
+
choices: [
|
|
99
|
+
{
|
|
100
|
+
message: { content: contentParts.join("") },
|
|
101
|
+
finish_reason: "stop",
|
|
102
|
+
index: 0,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
...(citations && { citations }),
|
|
106
|
+
...(usage && { usage }),
|
|
107
|
+
...(id && { id }),
|
|
108
|
+
...(model && { model }),
|
|
109
|
+
...(created && { created }),
|
|
110
|
+
};
|
|
111
|
+
return ChatCompletionResponseSchema.parse(assembled);
|
|
112
|
+
}
|
|
113
|
+
export async function performChatCompletion(messages, model = "sonar-pro", stripThinking = false, serviceOrigin, options) {
|
|
47
114
|
if (!PERPLEXITY_API_KEY) {
|
|
48
115
|
throw new Error("PERPLEXITY_API_KEY environment variable is required");
|
|
49
116
|
}
|
|
50
117
|
// Read timeout fresh each time to respect env var changes
|
|
51
118
|
const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10);
|
|
119
|
+
const useStreaming = model === "sonar-deep-research";
|
|
52
120
|
const url = new URL(`${PERPLEXITY_BASE_URL}/chat/completions`);
|
|
53
121
|
const body = {
|
|
54
122
|
model: model,
|
|
55
123
|
messages: messages,
|
|
124
|
+
...(useStreaming && { stream: true }),
|
|
125
|
+
...(options?.search_recency_filter && { search_recency_filter: options.search_recency_filter }),
|
|
126
|
+
...(options?.search_domain_filter && { search_domain_filter: options.search_domain_filter }),
|
|
127
|
+
...(options?.search_context_size && { web_search_options: { search_context_size: options.search_context_size } }),
|
|
128
|
+
...(options?.reasoning_effort && { reasoning_effort: options.reasoning_effort }),
|
|
56
129
|
};
|
|
57
130
|
const controller = new AbortController();
|
|
58
131
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
@@ -92,8 +165,13 @@ export async function performChatCompletion(messages, model = "sonar-pro", strip
|
|
|
92
165
|
}
|
|
93
166
|
let data;
|
|
94
167
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
168
|
+
if (useStreaming) {
|
|
169
|
+
data = await consumeSSEStream(response);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const json = await response.json();
|
|
173
|
+
data = ChatCompletionResponseSchema.parse(json);
|
|
174
|
+
}
|
|
97
175
|
}
|
|
98
176
|
catch (error) {
|
|
99
177
|
if (error instanceof z.ZodError) {
|
|
@@ -200,36 +278,76 @@ export async function performSearch(query, maxResults = 10, maxTokensPerPage = 1
|
|
|
200
278
|
export function createPerplexityServer(serviceOrigin) {
|
|
201
279
|
const server = new McpServer({
|
|
202
280
|
name: "io.github.perplexityai/mcp-server",
|
|
203
|
-
version: "0.
|
|
281
|
+
version: "0.8.0",
|
|
282
|
+
}, {
|
|
283
|
+
instructions: "Perplexity AI server for web-grounded search, research, and reasoning. " +
|
|
284
|
+
"Use perplexity_search for finding URLs, facts, and recent news. " +
|
|
285
|
+
"Use perplexity_ask for quick AI-answered questions with citations. Supports recency filters, domain restrictions, and search context size control. " +
|
|
286
|
+
"Use perplexity_research for in-depth multi-source investigation (slow, 30s+). Supports reasoning_effort parameter to control depth. " +
|
|
287
|
+
"Use perplexity_reason for complex analysis requiring step-by-step logic. Supports recency filters, domain restrictions, and search context size control. " +
|
|
288
|
+
"All tools are read-only and access live web data.",
|
|
204
289
|
});
|
|
205
290
|
const messageSchema = z.object({
|
|
206
|
-
role: z.
|
|
291
|
+
role: z.enum(["system", "user", "assistant"]).describe("Role of the message sender"),
|
|
207
292
|
content: z.string().describe("The content of the message"),
|
|
208
293
|
});
|
|
209
294
|
const messagesField = z.array(messageSchema).describe("Array of conversation messages");
|
|
210
295
|
const stripThinkingField = z.boolean().optional()
|
|
211
296
|
.describe("If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false.");
|
|
297
|
+
const searchRecencyFilterField = z.enum(["hour", "day", "week", "month", "year"]).optional()
|
|
298
|
+
.describe("Filter search results by recency. Use 'hour' for very recent news, 'day' for today's updates, 'week' for this week, etc.");
|
|
299
|
+
const searchDomainFilterField = z.array(z.string()).optional()
|
|
300
|
+
.describe("Restrict search results to specific domains (e.g., ['wikipedia.org', 'arxiv.org']). Use '-' prefix for exclusion (e.g., ['-reddit.com']).");
|
|
301
|
+
const searchContextSizeField = z.enum(["low", "medium", "high"]).optional()
|
|
302
|
+
.describe("Controls how much web context is retrieved. 'low' (default) is fastest, 'high' provides more comprehensive results.");
|
|
303
|
+
const reasoningEffortField = z.enum(["minimal", "low", "medium", "high"]).optional()
|
|
304
|
+
.describe("Controls depth of deep research reasoning. Higher values produce more thorough analysis.");
|
|
212
305
|
const responseOutputSchema = {
|
|
213
|
-
response: z.string().describe("
|
|
306
|
+
response: z.string().describe("AI-generated text response with numbered citation references"),
|
|
214
307
|
};
|
|
215
308
|
// Input schemas
|
|
216
|
-
const messagesOnlyInputSchema = {
|
|
217
|
-
|
|
309
|
+
const messagesOnlyInputSchema = {
|
|
310
|
+
messages: messagesField,
|
|
311
|
+
search_recency_filter: searchRecencyFilterField,
|
|
312
|
+
search_domain_filter: searchDomainFilterField,
|
|
313
|
+
search_context_size: searchContextSizeField,
|
|
314
|
+
};
|
|
315
|
+
const messagesWithStripThinkingInputSchema = {
|
|
316
|
+
messages: messagesField,
|
|
317
|
+
strip_thinking: stripThinkingField,
|
|
318
|
+
search_recency_filter: searchRecencyFilterField,
|
|
319
|
+
search_domain_filter: searchDomainFilterField,
|
|
320
|
+
search_context_size: searchContextSizeField,
|
|
321
|
+
};
|
|
322
|
+
const researchInputSchema = {
|
|
323
|
+
messages: messagesField,
|
|
324
|
+
strip_thinking: stripThinkingField,
|
|
325
|
+
reasoning_effort: reasoningEffortField,
|
|
326
|
+
};
|
|
218
327
|
server.registerTool("perplexity_ask", {
|
|
219
328
|
title: "Ask Perplexity",
|
|
220
|
-
description: "
|
|
221
|
-
"
|
|
222
|
-
"
|
|
329
|
+
description: "Answer a question using web-grounded AI (Sonar Pro model). " +
|
|
330
|
+
"Best for: quick factual questions, summaries, explanations, and general Q&A. " +
|
|
331
|
+
"Returns a text response with numbered citations. Fastest and cheapest option. " +
|
|
332
|
+
"Supports filtering by recency (hour/day/week/month/year), domain restrictions, and search context size. " +
|
|
333
|
+
"For in-depth multi-source research, use perplexity_research instead. " +
|
|
334
|
+
"For step-by-step reasoning and analysis, use perplexity_reason instead.",
|
|
223
335
|
inputSchema: messagesOnlyInputSchema,
|
|
224
336
|
outputSchema: responseOutputSchema,
|
|
225
337
|
annotations: {
|
|
226
338
|
readOnlyHint: true,
|
|
227
339
|
openWorldHint: true,
|
|
340
|
+
idempotentHint: true,
|
|
228
341
|
},
|
|
229
342
|
}, async (args) => {
|
|
230
|
-
const { messages } = args;
|
|
343
|
+
const { messages, search_recency_filter, search_domain_filter, search_context_size } = args;
|
|
231
344
|
validateMessages(messages, "perplexity_ask");
|
|
232
|
-
const
|
|
345
|
+
const options = {
|
|
346
|
+
...(search_recency_filter && { search_recency_filter }),
|
|
347
|
+
...(search_domain_filter && { search_domain_filter }),
|
|
348
|
+
...(search_context_size && { search_context_size }),
|
|
349
|
+
};
|
|
350
|
+
const result = await performChatCompletion(messages, "sonar-pro", false, serviceOrigin, Object.keys(options).length > 0 ? options : undefined);
|
|
233
351
|
return {
|
|
234
352
|
content: [{ type: "text", text: result }],
|
|
235
353
|
structuredContent: { response: result },
|
|
@@ -237,20 +355,27 @@ export function createPerplexityServer(serviceOrigin) {
|
|
|
237
355
|
});
|
|
238
356
|
server.registerTool("perplexity_research", {
|
|
239
357
|
title: "Deep Research",
|
|
240
|
-
description: "
|
|
241
|
-
"
|
|
242
|
-
"
|
|
243
|
-
|
|
358
|
+
description: "Conduct deep, multi-source research on a topic (Sonar Deep Research model). " +
|
|
359
|
+
"Best for: literature reviews, comprehensive overviews, investigative queries needing " +
|
|
360
|
+
"many sources. Returns a detailed response with numbered citations. " +
|
|
361
|
+
"Significantly slower than other tools (30+ seconds). " +
|
|
362
|
+
"For quick factual questions, use perplexity_ask instead. " +
|
|
363
|
+
"For logical analysis and reasoning, use perplexity_reason instead.",
|
|
364
|
+
inputSchema: researchInputSchema,
|
|
244
365
|
outputSchema: responseOutputSchema,
|
|
245
366
|
annotations: {
|
|
246
367
|
readOnlyHint: true,
|
|
247
368
|
openWorldHint: true,
|
|
369
|
+
idempotentHint: true,
|
|
248
370
|
},
|
|
249
371
|
}, async (args) => {
|
|
250
|
-
const { messages, strip_thinking } = args;
|
|
372
|
+
const { messages, strip_thinking, reasoning_effort } = args;
|
|
251
373
|
validateMessages(messages, "perplexity_research");
|
|
252
374
|
const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false;
|
|
253
|
-
const
|
|
375
|
+
const options = {
|
|
376
|
+
...(reasoning_effort && { reasoning_effort }),
|
|
377
|
+
};
|
|
378
|
+
const result = await performChatCompletion(messages, "sonar-deep-research", stripThinking, serviceOrigin, Object.keys(options).length > 0 ? options : undefined);
|
|
254
379
|
return {
|
|
255
380
|
content: [{ type: "text", text: result }],
|
|
256
381
|
structuredContent: { response: result },
|
|
@@ -258,20 +383,29 @@ export function createPerplexityServer(serviceOrigin) {
|
|
|
258
383
|
});
|
|
259
384
|
server.registerTool("perplexity_reason", {
|
|
260
385
|
title: "Advanced Reasoning",
|
|
261
|
-
description: "
|
|
262
|
-
"
|
|
263
|
-
"
|
|
386
|
+
description: "Analyze a question using step-by-step reasoning with web grounding (Sonar Reasoning Pro model). " +
|
|
387
|
+
"Best for: math, logic, comparisons, complex arguments, and tasks requiring chain-of-thought. " +
|
|
388
|
+
"Returns a reasoned response with numbered citations. " +
|
|
389
|
+
"Supports filtering by recency (hour/day/week/month/year), domain restrictions, and search context size. " +
|
|
390
|
+
"For quick factual questions, use perplexity_ask instead. " +
|
|
391
|
+
"For comprehensive multi-source research, use perplexity_research instead.",
|
|
264
392
|
inputSchema: messagesWithStripThinkingInputSchema,
|
|
265
393
|
outputSchema: responseOutputSchema,
|
|
266
394
|
annotations: {
|
|
267
395
|
readOnlyHint: true,
|
|
268
396
|
openWorldHint: true,
|
|
397
|
+
idempotentHint: true,
|
|
269
398
|
},
|
|
270
399
|
}, async (args) => {
|
|
271
|
-
const { messages, strip_thinking } = args;
|
|
400
|
+
const { messages, strip_thinking, search_recency_filter, search_domain_filter, search_context_size } = args;
|
|
272
401
|
validateMessages(messages, "perplexity_reason");
|
|
273
402
|
const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false;
|
|
274
|
-
const
|
|
403
|
+
const options = {
|
|
404
|
+
...(search_recency_filter && { search_recency_filter }),
|
|
405
|
+
...(search_domain_filter && { search_domain_filter }),
|
|
406
|
+
...(search_context_size && { search_context_size }),
|
|
407
|
+
};
|
|
408
|
+
const result = await performChatCompletion(messages, "sonar-reasoning-pro", stripThinking, serviceOrigin, Object.keys(options).length > 0 ? options : undefined);
|
|
275
409
|
return {
|
|
276
410
|
content: [{ type: "text", text: result }],
|
|
277
411
|
structuredContent: { response: result },
|
|
@@ -287,18 +421,20 @@ export function createPerplexityServer(serviceOrigin) {
|
|
|
287
421
|
.describe("ISO 3166-1 alpha-2 country code for regional results (e.g., 'US', 'GB')"),
|
|
288
422
|
};
|
|
289
423
|
const searchOutputSchema = {
|
|
290
|
-
results: z.string().describe("Formatted search results"),
|
|
424
|
+
results: z.string().describe("Formatted search results, each with title, URL, snippet, and date"),
|
|
291
425
|
};
|
|
292
426
|
server.registerTool("perplexity_search", {
|
|
293
427
|
title: "Search the Web",
|
|
294
|
-
description: "
|
|
295
|
-
"
|
|
296
|
-
"
|
|
428
|
+
description: "Search the web and return a ranked list of results with titles, URLs, snippets, and dates. " +
|
|
429
|
+
"Best for: finding specific URLs, checking recent news, verifying facts, discovering sources. " +
|
|
430
|
+
"Returns formatted results (title, URL, snippet, date) — no AI synthesis. " +
|
|
431
|
+
"For AI-generated answers with citations, use perplexity_ask instead.",
|
|
297
432
|
inputSchema: searchInputSchema,
|
|
298
433
|
outputSchema: searchOutputSchema,
|
|
299
434
|
annotations: {
|
|
300
435
|
readOnlyHint: true,
|
|
301
436
|
openWorldHint: true,
|
|
437
|
+
idempotentHint: true,
|
|
302
438
|
},
|
|
303
439
|
}, async (args) => {
|
|
304
440
|
const { query, max_results, max_tokens_per_page, country } = args;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perplexity-ai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"mcpName": "
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"mcpName": "ai.perplexityai/mcp-server",
|
|
5
5
|
"description": "Real-time web search, reasoning, and research through Perplexity's API",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"ai",
|