@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.
@@ -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.6.1"
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.6.1",
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 log level: `PERPLEXITY_LOG_LEVEL=DEBUG|INFO|WARN|ERROR` (default: ERROR)
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("https://api.perplexity.ai/chat/completions");
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("https://api.perplexity.ai/search");
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.6.1",
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.string().describe("Role of the message (e.g., system, user, assistant)"),
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("The response from Perplexity"),
232
+ response: z.string().describe("AI-generated text response with numbered citation references"),
213
233
  };
214
234
  // Input schemas
215
- const messagesOnlyInputSchema = { messages: messagesField };
216
- const messagesWithStripThinkingInputSchema = { messages: messagesField, strip_thinking: stripThinkingField };
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: "Engages in a conversation using the Sonar API. " +
220
- "Accepts an array of messages (each with a role and content) " +
221
- "and returns a chat completion response from the Perplexity model.",
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 result = await performChatCompletion(messages, "sonar-pro", false, serviceOrigin);
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: "Performs deep research using the Perplexity API. " +
240
- "Accepts an array of messages (each with a role and content) " +
241
- "and returns a comprehensive research response with citations.",
242
- inputSchema: messagesWithStripThinkingInputSchema,
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 result = await performChatCompletion(messages, "sonar-deep-research", stripThinking, serviceOrigin);
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: "Performs reasoning tasks using the Perplexity API. " +
261
- "Accepts an array of messages (each with a role and content) " +
262
- "and returns a well-reasoned response using the sonar-reasoning-pro model.",
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 result = await performChatCompletion(messages, "sonar-reasoning-pro", stripThinking, serviceOrigin);
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: "Performs web search using the Perplexity Search API. " +
294
- "Returns ranked search results with titles, URLs, snippets, and metadata. " +
295
- "Perfect for finding up-to-date facts, news, or specific information.",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perplexity-ai/mcp-server",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "mcpName": "io.github.perplexityai/mcp-server",
5
5
  "description": "Real-time web search, reasoning, and research through Perplexity's API",
6
6
  "keywords": [