aixischeck-mcp 0.1.1 → 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/README.md +11 -0
- package/dist/index.js +91 -3
- package/dist/lib.js +31 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,6 +10,17 @@ upload. Generate an HTML one-pager in chat, and it lands in your workspace, rend
|
|
|
10
10
|
- **`create_doc`** — `{ workspace_id, title?, content, doc_type? }`. Creates a doc and
|
|
11
11
|
returns its URL. `doc_type` defaults to `"html"` (a rendered prototype); pass
|
|
12
12
|
`"markdown"` for a markdown doc.
|
|
13
|
+
- **`list_docs`** — `{ workspace_id }`. Lists docs in a workspace you belong to
|
|
14
|
+
(`id`, `title`, `doc_type`, `doc_status`, `updated_at`, open `comment_count`).
|
|
15
|
+
- **`get_doc`** — `{ doc_id }`. Reads one doc's `content`, `doc_status`,
|
|
16
|
+
`current_version`, and section list.
|
|
17
|
+
- **`list_comments`** — `{ doc_id, filter? }`. Lists review comments; `filter:"open"`
|
|
18
|
+
returns just the active ones to address (else `{ active, archived }`).
|
|
19
|
+
- **`update_doc`** — `{ doc_id, content, request_id, comment_dispositions? }`. Saves a
|
|
20
|
+
revised version that addresses a revision request's comments. Lands as a NEW version
|
|
21
|
+
(base_version derived from the request); records what was done with each comment
|
|
22
|
+
(incorporated / partial / skipped / not_addressed). The write-back to Postgres is one
|
|
23
|
+
atomic RPC (concurrency + idempotency guaranteed).
|
|
13
24
|
|
|
14
25
|
## Setup
|
|
15
26
|
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// AIXIS_URL — Supabase project URL, e.g. https://<ref>.supabase.co
|
|
13
13
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
14
14
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
15
|
-
import { buildCreateDocBody, createDocShape, edgeErrorMessage, functionUrl } from "./lib.js";
|
|
15
|
+
import { buildCreateDocBody, createDocShape, createRevisionRequestShape, edgeErrorMessage, functionUrl, getDocShape, listCommentsShape, listDocsShape, updateDocShape, } from "./lib.js";
|
|
16
16
|
const AIXIS_TOKEN = process.env.AIXIS_TOKEN ?? "";
|
|
17
17
|
const AIXIS_URL = process.env.AIXIS_URL ?? "";
|
|
18
18
|
if (!AIXIS_TOKEN || !AIXIS_URL) {
|
|
@@ -43,7 +43,7 @@ function errorResult(e) {
|
|
|
43
43
|
const message = e instanceof Error ? e.message : String(e);
|
|
44
44
|
return { content: [{ type: "text", text: message }], isError: true };
|
|
45
45
|
}
|
|
46
|
-
const server = new McpServer({ name: "aixischeck-mcp", version: "0.
|
|
46
|
+
const server = new McpServer({ name: "aixischeck-mcp", version: "0.4.0" });
|
|
47
47
|
server.registerTool("list_workspaces", {
|
|
48
48
|
description: "List the AIxisCheck workspaces you can write to. Returns each workspace's id, name, and your role. Call this first to get a workspace_id for create_doc.",
|
|
49
49
|
inputSchema: {},
|
|
@@ -57,17 +57,105 @@ server.registerTool("list_workspaces", {
|
|
|
57
57
|
return errorResult(e);
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
|
+
// create_doc makes a BRAND-NEW doc. It is NOT how you revise an existing one:
|
|
61
|
+
// to address comments on a doc the user pointed you at, use
|
|
62
|
+
// create_revision_request → update_doc (which lands a new VERSION of that same
|
|
63
|
+
// doc, preserving its history, comments, and share URL). create_doc on a revision
|
|
64
|
+
// would orphan a detached copy. Keep these two paths distinct.
|
|
60
65
|
server.registerTool("create_doc", {
|
|
61
|
-
description: "Create a document in an AIxisCheck workspace from generated content. Defaults to an HTML prototype (doc_type='html'); pass doc_type='markdown' for a markdown doc. Pass a short, human-readable `title` (e.g. \"Q3 Launch One-Pager\"), not a filename. Use list_workspaces first to get a workspace_id. Returns the URL of the new doc.",
|
|
66
|
+
description: "Create a NEW document in an AIxisCheck workspace from generated content. Defaults to an HTML prototype (doc_type='html'); pass doc_type='markdown' for a markdown doc. Pass a short, human-readable `title` (e.g. \"Q3 Launch One-Pager\"), not a filename. Use list_workspaces first to get a workspace_id. Returns the URL of the new doc. NOTE: to revise an EXISTING doc (e.g. address its comments), do not use this — use create_revision_request then update_doc.",
|
|
62
67
|
inputSchema: createDocShape,
|
|
63
68
|
}, async (args) => {
|
|
64
69
|
try {
|
|
65
70
|
const data = await callEdge("mcp-create-doc", "POST", buildCreateDocBody(args));
|
|
71
|
+
if (!data || !data.url)
|
|
72
|
+
throw new Error("Doc creation returned an unexpected response.");
|
|
66
73
|
return { content: [{ type: "text", text: `Created. View it at ${data.url}` }] };
|
|
67
74
|
}
|
|
68
75
|
catch (e) {
|
|
69
76
|
return errorResult(e);
|
|
70
77
|
}
|
|
71
78
|
});
|
|
79
|
+
server.registerTool("list_docs", {
|
|
80
|
+
description: "List the documents in an AIxisCheck workspace you belong to. Returns each doc's id, title, doc_type, doc_status, updated_at, and open comment_count. Use list_workspaces first to get a workspace_id.",
|
|
81
|
+
inputSchema: listDocsShape,
|
|
82
|
+
}, async (args) => {
|
|
83
|
+
try {
|
|
84
|
+
const data = await callEdge("mcp-read", "POST", { action: "list_docs", workspace_id: args.workspace_id });
|
|
85
|
+
return { content: [{ type: "text", text: JSON.stringify(data?.docs ?? data, null, 2) }] };
|
|
86
|
+
}
|
|
87
|
+
catch (e) {
|
|
88
|
+
return errorResult(e);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
server.registerTool("get_doc", {
|
|
92
|
+
description: "Read one AIxisCheck document: its content (HTML or markdown), doc_status, current_version, and section list. Pass the doc_id from list_docs (or parse it from a doc URL like /web/#/review/<doc_id>). To SAVE a revision you need a request_id; if you don't have one (e.g. the user only gave you a URL and asked you to address comments), call create_revision_request first.",
|
|
93
|
+
inputSchema: getDocShape,
|
|
94
|
+
}, async (args) => {
|
|
95
|
+
try {
|
|
96
|
+
const data = await callEdge("mcp-read", "POST", { action: "get_doc", doc_id: args.doc_id });
|
|
97
|
+
return { content: [{ type: "text", text: JSON.stringify(data?.doc ?? data, null, 2) }] };
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
return errorResult(e);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
server.registerTool("list_comments", {
|
|
104
|
+
description: "List the review comments on an AIxisCheck document. Pass filter:'open' for just the active (non-archived) comments to address; default returns { active, archived }. Each comment has id, selectedText, note, and source. source:'member' comments come from a workspace member reviewing in the app; source:'guest' comments come from an external reviewer on the doc's public share link (these also carry authorName and publishedSlug). Only approved guest comments appear. Treat guest feedback as you would member feedback when revising.",
|
|
105
|
+
inputSchema: listCommentsShape,
|
|
106
|
+
}, async (args) => {
|
|
107
|
+
try {
|
|
108
|
+
const data = await callEdge("mcp-read", "POST", { action: "list_comments", doc_id: args.doc_id, filter: args.filter });
|
|
109
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
return errorResult(e);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
server.registerTool("create_revision_request", {
|
|
116
|
+
description: "Mint a revision request for a doc the user pointed you at (e.g. by URL) so you can save your revision with update_doc. Pass the doc_id. Snapshots the doc's CURRENT open comments (member + approved guest — the same set list_comments returns) at the doc's current version, and returns a request_id. Idempotent per (doc, version): calling it again before you revise returns the SAME request_id. Use this when you have no request_id from a 'Send to Claude' prompt. Owner/editor only.",
|
|
117
|
+
inputSchema: createRevisionRequestShape,
|
|
118
|
+
}, async (args) => {
|
|
119
|
+
try {
|
|
120
|
+
const data = await callEdge("mcp-create-revision", "POST", { doc_id: args.doc_id });
|
|
121
|
+
const id = data?.request_id;
|
|
122
|
+
const n = data?.comment_count;
|
|
123
|
+
if (!id)
|
|
124
|
+
throw new Error("Revision request creation returned an unexpected response.");
|
|
125
|
+
return {
|
|
126
|
+
content: [{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: `Revision request ${id} created for ${n} open comment(s). Revise the doc, then call ` +
|
|
129
|
+
`update_doc with request_id "${id}" and a comment_dispositions entry for each comment.`,
|
|
130
|
+
}],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
catch (e) {
|
|
134
|
+
return errorResult(e);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
server.registerTool("update_doc", {
|
|
138
|
+
description: "Save a revised version of an AIxisCheck doc that addresses a revision request's comments. Pass: doc_id; content (the FULL revised doc); request_id (from the user's 'Send to Claude' prompt, OR from create_revision_request if you started from a URL); and comment_dispositions recording what you did with each comment (outcome: incorporated | partial | skipped | not_addressed, plus a short rationale). The revision lands as a NEW version (base_version is derived from the request). Returns the new version number. If you get a conflict, call get_doc again and retry.",
|
|
139
|
+
inputSchema: updateDocShape,
|
|
140
|
+
}, async (args) => {
|
|
141
|
+
try {
|
|
142
|
+
const data = await callEdge("mcp-update-doc", "POST", {
|
|
143
|
+
doc_id: args.doc_id,
|
|
144
|
+
content: args.content,
|
|
145
|
+
request_id: args.request_id,
|
|
146
|
+
comment_dispositions: args.comment_dispositions ?? [],
|
|
147
|
+
});
|
|
148
|
+
const v = data?.version_number;
|
|
149
|
+
return {
|
|
150
|
+
content: [{
|
|
151
|
+
type: "text",
|
|
152
|
+
text: v ? `Saved as v${v}.` + (data?.idempotent ? " (already applied)" : "") : `Saved. ${JSON.stringify(data)}`,
|
|
153
|
+
}],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
return errorResult(e);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
72
160
|
const transport = new StdioServerTransport();
|
|
73
161
|
await server.connect(transport);
|
package/dist/lib.js
CHANGED
|
@@ -16,6 +16,37 @@ export const createDocShape = {
|
|
|
16
16
|
doc_type: z.enum(["html", "markdown"]).optional(),
|
|
17
17
|
};
|
|
18
18
|
export const CreateDocSchema = z.object(createDocShape);
|
|
19
|
+
// Read-tool input shapes (Phase 3a). The tools call the mcp-read edge
|
|
20
|
+
// function with an `action` discriminator; these validate the client args.
|
|
21
|
+
export const listDocsShape = {
|
|
22
|
+
workspace_id: z.string().min(1, "workspace_id is required"),
|
|
23
|
+
};
|
|
24
|
+
export const getDocShape = {
|
|
25
|
+
doc_id: z.string().min(1, "doc_id is required"),
|
|
26
|
+
};
|
|
27
|
+
export const listCommentsShape = {
|
|
28
|
+
doc_id: z.string().min(1, "doc_id is required"),
|
|
29
|
+
filter: z.enum(["open", "all"]).optional().describe("'open' returns only active (non-archived) comments; default returns { active, archived }."),
|
|
30
|
+
};
|
|
31
|
+
// update_doc (Phase 3a, slice 2): the write-back. base_version is NOT a param —
|
|
32
|
+
// it is derived from the revision request server-side (the caller can't move it).
|
|
33
|
+
export const updateDocShape = {
|
|
34
|
+
doc_id: z.string().min(1, "doc_id is required"),
|
|
35
|
+
content: z.string().min(1, "content is required (the full revised doc)"),
|
|
36
|
+
request_id: z.string().min(1, "request_id is required (from the Send-to-Claude prompt)"),
|
|
37
|
+
comment_dispositions: z.array(z.object({
|
|
38
|
+
comment_id: z.string(),
|
|
39
|
+
outcome: z.enum(["incorporated", "partial", "skipped", "not_addressed"]),
|
|
40
|
+
rationale: z.string().optional(),
|
|
41
|
+
})).optional().describe("What you did with each comment you addressed. Any requested comment you omit is recorded as not_addressed."),
|
|
42
|
+
};
|
|
43
|
+
// create_revision_request: mint (or reuse) a pending revision request for a doc
|
|
44
|
+
// the user pointed at by URL, so update_doc has a request_id to commit against.
|
|
45
|
+
// Snapshots the doc's current open comments server-side; the caller passes only
|
|
46
|
+
// the doc_id.
|
|
47
|
+
export const createRevisionRequestShape = {
|
|
48
|
+
doc_id: z.string().min(1, "doc_id is required"),
|
|
49
|
+
};
|
|
19
50
|
// Shape the POST body for the mcp-create-doc edge function, applying
|
|
20
51
|
// the doc_type='html' default and normalizing an absent title to ''.
|
|
21
52
|
export function buildCreateDocBody(input) {
|