proofread-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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +191 -0
  3. package/dist/cli.d.ts +9 -0
  4. package/dist/cli.js +65 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/client.d.ts +28 -0
  7. package/dist/client.js +107 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/config.d.ts +7 -0
  10. package/dist/config.js +8 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/errors.d.ts +13 -0
  13. package/dist/errors.js +76 -0
  14. package/dist/errors.js.map +1 -0
  15. package/dist/format.d.ts +13 -0
  16. package/dist/format.js +123 -0
  17. package/dist/format.js.map +1 -0
  18. package/dist/http.d.ts +17 -0
  19. package/dist/http.js +86 -0
  20. package/dist/http.js.map +1 -0
  21. package/dist/index.d.ts +9 -0
  22. package/dist/index.js +9 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/reports.d.ts +3 -0
  25. package/dist/reports.js +22 -0
  26. package/dist/reports.js.map +1 -0
  27. package/dist/resolve_format.d.ts +9 -0
  28. package/dist/resolve_format.js +150 -0
  29. package/dist/resolve_format.js.map +1 -0
  30. package/dist/server.d.ts +12 -0
  31. package/dist/server.js +23 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/sse.d.ts +14 -0
  34. package/dist/sse.js +68 -0
  35. package/dist/sse.js.map +1 -0
  36. package/dist/tools/check_citations.d.ts +13 -0
  37. package/dist/tools/check_citations.js +47 -0
  38. package/dist/tools/check_citations.js.map +1 -0
  39. package/dist/tools/check_document.d.ts +5 -0
  40. package/dist/tools/check_document.js +50 -0
  41. package/dist/tools/check_document.js.map +1 -0
  42. package/dist/tools/coverage.d.ts +1 -0
  43. package/dist/tools/coverage.js +19 -0
  44. package/dist/tools/coverage.js.map +1 -0
  45. package/dist/tools/index.d.ts +2 -0
  46. package/dist/tools/index.js +10 -0
  47. package/dist/tools/index.js.map +1 -0
  48. package/dist/tools/render_report.d.ts +5 -0
  49. package/dist/tools/render_report.js +39 -0
  50. package/dist/tools/render_report.js.map +1 -0
  51. package/dist/tools/resolve_citation.d.ts +4 -0
  52. package/dist/tools/resolve_citation.js +33 -0
  53. package/dist/tools/resolve_citation.js.map +1 -0
  54. package/dist/tools/resolve_citations.d.ts +4 -0
  55. package/dist/tools/resolve_citations.js +30 -0
  56. package/dist/tools/resolve_citations.js.map +1 -0
  57. package/dist/tools/tool.d.ts +25 -0
  58. package/dist/tools/tool.js +28 -0
  59. package/dist/tools/tool.js.map +1 -0
  60. package/dist/types.d.ts +140 -0
  61. package/dist/types.js +4 -0
  62. package/dist/types.js.map +1 -0
  63. package/package.json +57 -0
