@perplexity-ai/mcp-server 0.4.0 → 0.5.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 +62 -28
- package/dist/http.js +81 -0
- package/dist/index.js +12 -515
- package/dist/server.js +386 -0
- package/package.json +17 -4
- package/dist/index.test.js +0 -566
- package/dist/vitest.config.js +0 -19
package/dist/server.js
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { fetch as undiciFetch, ProxyAgent } from "undici";
|
|
4
|
+
// Retrieve the Perplexity API key from environment variables
|
|
5
|
+
const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY;
|
|
6
|
+
/**
|
|
7
|
+
* Gets the proxy URL from environment variables.
|
|
8
|
+
* Checks PERPLEXITY_PROXY, HTTPS_PROXY, HTTP_PROXY in order.
|
|
9
|
+
*
|
|
10
|
+
* @returns {string | undefined} The proxy URL if configured, undefined otherwise
|
|
11
|
+
*/
|
|
12
|
+
function getProxyUrl() {
|
|
13
|
+
return process.env.PERPLEXITY_PROXY ||
|
|
14
|
+
process.env.HTTPS_PROXY ||
|
|
15
|
+
process.env.HTTP_PROXY ||
|
|
16
|
+
undefined;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Creates a proxy-aware fetch function.
|
|
20
|
+
* Uses undici with ProxyAgent when a proxy is configured, otherwise uses native fetch.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} url - The URL to fetch
|
|
23
|
+
* @param {RequestInit} options - Fetch options
|
|
24
|
+
* @returns {Promise<Response>} The fetch response
|
|
25
|
+
*/
|
|
26
|
+
async function proxyAwareFetch(url, options = {}) {
|
|
27
|
+
const proxyUrl = getProxyUrl();
|
|
28
|
+
if (proxyUrl) {
|
|
29
|
+
// Use undici with ProxyAgent when proxy is configured
|
|
30
|
+
const proxyAgent = new ProxyAgent(proxyUrl);
|
|
31
|
+
const response = await undiciFetch(url, {
|
|
32
|
+
...options,
|
|
33
|
+
dispatcher: proxyAgent,
|
|
34
|
+
});
|
|
35
|
+
// Cast to native Response type for compatibility
|
|
36
|
+
return response;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Use native fetch when no proxy is configured
|
|
40
|
+
return fetch(url, options);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
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 {any} 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
|
+
function validateMessages(messages, toolName) {
|
|
52
|
+
if (!Array.isArray(messages)) {
|
|
53
|
+
throw new Error(`Invalid arguments for ${toolName}: 'messages' must be an array`);
|
|
54
|
+
}
|
|
55
|
+
for (let i = 0; i < messages.length; i++) {
|
|
56
|
+
const msg = messages[i];
|
|
57
|
+
if (!msg || typeof msg !== 'object') {
|
|
58
|
+
throw new Error(`Invalid message at index ${i}: must be an object`);
|
|
59
|
+
}
|
|
60
|
+
if (!msg.role || typeof msg.role !== 'string') {
|
|
61
|
+
throw new Error(`Invalid message at index ${i}: 'role' must be a string`);
|
|
62
|
+
}
|
|
63
|
+
if (msg.content === undefined || msg.content === null || typeof msg.content !== 'string') {
|
|
64
|
+
throw new Error(`Invalid message at index ${i}: 'content' must be a string`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
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
|
+
function stripThinkingTokens(content) {
|
|
76
|
+
return content.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
|
|
77
|
+
}
|
|
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 {Array<{ role: string; content: string }>} 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) {
|
|
89
|
+
if (!PERPLEXITY_API_KEY) {
|
|
90
|
+
throw new Error("PERPLEXITY_API_KEY environment variable is required");
|
|
91
|
+
}
|
|
92
|
+
// Read timeout fresh each time to respect env var changes
|
|
93
|
+
const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10);
|
|
94
|
+
// Construct the API endpoint URL and request body
|
|
95
|
+
const url = new URL("https://api.perplexity.ai/chat/completions");
|
|
96
|
+
const body = {
|
|
97
|
+
model: model,
|
|
98
|
+
messages: messages,
|
|
99
|
+
};
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
102
|
+
let response;
|
|
103
|
+
try {
|
|
104
|
+
response = await proxyAwareFetch(url.toString(), {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: {
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
"Authorization": `Bearer ${PERPLEXITY_API_KEY}`,
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify(body),
|
|
111
|
+
signal: controller.signal,
|
|
112
|
+
});
|
|
113
|
+
clearTimeout(timeoutId);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
clearTimeout(timeoutId);
|
|
117
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
118
|
+
throw new Error(`Request timeout: Perplexity API did not respond within ${TIMEOUT_MS}ms. Consider increasing PERPLEXITY_TIMEOUT_MS.`);
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`Network error while calling Perplexity API: ${error}`);
|
|
121
|
+
}
|
|
122
|
+
// Check for non-successful HTTP status
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
let errorText;
|
|
125
|
+
try {
|
|
126
|
+
errorText = await response.text();
|
|
127
|
+
}
|
|
128
|
+
catch (parseError) {
|
|
129
|
+
errorText = "Unable to parse error response";
|
|
130
|
+
}
|
|
131
|
+
throw new Error(`Perplexity API error: ${response.status} ${response.statusText}\n${errorText}`);
|
|
132
|
+
}
|
|
133
|
+
// Attempt to parse the JSON response from the API
|
|
134
|
+
let data;
|
|
135
|
+
try {
|
|
136
|
+
data = await response.json();
|
|
137
|
+
}
|
|
138
|
+
catch (jsonError) {
|
|
139
|
+
throw new Error(`Failed to parse JSON response from Perplexity API: ${jsonError}`);
|
|
140
|
+
}
|
|
141
|
+
// Validate response structure
|
|
142
|
+
if (!data.choices || !Array.isArray(data.choices) || data.choices.length === 0) {
|
|
143
|
+
throw new Error("Invalid API response: missing or empty choices array");
|
|
144
|
+
}
|
|
145
|
+
const firstChoice = data.choices[0];
|
|
146
|
+
if (!firstChoice.message || typeof firstChoice.message.content !== 'string') {
|
|
147
|
+
throw new Error("Invalid API response: missing message content");
|
|
148
|
+
}
|
|
149
|
+
// Directly retrieve the main message content from the response
|
|
150
|
+
let messageContent = firstChoice.message.content;
|
|
151
|
+
// Strip thinking tokens if requested
|
|
152
|
+
if (stripThinking) {
|
|
153
|
+
messageContent = stripThinkingTokens(messageContent);
|
|
154
|
+
}
|
|
155
|
+
// If citations are provided, append them to the message content
|
|
156
|
+
if (data.citations && Array.isArray(data.citations) && data.citations.length > 0) {
|
|
157
|
+
messageContent += "\n\nCitations:\n";
|
|
158
|
+
data.citations.forEach((citation, index) => {
|
|
159
|
+
messageContent += `[${index + 1}] ${citation}\n`;
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return messageContent;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Formats search results from the Perplexity Search API into a readable string.
|
|
166
|
+
*
|
|
167
|
+
* @param {any} data - The search response data from the API.
|
|
168
|
+
* @returns {string} Formatted search results.
|
|
169
|
+
*/
|
|
170
|
+
export function formatSearchResults(data) {
|
|
171
|
+
if (!data.results || !Array.isArray(data.results)) {
|
|
172
|
+
return "No search results found.";
|
|
173
|
+
}
|
|
174
|
+
let formattedResults = `Found ${data.results.length} search results:\n\n`;
|
|
175
|
+
data.results.forEach((result, index) => {
|
|
176
|
+
formattedResults += `${index + 1}. **${result.title}**\n`;
|
|
177
|
+
formattedResults += ` URL: ${result.url}\n`;
|
|
178
|
+
if (result.snippet) {
|
|
179
|
+
formattedResults += ` ${result.snippet}\n`;
|
|
180
|
+
}
|
|
181
|
+
if (result.date) {
|
|
182
|
+
formattedResults += ` Date: ${result.date}\n`;
|
|
183
|
+
}
|
|
184
|
+
formattedResults += `\n`;
|
|
185
|
+
});
|
|
186
|
+
return formattedResults;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Performs a web search using the Perplexity Search API.
|
|
190
|
+
*
|
|
191
|
+
* @param {string} query - The search query string.
|
|
192
|
+
* @param {number} maxResults - Maximum number of results to return (1-20).
|
|
193
|
+
* @param {number} maxTokensPerPage - Maximum tokens to extract per webpage.
|
|
194
|
+
* @param {string} country - Optional ISO country code for regional results.
|
|
195
|
+
* @returns {Promise<string>} The formatted search results.
|
|
196
|
+
* @throws Will throw an error if the API request fails.
|
|
197
|
+
*/
|
|
198
|
+
export async function performSearch(query, maxResults = 10, maxTokensPerPage = 1024, country) {
|
|
199
|
+
if (!PERPLEXITY_API_KEY) {
|
|
200
|
+
throw new Error("PERPLEXITY_API_KEY environment variable is required");
|
|
201
|
+
}
|
|
202
|
+
// Read timeout fresh each time to respect env var changes
|
|
203
|
+
const TIMEOUT_MS = parseInt(process.env.PERPLEXITY_TIMEOUT_MS || "300000", 10);
|
|
204
|
+
const url = new URL("https://api.perplexity.ai/search");
|
|
205
|
+
const body = {
|
|
206
|
+
query: query,
|
|
207
|
+
max_results: maxResults,
|
|
208
|
+
max_tokens_per_page: maxTokensPerPage,
|
|
209
|
+
};
|
|
210
|
+
if (country) {
|
|
211
|
+
body.country = country;
|
|
212
|
+
}
|
|
213
|
+
const controller = new AbortController();
|
|
214
|
+
const timeoutId = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
215
|
+
let response;
|
|
216
|
+
try {
|
|
217
|
+
response = await proxyAwareFetch(url.toString(), {
|
|
218
|
+
method: "POST",
|
|
219
|
+
headers: {
|
|
220
|
+
"Content-Type": "application/json",
|
|
221
|
+
"Authorization": `Bearer ${PERPLEXITY_API_KEY}`,
|
|
222
|
+
},
|
|
223
|
+
body: JSON.stringify(body),
|
|
224
|
+
signal: controller.signal,
|
|
225
|
+
});
|
|
226
|
+
clearTimeout(timeoutId);
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
clearTimeout(timeoutId);
|
|
230
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
231
|
+
throw new Error(`Request timeout: Perplexity Search API did not respond within ${TIMEOUT_MS}ms. Consider increasing PERPLEXITY_TIMEOUT_MS.`);
|
|
232
|
+
}
|
|
233
|
+
throw new Error(`Network error while calling Perplexity Search API: ${error}`);
|
|
234
|
+
}
|
|
235
|
+
// Check for non-successful HTTP status
|
|
236
|
+
if (!response.ok) {
|
|
237
|
+
let errorText;
|
|
238
|
+
try {
|
|
239
|
+
errorText = await response.text();
|
|
240
|
+
}
|
|
241
|
+
catch (parseError) {
|
|
242
|
+
errorText = "Unable to parse error response";
|
|
243
|
+
}
|
|
244
|
+
throw new Error(`Perplexity Search API error: ${response.status} ${response.statusText}\n${errorText}`);
|
|
245
|
+
}
|
|
246
|
+
let data;
|
|
247
|
+
try {
|
|
248
|
+
data = await response.json();
|
|
249
|
+
}
|
|
250
|
+
catch (jsonError) {
|
|
251
|
+
throw new Error(`Failed to parse JSON response from Perplexity Search API: ${jsonError}`);
|
|
252
|
+
}
|
|
253
|
+
return formatSearchResults(data);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Creates and configures the Perplexity MCP server with all tools.
|
|
257
|
+
* This factory function is transport-agnostic and returns a configured server instance.
|
|
258
|
+
*
|
|
259
|
+
* @returns The configured MCP server instance
|
|
260
|
+
*/
|
|
261
|
+
export function createPerplexityServer() {
|
|
262
|
+
const server = new McpServer({
|
|
263
|
+
name: "io.github.perplexityai/mcp-server",
|
|
264
|
+
version: "0.5.0",
|
|
265
|
+
});
|
|
266
|
+
// Register perplexity_ask tool
|
|
267
|
+
server.registerTool("perplexity_ask", {
|
|
268
|
+
title: "Ask Perplexity",
|
|
269
|
+
description: "Engages in a conversation using the Sonar API. " +
|
|
270
|
+
"Accepts an array of messages (each with a role and content) " +
|
|
271
|
+
"and returns a chat completion response from the Perplexity model.",
|
|
272
|
+
inputSchema: {
|
|
273
|
+
messages: z.array(z.object({
|
|
274
|
+
role: z.string().describe("Role of the message (e.g., system, user, assistant)"),
|
|
275
|
+
content: z.string().describe("The content of the message"),
|
|
276
|
+
})).describe("Array of conversation messages"),
|
|
277
|
+
},
|
|
278
|
+
outputSchema: {
|
|
279
|
+
response: z.string().describe("The chat completion response"),
|
|
280
|
+
},
|
|
281
|
+
annotations: {
|
|
282
|
+
readOnlyHint: true,
|
|
283
|
+
openWorldHint: true,
|
|
284
|
+
},
|
|
285
|
+
}, async ({ messages }) => {
|
|
286
|
+
validateMessages(messages, "perplexity_ask");
|
|
287
|
+
const result = await performChatCompletion(messages, "sonar-pro");
|
|
288
|
+
return {
|
|
289
|
+
content: [{ type: "text", text: result }],
|
|
290
|
+
structuredContent: { response: result },
|
|
291
|
+
};
|
|
292
|
+
});
|
|
293
|
+
// Register perplexity_research tool
|
|
294
|
+
server.registerTool("perplexity_research", {
|
|
295
|
+
title: "Deep Research",
|
|
296
|
+
description: "Performs deep research using the Perplexity API. " +
|
|
297
|
+
"Accepts an array of messages (each with a role and content) " +
|
|
298
|
+
"and returns a comprehensive research response with citations.",
|
|
299
|
+
inputSchema: {
|
|
300
|
+
messages: z.array(z.object({
|
|
301
|
+
role: z.string().describe("Role of the message (e.g., system, user, assistant)"),
|
|
302
|
+
content: z.string().describe("The content of the message"),
|
|
303
|
+
})).describe("Array of conversation messages"),
|
|
304
|
+
strip_thinking: z.boolean().optional()
|
|
305
|
+
.describe("If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false."),
|
|
306
|
+
},
|
|
307
|
+
outputSchema: {
|
|
308
|
+
response: z.string().describe("The research response"),
|
|
309
|
+
},
|
|
310
|
+
annotations: {
|
|
311
|
+
readOnlyHint: true,
|
|
312
|
+
openWorldHint: true,
|
|
313
|
+
},
|
|
314
|
+
}, async ({ messages, strip_thinking }) => {
|
|
315
|
+
validateMessages(messages, "perplexity_research");
|
|
316
|
+
const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false;
|
|
317
|
+
const result = await performChatCompletion(messages, "sonar-deep-research", stripThinking);
|
|
318
|
+
return {
|
|
319
|
+
content: [{ type: "text", text: result }],
|
|
320
|
+
structuredContent: { response: result },
|
|
321
|
+
};
|
|
322
|
+
});
|
|
323
|
+
// Register perplexity_reason tool
|
|
324
|
+
server.registerTool("perplexity_reason", {
|
|
325
|
+
title: "Advanced Reasoning",
|
|
326
|
+
description: "Performs reasoning tasks using the Perplexity API. " +
|
|
327
|
+
"Accepts an array of messages (each with a role and content) " +
|
|
328
|
+
"and returns a well-reasoned response using the sonar-reasoning-pro model.",
|
|
329
|
+
inputSchema: {
|
|
330
|
+
messages: z.array(z.object({
|
|
331
|
+
role: z.string().describe("Role of the message (e.g., system, user, assistant)"),
|
|
332
|
+
content: z.string().describe("The content of the message"),
|
|
333
|
+
})).describe("Array of conversation messages"),
|
|
334
|
+
strip_thinking: z.boolean().optional()
|
|
335
|
+
.describe("If true, removes <think>...</think> tags and their content from the response to save context tokens. Default is false."),
|
|
336
|
+
},
|
|
337
|
+
outputSchema: {
|
|
338
|
+
response: z.string().describe("The reasoning response"),
|
|
339
|
+
},
|
|
340
|
+
annotations: {
|
|
341
|
+
readOnlyHint: true,
|
|
342
|
+
openWorldHint: true,
|
|
343
|
+
},
|
|
344
|
+
}, async ({ messages, strip_thinking }) => {
|
|
345
|
+
validateMessages(messages, "perplexity_reason");
|
|
346
|
+
const stripThinking = typeof strip_thinking === "boolean" ? strip_thinking : false;
|
|
347
|
+
const result = await performChatCompletion(messages, "sonar-reasoning-pro", stripThinking);
|
|
348
|
+
return {
|
|
349
|
+
content: [{ type: "text", text: result }],
|
|
350
|
+
structuredContent: { response: result },
|
|
351
|
+
};
|
|
352
|
+
});
|
|
353
|
+
// Register perplexity_search tool
|
|
354
|
+
server.registerTool("perplexity_search", {
|
|
355
|
+
title: "Search the Web",
|
|
356
|
+
description: "Performs web search using the Perplexity Search API. " +
|
|
357
|
+
"Returns ranked search results with titles, URLs, snippets, and metadata. " +
|
|
358
|
+
"Perfect for finding up-to-date facts, news, or specific information.",
|
|
359
|
+
inputSchema: {
|
|
360
|
+
query: z.string().describe("Search query string"),
|
|
361
|
+
max_results: z.number().min(1).max(20).optional()
|
|
362
|
+
.describe("Maximum number of results to return (1-20, default: 10)"),
|
|
363
|
+
max_tokens_per_page: z.number().min(256).max(2048).optional()
|
|
364
|
+
.describe("Maximum tokens to extract per webpage (default: 1024)"),
|
|
365
|
+
country: z.string().optional()
|
|
366
|
+
.describe("ISO 3166-1 alpha-2 country code for regional results (e.g., 'US', 'GB')"),
|
|
367
|
+
},
|
|
368
|
+
outputSchema: {
|
|
369
|
+
results: z.string().describe("Formatted search results"),
|
|
370
|
+
},
|
|
371
|
+
annotations: {
|
|
372
|
+
readOnlyHint: true,
|
|
373
|
+
openWorldHint: true,
|
|
374
|
+
},
|
|
375
|
+
}, async ({ query, max_results, max_tokens_per_page, country }) => {
|
|
376
|
+
const maxResults = typeof max_results === "number" ? max_results : 10;
|
|
377
|
+
const maxTokensPerPage = typeof max_tokens_per_page === "number" ? max_tokens_per_page : 1024;
|
|
378
|
+
const countryCode = typeof country === "string" ? country : undefined;
|
|
379
|
+
const result = await performSearch(query, maxResults, maxTokensPerPage, countryCode);
|
|
380
|
+
return {
|
|
381
|
+
content: [{ type: "text", text: result }],
|
|
382
|
+
structuredContent: { results: result },
|
|
383
|
+
};
|
|
384
|
+
});
|
|
385
|
+
return server.server;
|
|
386
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@perplexity-ai/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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": [
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
"perplexity-mcp": "dist/index.js"
|
|
29
29
|
},
|
|
30
30
|
"files": [
|
|
31
|
-
"dist",
|
|
31
|
+
"dist/*.js",
|
|
32
|
+
"!dist/*.test.js",
|
|
33
|
+
"!dist/vitest.config.js",
|
|
32
34
|
"README.md",
|
|
33
35
|
".claude-plugin"
|
|
34
36
|
],
|
|
@@ -36,19 +38,30 @@
|
|
|
36
38
|
"build": "tsc && shx chmod +x dist/*.js",
|
|
37
39
|
"prepare": "npm run build",
|
|
38
40
|
"watch": "tsc --watch",
|
|
41
|
+
"start": "node dist/index.js",
|
|
42
|
+
"start:http": "node dist/http.js",
|
|
43
|
+
"start:http:public": "BIND_ADDRESS=0.0.0.0 ALLOWED_ORIGINS=* node dist/http.js",
|
|
44
|
+
"dev": "tsx src/index.ts",
|
|
45
|
+
"dev:http": "tsx src/http.ts",
|
|
46
|
+
"dev:http:public": "BIND_ADDRESS=0.0.0.0 ALLOWED_ORIGINS=* tsx src/http.ts",
|
|
39
47
|
"test": "vitest run",
|
|
40
48
|
"test:watch": "vitest",
|
|
41
49
|
"test:coverage": "vitest run --coverage"
|
|
42
50
|
},
|
|
43
51
|
"dependencies": {
|
|
44
52
|
"@modelcontextprotocol/sdk": "^1.21.1",
|
|
45
|
-
"
|
|
46
|
-
"
|
|
53
|
+
"cors": "^2.8.5",
|
|
54
|
+
"express": "^4.21.2",
|
|
55
|
+
"undici": "^6.20.0",
|
|
56
|
+
"zod": "^3.25.46"
|
|
47
57
|
},
|
|
48
58
|
"devDependencies": {
|
|
59
|
+
"@types/cors": "^2.8.17",
|
|
60
|
+
"@types/express": "^5.0.0",
|
|
49
61
|
"@types/node": "^20",
|
|
50
62
|
"@vitest/coverage-v8": "^4.0.5",
|
|
51
63
|
"shx": "^0.4.0",
|
|
64
|
+
"tsx": "^4.19.4",
|
|
52
65
|
"typescript": "^5.9.3",
|
|
53
66
|
"vitest": "^4.0.5"
|
|
54
67
|
},
|