Paca Plugin SDK for MCP (Model Context Protocol) tools.
Use this package to add AI-callable tools to your Paca plugin. When installed, those tools are automatically exposed through the Paca MCP server to any connected AI client (Claude, GitHub Copilot, Cursor, etc.).
Paca's MCP server loads plugin MCP modules at startup. Each plugin can ship a small Node.js-compatible ESM module (mcp.js) that declares its tools and handles tool calls. The server fetches the list of enabled plugins from GET /api/v1/plugins, checks whether each plugin's manifest declares an mcp.remoteEntryUrl, and dynamically imports those modules.
npm install @paca-ai/plugin-sdk-mcp
# or
bun add @paca-ai/plugin-sdk-mcp@modelcontextprotocol/sdk must also be available as a peer dependency.
{
"id": "com.example.my-plugin",
"displayName": "My Plugin",
"version": "0.1.0",
"mcp": {
"remoteEntryUrl": "https://cdn.example.com/my-plugin/0.1.0/mcp.js"
}
}Create frontend/src/mcp.ts (or wherever your build system outputs it):
import type { PluginMCPEntry } from "@paca-ai/plugin-sdk-mcp";
import { PluginAPIClient, textResult, errorResult } from "@paca-ai/plugin-sdk-mcp";
const PLUGIN_ID = "com.example.my-plugin";
const entry: PluginMCPEntry = {
tools: [
{
name: "my_plugin_list_items",
description: "List items for a task in My Plugin.",
inputSchema: {
type: "object",
properties: {
project_id: { type: "string", description: "Project ID" },
task_id: { type: "string", description: "Task ID" },
},
required: ["project_id", "task_id"],
},
},
{
name: "my_plugin_create_item",
description: "Create a new item in My Plugin for a task.",
inputSchema: {
type: "object",
properties: {
project_id: { type: "string", description: "Project ID" },
task_id: { type: "string", description: "Task ID" },
title: { type: "string", description: "Item title" },
},
required: ["project_id", "task_id", "title"],
},
},
],
async handleToolCall(name, args, context) {
const api = new PluginAPIClient(context);
const { project_id, task_id } = args as { project_id: string; task_id: string };
try {
if (name === "my_plugin_list_items") {
const items = await api.pluginGet<Item[]>(
`projects/${project_id}/tasks/${task_id}/items`,
);
return textResult(JSON.stringify(items, null, 2));
}
if (name === "my_plugin_create_item") {
const { title } = args as { title: string };
const item = await api.pluginPost<Item>(
`projects/${project_id}/tasks/${task_id}/items`,
{ title },
);
return textResult(JSON.stringify(item, null, 2));
}
return errorResult(`Unknown tool: ${name}`);
} catch (err) {
return errorResult(err instanceof Error ? err.message : String(err));
}
},
};
export default entry;
interface Item {
id: string;
task_id: string;
title: string;
}Configure your build tool to output a Node.js-compatible ESM bundle. With Vite:
// vite.mcp.config.ts
import { defineConfig } from "vite";
export default defineConfig({
build: {
lib: {
entry: "./src/mcp.ts",
formats: ["es"],
fileName: () => "mcp.js",
},
outDir: "dist/mcp",
rollupOptions: {
// Do not bundle the SDK peer dependency
external: ["@paca-ai/plugin-sdk-mcp"],
},
},
});Then deploy dist/mcp/mcp.js to your CDN and set mcp.remoteEntryUrl in your manifest.
The interface your default export must implement.
interface PluginMCPEntry {
tools: Tool[];
handleToolCall(
name: string,
args: Record<string, unknown>,
context: PluginMCPContext,
): Promise<PluginToolResult>;
}Runtime context injected by the host into every handleToolCall call.
interface PluginMCPContext {
pluginId: string; // e.g. "com.example.my-plugin"
baseURL: string; // e.g. "http://localhost:8080"
apiKey: string; // Paca API key for authentication
}Scoped HTTP client. Construct one per tool call using the injected context.
class PluginAPIClient {
constructor(context: PluginMCPContext)
// Plugin route helpers (prefix: /api/v1/plugins/{pluginId}/)
pluginGet<T>(path: string): Promise<T>
pluginPost<T>(path: string, body: unknown): Promise<T>
pluginPatch<T>(path: string, body: unknown): Promise<T>
pluginDelete(path: string): Promise<void>
// Core Paca API (prefix: /api/v1/)
coreGet<T>(path: string): Promise<T>
}Convenience helpers for building tool results.
function textResult(text: string): PluginToolResult
function errorResult(message: string): PluginToolResult- Tool names must be unique across all enabled plugins. Prefix tool names with a short identifier derived from your plugin ID to avoid collisions. For example, a plugin
com.example.checklistshould usechecklist_list_items,checklist_create_item, etc. - Tool names must match the pattern
[a-z][a-z0-9_]*(lowercase, underscores).
- The plugin MCP module runs inside the same Node.js process as the Paca MCP server. Only publish modules from trusted sources.
- The
PluginAPIClientauthenticates using the same API key as the MCP server. Access is scoped by Paca's existing authorization model (the plugin's own routes under/api/v1/plugins/{pluginId}/). - Never embed secrets in the mcp.js bundle. Use the host-provided
context.apiKeyfor all authenticated calls.
github.com/Paca-AI/plugin-sdk-mcp
Apache-2.0