@scottsus/mdreview-mcp 0.1.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 +58 -0
- package/dist/api-client.d.ts.map +1 -0
- package/dist/api-client.js +55 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +133 -0
- package/package.json +42 -0
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
interface CreateReviewResponse {
|
|
2
|
+
id: string;
|
|
3
|
+
slug: string;
|
|
4
|
+
url: string;
|
|
5
|
+
status: string;
|
|
6
|
+
createdAt: string;
|
|
7
|
+
}
|
|
8
|
+
interface ReviewResponse {
|
|
9
|
+
id: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
url: string;
|
|
12
|
+
content: string;
|
|
13
|
+
title: string | null;
|
|
14
|
+
status: string;
|
|
15
|
+
decisionMessage: string | null;
|
|
16
|
+
decidedAt: string | null;
|
|
17
|
+
threads: ThreadResponse[];
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}
|
|
21
|
+
interface ThreadResponse {
|
|
22
|
+
id: string;
|
|
23
|
+
startOffset: number;
|
|
24
|
+
endOffset: number;
|
|
25
|
+
selectedText: string;
|
|
26
|
+
resolved: boolean;
|
|
27
|
+
comments: CommentResponse[];
|
|
28
|
+
}
|
|
29
|
+
interface CommentResponse {
|
|
30
|
+
id: string;
|
|
31
|
+
body: string;
|
|
32
|
+
authorType: string;
|
|
33
|
+
authorName: string | null;
|
|
34
|
+
createdAt: string;
|
|
35
|
+
}
|
|
36
|
+
interface WaitResponse {
|
|
37
|
+
id: string;
|
|
38
|
+
status: string;
|
|
39
|
+
decisionMessage: string | null;
|
|
40
|
+
decidedAt: string | null;
|
|
41
|
+
threads: ThreadResponse[];
|
|
42
|
+
summary: {
|
|
43
|
+
totalThreads: number;
|
|
44
|
+
resolvedThreads: number;
|
|
45
|
+
unresolvedThreads: number;
|
|
46
|
+
totalComments: number;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
export declare class ApiClient {
|
|
50
|
+
private baseUrl;
|
|
51
|
+
constructor(baseUrl?: string);
|
|
52
|
+
createReview(content: string, title?: string): Promise<CreateReviewResponse>;
|
|
53
|
+
getReview(reviewId: string): Promise<ReviewResponse>;
|
|
54
|
+
waitForReview(reviewId: string, timeoutSeconds?: number): Promise<WaitResponse>;
|
|
55
|
+
addComment(threadId: string, body: string): Promise<CommentResponse>;
|
|
56
|
+
}
|
|
57
|
+
export {};
|
|
58
|
+
//# sourceMappingURL=api-client.d.ts.map
|
|
@@ -0,0 +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,YAAY;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,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,OAAO,EAAE;QACP,YAAY,EAAE,MAAM,CAAC;QACrB,eAAe,EAAE,MAAM,CAAC;QACxB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;CACH;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,CAAC,EAAE,MAAM;IAItB,YAAY,CAChB,OAAO,EAAE,MAAM,EACf,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,oBAAoB,CAAC;IAmB1B,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAWpD,aAAa,CACjB,QAAQ,EAAE,MAAM,EAChB,cAAc,SAAM,GACnB,OAAO,CAAC,YAAY,CAAC;IAalB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;CAqB3E"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const BASE_URL = process.env.MDREVIEW_BASE_URL || "https://mdreview.vercel.app";
|
|
2
|
+
export class ApiClient {
|
|
3
|
+
baseUrl;
|
|
4
|
+
constructor(baseUrl) {
|
|
5
|
+
this.baseUrl = baseUrl || BASE_URL;
|
|
6
|
+
}
|
|
7
|
+
async createReview(content, title) {
|
|
8
|
+
const response = await fetch(`${this.baseUrl}/api/reviews`, {
|
|
9
|
+
method: "POST",
|
|
10
|
+
headers: { "Content-Type": "application/json" },
|
|
11
|
+
body: JSON.stringify({
|
|
12
|
+
content,
|
|
13
|
+
title,
|
|
14
|
+
source: "agent",
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
const error = await response.json();
|
|
19
|
+
throw new Error(error.message || "Failed to create review");
|
|
20
|
+
}
|
|
21
|
+
return response.json();
|
|
22
|
+
}
|
|
23
|
+
async getReview(reviewId) {
|
|
24
|
+
const response = await fetch(`${this.baseUrl}/api/reviews/${reviewId}`);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
const error = await response.json();
|
|
27
|
+
throw new Error(error.message || "Failed to get review");
|
|
28
|
+
}
|
|
29
|
+
return response.json();
|
|
30
|
+
}
|
|
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
|
+
async addComment(threadId, body) {
|
|
40
|
+
const response = await fetch(`${this.baseUrl}/api/threads/${threadId}/replies`, {
|
|
41
|
+
method: "POST",
|
|
42
|
+
headers: { "Content-Type": "application/json" },
|
|
43
|
+
body: JSON.stringify({
|
|
44
|
+
body,
|
|
45
|
+
authorType: "agent",
|
|
46
|
+
authorName: "AI Agent",
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const error = await response.json();
|
|
51
|
+
throw new Error(error.message || "Failed to add comment");
|
|
52
|
+
}
|
|
53
|
+
return response.json();
|
|
54
|
+
}
|
|
55
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { ApiClient } from "./api-client.js";
|
|
6
|
+
const server = new McpServer({
|
|
7
|
+
name: "mdreview",
|
|
8
|
+
version: "0.1.0",
|
|
9
|
+
});
|
|
10
|
+
const apiClient = new ApiClient(process.env.MDREVIEW_BASE_URL);
|
|
11
|
+
// Tool: request_review
|
|
12
|
+
server.registerTool("request_review", {
|
|
13
|
+
title: "Request Review",
|
|
14
|
+
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
|
+
inputSchema: {
|
|
16
|
+
content: z.string().describe("The markdown content to review"),
|
|
17
|
+
title: z.string().optional().describe("Optional title for the review"),
|
|
18
|
+
},
|
|
19
|
+
}, async ({ content, title }) => {
|
|
20
|
+
const result = await apiClient.createReview(content, title);
|
|
21
|
+
return {
|
|
22
|
+
content: [
|
|
23
|
+
{
|
|
24
|
+
type: "text",
|
|
25
|
+
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 'wait_for_review' with reviewId "${result.id}" to wait for the review to complete.`,
|
|
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") {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: `Review is still pending after ${timeoutSeconds} seconds. You can call wait_for_review again to continue waiting.`,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
structuredContent: { status: "pending", timedOut: true },
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const commentsText = result.threads
|
|
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");
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
{
|
|
72
|
+
type: "text",
|
|
73
|
+
text: `Review completed!\n\nStatus: ${result.status.toUpperCase()}\nMessage: ${result.decisionMessage || "(none)"}\n\nSummary:\n- Total threads: ${result.summary.totalThreads}\n- Resolved: ${result.summary.resolvedThreads}\n- Unresolved: ${result.summary.unresolvedThreads}\n- Total comments: ${result.summary.totalComments}\n\n${commentsText ? `Comments:\n${commentsText}` : "No comments."}`,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
structuredContent: { ...result },
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
// Tool: get_review_status
|
|
80
|
+
server.registerTool("get_review_status", {
|
|
81
|
+
title: "Get Review Status",
|
|
82
|
+
description: "Get the current status of a review without waiting. Use this to check if a review has been completed.",
|
|
83
|
+
inputSchema: {
|
|
84
|
+
reviewId: z.string().describe("The review ID to check"),
|
|
85
|
+
},
|
|
86
|
+
}, async ({ reviewId }) => {
|
|
87
|
+
const result = await apiClient.getReview(reviewId);
|
|
88
|
+
return {
|
|
89
|
+
content: [
|
|
90
|
+
{
|
|
91
|
+
type: "text",
|
|
92
|
+
text: `Review Status: ${result.status.toUpperCase()}\nTitle: ${result.title || "(untitled)"}\nThreads: ${result.threads.length}\nURL: ${result.url}`,
|
|
93
|
+
},
|
|
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
|
+
},
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
// Tool: add_comment
|
|
103
|
+
server.registerTool("add_comment", {
|
|
104
|
+
title: "Add Comment",
|
|
105
|
+
description: "Add a reply to an existing comment thread. Use this to respond to reviewer feedback.",
|
|
106
|
+
inputSchema: {
|
|
107
|
+
threadId: z.string().describe("The thread ID to reply to"),
|
|
108
|
+
body: z.string().describe("The comment text"),
|
|
109
|
+
},
|
|
110
|
+
}, async ({ threadId, body }) => {
|
|
111
|
+
const result = await apiClient.addComment(threadId, body);
|
|
112
|
+
return {
|
|
113
|
+
content: [
|
|
114
|
+
{
|
|
115
|
+
type: "text",
|
|
116
|
+
text: `Comment added successfully!\n\nThread: ${result.id}\nComment: ${result.body}`,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
structuredContent: {
|
|
120
|
+
commentId: result.id,
|
|
121
|
+
createdAt: result.createdAt,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
async function main() {
|
|
126
|
+
const transport = new StdioServerTransport();
|
|
127
|
+
await server.connect(transport);
|
|
128
|
+
console.error("MDReview MCP Server running on stdio");
|
|
129
|
+
}
|
|
130
|
+
main().catch((error) => {
|
|
131
|
+
console.error("Fatal error:", error);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@scottsus/mdreview-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for MDReview - markdown document review tool",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mdreview-mcp": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
16
|
+
"dev": "tsc --watch",
|
|
17
|
+
"typecheck": "tsc --noEmit"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
21
|
+
"zod": "^3.24.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@mdreview/typescript-config": "workspace:*",
|
|
25
|
+
"@types/node": "^22.10.0",
|
|
26
|
+
"typescript": "^5.7.0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"markdown",
|
|
31
|
+
"review",
|
|
32
|
+
"ai",
|
|
33
|
+
"claude",
|
|
34
|
+
"cursor",
|
|
35
|
+
"opencode"
|
|
36
|
+
],
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/scottsus/mdreview"
|
|
41
|
+
}
|
|
42
|
+
}
|