@perplexity-ai/mcp-server 0.5.2 → 0.6.1
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 +42 -100
- package/dist/http.js +2 -16
- package/dist/index.js +1 -7
- package/dist/server.js +65 -138
- package/dist/types.js +0 -3
- 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.6.1"
|
|
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.6.1",
|
|
17
17
|
"author": {
|
|
18
18
|
"name": "Perplexity AI",
|
|
19
19
|
"email": "api@perplexity.ai"
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ The official MCP server implementation for the Perplexity API Platform, providin
|
|
|
13
13
|
### **perplexity_search**
|
|
14
14
|
Direct web search using the Perplexity Search API. Returns ranked search results with metadata, perfect for finding current information.
|
|
15
15
|
|
|
16
|
-
### **perplexity_ask**
|
|
16
|
+
### **perplexity_ask**
|
|
17
17
|
General-purpose conversational AI with real-time web search using the `sonar-pro` model. Great for quick questions and everyday searches.
|
|
18
18
|
|
|
19
19
|
### **perplexity_research**
|
|
@@ -30,61 +30,37 @@ Advanced reasoning and problem-solving using the `sonar-reasoning-pro` model. Pe
|
|
|
30
30
|
## Configuration
|
|
31
31
|
|
|
32
32
|
### Get Your API Key
|
|
33
|
+
|
|
33
34
|
1. Get your Perplexity API Key from the [API Portal](https://www.perplexity.ai/account/api/group)
|
|
34
|
-
2.
|
|
35
|
-
3. (Optional) Set
|
|
36
|
-
4. (Optional) Set log level
|
|
35
|
+
2. Replace `your_key_here` in the configurations below with your API key
|
|
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
38
|
|
|
38
39
|
### Claude Code
|
|
39
40
|
|
|
40
|
-
#### Option 1: Install via Plugin (Recommended)
|
|
41
|
-
|
|
42
|
-
The easiest way to get started with Perplexity in Claude Code, set your API key:
|
|
43
41
|
```bash
|
|
44
|
-
|
|
42
|
+
claude mcp add perplexity --env PERPLEXITY_API_KEY="your_key_here" -- npx -y @perplexity-ai/mcp-server
|
|
45
43
|
```
|
|
46
|
-
|
|
44
|
+
|
|
45
|
+
Or install via plugin:
|
|
47
46
|
```bash
|
|
48
|
-
|
|
47
|
+
export PERPLEXITY_API_KEY="your_key_here"
|
|
49
48
|
claude
|
|
50
|
-
|
|
51
|
-
#
|
|
52
|
-
/plugin marketplace add perplexityai/modelcontextprotocol
|
|
53
|
-
|
|
54
|
-
# Install the plugin
|
|
55
|
-
/plugin install perplexity
|
|
49
|
+
# Then run: /plugin marketplace add perplexityai/modelcontextprotocol
|
|
50
|
+
# Then run: /plugin install perplexity
|
|
56
51
|
```
|
|
57
52
|
|
|
58
|
-
|
|
53
|
+
### Cursor, Claude Desktop & Windsurf
|
|
59
54
|
|
|
60
|
-
|
|
55
|
+
We recommend using the one-click install badge at the top of this README for Cursor.
|
|
61
56
|
|
|
62
|
-
|
|
63
|
-
claude mcp add perplexity --transport stdio --env PERPLEXITY_API_KEY=your_key_here -- npx -y perplexity-mcp
|
|
64
|
-
```
|
|
57
|
+
For manual setup, all these clients use the same `mcpServers` format:
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"type": "stdio",
|
|
72
|
-
"command": "npx",
|
|
73
|
-
"args": [
|
|
74
|
-
"-y",
|
|
75
|
-
"perplexity-mcp"
|
|
76
|
-
],
|
|
77
|
-
"env": {
|
|
78
|
-
"PERPLEXITY_API_KEY": "your_key_here",
|
|
79
|
-
"PERPLEXITY_TIMEOUT_MS": "600000"
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
### Cursor
|
|
86
|
-
|
|
87
|
-
Add to your `mcp.json` (Cursor):
|
|
59
|
+
| Client | Config File |
|
|
60
|
+
|--------|-------------|
|
|
61
|
+
| Cursor | `~/.cursor/mcp.json` |
|
|
62
|
+
| Claude Desktop | `claude_desktop_config.json` |
|
|
63
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` |
|
|
88
64
|
|
|
89
65
|
```json
|
|
90
66
|
{
|
|
@@ -102,63 +78,33 @@ Add to your `mcp.json` (Cursor):
|
|
|
102
78
|
|
|
103
79
|
### VS Code
|
|
104
80
|
|
|
105
|
-
|
|
81
|
+
We recommend using the one-click install badge at the top of this README for VS Code, or for manual setup, add to `.vscode/mcp.json`:
|
|
106
82
|
|
|
107
83
|
```json
|
|
108
84
|
{
|
|
109
|
-
|
|
110
|
-
"perplexity": {
|
|
111
|
-
"type": "stdio",
|
|
112
|
-
"command": "npx",
|
|
113
|
-
"args": [
|
|
114
|
-
"-y",
|
|
115
|
-
"@perplexity-ai/mcp-server"
|
|
116
|
-
],
|
|
117
|
-
"env": {
|
|
118
|
-
"PERPLEXITY_API_KEY": "your_key_here"
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
Or use the one-click install badges at the top of this README.
|
|
126
|
-
|
|
127
|
-
### Codex
|
|
128
|
-
|
|
129
|
-
Run in your terminal:
|
|
130
|
-
|
|
131
|
-
```bash
|
|
132
|
-
codex mcp add perplexity --env PERPLEXITY_API_KEY=your_key_here -- npx -y @perplexity-ai/mcp-server
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Claude Desktop
|
|
136
|
-
|
|
137
|
-
Add to your `claude_desktop_config.json`:
|
|
138
|
-
|
|
139
|
-
```json
|
|
140
|
-
{
|
|
141
|
-
"mcpServers": {
|
|
85
|
+
"servers": {
|
|
142
86
|
"perplexity": {
|
|
87
|
+
"type": "stdio",
|
|
143
88
|
"command": "npx",
|
|
144
89
|
"args": ["-y", "@perplexity-ai/mcp-server"],
|
|
145
90
|
"env": {
|
|
146
|
-
"PERPLEXITY_API_KEY": "your_key_here"
|
|
147
|
-
"PERPLEXITY_TIMEOUT_MS": "600000"
|
|
91
|
+
"PERPLEXITY_API_KEY": "your_key_here"
|
|
148
92
|
}
|
|
149
93
|
}
|
|
150
94
|
}
|
|
151
95
|
}
|
|
152
96
|
```
|
|
153
97
|
|
|
154
|
-
###
|
|
155
|
-
|
|
156
|
-
For any MCP-compatible client, use:
|
|
98
|
+
### Codex
|
|
157
99
|
|
|
158
100
|
```bash
|
|
159
|
-
npx @perplexity-ai/mcp-server
|
|
101
|
+
codex mcp add perplexity --env PERPLEXITY_API_KEY="your_key_here" -- npx -y @perplexity-ai/mcp-server
|
|
160
102
|
```
|
|
161
103
|
|
|
104
|
+
### Other MCP Clients
|
|
105
|
+
|
|
106
|
+
Most clients can be manually configured to use the `mcpServers` wrapper in their configuration file (like Cursor). If your client doesn't work, check its documentation for the correct wrapper format.
|
|
107
|
+
|
|
162
108
|
### Proxy Setup (For Corporate Networks)
|
|
163
109
|
|
|
164
110
|
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:
|
|
@@ -190,39 +136,34 @@ If you'd rather use the standard variables, we support `HTTPS_PROXY` and `HTTP_P
|
|
|
190
136
|
> The server checks proxy settings in this order: `PERPLEXITY_PROXY` → `HTTPS_PROXY` → `HTTP_PROXY`. If none are set, it connects directly to the internet.
|
|
191
137
|
> URLs must include `https://`. Typical ports are `8080`, `3128`, and `80`.
|
|
192
138
|
|
|
193
|
-
|
|
194
139
|
### HTTP Server Deployment
|
|
195
140
|
|
|
196
|
-
For cloud or shared deployments,
|
|
141
|
+
For cloud or shared deployments, run the server in HTTP mode.
|
|
197
142
|
|
|
198
143
|
#### Environment Variables
|
|
199
144
|
|
|
200
|
-
|
|
145
|
+
| Variable | Description | Default |
|
|
146
|
+
|----------|-------------|---------|
|
|
147
|
+
| `PERPLEXITY_API_KEY` | Your Perplexity API key | *Required* |
|
|
148
|
+
| `PORT` | HTTP server port | `8080` |
|
|
149
|
+
| `BIND_ADDRESS` | Network interface to bind to | `0.0.0.0` |
|
|
150
|
+
| `ALLOWED_ORIGINS` | CORS origins (comma-separated) | `*` |
|
|
201
151
|
|
|
202
|
-
|
|
203
|
-
- **`BIND_ADDRESS`** - Network interface to bind to (default: `127.0.0.1` for local, use `0.0.0.0` for hosted)
|
|
204
|
-
- **`ALLOWED_ORIGINS`** - Comma-separated list of allowed CORS origins (default: `http://localhost:3000,http://127.0.0.1:3000`, use `*` for public service)
|
|
205
|
-
- **`PERPLEXITY_API_KEY`** - Your Perplexity API key (required)
|
|
206
|
-
|
|
207
|
-
#### Using Docker
|
|
152
|
+
#### Docker
|
|
208
153
|
|
|
209
154
|
```bash
|
|
210
155
|
docker build -t perplexity-mcp-server .
|
|
211
156
|
docker run -p 8080:8080 -e PERPLEXITY_API_KEY=your_key_here perplexity-mcp-server
|
|
212
157
|
```
|
|
213
158
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
#### Using Node.js Directly
|
|
159
|
+
#### Node.js
|
|
217
160
|
|
|
218
161
|
```bash
|
|
219
|
-
|
|
220
|
-
npm run build
|
|
221
|
-
npm run start:http
|
|
162
|
+
export PERPLEXITY_API_KEY=your_key_here
|
|
163
|
+
npm install && npm run build && npm run start:http
|
|
222
164
|
```
|
|
223
165
|
|
|
224
|
-
|
|
225
|
-
|
|
166
|
+
The server will be accessible at `http://localhost:8080/mcp`
|
|
226
167
|
|
|
227
168
|
## Troubleshooting
|
|
228
169
|
|
|
@@ -231,6 +172,7 @@ Connect your MCP client to: `http://localhost:8080/mcp`
|
|
|
231
172
|
- **Tool Not Found**: Make sure the package is installed and the command path is correct
|
|
232
173
|
- **Timeout Errors**: For very long research queries, set `PERPLEXITY_TIMEOUT_MS` to a higher value
|
|
233
174
|
- **Proxy Issues**: Verify your `PERPLEXITY_PROXY` or `HTTPS_PROXY` setup and ensure `api.perplexity.ai` isn't blocked by your firewall.
|
|
175
|
+
- **EOF / Initialize Errors**: Some strict MCP clients fail because `npx` writes installation messages to stdout. Use `npx -yq` instead of `npx -y` to suppress this output.
|
|
234
176
|
|
|
235
177
|
For support, visit [community.perplexity.ai](https://community.perplexity.ai) or [file an issue](https://github.com/perplexityai/modelcontextprotocol/issues).
|
|
236
178
|
|
package/dist/http.js
CHANGED
|
@@ -4,7 +4,6 @@ import cors from "cors";
|
|
|
4
4
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
5
|
import { createPerplexityServer } from "./server.js";
|
|
6
6
|
import { logger } from "./logger.js";
|
|
7
|
-
// Check for required API key
|
|
8
7
|
const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY;
|
|
9
8
|
if (!PERPLEXITY_API_KEY) {
|
|
10
9
|
logger.error("PERPLEXITY_API_KEY environment variable is required");
|
|
@@ -12,11 +11,8 @@ if (!PERPLEXITY_API_KEY) {
|
|
|
12
11
|
}
|
|
13
12
|
const app = express();
|
|
14
13
|
const PORT = parseInt(process.env.PORT || "8080", 10);
|
|
15
|
-
const BIND_ADDRESS = process.env.BIND_ADDRESS || "
|
|
16
|
-
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(",") || [
|
|
17
|
-
"http://localhost:3000",
|
|
18
|
-
"http://127.0.0.1:3000",
|
|
19
|
-
];
|
|
14
|
+
const BIND_ADDRESS = process.env.BIND_ADDRESS || "0.0.0.0";
|
|
15
|
+
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(",") || ["*"];
|
|
20
16
|
// CORS configuration for browser-based MCP clients
|
|
21
17
|
app.use(cors({
|
|
22
18
|
origin: (origin, callback) => {
|
|
@@ -37,10 +33,6 @@ app.use(cors({
|
|
|
37
33
|
}));
|
|
38
34
|
app.use(express.json());
|
|
39
35
|
const mcpServer = createPerplexityServer();
|
|
40
|
-
/**
|
|
41
|
-
* POST: client-to-server messages (requests, responses, notifications)
|
|
42
|
-
* GET: SSE stream for server-to-client messages (notifications, requests)
|
|
43
|
-
*/
|
|
44
36
|
app.all("/mcp", async (req, res) => {
|
|
45
37
|
try {
|
|
46
38
|
const transport = new StreamableHTTPServerTransport({
|
|
@@ -64,15 +56,9 @@ app.all("/mcp", async (req, res) => {
|
|
|
64
56
|
}
|
|
65
57
|
}
|
|
66
58
|
});
|
|
67
|
-
/**
|
|
68
|
-
* Health check endpoint
|
|
69
|
-
*/
|
|
70
59
|
app.get("/health", (req, res) => {
|
|
71
60
|
res.json({ status: "ok", service: "perplexity-mcp-server" });
|
|
72
61
|
});
|
|
73
|
-
/**
|
|
74
|
-
* Start the HTTP server
|
|
75
|
-
*/
|
|
76
62
|
app.listen(PORT, BIND_ADDRESS, () => {
|
|
77
63
|
logger.info(`Perplexity MCP Server listening on http://${BIND_ADDRESS}:${PORT}/mcp`);
|
|
78
64
|
logger.info(`Allowed origins: ${ALLOWED_ORIGINS.join(", ")}`);
|
package/dist/index.js
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { createPerplexityServer } from "./server.js";
|
|
4
|
-
// Check for required API key
|
|
5
4
|
const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY;
|
|
6
5
|
if (!PERPLEXITY_API_KEY) {
|
|
7
6
|
console.error("Error: PERPLEXITY_API_KEY environment variable is required");
|
|
8
7
|
process.exit(1);
|
|
9
8
|
}
|
|
10
|
-
/**
|
|
11
|
-
* Initializes and runs the server using standard I/O for communication.
|
|
12
|
-
* Logs an error and exits if the server fails to start.
|
|
13
|
-
*/
|
|
14
9
|
async function main() {
|
|
15
10
|
try {
|
|
16
|
-
const server = createPerplexityServer();
|
|
11
|
+
const server = createPerplexityServer("local-mcp");
|
|
17
12
|
const transport = new StdioServerTransport();
|
|
18
13
|
await server.connect(transport);
|
|
19
14
|
}
|
|
@@ -22,7 +17,6 @@ async function main() {
|
|
|
22
17
|
process.exit(1);
|
|
23
18
|
}
|
|
24
19
|
}
|
|
25
|
-
// Start the server and catch any startup errors
|
|
26
20
|
main().catch((error) => {
|
|
27
21
|
console.error("Fatal error running server:", error);
|
|
28
22
|
process.exit(1);
|
package/dist/server.js
CHANGED
|
@@ -2,52 +2,26 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { fetch as undiciFetch, ProxyAgent } from "undici";
|
|
4
4
|
import { ChatCompletionResponseSchema, SearchResponseSchema } from "./validation.js";
|
|
5
|
-
// Retrieve the Perplexity API key from environment variables
|
|
6
5
|
const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY;
|
|
7
|
-
/**
|
|
8
|
-
* Gets the proxy URL from environment variables.
|
|
9
|
-
* Checks PERPLEXITY_PROXY, HTTPS_PROXY, HTTP_PROXY in order.
|
|
10
|
-
*
|
|
11
|
-
* @returns {string | undefined} The proxy URL if configured, undefined otherwise
|
|
12
|
-
*/
|
|
13
6
|
export function getProxyUrl() {
|
|
14
7
|
return process.env.PERPLEXITY_PROXY ||
|
|
15
8
|
process.env.HTTPS_PROXY ||
|
|
16
9
|
process.env.HTTP_PROXY ||
|
|
17
10
|
undefined;
|
|
18
11
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Creates a proxy-aware fetch function.
|
|
21
|
-
* Uses undici with ProxyAgent when a proxy is configured, otherwise uses native fetch.
|
|
22
|
-
*
|
|
23
|
-
* @param {string} url - The URL to fetch
|
|
24
|
-
* @param {RequestInit} options - Fetch options
|
|
25
|
-
* @returns {Promise<Response>} The fetch response
|
|
26
|
-
*/
|
|
27
12
|
export async function proxyAwareFetch(url, options = {}) {
|
|
28
13
|
const proxyUrl = getProxyUrl();
|
|
29
14
|
if (proxyUrl) {
|
|
30
|
-
// Use undici with ProxyAgent when proxy is configured
|
|
31
15
|
const proxyAgent = new ProxyAgent(proxyUrl);
|
|
32
16
|
const undiciOptions = {
|
|
33
17
|
...options,
|
|
34
18
|
dispatcher: proxyAgent,
|
|
35
19
|
};
|
|
36
20
|
const response = await undiciFetch(url, undiciOptions);
|
|
37
|
-
// Cast to native Response type for compatibility
|
|
38
21
|
return response;
|
|
39
22
|
}
|
|
40
|
-
// Use native fetch when no proxy is configured
|
|
41
23
|
return fetch(url, options);
|
|
42
24
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Validates an array of message objects for chat completion tools.
|
|
45
|
-
* Ensures each message has a valid role and content field.
|
|
46
|
-
*
|
|
47
|
-
* @param {unknown} messages - The messages to validate
|
|
48
|
-
* @param {string} toolName - The name of the tool calling this validation (for error messages)
|
|
49
|
-
* @throws {Error} If messages is not an array or if any message is invalid
|
|
50
|
-
*/
|
|
51
25
|
export function validateMessages(messages, toolName) {
|
|
52
26
|
if (!Array.isArray(messages)) {
|
|
53
27
|
throw new Error(`Invalid arguments for ${toolName}: 'messages' must be an array`);
|
|
@@ -65,33 +39,15 @@ export function validateMessages(messages, toolName) {
|
|
|
65
39
|
}
|
|
66
40
|
}
|
|
67
41
|
}
|
|
68
|
-
/**
|
|
69
|
-
* Strips thinking tokens (content within <think>...</think> tags) from the response.
|
|
70
|
-
* This helps reduce context usage when the thinking process is not needed.
|
|
71
|
-
*
|
|
72
|
-
* @param {string} content - The content to process
|
|
73
|
-
* @returns {string} The content with thinking tokens removed
|
|
74
|
-
*/
|
|
75
42
|
export function stripThinkingTokens(content) {
|
|
76
43
|
return content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
77
44
|
}
|
|
78
|
-
|
|
79
|
-
* Performs a chat completion by sending a request to the Perplexity API.
|
|
80
|
-
* Appends citations to the returned message content if they exist.
|
|
81
|
-
*
|
|
82
|
-
* @param {Message[]} messages - An array of message objects.
|
|
83
|
-
* @param {string} model - The model to use for the completion.
|
|
84
|
-
* @param {boolean} stripThinking - If true, removes <think>...</think> tags from the response.
|
|
85
|
-
* @returns {Promise<string>} The chat completion result with appended citations.
|
|
86
|
-
* @throws Will throw an error if the API request fails.
|
|
87
|
-
*/
|
|
88
|
-
export async function performChatCompletion(messages, model = "sonar-pro", stripThinking = false) {
|
|
45
|
+
export async function performChatCompletion(messages, model = "sonar-pro", stripThinking = false, serviceOrigin) {
|
|
89
46
|
if (!PERPLEXITY_API_KEY) {
|
|
90
47
|
throw new Error("PERPLEXITY_API_KEY environment variable is required");
|
|
91
48
|
}
|
|
92
49
|
// Read timeout fresh each time to respect env var changes
|
|
93
50
|
const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10);
|
|
94
|
-
// Construct the API endpoint URL and request body
|
|
95
51
|
const url = new URL("https://api.perplexity.ai/chat/completions");
|
|
96
52
|
const body = {
|
|
97
53
|
model: model,
|
|
@@ -101,12 +57,16 @@ export async function performChatCompletion(messages, model = "sonar-pro", strip
|
|
|
101
57
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
102
58
|
let response;
|
|
103
59
|
try {
|
|
60
|
+
const headers = {
|
|
61
|
+
"Content-Type": "application/json",
|
|
62
|
+
"Authorization": `Bearer ${PERPLEXITY_API_KEY}`,
|
|
63
|
+
};
|
|
64
|
+
if (serviceOrigin) {
|
|
65
|
+
headers["X-Service"] = serviceOrigin;
|
|
66
|
+
}
|
|
104
67
|
response = await proxyAwareFetch(url.toString(), {
|
|
105
68
|
method: "POST",
|
|
106
|
-
headers
|
|
107
|
-
"Content-Type": "application/json",
|
|
108
|
-
"Authorization": `Bearer ${PERPLEXITY_API_KEY}`,
|
|
109
|
-
},
|
|
69
|
+
headers,
|
|
110
70
|
body: JSON.stringify(body),
|
|
111
71
|
signal: controller.signal,
|
|
112
72
|
});
|
|
@@ -119,7 +79,6 @@ export async function performChatCompletion(messages, model = "sonar-pro", strip
|
|
|
119
79
|
}
|
|
120
80
|
throw new Error(`Network error while calling Perplexity API: ${error}`);
|
|
121
81
|
}
|
|
122
|
-
// Check for non-successful HTTP status
|
|
123
82
|
if (!response.ok) {
|
|
124
83
|
let errorText;
|
|
125
84
|
try {
|
|
@@ -148,13 +107,10 @@ export async function performChatCompletion(messages, model = "sonar-pro", strip
|
|
|
148
107
|
throw new Error(`Failed to parse JSON response from Perplexity API: ${error}`);
|
|
149
108
|
}
|
|
150
109
|
const firstChoice = data.choices[0];
|
|
151
|
-
// Directly retrieve the main message content from the response
|
|
152
110
|
let messageContent = firstChoice.message.content;
|
|
153
|
-
// Strip thinking tokens if requested
|
|
154
111
|
if (stripThinking) {
|
|
155
112
|
messageContent = stripThinkingTokens(messageContent);
|
|
156
113
|
}
|
|
157
|
-
// If citations are provided, append them to the message content
|
|
158
114
|
if (data.citations && Array.isArray(data.citations) && data.citations.length > 0) {
|
|
159
115
|
messageContent += "\n\nCitations:\n";
|
|
160
116
|
data.citations.forEach((citation, index) => {
|
|
@@ -163,12 +119,6 @@ export async function performChatCompletion(messages, model = "sonar-pro", strip
|
|
|
163
119
|
}
|
|
164
120
|
return messageContent;
|
|
165
121
|
}
|
|
166
|
-
/**
|
|
167
|
-
* Formats search results from the Perplexity Search API into a readable string.
|
|
168
|
-
*
|
|
169
|
-
* @param {SearchResponse} data - The search response data from the API.
|
|
170
|
-
* @returns {string} Formatted search results.
|
|
171
|
-
*/
|
|
172
122
|
export function formatSearchResults(data) {
|
|
173
123
|
if (!data.results || !Array.isArray(data.results)) {
|
|
174
124
|
return "No search results found.";
|
|
@@ -187,17 +137,7 @@ export function formatSearchResults(data) {
|
|
|
187
137
|
});
|
|
188
138
|
return formattedResults;
|
|
189
139
|
}
|
|
190
|
-
|
|
191
|
-
* Performs a web search using the Perplexity Search API.
|
|
192
|
-
*
|
|
193
|
-
* @param {string} query - The search query string.
|
|
194
|
-
* @param {number} maxResults - Maximum number of results to return (1-20).
|
|
195
|
-
* @param {number} maxTokensPerPage - Maximum tokens to extract per webpage.
|
|
196
|
-
* @param {string} country - Optional ISO country code for regional results.
|
|
197
|
-
* @returns {Promise<string>} The formatted search results.
|
|
198
|
-
* @throws Will throw an error if the API request fails.
|
|
199
|
-
*/
|
|
200
|
-
export async function performSearch(query, maxResults = 10, maxTokensPerPage = 1024, country) {
|
|
140
|
+
export async function performSearch(query, maxResults = 10, maxTokensPerPage = 1024, country, serviceOrigin) {
|
|
201
141
|
if (!PERPLEXITY_API_KEY) {
|
|
202
142
|
throw new Error("PERPLEXITY_API_KEY environment variable is required");
|
|
203
143
|
}
|
|
@@ -214,12 +154,16 @@ export async function performSearch(query, maxResults = 10, maxTokensPerPage = 1
|
|
|
214
154
|
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
215
155
|
let response;
|
|
216
156
|
try {
|
|
157
|
+
const headers = {
|
|
158
|
+
"Content-Type": "application/json",
|
|
159
|
+
"Authorization": `Bearer ${PERPLEXITY_API_KEY}`,
|
|
160
|
+
};
|
|
161
|
+
if (serviceOrigin) {
|
|
162
|
+
headers["X-Service"] = serviceOrigin;
|
|
163
|
+
}
|
|
217
164
|
response = await proxyAwareFetch(url.toString(), {
|
|
218
165
|
method: "POST",
|
|
219
|
-
headers
|
|
220
|
-
"Content-Type": "application/json",
|
|
221
|
-
"Authorization": `Bearer ${PERPLEXITY_API_KEY}`,
|
|
222
|
-
},
|
|
166
|
+
headers,
|
|
223
167
|
body: JSON.stringify(body),
|
|
224
168
|
signal: controller.signal,
|
|
225
169
|
});
|
|
@@ -232,7 +176,6 @@ export async function performSearch(query, maxResults = 10, maxTokensPerPage = 1
|
|
|
232
176
|
}
|
|
233
177
|
throw new Error(`Network error while calling Perplexity Search API: ${error}`);
|
|
234
178
|
}
|
|
235
|
-
// Check for non-successful HTTP status
|
|
236
179
|
if (!response.ok) {
|
|
237
180
|
let errorText;
|
|
238
181
|
try {
|
|
@@ -253,131 +196,115 @@ export async function performSearch(query, maxResults = 10, maxTokensPerPage = 1
|
|
|
253
196
|
}
|
|
254
197
|
return formatSearchResults(data);
|
|
255
198
|
}
|
|
256
|
-
|
|
257
|
-
* Creates and configures the Perplexity MCP server with all tools.
|
|
258
|
-
* This factory function is transport-agnostic and returns a configured server instance.
|
|
259
|
-
*
|
|
260
|
-
* @returns The configured MCP server instance
|
|
261
|
-
*/
|
|
262
|
-
export function createPerplexityServer() {
|
|
199
|
+
export function createPerplexityServer(serviceOrigin) {
|
|
263
200
|
const server = new McpServer({
|
|
264
201
|
name: "io.github.perplexityai/mcp-server",
|
|
265
|
-
version: "0.
|
|
202
|
+
version: "0.6.1",
|
|
266
203
|
});
|
|
267
|
-
|
|
204
|
+
const messageSchema = z.object({
|
|
205
|
+
role: z.string().describe("Role of the message (e.g., system, user, assistant)"),
|
|
206
|
+
content: z.string().describe("The content of the message"),
|
|
207
|
+
});
|
|
208
|
+
const messagesField = z.array(messageSchema).describe("Array of conversation messages");
|
|
209
|
+
const stripThinkingField = z.boolean().optional()
|
|
210
|
+
.describe("If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false.");
|
|
211
|
+
const responseOutputSchema = {
|
|
212
|
+
response: z.string().describe("The response from Perplexity"),
|
|
213
|
+
};
|
|
214
|
+
// Input schemas
|
|
215
|
+
const messagesOnlyInputSchema = { messages: messagesField };
|
|
216
|
+
const messagesWithStripThinkingInputSchema = { messages: messagesField, strip_thinking: stripThinkingField };
|
|
268
217
|
server.registerTool("perplexity_ask", {
|
|
269
218
|
title: "Ask Perplexity",
|
|
270
219
|
description: "Engages in a conversation using the Sonar API. " +
|
|
271
220
|
"Accepts an array of messages (each with a role and content) " +
|
|
272
221
|
"and returns a chat completion response from the Perplexity model.",
|
|
273
|
-
inputSchema:
|
|
274
|
-
|
|
275
|
-
role: z.string().describe("Role of the message (e.g., system, user, assistant)"),
|
|
276
|
-
content: z.string().describe("The content of the message"),
|
|
277
|
-
})).describe("Array of conversation messages"),
|
|
278
|
-
},
|
|
279
|
-
outputSchema: {
|
|
280
|
-
response: z.string().describe("The chat completion response"),
|
|
281
|
-
},
|
|
222
|
+
inputSchema: messagesOnlyInputSchema,
|
|
223
|
+
outputSchema: responseOutputSchema,
|
|
282
224
|
annotations: {
|
|
283
225
|
readOnlyHint: true,
|
|
284
226
|
openWorldHint: true,
|
|
285
227
|
},
|
|
286
|
-
}, async (
|
|
228
|
+
}, async (args) => {
|
|
229
|
+
const { messages } = args;
|
|
287
230
|
validateMessages(messages, "perplexity_ask");
|
|
288
|
-
const result = await performChatCompletion(messages, "sonar-pro");
|
|
231
|
+
const result = await performChatCompletion(messages, "sonar-pro", false, serviceOrigin);
|
|
289
232
|
return {
|
|
290
233
|
content: [{ type: "text", text: result }],
|
|
291
234
|
structuredContent: { response: result },
|
|
292
235
|
};
|
|
293
236
|
});
|
|
294
|
-
// Register perplexity_research tool
|
|
295
237
|
server.registerTool("perplexity_research", {
|
|
296
238
|
title: "Deep Research",
|
|
297
239
|
description: "Performs deep research using the Perplexity API. " +
|
|
298
240
|
"Accepts an array of messages (each with a role and content) " +
|
|
299
241
|
"and returns a comprehensive research response with citations.",
|
|
300
|
-
inputSchema:
|
|
301
|
-
|
|
302
|
-
role: z.string().describe("Role of the message (e.g., system, user, assistant)"),
|
|
303
|
-
content: z.string().describe("The content of the message"),
|
|
304
|
-
})).describe("Array of conversation messages"),
|
|
305
|
-
strip_thinking: z.boolean().optional()
|
|
306
|
-
.describe("If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false."),
|
|
307
|
-
},
|
|
308
|
-
outputSchema: {
|
|
309
|
-
response: z.string().describe("The research response"),
|
|
310
|
-
},
|
|
242
|
+
inputSchema: messagesWithStripThinkingInputSchema,
|
|
243
|
+
outputSchema: responseOutputSchema,
|
|
311
244
|
annotations: {
|
|
312
245
|
readOnlyHint: true,
|
|
313
246
|
openWorldHint: true,
|
|
314
247
|
},
|
|
315
|
-
}, async (
|
|
248
|
+
}, async (args) => {
|
|
249
|
+
const { messages, strip_thinking } = args;
|
|
316
250
|
validateMessages(messages, "perplexity_research");
|
|
317
251
|
const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false;
|
|
318
|
-
const result = await performChatCompletion(messages, "sonar-deep-research", stripThinking);
|
|
252
|
+
const result = await performChatCompletion(messages, "sonar-deep-research", stripThinking, serviceOrigin);
|
|
319
253
|
return {
|
|
320
254
|
content: [{ type: "text", text: result }],
|
|
321
255
|
structuredContent: { response: result },
|
|
322
256
|
};
|
|
323
257
|
});
|
|
324
|
-
// Register perplexity_reason tool
|
|
325
258
|
server.registerTool("perplexity_reason", {
|
|
326
259
|
title: "Advanced Reasoning",
|
|
327
260
|
description: "Performs reasoning tasks using the Perplexity API. " +
|
|
328
261
|
"Accepts an array of messages (each with a role and content) " +
|
|
329
262
|
"and returns a well-reasoned response using the sonar-reasoning-pro model.",
|
|
330
|
-
inputSchema:
|
|
331
|
-
|
|
332
|
-
role: z.string().describe("Role of the message (e.g., system, user, assistant)"),
|
|
333
|
-
content: z.string().describe("The content of the message"),
|
|
334
|
-
})).describe("Array of conversation messages"),
|
|
335
|
-
strip_thinking: z.boolean().optional()
|
|
336
|
-
.describe("If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false."),
|
|
337
|
-
},
|
|
338
|
-
outputSchema: {
|
|
339
|
-
response: z.string().describe("The reasoning response"),
|
|
340
|
-
},
|
|
263
|
+
inputSchema: messagesWithStripThinkingInputSchema,
|
|
264
|
+
outputSchema: responseOutputSchema,
|
|
341
265
|
annotations: {
|
|
342
266
|
readOnlyHint: true,
|
|
343
267
|
openWorldHint: true,
|
|
344
268
|
},
|
|
345
|
-
}, async (
|
|
269
|
+
}, async (args) => {
|
|
270
|
+
const { messages, strip_thinking } = args;
|
|
346
271
|
validateMessages(messages, "perplexity_reason");
|
|
347
272
|
const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false;
|
|
348
|
-
const result = await performChatCompletion(messages, "sonar-reasoning-pro", stripThinking);
|
|
273
|
+
const result = await performChatCompletion(messages, "sonar-reasoning-pro", stripThinking, serviceOrigin);
|
|
349
274
|
return {
|
|
350
275
|
content: [{ type: "text", text: result }],
|
|
351
276
|
structuredContent: { response: result },
|
|
352
277
|
};
|
|
353
278
|
});
|
|
354
|
-
|
|
279
|
+
const searchInputSchema = {
|
|
280
|
+
query: z.string().describe("Search query string"),
|
|
281
|
+
max_results: z.number().min(1).max(20).optional()
|
|
282
|
+
.describe("Maximum number of results to return (1-20, default: 10)"),
|
|
283
|
+
max_tokens_per_page: z.number().min(256).max(2048).optional()
|
|
284
|
+
.describe("Maximum tokens to extract per webpage (default: 1024)"),
|
|
285
|
+
country: z.string().optional()
|
|
286
|
+
.describe("ISO 3166-1 alpha-2 country code for regional results (e.g., 'US', 'GB')"),
|
|
287
|
+
};
|
|
288
|
+
const searchOutputSchema = {
|
|
289
|
+
results: z.string().describe("Formatted search results"),
|
|
290
|
+
};
|
|
355
291
|
server.registerTool("perplexity_search", {
|
|
356
292
|
title: "Search the Web",
|
|
357
293
|
description: "Performs web search using the Perplexity Search API. " +
|
|
358
294
|
"Returns ranked search results with titles, URLs, snippets, and metadata. " +
|
|
359
295
|
"Perfect for finding up-to-date facts, news, or specific information.",
|
|
360
|
-
inputSchema:
|
|
361
|
-
|
|
362
|
-
max_results: z.number().min(1).max(20).optional()
|
|
363
|
-
.describe("Maximum number of results to return (1-20, default: 10)"),
|
|
364
|
-
max_tokens_per_page: z.number().min(256).max(2048).optional()
|
|
365
|
-
.describe("Maximum tokens to extract per webpage (default: 1024)"),
|
|
366
|
-
country: z.string().optional()
|
|
367
|
-
.describe("ISO 3166-1 alpha-2 country code for regional results (e.g., 'US', 'GB')"),
|
|
368
|
-
},
|
|
369
|
-
outputSchema: {
|
|
370
|
-
results: z.string().describe("Formatted search results"),
|
|
371
|
-
},
|
|
296
|
+
inputSchema: searchInputSchema,
|
|
297
|
+
outputSchema: searchOutputSchema,
|
|
372
298
|
annotations: {
|
|
373
299
|
readOnlyHint: true,
|
|
374
300
|
openWorldHint: true,
|
|
375
301
|
},
|
|
376
|
-
}, async (
|
|
302
|
+
}, async (args) => {
|
|
303
|
+
const { query, max_results, max_tokens_per_page, country } = args;
|
|
377
304
|
const maxResults = typeof max_results === "number" ? max_results : 10;
|
|
378
305
|
const maxTokensPerPage = typeof max_tokens_per_page === "number" ? max_tokens_per_page : 1024;
|
|
379
306
|
const countryCode = typeof country === "string" ? country : undefined;
|
|
380
|
-
const result = await performSearch(query, maxResults, maxTokensPerPage, countryCode);
|
|
307
|
+
const result = await performSearch(query, maxResults, maxTokensPerPage, countryCode, serviceOrigin);
|
|
381
308
|
return {
|
|
382
309
|
content: [{ type: "text", text: result }],
|
|
383
310
|
structuredContent: { results: result },
|
package/dist/types.js
CHANGED
package/package.json
CHANGED