@paddle/paddle-mcp
Version:
MCP Server for Paddle Billing
86 lines (85 loc) • 3.81 kB
JavaScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import PaddleAPI from "./api.js";
import tools from "./tools.js";
function isMetricsTool(tool) {
return tool.actions.metrics !== undefined;
}
class PaddleMCPServer extends McpServer {
_paddle;
constructor({ apiKey, environment, toolFilter = { mode: "non-destructive" } }) {
super({
name: "paddle",
version: "0.1.3",
});
this._paddle = new PaddleAPI(apiKey, environment);
// Validate tools
let requestedTools;
if (toolFilter.mode === "specific" && toolFilter.tools) {
requestedTools = new Set(toolFilter.tools);
const availableTools = new Set(tools.map((tool) => tool.method));
const invalidTools = toolFilter.tools.filter((method) => !availableTools.has(method));
if (invalidTools.length > 0) {
throw new Error(`Invalid tool value(s) provided: ${invalidTools.join(", ")}. ` +
`Accepted values are all, read-only, non-destructive, or a comma-separated list of valid tools: ${Array.from(availableTools).sort().join(", ")}`);
}
}
// Register tools
let registeredCount = 0;
const isWriteTool = (tool) => Object.values(tool.actions).some((action) => action.write === true);
const isDestructiveTool = (tool) => Object.values(tool.actions).some((action) => action.delete === true || action.update === true);
tools.forEach((tool) => {
const write = isWriteTool(tool);
const destructive = isDestructiveTool(tool);
let shouldRegister = false;
if (toolFilter.mode === "all") {
shouldRegister = true;
}
else if (toolFilter.mode === "read-only") {
shouldRegister = !write;
}
else if (toolFilter.mode === "non-destructive") {
shouldRegister = !destructive;
}
else if (toolFilter.mode === "specific") {
shouldRegister = requestedTools?.has(tool.method) ?? false;
}
if (!shouldRegister) {
return;
}
if (environment === "sandbox" && isMetricsTool(tool)) {
return;
}
const annotations = {
readOnlyHint: !write,
destructiveHint: write ? destructive : undefined,
};
this.tool(tool.method, tool.description, tool.parameters.shape, annotations, async (arg, _extra) => {
const result = await this._paddle.run(tool.method, arg);
return {
content: [
{
type: "text",
text: String(result),
},
],
};
});
registeredCount++;
});
if (registeredCount === 0) {
const sandboxMetricsHint = environment === "sandbox"
? " Metrics tools are omitted in sandbox because they only work with production data; add other tools to your filter or set the environment to production."
: "";
throw new Error("No tools were registered with the current filter settings. " +
"The value of the --tools parameter must be 'all', 'read-only', 'non-destructive', or a comma-separated list of valid tools." +
sandboxMetricsHint);
}
this.server.oninitialized = () => {
const clientVersion = this.server.getClientVersion();
if (clientVersion) {
this._paddle.setClientInfo(clientVersion);
}
};
}
}
export default PaddleMCPServer;