Reorganize community environments - Move lean_proof_env, router_env, and philosophical_rlaif_env.py to environments/community/ - Add comprehensive README for community environments - This organizes community-contributed environments into a dedicated community folder for better maintainability and discoverability
|
|
@ -0,0 +1,21 @@
|
|||
FROM node:22.12-alpine AS builder
|
||||
|
||||
COPY . /app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN --mount=type=cache,target=/root/.npm npm install
|
||||
|
||||
FROM node:22-alpine AS release
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=builder /app/dist /app/dist
|
||||
COPY --from=builder /app/package.json /app/package.json
|
||||
COPY --from=builder /app/package-lock.json /app/package-lock.json
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN npm ci --ignore-scripts --omit-dev
|
||||
|
||||
ENTRYPOINT ["node", "dist/index.js"]
|
||||
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 235 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
|
@ -0,0 +1,310 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||
import {
|
||||
CallToolRequestSchema,
|
||||
ListToolsRequestSchema,
|
||||
Tool,
|
||||
} 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: 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"],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Definition of the Perplexity Research Tool.
|
||||
* This tool performs deep research queries using the Perplexity API.
|
||||
*/
|
||||
const PERPLEXITY_RESEARCH_TOOL: Tool = {
|
||||
name: "perplexity_research",
|
||||
description:
|
||||
"Performs deep research using the Perplexity API. " +
|
||||
"Accepts an array of messages (each with a role and content) " +
|
||||
"and returns a comprehensive research response with citations.",
|
||||
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"],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Definition of the Perplexity Reason Tool.
|
||||
* This tool performs reasoning queries using the Perplexity API.
|
||||
*/
|
||||
const PERPLEXITY_REASON_TOOL: Tool = {
|
||||
name: "perplexity_reason",
|
||||
description:
|
||||
"Performs reasoning tasks using the Perplexity API. " +
|
||||
"Accepts an array of messages (each with a role and content) " +
|
||||
"and returns a well-reasoned response using the sonar-reasoning-pro 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.
|
||||
* @param {string} model - The model to use for the completion.
|
||||
* @returns {Promise<string>} The chat completion result with appended citations.
|
||||
* @throws Will throw an error if the API request fails.
|
||||
*/
|
||||
async function performChatCompletion(
|
||||
messages: Array<{ role: string; content: string }>,
|
||||
model: string = "sonar-pro"
|
||||
): Promise<string> {
|
||||
// Construct the API endpoint URL and request body
|
||||
const url = new URL("https://api.perplexity.ai/chat/completions");
|
||||
const body = {
|
||||
model: model, // Model identifier passed as parameter
|
||||
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 = await 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 = await 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 = await 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: string, index: number) => {
|
||||
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 all available Perplexity tools.
|
||||
*/
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
||||
tools: [PERPLEXITY_ASK_TOOL, PERPLEXITY_RESEARCH_TOOL, PERPLEXITY_REASON_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, async (request) => {
|
||||
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 = await performChatCompletion(messages, "sonar-pro");
|
||||
return {
|
||||
content: [{ type: "text", text: result }],
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
case "perplexity_research": {
|
||||
if (!Array.isArray(args.messages)) {
|
||||
throw new Error("Invalid arguments for perplexity_research: 'messages' must be an array");
|
||||
}
|
||||
// Invoke the chat completion function with the provided messages using the deep research model
|
||||
const messages = args.messages;
|
||||
const result = await performChatCompletion(messages, "sonar-deep-research");
|
||||
return {
|
||||
content: [{ type: "text", text: result }],
|
||||
isError: false,
|
||||
};
|
||||
}
|
||||
case "perplexity_reason": {
|
||||
if (!Array.isArray(args.messages)) {
|
||||
throw new Error("Invalid arguments for perplexity_reason: 'messages' must be an array");
|
||||
}
|
||||
// Invoke the chat completion function with the provided messages using the reasoning model
|
||||
const messages = args.messages;
|
||||
const result = await performChatCompletion(messages, "sonar-reasoning-pro");
|
||||
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.
|
||||
*/
|
||||
async function runServer() {
|
||||
try {
|
||||
const transport = new StdioServerTransport();
|
||||
await server.connect(transport);
|
||||
console.error("Perplexity MCP Server running on stdio with Ask, Research, and Reason tools");
|
||||
} 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);
|
||||
});
|
||||