🌐 AI搜索 & 代理 主页
Skip to content

Commit 70a8967

Browse files
committed
Add mcp commands for list comments and post a comment
1 parent c09a054 commit 70a8967

File tree

2 files changed

+73
-18
lines changed

2 files changed

+73
-18
lines changed

cli/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ postgres-ai mon shell <service> # Open shell to monitoring servic
9696
### MCP server (`mcp` group)
9797

9898
```bash
99-
pgai mcp start # Start MCP stdio server exposing list_issues tool
99+
pgai mcp start # Start MCP stdio server exposing tools
100100
```
101101

102102
Cursor configuration example (Settings → MCP):
@@ -117,6 +117,8 @@ Cursor configuration example (Settings → MCP):
117117

118118
Tools exposed:
119119
- list_issues: returns the same JSON as `pgai issues list`.
120+
- list_issue_comments: list comments for an issue (args: { issue_id, debug? })
121+
- post_issue_comment: post a comment (args: { issue_id, content, parent_comment_id?, debug? })
120122

121123
### Issues management (`issues` group)
122124

cli/lib/mcp-server.ts

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as pkg from "../package.json";
22
import * as config from "./config";
3-
import { fetchIssues } from "./issues";
3+
import { fetchIssues, fetchIssueComments, createIssueComment } from "./issues";
44
import { 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

Comments
 (0)