@@ -0,0 +1,47 @@
1
+ import { z } from "zod";
2
+ import { formatReport } from "../format.js";
3
+ import { rememberReport } from "../reports.js";
4
+ import { DEEP_NOTE, LIMITS_NOTE, defineTool, fail, ok, progressReporter } from "./tool.js";
5
+ export const checkCitations = defineTool({
6
+ name: "check_citations",
7
+ title: "Check legal citations in text",
8
+ description: "Check every case citation in a text against proofread.law's register of about 10 million US court opinions (CourtListener bulk data). " +
9
+ "Use it on a draft brief, memo, letter or any prose that cites cases, before the citations are relied on. " +
10
+ "Returns the coverage statement, counts per tier, one line per row that needs a human " +
11
+ "(red = check this: the register holds something concrete that disagrees, such as a different case at that citation or quoted words not in the opinion; " +
12
+ "orange = cannot verify: nothing to check against, such as a Westlaw/Lexis identifier or a volume newer than the register), " +
13
+ "the number of citations found, and a report id for render_report. " +
14
+ LIMITS_NOTE + " " + DEEP_NOTE + " Free tier: 20 checks a month per IP; a Firm API key in PROOFREAD_API_KEY lifts that.",
15
+ inputSchema: {
16
+ text: z.string().min(1).max(2_000_000).describe("The text to check, as written (paragraphs, footnotes, a whole brief). Pasted text is fine."),
17
+ deep: z.boolean().optional().describe("Also check whether each cited opinion supports the sentence it is cited for. Slower, opt-in, 3 per month on the free tier."),
18
+ },
19
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
20
+ async run({ text, deep }, { client }, extra) {
21
+ try {
22
+ const report = await client.verifyText(text, verifyOptions(deep, extra));
23
+ return ok(formatReport(report, rememberReport(report)), { summary: report.summary, coverage: report.coverage });
24
+ }
25
+ catch (err) {
26
+ return fail(err);
27
+ }
28
+ },
29
+ });
30
+ /** Deep checks stream rows; each finished row becomes a progress notification when the client asked for progress. */
31
+ export function verifyOptions(deep, extra) {
32
+ if (!deep)
33
+ return { signal: extra.signal };
34
+ const progress = progressReporter(extra);
35
+ if (extra._meta?.progressToken === undefined)
36
+ return { deep: true, signal: extra.signal };
37
+ let done = 0;
38
+ return {
39
+ deep: true,
40
+ signal: extra.signal,
41
+ onRow: (row, report) => {
42
+ done += 1;
43
+ progress(done, report.summary.deep_pending, `${row.citation}: ${row.support?.headline ?? row.headline}`);
44
+ },
45
+ };
46
+ }
47
+ //# sourceMappingURL=check_citations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check_citations.js","sourceRoot":"","sources":["../../src/tools/check_citations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,gBAAgB,EAAkB,MAAM,WAAW,CAAC;AAE3G,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;IACvC,IAAI,EAAE,iBAAiB;IACvB,KAAK,EAAE,+BAA+B;IACtC,WAAW,EACT,wIAAwI;QACxI,2GAA2G;QAC3G,uFAAuF;QACvF,yJAAyJ;QACzJ,6HAA6H;QAC7H,oEAAoE;QACpE,WAAW,GAAG,GAAG,GAAG,SAAS,GAAG,uFAAuF;IACzH,WAAW,EAAE;QACX,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,4FAA4F,CAAC;QAC7I,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,4HAA4H,CAAC;KACpK;IACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IACtG,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;YACzE,OAAO,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QAClH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;CACF,CAAC,CAAC;AAEH,qHAAqH;AACrH,MAAM,UAAU,aAAa,CAAC,IAAyB,EAAE,KAAgB;IACvE,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC;IACzC,IAAI,KAAK,CAAC,KAAK,EAAE,aAAa,KAAK,SAAS;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IAC1F,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,OAAO;QACL,IAAI,EAAE,IAAI;QACV,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YACrB,IAAI,IAAI,CAAC,CAAC;YACV,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,GAAG,CAAC,QAAQ,KAAK,GAAG,CAAC,OAAO,EAAE,QAAQ,IAAI,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC3G,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+ export declare const checkDocument: import("./tool.js").ToolDef<{
3
+ path: z.ZodString;
4
+ deep: z.ZodOptional<z.ZodBoolean>;
5
+ }>;
@@ -0,0 +1,50 @@
1
+ import { readFile, stat } from "node:fs/promises";
2
+ import { basename, extname } from "node:path";
3
+ import { z } from "zod";
4
+ import { MAX_UPLOAD_BYTES } from "../client.js";
5
+ import { formatReport } from "../format.js";
6
+ import { rememberReport } from "../reports.js";
7
+ import { verifyOptions } from "./check_citations.js";
8
+ import { DEEP_NOTE, LIMITS_NOTE, defineTool, fail, ok } from "./tool.js";
9
+ const ALLOWED = new Set([".pdf", ".docx", ".txt", ".md"]);
10
+ export const checkDocument = defineTool({
11
+ name: "check_document",
12
+ title: "Check legal citations in a file",
13
+ description: "Check every case citation in a document on disk (PDF, DOCX or TXT, up to 10 MB) against proofread.law's register of about 10 million US court opinions. " +
14
+ "The file is read here and uploaded to proofread.law, which extracts the text in memory, checks it and discards it. " +
15
+ "Returns the same compact result as check_citations: coverage statement, counts per tier, one line per red (check this) or orange (cannot verify) row, " +
16
+ "the number of citations found, and a report id for render_report. " +
17
+ "Scanned PDFs without a text layer, encrypted PDFs and legacy .doc files cannot be read; .docx needs a paid plan. " +
18
+ LIMITS_NOTE + " " + DEEP_NOTE,
19
+ inputSchema: {
20
+ path: z.string().min(1).describe("Absolute path to a .pdf, .docx or .txt file on this machine."),
21
+ deep: z.boolean().optional().describe("Also check whether each cited opinion supports the sentence it is cited for. Slower, opt-in, 3 per month on the free tier."),
22
+ },
23
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
24
+ async run({ path, deep }, { client }, extra) {
25
+ const ext = extname(path).toLowerCase();
26
+ if (!ALLOWED.has(ext)) {
27
+ return fail(new Error(`${path}: only .pdf, .docx, .txt and .md files can be checked`));
28
+ }
29
+ let bytes;
30
+ try {
31
+ const info = await stat(path);
32
+ if (!info.isFile())
33
+ return fail(new Error(`${path} is not a file`));
34
+ if (info.size > MAX_UPLOAD_BYTES)
35
+ return fail(new Error(`${path} is ${info.size} bytes; the cap is 10 MB`));
36
+ bytes = await readFile(path);
37
+ }
38
+ catch (err) {
39
+ return fail(new Error(`could not read ${path}: ${err instanceof Error ? err.message : String(err)}`));
40
+ }
41
+ try {
42
+ const report = await client.verifyFile(bytes, basename(path), verifyOptions(deep, extra));
43
+ return ok(formatReport(report, rememberReport(report)), { summary: report.summary, coverage: report.coverage, input: report.input ?? null });
44
+ }
45
+ catch (err) {
46
+ return fail(err);
47
+ }
48
+ },
49
+ });
50
+ //# sourceMappingURL=check_document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check_document.js","sourceRoot":"","sources":["../../src/tools/check_document.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export declare const coverage: import("./tool.js").ToolDef<{}>;
@@ -0,0 +1,19 @@
1
+ import { defineTool, fail, ok } from "./tool.js";
2
+ export const coverage = defineTool({
3
+ name: "coverage",
4
+ title: "What proofread.law checks against",
5
+ description: "The coverage statement (which opinions the register holds, its date, its known gaps, what is not checked: Westlaw/Lexis identifiers, statutes, regulations, secondary sources) " +
6
+ "and the storage notice (nothing submitted is stored). Call it when a user asks what the check covers, how current it is, or what happens to their text. Free, not counted as a check.",
7
+ inputSchema: {},
8
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
9
+ async run(_args, { client }, extra) {
10
+ try {
11
+ const c = await client.coverage(extra.signal);
12
+ return ok(`Coverage: ${c.coverage}\nStorage: ${c.storage}`, { coverage: c.coverage, storage: c.storage });
13
+ }
14
+ catch (err) {
15
+ return fail(err);
16
+ }
17
+ },
18
+ });
19
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/tools/coverage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEjD,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC;IACjC,IAAI,EAAE,UAAU;IAChB,KAAK,EAAE,mCAAmC;IAC1C,WAAW,EACT,iLAAiL;QACjL,uLAAuL;IACzL,WAAW,EAAE,EAAE;IACf,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IACtG,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK;QAChC,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;YAC9C,OAAO,EAAE,CAAC,aAAa,CAAC,CAAC,QAAQ,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;QAC5G,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { ToolDef } from "./tool.js";
2
+ export declare const tools: ToolDef[];
@@ -0,0 +1,10 @@
1
+ import { checkCitations } from "./check_citations.js";
2
+ import { checkDocument } from "./check_document.js";
3
+ import { coverage } from "./coverage.js";
4
+ import { renderReport } from "./render_report.js";
5
+ import { resolveCitation } from "./resolve_citation.js";
6
+ import { resolveCitations } from "./resolve_citations.js";
7
+ // One file per tool. The remaining register routes (/v1/extract, /v1/case/{id}, /v1/coverage per reporter) slot in the same way:
8
+ // a method on the client, a file here.
9
+ export const tools = [checkCitations, checkDocument, resolveCitation, resolveCitations, coverage, renderReport];
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAG1D,iIAAiI;AACjI,uCAAuC;AACvC,MAAM,CAAC,MAAM,KAAK,GAAc,CAAC,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,QAAQ,EAAE,YAAY,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+ export declare const renderReport: import("./tool.js").ToolDef<{
3
+ report_id: z.ZodOptional<z.ZodString>;
4
+ report: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
5
+ }>;
@@ -0,0 +1,39 @@
1
+ import { z } from "zod";
2
+ import { recallReport } from "../reports.js";
3
+ import { defineTool, fail, ok } from "./tool.js";
4
+ export const renderReport = defineTool({
5
+ name: "render_report",
6
+ title: "Render a check as a markdown report",
7
+ description: "Turn a finished check into a markdown diligence report: header, coverage and storage notices, a summary table sorted check-this, cannot-verify, support, found, " +
8
+ "and a detail block per flagged row with the register evidence. Use it when the user wants a report to keep or attach to the file. " +
9
+ "Pass the report_id returned by check_citations or check_document (ids live in this server's memory until it exits), or the full report JSON from proofread.law's /verify endpoint.",
10
+ inputSchema: {
11
+ report_id: z.string().regex(/^r_[0-9a-f]{8}$/).optional().describe("The 'Report id' from a previous check_citations or check_document result."),
12
+ report: z.record(z.string(), z.unknown()).optional().describe("A full report JSON as returned by POST /verify, if you have one instead of an id."),
13
+ },
14
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
15
+ async run({ report_id, report }, { client }, extra) {
16
+ let source;
17
+ if (report_id) {
18
+ source = recallReport(report_id);
19
+ if (!source)
20
+ return fail(new Error(`no report ${report_id} in memory (this server keeps the last 50 checks until it exits). Run the check again and use the new id.`));
21
+ }
22
+ else if (report && isReport(report)) {
23
+ source = report;
24
+ }
25
+ else {
26
+ return fail(new Error("give either report_id (from a previous check) or report (the full JSON from /verify with summary, rows, coverage and storage)."));
27
+ }
28
+ try {
29
+ return ok(await client.renderMarkdown(source, extra.signal));
30
+ }
31
+ catch (err) {
32
+ return fail(err);
33
+ }
34
+ },
35
+ });
36
+ function isReport(value) {
37
+ return Array.isArray(value.rows) && typeof value.summary === "object" && value.summary !== null && typeof value.coverage === "string";
38
+ }
39
+ //# sourceMappingURL=render_report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render_report.js","sourceRoot":"","sources":["../../src/tools/render_report.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAEjD,MAAM,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC;IACrC,IAAI,EAAE,eAAe;IACrB,KAAK,EAAE,qCAAqC;IAC5C,WAAW,EACT,kKAAkK;QAClK,oIAAoI;QACpI,oLAAoL;IACtL,WAAW,EAAE;QACX,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2EAA2E,CAAC;QAC/I,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,mFAAmF,CAAC;KACnJ;IACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IACtG,KAAK,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK;QAChD,IAAI,MAA0B,CAAC;QAC/B,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;YACjC,IAAI,CAAC,MAAM;gBAAE,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,aAAa,SAAS,2GAA2G,CAAC,CAAC,CAAC;QACzK,CAAC;aAAM,IAAI,MAAM,IAAI,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,MAAM,GAAG,MAAM,CAAC;QAClB,CAAC;aAAM,CAAC;YACN,OAAO,IAAI,CAAC,IAAI,KAAK,CAAC,gIAAgI,CAAC,CAAC,CAAC;QAC3J,CAAC;QACD,IAAI,CAAC;YACH,OAAO,EAAE,CAAC,MAAM,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;CACF,CAAC,CAAC;AAEH,SAAS,QAAQ,CAAC,KAA8B;IAC9C,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,OAAO,KAAK,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,CAAC,QAAQ,KAAK,QAAQ,CAAC;AACxI,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { z } from "zod";
2
+ export declare const resolveCitation: import("./tool.js").ToolDef<{
3
+ citation: z.ZodString;
4
+ }>;
@@ -0,0 +1,33 @@
1
+ import { z } from "zod";
2
+ import { formatResolve } from "../resolve_format.js";
3
+ import { LIMITS_NOTE, defineTool, fail, ok } from "./tool.js";
4
+ export const resolveCitation = defineTool({
5
+ name: "resolve_citation",
6
+ title: "Look up one citation in the register",
7
+ description: "Look up a single case citation (for example '590 U.S. 644', or 'Bostock v. Clayton County, 590 U.S. 644 (2020)') in proofread.law's register and answer: " +
8
+ "is there a case at this citation, which one (name, court, date, parallel citations, link), and how complete the register is for that volume. " +
9
+ "This is a register lookup of the citation, not a comparison with the case name you have: if the case it returns is not the one you expected, the citation points elsewhere. " +
10
+ "Statuses: found; ambiguous (several entries, candidates listed); not in the register (a register fact with a coverage qualifier, never proof that the case does not exist); " +
11
+ "cannot verify (a Westlaw/Lexis identifier, or a volume the register cannot see yet); known citation (other opinions cite it, the opinion itself is not held); no citation recognised. " +
12
+ "Use it when one citation is in doubt; use check_citations for prose, and resolve_citations for a list. " +
13
+ LIMITS_NOTE + " Counts against the resolve quota (1,000 a month free), not the check quota.",
14
+ inputSchema: {
15
+ citation: z.string().min(3).max(500).describe("One citation string. A case name and year around it are fine; only the reporter citation is resolved."),
16
+ },
17
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
18
+ async run({ citation }, { client }, extra) {
19
+ try {
20
+ const result = await client.resolveV1(citation, extra.signal);
21
+ return ok(formatResolve(result), {
22
+ status: result.status,
23
+ cite: result.normalized ?? result.cite,
24
+ case: result.case ? { id: result.case.id, name: result.case.name, court: result.case.court ?? null, date: result.case.date ?? null, url: result.case.url ?? null } : null,
25
+ coverage_statement: result.coverage_statement ?? null,
26
+ });
27
+ }
28
+ catch (err) {
29
+ return fail(err);
30
+ }
31
+ },
32
+ });
33
+ //# sourceMappingURL=resolve_citation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve_citation.js","sourceRoot":"","sources":["../../src/tools/resolve_citation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAE9D,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;IACxC,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,sCAAsC;IAC7C,WAAW,EACT,2JAA2J;QAC3J,+IAA+I;QAC/I,8KAA8K;QAC9K,8KAA8K;QAC9K,wLAAwL;QACxL,yGAAyG;QACzG,WAAW,GAAG,8EAA8E;IAC9F,WAAW,EAAE;QACX,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,uGAAuG,CAAC;KACvJ;IACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IACtG,KAAK,CAAC,GAAG,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK;QACvC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YAC9D,OAAO,EAAE,CAAC,aAAa,CAAC,MAAM,CAAC,EAAE;gBAC/B,MAAM,EAAE,MAAM,CAAC,MAAM;gBACrB,IAAI,EAAE,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,IAAI;gBACtC,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI;gBACzK,kBAAkB,EAAE,MAAM,CAAC,kBAAkB,IAAI,IAAI;aACtD,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { z } from "zod";
2
+ export declare const resolveCitations: import("./tool.js").ToolDef<{
3
+ cites: z.ZodArray<z.ZodString>;
4
+ }>;
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import { MAX_BATCH_CITES } from "../client.js";
3
+ import { formatResolveBatch } from "../resolve_format.js";
4
+ import { LIMITS_NOTE, defineTool, fail, ok } from "./tool.js";
5
+ export const resolveCitations = defineTool({
6
+ name: "resolve_citations",
7
+ title: "Look up a list of citations in the register",
8
+ description: "Look up up to 500 case citation strings in proofread.law's register in one call and get one line per citation, in input order: " +
9
+ "found (the case, court, date, link), ambiguous, not in the register (a register fact with a coverage qualifier, never proof that the case does not exist), " +
10
+ "cannot verify (Westlaw/Lexis identifier, or a volume the register cannot see yet), known citation, or no citation recognised. " +
11
+ "Use it for a table of authorities or any list of citations you already have; use check_citations for prose (it also checks names and quotations). " +
12
+ LIMITS_NOTE + " Each citation counts against the resolve quota (1,000 a month free), not the check quota.",
13
+ inputSchema: {
14
+ cites: z.array(z.string().min(1).max(500)).min(1).max(MAX_BATCH_CITES).describe("Citation strings, one per entry, up to 500."),
15
+ },
16
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
17
+ async run({ cites }, { client }, extra) {
18
+ try {
19
+ const batch = await client.resolveBatch(cites, extra.signal);
20
+ return ok(formatResolveBatch(batch), {
21
+ results: batch.results.map((r) => ({ cite: r.normalized ?? r.cite, status: r.status, case: r.case ? { id: r.case.id, name: r.case.name, url: r.case.url ?? null } : null })),
22
+ coverage_statement: batch.coverage_statement,
23
+ });
24
+ }
25
+ catch (err) {
26
+ return fail(err);
27
+ }
28
+ },
29
+ });
30
+ //# sourceMappingURL=resolve_citations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve_citations.js","sourceRoot":"","sources":["../../src/tools/resolve_citations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AAE9D,MAAM,CAAC,MAAM,gBAAgB,GAAG,UAAU,CAAC;IACzC,IAAI,EAAE,mBAAmB;IACzB,KAAK,EAAE,6CAA6C;IACpD,WAAW,EACT,iIAAiI;QACjI,6JAA6J;QAC7J,gIAAgI;QAChI,oJAAoJ;QACpJ,WAAW,GAAG,4FAA4F;IAC5G,WAAW,EAAE;QACX,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,QAAQ,CAAC,6CAA6C,CAAC;KAC/H;IACD,WAAW,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,eAAe,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE;IACtG,KAAK,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK;QACpC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;YAC7D,OAAO,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE;gBACnC,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC5K,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;aAC7C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,25 @@
1
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
2
+ import type { CallToolResult, ServerNotification, ServerRequest, ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
3
+ import type { z } from "zod";
4
+ import type { Client } from "../client.js";
5
+ export type ToolExtra = RequestHandlerExtra<ServerRequest, ServerNotification>;
6
+ export interface ToolContext {
7
+ client: Client;
8
+ }
9
+ /** One tool = one file. `run` gets parsed arguments, the shared client, and the request extras (signal, progress token). */
10
+ export interface ToolDef<Shape extends z.ZodRawShape = z.ZodRawShape> {
11
+ name: string;
12
+ title: string;
13
+ description: string;
14
+ inputSchema: Shape;
15
+ annotations?: ToolAnnotations;
16
+ run(args: z.infer<z.ZodObject<Shape>>, ctx: ToolContext, extra: ToolExtra): Promise<CallToolResult>;
17
+ }
18
+ export declare function defineTool<Shape extends z.ZodRawShape>(def: ToolDef<Shape>): ToolDef<Shape>;
19
+ export declare function ok(text: string, structuredContent?: Record<string, unknown>): CallToolResult;
20
+ /** Errors go back as a tool result the model can read and act on, not as a protocol error. */
21
+ export declare function fail(err: unknown): CallToolResult;
22
+ /** Sends MCP progress notifications when the client asked for them (deep checks take a while). */
23
+ export declare function progressReporter(extra: ToolExtra): (done: number, total: number | undefined, message: string) => void;
24
+ export declare const LIMITS_NOTE: string;
25
+ export declare const DEEP_NOTE: string;
@@ -0,0 +1,28 @@
1
+ import { explain } from "../errors.js";
2
+ export function defineTool(def) {
3
+ return def;
4
+ }
5
+ export function ok(text, structuredContent) {
6
+ return structuredContent ? { content: [{ type: "text", text }], structuredContent } : { content: [{ type: "text", text }] };
7
+ }
8
+ /** Errors go back as a tool result the model can read and act on, not as a protocol error. */
9
+ export function fail(err) {
10
+ return { content: [{ type: "text", text: explain(err) }], isError: true };
11
+ }
12
+ /** Sends MCP progress notifications when the client asked for them (deep checks take a while). */
13
+ export function progressReporter(extra) {
14
+ const token = extra._meta?.progressToken;
15
+ if (token === undefined)
16
+ return () => { };
17
+ return (done, total, message) => {
18
+ void extra
19
+ .sendNotification({ method: "notifications/progress", params: { progressToken: token, progress: done, ...(total !== undefined ? { total } : {}), message } })
20
+ .catch(() => { });
21
+ };
22
+ }
23
+ export const LIMITS_NOTE = "Cannot: resolve Westlaw (WL) or Lexis identifiers, check statutes, regulations or secondary sources, or say whether a case is still good law. " +
24
+ "A red row means 'check this', never 'this case does not exist'; an orange row means the register has nothing to check against, which is not evidence either way.";
25
+ export const DEEP_NOTE = "deep=true also asks, for each found citation, whether the opinion supports the sentence it is cited for (white rows). " +
26
+ "It is slower (1 to 2 s per citation), opt-in because the clause before each citation is sent to a model judge, limited to 3 per month on the free tier, " +
27
+ "and its answers are a review queue, not a verdict.";
28
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/tools/tool.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAkBvC,MAAM,UAAU,UAAU,CAA8B,GAAmB;IACzE,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,EAAE,CAAC,IAAY,EAAE,iBAA2C;IAC1E,OAAO,iBAAiB,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC9H,CAAC;AAED,8FAA8F;AAC9F,MAAM,UAAU,IAAI,CAAC,GAAY;IAC/B,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC5E,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,gBAAgB,CAAC,KAAgB;IAC/C,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,aAAa,CAAC;IACzC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IACzC,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;QAC9B,KAAK,KAAK;aACP,gBAAgB,CAAC,EAAE,MAAM,EAAE,wBAAwB,EAAE,MAAM,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;aAC5J,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrB,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,WAAW,GACtB,gJAAgJ;IAChJ,kKAAkK,CAAC;AAErK,MAAM,CAAC,MAAM,SAAS,GACpB,wHAAwH;IACxH,0JAA0J;IAC1J,oDAAoD,CAAC"}
@@ -0,0 +1,140 @@
1
+ export type Tier = "red" | "orange" | "green" | "white";
2
+ export interface Support {
3
+ status: "confirmed" | "likely" | "not_confirmed" | "opposite" | "not_checked";
4
+ headline?: string;
5
+ confidence?: number;
6
+ band?: "high" | "medium" | "low";
7
+ passage?: string;
8
+ note?: string;
9
+ }
10
+ export interface Evidence {
11
+ register_name?: string;
12
+ register_date?: string;
13
+ court?: string;
14
+ cluster_id?: number;
15
+ citations?: string[];
16
+ url?: string;
17
+ cite_points_to?: string;
18
+ resolve_match?: string;
19
+ }
20
+ export interface Row {
21
+ n: number;
22
+ tier: Tier;
23
+ tier_label?: string;
24
+ citation: string;
25
+ cite?: string;
26
+ parallel?: string[];
27
+ parties?: string;
28
+ year?: number;
29
+ headline: string;
30
+ detail?: string;
31
+ notes?: string[];
32
+ reason?: string;
33
+ occurrences?: number;
34
+ evidence?: Evidence;
35
+ support?: Support | null;
36
+ [key: string]: unknown;
37
+ }
38
+ export interface Summary {
39
+ citations: number;
40
+ rows: number;
41
+ red: number;
42
+ orange: number;
43
+ green: number;
44
+ white: number;
45
+ review_queue?: number;
46
+ database_ids?: number;
47
+ review_queue_excluding_database_ids?: number;
48
+ deep_pending?: number;
49
+ support?: {
50
+ confirmed: number;
51
+ likely: number;
52
+ not_confirmed: number;
53
+ opposite: number;
54
+ };
55
+ [key: string]: unknown;
56
+ }
57
+ export interface Report {
58
+ version?: string;
59
+ mode: "default" | "deep";
60
+ generated_at?: string;
61
+ input?: {
62
+ kind: string;
63
+ pages?: number;
64
+ bytes?: number;
65
+ chars?: number;
66
+ };
67
+ elapsed_s?: number;
68
+ coverage: string;
69
+ storage: string;
70
+ summary: Summary;
71
+ rows: Row[];
72
+ [key: string]: unknown;
73
+ }
74
+ export interface Coverage {
75
+ coverage: string;
76
+ storage: string;
77
+ }
78
+ /** The error envelope every non-2xx answer carries: {"error": {"code", "message", ...}}. */
79
+ export interface ApiErrorBody {
80
+ error: {
81
+ code: string;
82
+ message: string;
83
+ retry_after?: number;
84
+ plan?: string;
85
+ feature?: string;
86
+ upgrade?: string;
87
+ used?: number;
88
+ limit?: number;
89
+ [key: string]: unknown;
90
+ };
91
+ }
92
+ export type ResolveStatus = "found" | "ambiguous" | "not_found" | "unverifiable" | "beyond_register" | "known_cite" | "unresolvable" | "unparsed";
93
+ export interface ResolveCase {
94
+ id: number;
95
+ name: string;
96
+ court?: string;
97
+ date?: string;
98
+ citations?: string[];
99
+ url?: string;
100
+ }
101
+ export interface ResolveCoverage {
102
+ register_coverage: string;
103
+ reporter?: string;
104
+ volume?: string;
105
+ max_volume?: number;
106
+ volume_n?: number;
107
+ beyond_max?: boolean;
108
+ [key: string]: unknown;
109
+ }
110
+ export interface ResolveResult {
111
+ cite: string;
112
+ normalized: string | null;
113
+ status: ResolveStatus;
114
+ case: ResolveCase | null;
115
+ candidates: ResolveCase[];
116
+ coverage: ResolveCoverage | null;
117
+ match?: "exact" | "pincite";
118
+ known_as?: {
119
+ name: string;
120
+ year?: number;
121
+ court_hint?: string;
122
+ n_citing?: number;
123
+ };
124
+ freshness?: {
125
+ dump: string;
126
+ refreshed: string;
127
+ };
128
+ coverage_statement?: string;
129
+ [key: string]: unknown;
130
+ }
131
+ export interface ResolveBatch {
132
+ results: ResolveResult[];
133
+ freshness?: {
134
+ dump: string;
135
+ refreshed: string;
136
+ };
137
+ coverage_statement: string;
138
+ plan?: string;
139
+ elapsed_s?: number;
140
+ }
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ // Shapes returned by https://proofread.law (see the service contract in citation-bench/app/README.md).
2
+ // Only the fields the server reads are typed; everything else passes through untouched.
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,uGAAuG;AACvG,wFAAwF"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "proofread-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for proofread.law: check legal citations from Claude, Cursor or the OpenAI Agents SDK",
5
+ "license": "MIT",
6
+ "author": "Data Alchemy Labs",
7
+ "type": "module",
8
+ "bin": {
9
+ "proofread-mcp": "dist/cli.js"
10
+ },
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": [
14
+ "dist",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json && node -e \"require('fs').chmodSync('dist/cli.js', 0o755)\"",
23
+ "test": "vitest run",
24
+ "test:live": "LIVE=1 vitest run test/live.test.ts",
25
+ "start": "node dist/cli.js",
26
+ "prepublishOnly": "npm run build && npm test"
27
+ },
28
+ "keywords": [
29
+ "case-law",
30
+ "citations",
31
+ "claude",
32
+ "cursor",
33
+ "legal",
34
+ "mcp",
35
+ "model-context-protocol",
36
+ "openai",
37
+ "proofread",
38
+ "proofread.law"
39
+ ],
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.30.0",
42
+ "zod": "^4.6.5"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^26.6.2",
46
+ "typescript": "^7.0.2",
47
+ "vitest": "^5.0.1"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/Data-Alchemy-Labs/proofread-mcp.git"
52
+ },
53
+ "homepage": "https://github.com/Data-Alchemy-Labs/proofread-mcp#readme",
54
+ "bugs": {
55
+ "url": "https://github.com/Data-Alchemy-Labs/proofread-mcp/issues"
56
+ }
57
+ }