server-perplexity-ask
Version:
MCP server for Perplexity API integration
206 lines (205 loc) • 8.22 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
/**
* Definition of the Perplexity Ask Tool.
* This tool accepts an array of messages and returns a chat completion response
* from the Perplexity API, with citations appended to the message if provided.
*/
const PERPLEXITY_ASK_TOOL = {
name: "perplexity_ask",
description: "Engages in a conversation using the Sonar API. " +
"Accepts an array of messages (each with a role and content) " +
"and returns a ask completion response from the Perplexity model.",
inputSchema: {
type: "object",
properties: {
messages: {
type: "array",
items: {
type: "object",
properties: {
role: {
type: "string",
description: "Role of the message (e.g., system, user, assistant)",
},
content: {
type: "string",
description: "The content of the message",
},
},
required: ["role", "content"],
},
description: "Array of conversation messages",
},
},
required: ["messages"],
},
};
// Retrieve the Perplexity API key from environment variables
const PERPLEXITY_API_KEY = process.env.PERPLEXITY_API_KEY;
if (!PERPLEXITY_API_KEY) {
console.error("Error: PERPLEXITY_API_KEY environment variable is required");
process.exit(1);
}
/**
* Performs a chat completion by sending a request to the Perplexity API.
* Appends citations to the returned message content if they exist.
*
* @param {Array<{ role: string; content: string }>} messages - An array of message objects.
* @returns {Promise<string>} The chat completion result with appended citations.
* @throws Will throw an error if the API request fails.
*/
function performChatCompletion(messages) {
return __awaiter(this, void 0, void 0, function* () {
// Construct the API endpoint URL and request body
const url = new URL("https://api.perplexity.ai/chat/completions");
const body = {
model: "sonar-pro", // Model identifier; adjust as needed
messages: messages,
// Additional parameters can be added here if required (e.g., max_tokens, temperature, etc.)
// See the Sonar API documentation for more details:
// https://docs.perplexity.ai/api-reference/chat-completions
};
let response;
try {
response = yield fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${PERPLEXITY_API_KEY}`,
},
body: JSON.stringify(body),
});
}
catch (error) {
throw new Error(`Network error while calling Perplexity API: ${error}`);
}
// Check for non-successful HTTP status
if (!response.ok) {
let errorText;
try {
errorText = yield response.text();
}
catch (parseError) {
errorText = "Unable to parse error response";
}
throw new Error(`Perplexity API error: ${response.status} ${response.statusText}\n${errorText}`);
}
// Attempt to parse the JSON response from the API
let data;
try {
data = yield response.json();
}
catch (jsonError) {
throw new Error(`Failed to parse JSON response from Perplexity API: ${jsonError}`);
}
// Directly retrieve the main message content from the response
let messageContent = data.choices[0].message.content;
// If citations are provided, append them to the message content
if (data.citations && Array.isArray(data.citations) && data.citations.length > 0) {
messageContent += "\n\nCitations:\n";
data.citations.forEach((citation, index) => {
messageContent += `[${index + 1}] ${citation}\n`;
});
}
return messageContent;
});
}
// Initialize the server with tool metadata and capabilities
const server = new Server({
name: "example-servers/perplexity-ask",
version: "0.1.0",
}, {
capabilities: {
tools: {},
},
});
/**
* Registers a handler for listing available tools.
* When the client requests a list of tools, this handler returns the Perplexity Ask Tool.
*/
server.setRequestHandler(ListToolsRequestSchema, () => __awaiter(void 0, void 0, void 0, function* () {
return ({
tools: [PERPLEXITY_ASK_TOOL],
});
}));
/**
* Registers a handler for calling a specific tool.
* Processes requests by validating input and invoking the appropriate tool.
*
* @param {object} request - The incoming tool call request.
* @returns {Promise<object>} The response containing the tool's result or an error.
*/
server.setRequestHandler(CallToolRequestSchema, (request) => __awaiter(void 0, void 0, void 0, function* () {
try {
const { name, arguments: args } = request.params;
if (!args) {
throw new Error("No arguments provided");
}
switch (name) {
case "perplexity_ask": {
if (!Array.isArray(args.messages)) {
throw new Error("Invalid arguments for perplexity-ask: 'messages' must be an array");
}
// Invoke the chat completion function with the provided messages
const messages = args.messages;
const result = yield performChatCompletion(messages);
return {
content: [{ type: "text", text: result }],
isError: false,
};
}
default:
// Respond with an error if an unknown tool is requested
return {
content: [{ type: "text", text: `Unknown tool: ${name}` }],
isError: true,
};
}
}
catch (error) {
// Return error details in the response
return {
content: [
{
type: "text",
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
}));
/**
* Initializes and runs the server using standard I/O for communication.
* Logs an error and exits if the server fails to start.
*/
function runServer() {
return __awaiter(this, void 0, void 0, function* () {
try {
const transport = new StdioServerTransport();
yield server.connect(transport);
console.error("Perplexity Ask MCP Server running on stdio");
}
catch (error) {
console.error("Fatal error running server:", error);
process.exit(1);
}
});
}
// Start the server and catch any startup errors
runServer().catch((error) => {
console.error("Fatal error running server:", error);
process.exit(1);
});