Skip to content

Paca-AI/plugin-sdk-mcp

Repository files navigation

@paca-ai/plugin-sdk-mcp

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.).

Overview

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.

Installation

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.

Quick Start

1. Add mcp to your plugin.json manifest

{
  "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"
  }
}

2. Write your MCP entry module

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;
}

3. Build the MCP entry module

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.


API Reference

PluginMCPEntry

The interface your default export must implement.

interface PluginMCPEntry {
  tools: Tool[];
  handleToolCall(
    name: string,
    args: Record<string, unknown>,
    context: PluginMCPContext,
  ): Promise<PluginToolResult>;
}

PluginMCPContext

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
}

PluginAPIClient

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>
}

textResult(text) / errorResult(message)

Convenience helpers for building tool results.

function textResult(text: string): PluginToolResult
function errorResult(message: string): PluginToolResult

Naming Conventions

  • 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.checklist should use checklist_list_items, checklist_create_item, etc.
  • Tool names must match the pattern [a-z][a-z0-9_]* (lowercase, underscores).

Security Notes

  • The plugin MCP module runs inside the same Node.js process as the Paca MCP server. Only publish modules from trusted sources.
  • The PluginAPIClient authenticates 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.apiKey for all authenticated calls.

Repository

github.com/Paca-AI/plugin-sdk-mcp

License

Apache-2.0

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors