11import * as pkg from "../package.json" ;
22import * as config from "./config" ;
3- import { fetchIssues } from "./issues" ;
3+ import { fetchIssues , fetchIssueComments , createIssueComment } from "./issues" ;
44import { resolveBaseUrls } from "./util" ;
55
66// MCP SDK imports
@@ -29,6 +29,16 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
2929 { capabilities : { tools : { } } }
3030 ) ;
3131
32+ // Interpret escape sequences (e.g., \n -> newline). Input comes from JSON, but
33+ // we still normalize common escapes for consistency.
34+ const interpretEscapes = ( str : string ) : string =>
35+ ( str || "" )
36+ . replace ( / \\ n / g, "\n" )
37+ . replace ( / \\ t / g, "\t" )
38+ . replace ( / \\ r / g, "\r" )
39+ . replace ( / \\ " / g, '"' )
40+ . replace ( / \\ ' / g, "'" ) ;
41+
3242 server . setRequestHandler ( ListToolsRequestSchema , async ( ) => {
3343 return {
3444 tools : [
@@ -43,6 +53,34 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
4353 additionalProperties : false ,
4454 } ,
4555 } ,
56+ {
57+ name : "list_issue_comments" ,
58+ description : "List comments for a specific issue (issue_id UUID)" ,
59+ inputSchema : {
60+ type : "object" ,
61+ properties : {
62+ issue_id : { type : "string" , description : "Issue ID (UUID)" } ,
63+ debug : { type : "boolean" , description : "Enable verbose debug logs" } ,
64+ } ,
65+ required : [ "issue_id" ] ,
66+ additionalProperties : false ,
67+ } ,
68+ } ,
69+ {
70+ name : "post_issue_comment" ,
71+ description : "Post a new comment to an issue (optionally as a reply)" ,
72+ inputSchema : {
73+ type : "object" ,
74+ properties : {
75+ issue_id : { type : "string" , description : "Issue ID (UUID)" } ,
76+ content : { type : "string" , description : "Comment text (supports \\n as newline)" } ,
77+ parent_comment_id : { type : "string" , description : "Parent comment ID (UUID) for replies" } ,
78+ debug : { type : "boolean" , description : "Enable verbose debug logs" } ,
79+ } ,
80+ required : [ "issue_id" , "content" ] ,
81+ additionalProperties : false ,
82+ } ,
83+ } ,
4684 ] ,
4785 } ;
4886 } ) ;
@@ -51,10 +89,6 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
5189 const toolName = req . params . name ;
5290 const args = ( req . params . arguments as Record < string , unknown > ) || { } ;
5391
54- if ( toolName !== "list_issues" ) {
55- throw new Error ( `Unknown tool: ${ toolName } ` ) ;
56- }
57-
5892 const cfg = config . readConfig ( ) ;
5993 const apiKey = ( rootOpts ?. apiKey || process . env . PGAI_API_KEY || cfg . apiKey || "" ) . toString ( ) ;
6094 const { apiBaseUrl } = resolveBaseUrls ( rootOpts , cfg ) ;
@@ -74,20 +108,39 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
74108 }
75109
76110 try {
77- const result = await fetchIssues ( { apiKey, apiBaseUrl, debug } ) ;
78- return {
79- content : [
80- { type : "text" , text : JSON . stringify ( result , null , 2 ) } ,
81- ] ,
82- } ;
111+ if ( toolName === "list_issues" ) {
112+ const result = await fetchIssues ( { apiKey, apiBaseUrl, debug } ) ;
113+ return { content : [ { type : "text" , text : JSON . stringify ( result , null , 2 ) } ] } ;
114+ }
115+
116+ if ( toolName === "list_issue_comments" ) {
117+ const issueId = String ( args . issue_id || "" ) . trim ( ) ;
118+ if ( ! issueId ) {
119+ return { content : [ { type : "text" , text : "issue_id is required" } ] , isError : true } ;
120+ }
121+ const result = await fetchIssueComments ( { apiKey, apiBaseUrl, issueId, debug } ) ;
122+ return { content : [ { type : "text" , text : JSON . stringify ( result , null , 2 ) } ] } ;
123+ }
124+
125+ if ( toolName === "post_issue_comment" ) {
126+ const issueId = String ( args . issue_id || "" ) . trim ( ) ;
127+ const rawContent = String ( args . content || "" ) ;
128+ const parentCommentId = args . parent_comment_id ? String ( args . parent_comment_id ) : undefined ;
129+ if ( ! issueId ) {
130+ return { content : [ { type : "text" , text : "issue_id is required" } ] , isError : true } ;
131+ }
132+ if ( ! rawContent ) {
133+ return { content : [ { type : "text" , text : "content is required" } ] , isError : true } ;
134+ }
135+ const content = interpretEscapes ( rawContent ) ;
136+ const result = await createIssueComment ( { apiKey, apiBaseUrl, issueId, content, parentCommentId, debug } ) ;
137+ return { content : [ { type : "text" , text : JSON . stringify ( result , null , 2 ) } ] } ;
138+ }
139+
140+ throw new Error ( `Unknown tool: ${ toolName } ` ) ;
83141 } catch ( err ) {
84142 const message = err instanceof Error ? err . message : String ( err ) ;
85- return {
86- content : [
87- { type : "text" , text : message } ,
88- ] ,
89- isError : true ,
90- } ;
143+ return { content : [ { type : "text" , text : message } ] , isError : true } ;
91144 }
92145 } ) ;
93146
0 commit comments