From 5f4f2d67169b7b4c45efead37bd2025196db2fbf Mon Sep 17 00:00:00 2001 From: pikann22 Date: Sun, 9 Aug 2026 14:38:35 +0000 Subject: [PATCH] feat: add getToolContext method to PluginMCPEntry interface --- .github/workflows/cd.yml | 2 +- README.md | 11 +++++++++ src/types.ts | 50 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 35f12be..7c1b629 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -48,7 +48,7 @@ jobs: - name: Setup Node.js with npm registry uses: actions/setup-node@v4 with: - node-version: "20" + node-version: "24" registry-url: "https://registry.npmjs.org" scope: "@paca-ai" diff --git a/README.md b/README.md index 5719a69..0b2502a 100644 --- a/README.md +++ b/README.md @@ -151,9 +151,20 @@ interface PluginMCPEntry { args: Record, context: PluginMCPContext, ): Promise; + + // Optional: contribute additional text to the response of ANY core tool + // call (not just tools this plugin owns). Switch on toolId to decide + // what, if anything, to add; return null/undefined for the rest. + getToolContext?( + toolId: string, + args: Record, + context: PluginMCPContext, + ): Promise; } ``` +See [mcp-plugin-system.md](../paca/docs/plugins/mcp-plugin-system.md#contributing-to-any-core-tools-response-gettoolcontext) for the full `getToolContext` contract and examples. + ### `PluginMCPContext` Runtime context injected by the host into every `handleToolCall` call. diff --git a/src/types.ts b/src/types.ts index 14041f0..0ce7847 100644 --- a/src/types.ts +++ b/src/types.ts @@ -72,6 +72,49 @@ export interface PluginMCPEntry { args: Record, context: PluginMCPContext, ): Promise; + + /** + * Optional: contribute additional text to the response of **any** core + * Paca tool call (not just tools this plugin owns) — e.g. attach a + * "linked branches" section to `get_task`, or a note to `list_sprints`. + * + * Called once per loaded plugin after every successful core tool call. + * Switch on `toolId` to decide what — if anything — to contribute; return + * `null` (or `undefined`) for tools/args the plugin has nothing to add + * for, so the host omits the section instead of rendering empty + * boilerplate on every call. + * + * Errors are caught and logged by the host; a failing plugin cannot + * blank out the rest of the response, so this does not need its own + * try/catch purely for that purpose. + * + * @param toolId - The core tool's name, e.g. `"get_task"`, + * `"list_tasks"`, `"get_project"`. See `ALL_TOOLS.md` in `apps/mcp` + * for the full list of core tool names and their argument shapes. + * @param args - The raw arguments the AI client passed for this call + * (same shape the tool itself receives — not necessarily containing + * every ID your plugin needs; e.g. `get_task_by_number` has no + * `taskId`, only `taskNumber`). + * + * @example + * ```ts + * async getToolContext(toolId, args, context) { + * if (toolId !== "get_task") return null; + * const { projectId, taskId } = args as { projectId: string; taskId: string }; + * const api = new PluginAPIClient(context); + * const items = await api.pluginGet( + * `projects/${projectId}/tasks/${taskId}/items`, + * ); + * if (items.length === 0) return null; + * return `## My Plugin\n\n${items.map((i) => `- ${i.title}`).join("\n")}`; + * } + * ``` + */ + getToolContext?( + toolId: string, + args: Record, + context: PluginMCPContext, + ): Promise; } // ── Context ─────────────────────────────────────────────────────────────────── @@ -124,6 +167,13 @@ export interface InstalledPlugin { mcp?: { /** URL to the plugin's MCP entry module (Node.js-compatible ESM). */ remoteEntryUrl: string; + /** + * Core tool IDs (e.g. `"get_task"`) this plugin's `getToolContext` + * can contribute to. The host only invokes `getToolContext` for + * plugins that declare the tool here — implementing the method + * without declaring it here means it is never called. + */ + toolContextHooks?: string[]; }; }; }