UNPKG

server-perplexity-ask

Version:

MCP server for Perplexity API integration

206 lines (205 loc) 8.22 kB
#!/usr/bin/env node 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); });