@perplexity-ai/mcp-server 0.3.0 → 0.4.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.
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "perplexity-mcp-server",
3
+ "owner": {
4
+ "name": "Perplexity AI",
5
+ "email": "api@perplexity.ai"
6
+ },
7
+ "metadata": {
8
+ "description": "Official Perplexity AI plugin providing real-time web search, reasoning, and research capabilities",
9
+ "version": "0.4.0"
10
+ },
11
+ "plugins": [
12
+ {
13
+ "name": "perplexity",
14
+ "source": "./",
15
+ "description": "Real-time web search, reasoning, and research through Perplexity's API",
16
+ "version": "0.4.0",
17
+ "author": {
18
+ "name": "Perplexity AI",
19
+ "email": "api@perplexity.ai"
20
+ },
21
+ "homepage": "https://docs.perplexity.ai/guides/mcp-server",
22
+ "repository": "https://github.com/perplexityai/modelcontextprotocol",
23
+ "license": "MIT",
24
+ "keywords": [
25
+ "mcp",
26
+ "search",
27
+ "web-search",
28
+ "perplexity",
29
+ "research",
30
+ "reasoning",
31
+ "ai"
32
+ ],
33
+ "category": "productivity",
34
+ "strict": false,
35
+ "mcpServers": {
36
+ "perplexity": {
37
+ "type": "stdio",
38
+ "command": "npx",
39
+ "args": ["-y", "@perplexity-ai/mcp-server"],
40
+ "env": {
41
+ "PERPLEXITY_API_KEY": "${PERPLEXITY_API_KEY}",
42
+ "PERPLEXITY_TIMEOUT_MS": "${PERPLEXITY_TIMEOUT_MS:-600000}"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ]
48
+ }
49
+
package/README.md CHANGED
@@ -18,6 +18,11 @@ Deep, comprehensive research using the `sonar-deep-research` model. Ideal for th
18
18
  ### **perplexity_reason**
19
19
  Advanced reasoning and problem-solving using the `sonar-reasoning-pro` model. Perfect for complex analytical tasks.
20
20
 
21
+ > [!TIP]
22
+ > Available as an optional parameter for **perplexity_reason** and **perplexity_research**: `strip_thinking`
23
+ >
24
+ > Set to `true` to remove `<think>...</think>` tags from the response, saving context tokens. Default: `false`
25
+
21
26
  ## Configuration
22
27
 
23
28
  ### Get Your API Key
@@ -27,6 +32,25 @@ Advanced reasoning and problem-solving using the `sonar-reasoning-pro` model. Pe
27
32
 
28
33
  ### Claude Code
29
34
 
35
+ #### Option 1: Install via Plugin (Recommended)
36
+
37
+ The easiest way to get started with Perplexity in Claude Code:
38
+
39
+ ```bash
40
+ # Add the Perplexity marketplace
41
+ /plugin marketplace add perplexityai/modelcontextprotocol
42
+
43
+ # Install the plugin
44
+ /plugin install perplexity
45
+ ```
46
+
47
+ Then set your API key:
48
+ ```bash
49
+ export PERPLEXITY_API_KEY="your_key_here"
50
+ ```
51
+
52
+ #### Option 2: Manual Configuration
53
+
30
54
  Run in your terminal:
31
55
 
32
56
  ```bash
@@ -106,12 +130,51 @@ For any MCP-compatible client, use:
106
130
  npx @perplexity-ai/mcp-server
107
131
  ```
108
132
 
133
+ ### Proxy Setup (For Corporate Networks)
134
+
135
+ If you are running this server at work—especially behind a company firewall or proxy—you may need to tell the program how to send its internet traffic through your network's proxy. Follow these steps:
136
+
137
+ **1. Get your proxy details**
138
+
139
+ - Ask your IT department for your HTTP(S) proxy address and port.
140
+ - You may also need a username and password.
141
+
142
+ **2. Set the proxy environment variable**
143
+
144
+ The easiest and most reliable way for Perplexity MCP is to use `PERPLEXITY_PROXY`. For example:
145
+
146
+ ```bash
147
+ export PERPLEXITY_PROXY=http://your-proxy-host:8080
148
+ ```
149
+
150
+ - If your proxy needs a username and password, use:
151
+ ```bash
152
+ export PERPLEXITY_PROXY=http://username:password@your-proxy-host:8080
153
+ ```
154
+
155
+ **3. Alternate: Standard environment variables**
156
+
157
+ If you'd rather use the standard variables, we support `HTTPS_PROXY` and `HTTP_PROXY`.
158
+
159
+ > [!NOTE]
160
+ >The server checks proxy settings in this order: `PERPLEXITY_PROXY` → `HTTPS_PROXY` → `HTTP_PROXY`. If none are set, it connects directly to the internet.
161
+
109
162
  ## Troubleshooting
110
163
 
111
164
  - **API Key Issues**: Ensure `PERPLEXITY_API_KEY` is set correctly
112
165
  - **Connection Errors**: Check your internet connection and API key validity
113
166
  - **Tool Not Found**: Make sure the package is installed and the command path is correct
114
167
  - **Timeout Errors**: For very long research queries, set `PERPLEXITY_TIMEOUT_MS` to a higher value
168
+ - **Proxy Issues**: If you're behind a corporate firewall and experience connection errors, you likely need to set up a proxy:
169
+ - Obtain your proxy server address and port from your IT department.
170
+ - Set the environment variable before running the server, e.g.:
171
+ - `export PERPLEXITY_PROXY=http://proxy-address:port`
172
+ - If authentication is needed: `export PERPLEXITY_PROXY=http://username:password@proxy-address:port`
173
+ - Typical proxy ports include 8080, 3128, or 80.
174
+ - The format for authenticated proxies is:
175
+ `http://username:password@proxy-host:port`
176
+ - Double-check the address, port, and credentials if connections fail or time out.
177
+ - If you continue to have issues, your firewall may be blocking traffic; ask IT if traffic for `api.perplexity.ai` is being restricted.
115
178
 
116
179
  For support, visit [community.perplexity.ai](https://community.perplexity.ai) or [file an issue](https://github.com/perplexityai/modelcontextprotocol/issues).
117
180
 
package/dist/index.js CHANGED
@@ -11,6 +11,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
12
12
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
13
  import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
14
+ import { fetch as undiciFetch, ProxyAgent } from "undici";
14
15
  /**
15
16
  * Definition of the Perplexity Ask Tool.
16
17
  * This tool accepts an array of messages and returns a chat completion response
@@ -76,6 +77,10 @@ const PERPLEXITY_RESEARCH_TOOL = {
76
77
  },
77
78
  description: "Array of conversation messages",
78
79
  },
80
+ strip_thinking: {
81
+ type: "boolean",
82
+ description: "If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false.",
83
+ },
79
84
  },
80
85
  required: ["messages"],
81
86
  },
@@ -110,6 +115,10 @@ const PERPLEXITY_REASON_TOOL = {
110
115
  },
111
116
  description: "Array of conversation messages",
112
117
  },
118
+ strip_thinking: {
119
+ type: "boolean",
120
+ description: "If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false.",
121
+ },
113
122
  },
114
123
  required: ["messages"],
115
124
  },
@@ -155,6 +164,42 @@ if (!PERPLEXITY_API_KEY) {
155
164
  console.error("Error: PERPLEXITY_API_KEY environment variable is required");
156
165
  process.exit(1);
157
166
  }
167
+ /**
168
+ * Gets the proxy URL from environment variables.
169
+ * Checks PERPLEXITY_PROXY, HTTPS_PROXY, HTTP_PROXY in order.
170
+ *
171
+ * @returns {string | undefined} The proxy URL if configured, undefined otherwise
172
+ */
173
+ function getProxyUrl() {
174
+ return process.env.PERPLEXITY_PROXY ||
175
+ process.env.HTTPS_PROXY ||
176
+ process.env.HTTP_PROXY ||
177
+ undefined;
178
+ }
179
+ /**
180
+ * Creates a proxy-aware fetch function.
181
+ * Uses undici with ProxyAgent when a proxy is configured, otherwise uses native fetch.
182
+ *
183
+ * @param {string} url - The URL to fetch
184
+ * @param {RequestInit} options - Fetch options
185
+ * @returns {Promise<Response>} The fetch response
186
+ */
187
+ function proxyAwareFetch(url_1) {
188
+ return __awaiter(this, arguments, void 0, function* (url, options = {}) {
189
+ const proxyUrl = getProxyUrl();
190
+ if (proxyUrl) {
191
+ // Use undici with ProxyAgent when proxy is configured
192
+ const proxyAgent = new ProxyAgent(proxyUrl);
193
+ const response = yield undiciFetch(url, Object.assign(Object.assign({}, options), { dispatcher: proxyAgent }));
194
+ // Cast to native Response type for compatibility
195
+ return response;
196
+ }
197
+ else {
198
+ // Use native fetch when no proxy is configured
199
+ return fetch(url, options);
200
+ }
201
+ });
202
+ }
158
203
  /**
159
204
  * Validates an array of message objects for chat completion tools.
160
205
  * Ensures each message has a valid role and content field.
@@ -180,17 +225,28 @@ function validateMessages(messages, toolName) {
180
225
  }
181
226
  }
182
227
  }
228
+ /**
229
+ * Strips thinking tokens (content within <think>...</think> tags) from the response.
230
+ * This helps reduce context usage when the thinking process is not needed.
231
+ *
232
+ * @param {string} content - The content to process
233
+ * @returns {string} The content with thinking tokens removed
234
+ */
235
+ function stripThinkingTokens(content) {
236
+ return content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
237
+ }
183
238
  /**
184
239
  * Performs a chat completion by sending a request to the Perplexity API.
185
240
  * Appends citations to the returned message content if they exist.
186
241
  *
187
242
  * @param {Array<{ role: string; content: string }>} messages - An array of message objects.
188
243
  * @param {string} model - The model to use for the completion.
244
+ * @param {boolean} stripThinking - If true, removes <think>...</think> tags from the response.
189
245
  * @returns {Promise<string>} The chat completion result with appended citations.
190
246
  * @throws Will throw an error if the API request fails.
191
247
  */
192
248
  export function performChatCompletion(messages_1) {
193
- return __awaiter(this, arguments, void 0, function* (messages, model = "sonar-pro") {
249
+ return __awaiter(this, arguments, void 0, function* (messages, model = "sonar-pro", stripThinking = false) {
194
250
  // Read timeout fresh each time to respect env var changes
195
251
  const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10);
196
252
  // Construct the API endpoint URL and request body
@@ -206,7 +262,7 @@ export function performChatCompletion(messages_1) {
206
262
  const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
207
263
  let response;
208
264
  try {
209
- response = yield fetch(url.toString(), {
265
+ response = yield proxyAwareFetch(url.toString(), {
210
266
  method: "POST",
211
267
  headers: {
212
268
  "Content-Type": "application/json",
@@ -253,6 +309,10 @@ export function performChatCompletion(messages_1) {
253
309
  }
254
310
  // Directly retrieve the main message content from the response
255
311
  let messageContent = firstChoice.message.content;
312
+ // Strip thinking tokens if requested
313
+ if (stripThinking) {
314
+ messageContent = stripThinkingTokens(messageContent);
315
+ }
256
316
  // If citations are provided, append them to the message content
257
317
  if (data.citations && Array.isArray(data.citations) && data.citations.length > 0) {
258
318
  messageContent += "\n\nCitations:\n";
@@ -314,7 +374,7 @@ export function performSearch(query_1) {
314
374
  const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
315
375
  let response;
316
376
  try {
317
- response = yield fetch(url.toString(), {
377
+ response = yield proxyAwareFetch(url.toString(), {
318
378
  method: "POST",
319
379
  headers: {
320
380
  "Content-Type": "application/json",
@@ -397,7 +457,8 @@ server.setRequestHandler(CallToolRequestSchema, (request) => __awaiter(void 0, v
397
457
  case "perplexity_research": {
398
458
  validateMessages(args.messages, "perplexity_research");
399
459
  const messages = args.messages;
400
- const result = yield performChatCompletion(messages, "sonar-deep-research");
460
+ const stripThinking = typeof args.strip_thinking === "boolean" ? args.strip_thinking : false;
461
+ const result = yield performChatCompletion(messages, "sonar-deep-research", stripThinking);
401
462
  return {
402
463
  content: [{ type: "text", text: result }],
403
464
  isError: false,
@@ -406,7 +467,8 @@ server.setRequestHandler(CallToolRequestSchema, (request) => __awaiter(void 0, v
406
467
  case "perplexity_reason": {
407
468
  validateMessages(args.messages, "perplexity_reason");
408
469
  const messages = args.messages;
409
- const result = yield performChatCompletion(messages, "sonar-reasoning-pro");
470
+ const stripThinking = typeof args.strip_thinking === "boolean" ? args.strip_thinking : false;
471
+ const result = yield performChatCompletion(messages, "sonar-reasoning-pro", stripThinking);
410
472
  return {
411
473
  content: [{ type: "text", text: result }],
412
474
  isError: false,
@@ -484,4 +484,83 @@ describe("Perplexity MCP Server", () => {
484
484
  expect(formatted).not.toContain("12345");
485
485
  });
486
486
  });
487
+ describe("strip_thinking parameter", () => {
488
+ it("should strip thinking tokens when true and keep them when false", () => __awaiter(void 0, void 0, void 0, function* () {
489
+ const mockResponse = {
490
+ choices: [
491
+ {
492
+ message: {
493
+ content: "<think>This is my reasoning process</think>\n\nThe answer is 4.",
494
+ },
495
+ },
496
+ ],
497
+ };
498
+ // Test with stripThinking = true
499
+ global.fetch = vi.fn().mockResolvedValue({
500
+ ok: true,
501
+ json: () => __awaiter(void 0, void 0, void 0, function* () { return mockResponse; }),
502
+ });
503
+ const messages = [{ role: "user", content: "What is 2+2?" }];
504
+ const resultStripped = yield performChatCompletion(messages, "sonar-reasoning-pro", true);
505
+ expect(resultStripped).not.toContain("<think>");
506
+ expect(resultStripped).not.toContain("</think>");
507
+ expect(resultStripped).not.toContain("This is my reasoning process");
508
+ expect(resultStripped).toContain("The answer is 4.");
509
+ // Test with stripThinking = false
510
+ global.fetch = vi.fn().mockResolvedValue({
511
+ ok: true,
512
+ json: () => __awaiter(void 0, void 0, void 0, function* () { return mockResponse; }),
513
+ });
514
+ const resultKept = yield performChatCompletion(messages, "sonar-reasoning-pro", false);
515
+ expect(resultKept).toContain("<think>This is my reasoning process</think>");
516
+ expect(resultKept).toContain("The answer is 4.");
517
+ }));
518
+ });
519
+ describe("Proxy Support", () => {
520
+ const originalEnv = process.env;
521
+ beforeEach(() => {
522
+ // Reset environment variables
523
+ process.env = Object.assign({}, originalEnv);
524
+ delete process.env.PERPLEXITY_PROXY;
525
+ delete process.env.HTTPS_PROXY;
526
+ delete process.env.HTTP_PROXY;
527
+ });
528
+ afterEach(() => {
529
+ process.env = originalEnv;
530
+ });
531
+ it("should use native fetch when no proxy is configured", () => __awaiter(void 0, void 0, void 0, function* () {
532
+ const mockResponse = {
533
+ choices: [{ message: { content: "Test response" } }],
534
+ };
535
+ global.fetch = vi.fn().mockResolvedValue({
536
+ ok: true,
537
+ json: () => __awaiter(void 0, void 0, void 0, function* () { return mockResponse; }),
538
+ });
539
+ const messages = [{ role: "user", content: "test" }];
540
+ yield performChatCompletion(messages);
541
+ // Verify native fetch was called (not undici)
542
+ expect(global.fetch).toHaveBeenCalled();
543
+ }));
544
+ it("should read PERPLEXITY_PROXY environment variable", () => {
545
+ process.env.PERPLEXITY_PROXY = "http://proxy.example.com:8080";
546
+ expect(process.env.PERPLEXITY_PROXY).toBe("http://proxy.example.com:8080");
547
+ });
548
+ it("should prioritize PERPLEXITY_PROXY over HTTPS_PROXY", () => {
549
+ process.env.PERPLEXITY_PROXY = "http://perplexity-proxy.example.com:8080";
550
+ process.env.HTTPS_PROXY = "http://https-proxy.example.com:8080";
551
+ // PERPLEXITY_PROXY should take precedence
552
+ expect(process.env.PERPLEXITY_PROXY).toBe("http://perplexity-proxy.example.com:8080");
553
+ });
554
+ it("should fall back to HTTPS_PROXY when PERPLEXITY_PROXY is not set", () => {
555
+ delete process.env.PERPLEXITY_PROXY;
556
+ process.env.HTTPS_PROXY = "http://https-proxy.example.com:8080";
557
+ expect(process.env.HTTPS_PROXY).toBe("http://https-proxy.example.com:8080");
558
+ });
559
+ it("should fall back to HTTP_PROXY when others are not set", () => {
560
+ delete process.env.PERPLEXITY_PROXY;
561
+ delete process.env.HTTPS_PROXY;
562
+ process.env.HTTP_PROXY = "http://http-proxy.example.com:8080";
563
+ expect(process.env.HTTP_PROXY).toBe("http://http-proxy.example.com:8080");
564
+ });
565
+ });
487
566
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perplexity-ai/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.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": [
@@ -29,7 +29,8 @@
29
29
  },
30
30
  "files": [
31
31
  "dist",
32
- "README.md"
32
+ "README.md",
33
+ ".claude-plugin"
33
34
  ],
34
35
  "scripts": {
35
36
  "build": "tsc && shx chmod +x dist/*.js",
@@ -40,8 +41,9 @@
40
41
  "test:coverage": "vitest run --coverage"
41
42
  },
42
43
  "dependencies": {
43
- "@modelcontextprotocol/sdk": "^1.20.2",
44
- "dotenv": "^16.6.1"
44
+ "@modelcontextprotocol/sdk": "^1.21.1",
45
+ "dotenv": "^16.6.1",
46
+ "undici": "^6.20.0"
45
47
  },
46
48
  "devDependencies": {
47
49
  "@types/node": "^20",