@scottsus/mdreview-mcp 0.1.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-client.d.ts +10 -11
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +30 -12
- package/dist/index.js +105 -55
- package/package.json +1 -1
package/dist/api-client.d.ts
CHANGED
|
@@ -33,26 +33,25 @@ interface CommentResponse {
|
|
|
33
33
|
authorName: string | null;
|
|
34
34
|
createdAt: string;
|
|
35
35
|
}
|
|
36
|
-
interface
|
|
36
|
+
interface SubmitDecisionResponse {
|
|
37
37
|
id: string;
|
|
38
38
|
status: string;
|
|
39
39
|
decisionMessage: string | null;
|
|
40
40
|
decidedAt: string | null;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
totalComments: number;
|
|
47
|
-
};
|
|
41
|
+
}
|
|
42
|
+
interface ResolveThreadResponse {
|
|
43
|
+
id: string;
|
|
44
|
+
resolved: boolean;
|
|
45
|
+
resolvedAt: string | null;
|
|
48
46
|
}
|
|
49
47
|
export declare class ApiClient {
|
|
50
48
|
private baseUrl;
|
|
51
49
|
constructor(baseUrl?: string);
|
|
52
|
-
createReview(content: string
|
|
53
|
-
getReview(reviewId: string): Promise<ReviewResponse>;
|
|
54
|
-
waitForReview(reviewId: string, timeoutSeconds?: number): Promise<WaitResponse>;
|
|
50
|
+
createReview(content: string): Promise<CreateReviewResponse>;
|
|
51
|
+
getReview(reviewId: string, includeContent?: boolean): Promise<ReviewResponse>;
|
|
55
52
|
addComment(threadId: string, body: string): Promise<CommentResponse>;
|
|
53
|
+
submitDecision(reviewId: string, decision: "approved" | "rejected" | "changes_requested", message?: string): Promise<SubmitDecisionResponse>;
|
|
54
|
+
resolveThread(threadId: string): Promise<ResolveThreadResponse>;
|
|
56
55
|
}
|
|
57
56
|
export {};
|
|
58
57
|
//# sourceMappingURL=api-client.d.ts.map
|
package/dist/api-client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAEA,UAAU,oBAAoB;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAEA,UAAU,oBAAoB;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,cAAc;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,UAAU,eAAe;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,sBAAsB;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,UAAU,qBAAqB;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,CAAC,EAAE,MAAM;IAItB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAkB5D,SAAS,CACb,QAAQ,EAAE,MAAM,EAChB,cAAc,UAAQ,GACrB,OAAO,CAAC,cAAc,CAAC;IAcpB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAsBpE,cAAc,CAClB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,UAAU,GAAG,UAAU,GAAG,mBAAmB,EACvD,OAAO,CAAC,EAAE,MAAM,GACf,OAAO,CAAC,sBAAsB,CAAC;IAkB5B,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,CAAC;CActE"}
|
package/dist/api-client.js
CHANGED
|
@@ -4,13 +4,12 @@ export class ApiClient {
|
|
|
4
4
|
constructor(baseUrl) {
|
|
5
5
|
this.baseUrl = baseUrl || BASE_URL;
|
|
6
6
|
}
|
|
7
|
-
async createReview(content
|
|
7
|
+
async createReview(content) {
|
|
8
8
|
const response = await fetch(`${this.baseUrl}/api/reviews`, {
|
|
9
9
|
method: "POST",
|
|
10
10
|
headers: { "Content-Type": "application/json" },
|
|
11
11
|
body: JSON.stringify({
|
|
12
12
|
content,
|
|
13
|
-
title,
|
|
14
13
|
source: "agent",
|
|
15
14
|
}),
|
|
16
15
|
});
|
|
@@ -20,22 +19,17 @@ export class ApiClient {
|
|
|
20
19
|
}
|
|
21
20
|
return response.json();
|
|
22
21
|
}
|
|
23
|
-
async getReview(reviewId) {
|
|
24
|
-
const
|
|
22
|
+
async getReview(reviewId, includeContent = false) {
|
|
23
|
+
const url = includeContent
|
|
24
|
+
? `${this.baseUrl}/api/reviews/${reviewId}?includeContent=true`
|
|
25
|
+
: `${this.baseUrl}/api/reviews/${reviewId}`;
|
|
26
|
+
const response = await fetch(url);
|
|
25
27
|
if (!response.ok) {
|
|
26
28
|
const error = await response.json();
|
|
27
29
|
throw new Error(error.message || "Failed to get review");
|
|
28
30
|
}
|
|
29
31
|
return response.json();
|
|
30
32
|
}
|
|
31
|
-
async waitForReview(reviewId, timeoutSeconds = 300) {
|
|
32
|
-
const response = await fetch(`${this.baseUrl}/api/reviews/${reviewId}/wait?timeout=${timeoutSeconds}`);
|
|
33
|
-
if (!response.ok) {
|
|
34
|
-
const error = await response.json();
|
|
35
|
-
throw new Error(error.message || "Failed to wait for review");
|
|
36
|
-
}
|
|
37
|
-
return response.json();
|
|
38
|
-
}
|
|
39
33
|
async addComment(threadId, body) {
|
|
40
34
|
const response = await fetch(`${this.baseUrl}/api/threads/${threadId}/replies`, {
|
|
41
35
|
method: "POST",
|
|
@@ -52,4 +46,28 @@ export class ApiClient {
|
|
|
52
46
|
}
|
|
53
47
|
return response.json();
|
|
54
48
|
}
|
|
49
|
+
async submitDecision(reviewId, decision, message) {
|
|
50
|
+
const response = await fetch(`${this.baseUrl}/api/reviews/${reviewId}/submit`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: { "Content-Type": "application/json" },
|
|
53
|
+
body: JSON.stringify({ decision, message }),
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) {
|
|
56
|
+
const error = await response.json();
|
|
57
|
+
throw new Error(error.message || "Failed to submit decision");
|
|
58
|
+
}
|
|
59
|
+
return response.json();
|
|
60
|
+
}
|
|
61
|
+
async resolveThread(threadId) {
|
|
62
|
+
const response = await fetch(`${this.baseUrl}/api/threads/${threadId}`, {
|
|
63
|
+
method: "PATCH",
|
|
64
|
+
headers: { "Content-Type": "application/json" },
|
|
65
|
+
body: JSON.stringify({ resolved: true }),
|
|
66
|
+
});
|
|
67
|
+
if (!response.ok) {
|
|
68
|
+
const error = await response.json();
|
|
69
|
+
throw new Error(error.message || "Failed to resolve thread");
|
|
70
|
+
}
|
|
71
|
+
return response.json();
|
|
72
|
+
}
|
|
55
73
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
2
3
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
import { ApiClient } from "./api-client.js";
|
|
6
7
|
const server = new McpServer({
|
|
7
8
|
name: "mdreview",
|
|
8
|
-
version: "0.
|
|
9
|
+
version: "0.4.0",
|
|
9
10
|
});
|
|
10
11
|
const apiClient = new ApiClient(process.env.MDREVIEW_BASE_URL);
|
|
11
12
|
// Tool: request_review
|
|
@@ -13,67 +14,38 @@ server.registerTool("request_review", {
|
|
|
13
14
|
title: "Request Review",
|
|
14
15
|
description: "Create a markdown document review and get a shareable URL. Share this URL with the reviewer and they can add inline comments and approve/reject the document.",
|
|
15
16
|
inputSchema: {
|
|
16
|
-
|
|
17
|
-
title: z.string().optional().describe("Optional title for the review"),
|
|
17
|
+
filePath: z.string().describe("The path to the markdown file to review"),
|
|
18
18
|
},
|
|
19
|
-
}, async ({
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
content
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
structuredContent: {
|
|
29
|
-
reviewId: result.id,
|
|
30
|
-
url: result.url,
|
|
31
|
-
status: result.status,
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
});
|
|
35
|
-
// Tool: wait_for_review
|
|
36
|
-
server.registerTool("wait_for_review", {
|
|
37
|
-
title: "Wait for Review",
|
|
38
|
-
description: "Wait for a review to be completed (approved, rejected, or changes requested). This will block until the reviewer makes a decision or the timeout is reached.",
|
|
39
|
-
inputSchema: {
|
|
40
|
-
reviewId: z.string().describe("The review ID to wait for"),
|
|
41
|
-
timeoutSeconds: z
|
|
42
|
-
.number()
|
|
43
|
-
.min(1)
|
|
44
|
-
.max(300)
|
|
45
|
-
.default(300)
|
|
46
|
-
.describe("Maximum time to wait in seconds (default: 300)"),
|
|
47
|
-
},
|
|
48
|
-
}, async ({ reviewId, timeoutSeconds }) => {
|
|
49
|
-
const result = await apiClient.waitForReview(reviewId, timeoutSeconds);
|
|
50
|
-
if (result.status === "pending") {
|
|
19
|
+
}, async ({ filePath }) => {
|
|
20
|
+
let content;
|
|
21
|
+
try {
|
|
22
|
+
content = fs.readFileSync(filePath, "utf-8");
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
const message = error instanceof Error ? error.message : "Unknown error reading file";
|
|
51
26
|
return {
|
|
52
27
|
content: [
|
|
53
28
|
{
|
|
54
29
|
type: "text",
|
|
55
|
-
text: `
|
|
30
|
+
text: `Error: Failed to read file at "${filePath}"\n\n${message}`,
|
|
56
31
|
},
|
|
57
32
|
],
|
|
58
|
-
|
|
33
|
+
isError: true,
|
|
59
34
|
};
|
|
60
35
|
}
|
|
61
|
-
const
|
|
62
|
-
.map((thread) => {
|
|
63
|
-
const comments = thread.comments
|
|
64
|
-
.map((c) => ` - ${c.authorType}: ${c.body}`)
|
|
65
|
-
.join("\n");
|
|
66
|
-
return `[${thread.resolved ? "RESOLVED" : "UNRESOLVED"}] "${thread.selectedText}"\n${comments}`;
|
|
67
|
-
})
|
|
68
|
-
.join("\n\n");
|
|
36
|
+
const result = await apiClient.createReview(content);
|
|
69
37
|
return {
|
|
70
38
|
content: [
|
|
71
39
|
{
|
|
72
40
|
type: "text",
|
|
73
|
-
text: `Review
|
|
41
|
+
text: `Review created successfully!\n\nReview URL: ${result.url}\n\nShare this URL with your reviewer. They can:\n- Add inline comments by selecting text\n- Approve, reject, or request changes\n\nUse 'get_review_status' with reviewId "${result.id}" to check the review status.`,
|
|
74
42
|
},
|
|
75
43
|
],
|
|
76
|
-
structuredContent: {
|
|
44
|
+
structuredContent: {
|
|
45
|
+
reviewId: result.id,
|
|
46
|
+
url: result.url,
|
|
47
|
+
status: result.status,
|
|
48
|
+
},
|
|
77
49
|
};
|
|
78
50
|
});
|
|
79
51
|
// Tool: get_review_status
|
|
@@ -82,21 +54,45 @@ server.registerTool("get_review_status", {
|
|
|
82
54
|
description: "Get the current status of a review without waiting. Use this to check if a review has been completed.",
|
|
83
55
|
inputSchema: {
|
|
84
56
|
reviewId: z.string().describe("The review ID to check"),
|
|
57
|
+
includeContent: z
|
|
58
|
+
.boolean()
|
|
59
|
+
.default(false)
|
|
60
|
+
.describe("Include the full markdown content in the response"),
|
|
85
61
|
},
|
|
86
|
-
}, async ({ reviewId }) => {
|
|
87
|
-
const result = await apiClient.getReview(reviewId);
|
|
62
|
+
}, async ({ reviewId, includeContent }) => {
|
|
63
|
+
const result = await apiClient.getReview(reviewId, includeContent);
|
|
64
|
+
const commentsText = result.threads
|
|
65
|
+
.map((thread) => {
|
|
66
|
+
const comments = thread.comments
|
|
67
|
+
.map((c) => ` - ${c.authorType}: ${c.body}`)
|
|
68
|
+
.join("\n");
|
|
69
|
+
return `[${thread.resolved ? "RESOLVED" : "UNRESOLVED"}] Thread: ${thread.id}\n"${thread.selectedText}"\n${comments}`;
|
|
70
|
+
})
|
|
71
|
+
.join("\n\n");
|
|
72
|
+
const summary = {
|
|
73
|
+
totalThreads: result.threads.length,
|
|
74
|
+
resolvedThreads: result.threads.filter((t) => t.resolved).length,
|
|
75
|
+
unresolvedThreads: result.threads.filter((t) => !t.resolved).length,
|
|
76
|
+
totalComments: result.threads.reduce((sum, t) => sum + t.comments.length, 0),
|
|
77
|
+
};
|
|
78
|
+
const structuredContent = {
|
|
79
|
+
status: result.status,
|
|
80
|
+
decisionMessage: result.decisionMessage,
|
|
81
|
+
decidedAt: result.decidedAt,
|
|
82
|
+
threads: result.threads,
|
|
83
|
+
summary,
|
|
84
|
+
};
|
|
85
|
+
if (includeContent) {
|
|
86
|
+
structuredContent.content = result.content;
|
|
87
|
+
}
|
|
88
88
|
return {
|
|
89
89
|
content: [
|
|
90
90
|
{
|
|
91
91
|
type: "text",
|
|
92
|
-
text: `Review Status: ${result.status.toUpperCase()}\nTitle: ${result.title || "(untitled)"}\
|
|
92
|
+
text: `Review Status: ${result.status.toUpperCase()}\nTitle: ${result.title || "(untitled)"}\nMessage: ${result.decisionMessage || "(none)"}\nURL: ${result.url}\n\nSummary:\n- Total threads: ${summary.totalThreads}\n- Resolved: ${summary.resolvedThreads}\n- Unresolved: ${summary.unresolvedThreads}\n- Total comments: ${summary.totalComments}\n\n${commentsText ? `Comments:\n${commentsText}` : "No comments."}`,
|
|
93
93
|
},
|
|
94
94
|
],
|
|
95
|
-
structuredContent
|
|
96
|
-
status: result.status,
|
|
97
|
-
threadCount: result.threads.length,
|
|
98
|
-
commentCount: result.threads.reduce((sum, t) => sum + t.comments.length, 0),
|
|
99
|
-
},
|
|
95
|
+
structuredContent,
|
|
100
96
|
};
|
|
101
97
|
});
|
|
102
98
|
// Tool: add_comment
|
|
@@ -122,6 +118,60 @@ server.registerTool("add_comment", {
|
|
|
122
118
|
},
|
|
123
119
|
};
|
|
124
120
|
});
|
|
121
|
+
// Tool: resolve_thread
|
|
122
|
+
server.registerTool("resolve_thread", {
|
|
123
|
+
title: "Resolve Thread",
|
|
124
|
+
description: "Mark a comment thread as resolved. Use this after addressing reviewer feedback.",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
threadId: z.string().describe("The thread ID to resolve"),
|
|
127
|
+
},
|
|
128
|
+
}, async ({ threadId }) => {
|
|
129
|
+
const result = await apiClient.resolveThread(threadId);
|
|
130
|
+
return {
|
|
131
|
+
content: [
|
|
132
|
+
{
|
|
133
|
+
type: "text",
|
|
134
|
+
text: `Thread resolved successfully!\n\nThread ID: ${result.id}\nResolved at: ${result.resolvedAt}`,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
structuredContent: {
|
|
138
|
+
threadId: result.id,
|
|
139
|
+
resolved: result.resolved,
|
|
140
|
+
resolvedAt: result.resolvedAt,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
// Tool: submit_decision
|
|
145
|
+
server.registerTool("submit_decision", {
|
|
146
|
+
title: "Submit Decision",
|
|
147
|
+
description: "Submit a review decision (approve, reject, or request changes). Use this to finalize a review.",
|
|
148
|
+
inputSchema: {
|
|
149
|
+
reviewId: z.string().describe("The review ID"),
|
|
150
|
+
decision: z
|
|
151
|
+
.enum(["approved", "rejected", "changes_requested"])
|
|
152
|
+
.describe("The decision"),
|
|
153
|
+
message: z
|
|
154
|
+
.string()
|
|
155
|
+
.optional()
|
|
156
|
+
.describe("Optional message explaining the decision"),
|
|
157
|
+
},
|
|
158
|
+
}, async ({ reviewId, decision, message }) => {
|
|
159
|
+
const result = await apiClient.submitDecision(reviewId, decision, message);
|
|
160
|
+
return {
|
|
161
|
+
content: [
|
|
162
|
+
{
|
|
163
|
+
type: "text",
|
|
164
|
+
text: `Decision submitted successfully!\n\nReview ID: ${result.id}\nDecision: ${result.status.toUpperCase()}\nMessage: ${result.decisionMessage || "(none)"}`,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
structuredContent: {
|
|
168
|
+
reviewId: result.id,
|
|
169
|
+
status: result.status,
|
|
170
|
+
decisionMessage: result.decisionMessage,
|
|
171
|
+
decidedAt: result.decidedAt,
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
});
|
|
125
175
|
async function main() {
|
|
126
176
|
const transport = new StdioServerTransport();
|
|
127
177
|
await server.connect(transport);
|