Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/common/request-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export async function makePlaneRequest<T>(method: string, path: string, body: an
};

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

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

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

Expand Down
104 changes: 99 additions & 5 deletions src/tools/issues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,109 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";

import { makePlaneRequest } from "../common/request-helper.js";
import { Issue as IssueSchema } from "../schemas.js";
import { type Issue, Issue as IssueSchema } from "../schemas.js";

type IssueStateSummary = {
id: string;
name?: string;
color?: string;
group?: string;
};

type IssuePrioritySummary = {
id?: string;
label?: string;
name?: string;
key?: string;
};

type IssueWithDetails = Issue & {
state_detail?: IssueStateSummary | null;
priority_detail?: IssuePrioritySummary | null;
};

type IssuesResponse = {
grouped_by: null;
sub_grouped_by: null;
total_count: number;
next_cursor: string;
prev_cursor: string;
next_page_results: boolean;
prev_page_results: boolean;
count: number;
total_pages: number;
total_results: number;
extra_stats: null;
results: IssueWithDetails[];
};

export const registerIssueTools = (server: McpServer): void => {
server.tool(
"list_project_issues",
"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",
{
project_id: z.string().describe("The uuid identifier of the project to get issues for"),
},
async ({ project_id }) => {
const issuesResponse: IssuesResponse = await makePlaneRequest<IssuesResponse>(
"GET",
`workspaces/${process.env.PLANE_WORKSPACE_SLUG}/projects/${project_id}/issues/`
);

// Return only essential fields to reduce token usage and improve LLM processing
const simplifiedIssues = issuesResponse.results.map((issue) => {
const stateDetail = issue.state_detail ?? null;
const priorityDetail =
issue.priority_detail ??
(typeof issue.priority === "object" && issue.priority !== null
? (issue.priority as IssuePrioritySummary)
: null);

return {
id: issue.id,
name: issue.name,
sequence_id: issue.sequence_id,
state: {
id: issue.state ?? stateDetail?.id ?? null,
name: stateDetail?.name ?? null,
color: stateDetail?.color ?? null,
group: stateDetail?.group ?? null,
},
priority: {
id: typeof issue.priority === "string" ? issue.priority : (priorityDetail?.id ?? null),
label: priorityDetail?.label ?? priorityDetail?.name ?? null,
key: priorityDetail?.key ?? null,
},
created_at: issue.created_at,
updated_at: issue.updated_at,
};
});

return {
content: [
{
type: "text",
text: JSON.stringify(
{
total_count: issuesResponse.total_count,
count: issuesResponse.count,
results: simplifiedIssues,
},
null,
2
),
},
],
};
}
);

export const registerIssueTools = (server: McpServer) => {
server.tool(
"get_issue_using_readable_identifier",
"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",
"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",
{
project_identifier: z.string().describe("The readable identifier of the project to get issues for"),
issue_identifier: z.string().describe("The identifier of the issue to get"),
project_identifier: z.string().describe("The readable identifier of the project (e.g., 'FIRST' for FIRST-123)"),
issue_identifier: z.string().describe("The issue number (e.g., '123' for FIRST-123)"),
},
async ({ project_identifier, issue_identifier }) => {
const issue = await makePlaneRequest(
Expand Down
2 changes: 1 addition & 1 deletion src/tools/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { z } from "zod";
import { makePlaneRequest } from "../common/request-helper.js";
import { Module as ModuleSchema } from "../schemas.js";

export const registerModuleTools = (server: McpServer) => {
export const registerModuleTools = (server: McpServer): void => {
server.tool(
"list_modules",
"Get all modules for a specific project",
Expand Down