Skip to content

Commit 6d6fa51

Browse files
feat: Add list_project_issues tool to fetch all issues in a project (#39)
* feat: Add list_project_issues tool to fetch all issues in a project * feat: Enhance issue response structure with detailed state and priority information --------- Co-authored-by: Surya Prashanth <[email protected]>
1 parent 44c90b9 commit 6d6fa51

File tree

3 files changed

+102
-8
lines changed

3 files changed

+102
-8
lines changed

src/common/request-helper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export async function makePlaneRequest<T>(method: string, path: string, body: an
99
};
1010

1111
// Only add Content-Type for non-GET requests
12-
if (method.toUpperCase() !== 'GET') {
12+
if (method.toUpperCase() !== "GET") {
1313
headers["Content-Type"] = "application/json";
1414
}
1515

@@ -21,7 +21,7 @@ export async function makePlaneRequest<T>(method: string, path: string, body: an
2121
};
2222

2323
// Only include body for non-GET requests
24-
if (method.toUpperCase() !== 'GET' && body !== null) {
24+
if (method.toUpperCase() !== "GET" && body !== null) {
2525
config.data = body;
2626
}
2727

src/tools/issues.ts

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,109 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { z } from "zod";
33

44
import { makePlaneRequest } from "../common/request-helper.js";
5-
import { Issue as IssueSchema } from "../schemas.js";
5+
import { type Issue, Issue as IssueSchema } from "../schemas.js";
6+
7+
type IssueStateSummary = {
8+
id: string;
9+
name?: string;
10+
color?: string;
11+
group?: string;
12+
};
13+
14+
type IssuePrioritySummary = {
15+
id?: string;
16+
label?: string;
17+
name?: string;
18+
key?: string;
19+
};
20+
21+
type IssueWithDetails = Issue & {
22+
state_detail?: IssueStateSummary | null;
23+
priority_detail?: IssuePrioritySummary | null;
24+
};
25+
26+
type IssuesResponse = {
27+
grouped_by: null;
28+
sub_grouped_by: null;
29+
total_count: number;
30+
next_cursor: string;
31+
prev_cursor: string;
32+
next_page_results: boolean;
33+
prev_page_results: boolean;
34+
count: number;
35+
total_pages: number;
36+
total_results: number;
37+
extra_stats: null;
38+
results: IssueWithDetails[];
39+
};
40+
41+
export const registerIssueTools = (server: McpServer): void => {
42+
server.tool(
43+
"list_project_issues",
44+
"Get all issues for a specific project. 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",
45+
{
46+
project_id: z.string().describe("The uuid identifier of the project to get issues for"),
47+
},
48+
async ({ project_id }) => {
49+
const issuesResponse: IssuesResponse = await makePlaneRequest<IssuesResponse>(
50+
"GET",
51+
`workspaces/${process.env.PLANE_WORKSPACE_SLUG}/projects/${project_id}/issues/`
52+
);
53+
54+
// Return only essential fields to reduce token usage and improve LLM processing
55+
const simplifiedIssues = issuesResponse.results.map((issue) => {
56+
const stateDetail = issue.state_detail ?? null;
57+
const priorityDetail =
58+
issue.priority_detail ??
59+
(typeof issue.priority === "object" && issue.priority !== null
60+
? (issue.priority as IssuePrioritySummary)
61+
: null);
62+
63+
return {
64+
id: issue.id,
65+
name: issue.name,
66+
sequence_id: issue.sequence_id,
67+
state: {
68+
id: issue.state ?? stateDetail?.id ?? null,
69+
name: stateDetail?.name ?? null,
70+
color: stateDetail?.color ?? null,
71+
group: stateDetail?.group ?? null,
72+
},
73+
priority: {
74+
id: typeof issue.priority === "string" ? issue.priority : (priorityDetail?.id ?? null),
75+
label: priorityDetail?.label ?? priorityDetail?.name ?? null,
76+
key: priorityDetail?.key ?? null,
77+
},
78+
created_at: issue.created_at,
79+
updated_at: issue.updated_at,
80+
};
81+
});
82+
83+
return {
84+
content: [
85+
{
86+
type: "text",
87+
text: JSON.stringify(
88+
{
89+
total_count: issuesResponse.total_count,
90+
count: issuesResponse.count,
91+
results: simplifiedIssues,
92+
},
93+
null,
94+
2
95+
),
96+
},
97+
],
98+
};
99+
}
100+
);
6101

7-
export const registerIssueTools = (server: McpServer) => {
8102
server.tool(
9103
"get_issue_using_readable_identifier",
10-
"Get all issues for a specific project. When issue identifier is provided something like FIRST-123, ABC-123, etc. For FIRST-123, project_identifier is FIRST and issue_identifier is 123",
104+
"Get a specific issue using its readable identifier. When issue identifier is provided something like FIRST-123, ABC-123, etc. For FIRST-123, project_identifier is FIRST and issue_identifier is 123",
11105
{
12-
project_identifier: z.string().describe("The readable identifier of the project to get issues for"),
13-
issue_identifier: z.string().describe("The identifier of the issue to get"),
106+
project_identifier: z.string().describe("The readable identifier of the project (e.g., 'FIRST' for FIRST-123)"),
107+
issue_identifier: z.string().describe("The issue number (e.g., '123' for FIRST-123)"),
14108
},
15109
async ({ project_identifier, issue_identifier }) => {
16110
const issue = await makePlaneRequest(

src/tools/modules.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { z } from "zod";
44
import { makePlaneRequest } from "../common/request-helper.js";
55
import { Module as ModuleSchema } from "../schemas.js";
66

7-
export const registerModuleTools = (server: McpServer) => {
7+
export const registerModuleTools = (server: McpServer): void => {
88
server.tool(
99
"list_modules",
1010
"Get all modules for a specific project",

0 commit comments

Comments
 (0)