From d2841de66ab32d613a110db99ec8aa624cf5da1a Mon Sep 17 00:00:00 2001 From: Sarah Yun Date: Wed, 17 Sep 2025 09:39:23 -0700 Subject: [PATCH 1/3] feat: add search issues tools --- src/tools/index.ts | 2 ++ src/tools/search-issues.ts | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 src/tools/search-issues.ts diff --git a/src/tools/index.ts b/src/tools/index.ts index 13e9d53..9e78a57 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -7,6 +7,7 @@ import { registerMetadataTools } from "./metadata.js"; import { registerModuleIssueTools } from "./module-issues.js"; import { registerModuleTools } from "./modules.js"; import { registerProjectTools } from "./projects.js"; +import { registerSearchIssueTools } from "./search-issues.js"; import { registerUserTools } from "./user.js"; import { registerWorkLogTools } from "./work-log.js"; @@ -15,6 +16,7 @@ export const registerTools = (server: McpServer) => { registerUserTools(server); registerProjectTools(server); + registerSearchIssueTools(server); registerModuleTools(server); registerModuleIssueTools(server); registerIssueTools(server); diff --git a/src/tools/search-issues.ts b/src/tools/search-issues.ts new file mode 100644 index 0000000..541f793 --- /dev/null +++ b/src/tools/search-issues.ts @@ -0,0 +1,38 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; + +import { makePlaneRequest } from "../common/request-helper.js"; + +export const registerSearchIssueTools = (server: McpServer): void => { + server.tool( + "search_issues", + "Use this to search issues by text query. This requests project_id as uuid parameter. If you have a readable identifier for project, you can use the get_projects tool to get the project_id from it", + { + limit: z.number().describe("The number of issues to return").optional(), + project_id: z.string().describe("The uuid identifier of the project to search issues for").optional(), + search: z.string().describe("The search query"), + workspace_search: z.boolean().describe("Whether to search across all projects in the workspace").optional(), + }, + async ({ limit, project_id, search, workspace_search }) => { + // send the params if not null as query string + const queryParams = new URLSearchParams(); + if (limit) queryParams.set("limit", limit.toString()); + if (project_id) queryParams.set("project_id", project_id); + if (workspace_search) queryParams.set("workspace_search", workspace_search.toString()); + if (search) queryParams.set("search", search); + + const response = await makePlaneRequest( + "GET", + `workspaces/${process.env.PLANE_WORKSPACE_SLUG}/issues/search/?${queryParams.toString()}` + ); + return { + content: [ + { + type: "text", + text: JSON.stringify(response, null, 2), + }, + ], + }; + } + ); +}; From b1c6943501e1da36dfc140cc26eba1a6a8bccee9 Mon Sep 17 00:00:00 2001 From: Sarah Yun Date: Thu, 18 Sep 2025 11:37:22 -0700 Subject: [PATCH 2/3] Default to searching TEST project for codex --- src/tools/search-issues.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/tools/search-issues.ts b/src/tools/search-issues.ts index 541f793..2c5727a 100644 --- a/src/tools/search-issues.ts +++ b/src/tools/search-issues.ts @@ -6,7 +6,7 @@ import { makePlaneRequest } from "../common/request-helper.js"; export const registerSearchIssueTools = (server: McpServer): void => { server.tool( "search_issues", - "Use this to search issues by text query. This requests project_id as uuid parameter. If you have a readable identifier for project, you can use the get_projects tool to get the project_id from it", + "Use this to search issues by text query. If no project_id is provided, defaults to the 'TEST' project. If you have a readable identifier for project, you can use the get_projects tool to get the project_id from it", { limit: z.number().describe("The number of issues to return").optional(), project_id: z.string().describe("The uuid identifier of the project to search issues for").optional(), @@ -17,13 +17,27 @@ export const registerSearchIssueTools = (server: McpServer): void => { // send the params if not null as query string const queryParams = new URLSearchParams(); if (limit) queryParams.set("limit", limit.toString()); - if (project_id) queryParams.set("project_id", project_id); if (workspace_search) queryParams.set("workspace_search", workspace_search.toString()); if (search) queryParams.set("search", search); + // If no project_id provided, default to "TEST" project + let targetProjectId = project_id; + if (!targetProjectId) { + // Look up the "TEST" project to get its UUID + const projectsResponse = await makePlaneRequest( + "GET", + `workspaces/${process.env.PLANE_WORKSPACE_SLUG}/projects/` + ); + const testProject = (projectsResponse as any).results?.find((p: any) => p.identifier === "TEST"); + if (!testProject) { + throw new Error("TEST project not found. Please provide a project_id or ensure TEST project exists."); + } + targetProjectId = testProject.id; + } + const response = await makePlaneRequest( "GET", - `workspaces/${process.env.PLANE_WORKSPACE_SLUG}/issues/search/?${queryParams.toString()}` + `workspaces/${process.env.PLANE_WORKSPACE_SLUG}/projects/${targetProjectId}/issues/?${queryParams.toString()}` ); return { content: [ From 7c20247617fd8c0f5d5f053b0231abed71698a13 Mon Sep 17 00:00:00 2001 From: Sarah Yun Date: Thu, 18 Sep 2025 11:39:20 -0700 Subject: [PATCH 3/3] Revert "Default to searching TEST project for codex" This reverts commit b1c6943501e1da36dfc140cc26eba1a6a8bccee9. --- src/tools/search-issues.ts | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/src/tools/search-issues.ts b/src/tools/search-issues.ts index 2c5727a..541f793 100644 --- a/src/tools/search-issues.ts +++ b/src/tools/search-issues.ts @@ -6,7 +6,7 @@ import { makePlaneRequest } from "../common/request-helper.js"; export const registerSearchIssueTools = (server: McpServer): void => { server.tool( "search_issues", - "Use this to search issues by text query. If no project_id is provided, defaults to the 'TEST' project. If you have a readable identifier for project, you can use the get_projects tool to get the project_id from it", + "Use this to search issues by text query. This requests project_id as uuid parameter. If you have a readable identifier for project, you can use the get_projects tool to get the project_id from it", { limit: z.number().describe("The number of issues to return").optional(), project_id: z.string().describe("The uuid identifier of the project to search issues for").optional(), @@ -17,27 +17,13 @@ export const registerSearchIssueTools = (server: McpServer): void => { // send the params if not null as query string const queryParams = new URLSearchParams(); if (limit) queryParams.set("limit", limit.toString()); + if (project_id) queryParams.set("project_id", project_id); if (workspace_search) queryParams.set("workspace_search", workspace_search.toString()); if (search) queryParams.set("search", search); - // If no project_id provided, default to "TEST" project - let targetProjectId = project_id; - if (!targetProjectId) { - // Look up the "TEST" project to get its UUID - const projectsResponse = await makePlaneRequest( - "GET", - `workspaces/${process.env.PLANE_WORKSPACE_SLUG}/projects/` - ); - const testProject = (projectsResponse as any).results?.find((p: any) => p.identifier === "TEST"); - if (!testProject) { - throw new Error("TEST project not found. Please provide a project_id or ensure TEST project exists."); - } - targetProjectId = testProject.id; - } - const response = await makePlaneRequest( "GET", - `workspaces/${process.env.PLANE_WORKSPACE_SLUG}/projects/${targetProjectId}/issues/?${queryParams.toString()}` + `workspaces/${process.env.PLANE_WORKSPACE_SLUG}/issues/search/?${queryParams.toString()}` ); return { content: [