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.
- package/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.js +65 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +28 -0
- package/dist/client.js +107 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +8 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.js +76 -0
- package/dist/errors.js.map +1 -0
- package/dist/format.d.ts +13 -0
- package/dist/format.js +123 -0
- package/dist/format.js.map +1 -0
- package/dist/http.d.ts +17 -0
- package/dist/http.js +86 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/reports.d.ts +3 -0
- package/dist/reports.js +22 -0
- package/dist/reports.js.map +1 -0
- package/dist/resolve_format.d.ts +9 -0
- package/dist/resolve_format.js +150 -0
- package/dist/resolve_format.js.map +1 -0
- package/dist/server.d.ts +12 -0
- package/dist/server.js +23 -0
- package/dist/server.js.map +1 -0
- package/dist/sse.d.ts +14 -0
- package/dist/sse.js +68 -0
- package/dist/sse.js.map +1 -0
- package/dist/tools/check_citations.d.ts +13 -0
- package/dist/tools/check_citations.js +47 -0
- package/dist/tools/check_citations.js.map +1 -0
- package/dist/tools/check_document.d.ts +5 -0
- package/dist/tools/check_document.js +50 -0
- package/dist/tools/check_document.js.map +1 -0
- package/dist/tools/coverage.d.ts +1 -0
- package/dist/tools/coverage.js +19 -0
- package/dist/tools/coverage.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.js +10 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/render_report.d.ts +5 -0
- package/dist/tools/render_report.js +39 -0
- package/dist/tools/render_report.js.map +1 -0
- package/dist/tools/resolve_citation.d.ts +4 -0
- package/dist/tools/resolve_citation.js +33 -0
- package/dist/tools/resolve_citation.js.map +1 -0
- package/dist/tools/resolve_citations.d.ts +4 -0
- package/dist/tools/resolve_citations.js +30 -0
- package/dist/tools/resolve_citations.js.map +1 -0
- package/dist/tools/tool.d.ts +25 -0
- package/dist/tools/tool.js +28 -0
- package/dist/tools/tool.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/package.json +57 -0
package/dist/format.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/** The words a user sees per tier (PRODUCT.md): say what was checked and what was found, never a verdict on the author. */
|
|
2
|
+
export const TIER_WORD = {
|
|
3
|
+
red: "CHECK THIS",
|
|
4
|
+
orange: "CANNOT VERIFY",
|
|
5
|
+
green: "FOUND",
|
|
6
|
+
white: "DEEP CHECK",
|
|
7
|
+
};
|
|
8
|
+
const PASSAGE_CHARS = 300;
|
|
9
|
+
/**
|
|
10
|
+
* A compact, model-friendly view of a report: the coverage statement first, then the counts,
|
|
11
|
+
* then one line per row that needs a human (red, orange and, in deep mode, white), then the count of found rows.
|
|
12
|
+
* Westlaw/Lexis identifiers are collapsed into one line, as on the site.
|
|
13
|
+
*/
|
|
14
|
+
export function formatReport(report, reportId) {
|
|
15
|
+
const s = report.summary;
|
|
16
|
+
const lines = [];
|
|
17
|
+
lines.push(`Coverage: ${report.coverage}`);
|
|
18
|
+
lines.push(`Summary: ${s.citations} citation${s.citations === 1 ? "" : "s"}, ${s.rows} row${s.rows === 1 ? "" : "s"}. ` +
|
|
19
|
+
`Check this (red): ${s.red}. Cannot verify (orange): ${s.orange}` +
|
|
20
|
+
(s.database_ids ? `, of which ${s.database_ids} Westlaw/Lexis identifier${s.database_ids === 1 ? "" : "s"}` : "") +
|
|
21
|
+
`. Found (green): ${s.green}.` +
|
|
22
|
+
(report.mode === "deep" ? ` Deep check (white): ${s.white}.` : ""));
|
|
23
|
+
const flagged = report.rows.filter((r) => r.tier !== "green");
|
|
24
|
+
const ids = flagged.filter((r) => r.reason === "database_id");
|
|
25
|
+
const rest = flagged.filter((r) => r.reason !== "database_id");
|
|
26
|
+
if (flagged.length === 0) {
|
|
27
|
+
lines.push("Flagged rows: none.");
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
lines.push("Flagged rows:");
|
|
31
|
+
for (const row of rest)
|
|
32
|
+
lines.push(formatRowLine(row));
|
|
33
|
+
if (ids.length)
|
|
34
|
+
lines.push(databaseIdsLine(ids));
|
|
35
|
+
}
|
|
36
|
+
lines.push(`Found: ${s.green} row${s.green === 1 ? "" : "s"} resolved to a case in the register.`);
|
|
37
|
+
if (report.mode === "deep") {
|
|
38
|
+
lines.push("Deep check rows are a review queue, not a verdict: confirm the passage yourself before relying on it.");
|
|
39
|
+
}
|
|
40
|
+
const tail = [];
|
|
41
|
+
if (reportId)
|
|
42
|
+
tail.push(`Report id: ${reportId} (give it to render_report for a markdown report).`);
|
|
43
|
+
if (report.elapsed_s !== undefined)
|
|
44
|
+
tail.push(`Elapsed: ${report.elapsed_s} s.`);
|
|
45
|
+
if (tail.length)
|
|
46
|
+
lines.push(tail.join(" "));
|
|
47
|
+
lines.push(`Storage: ${report.storage}`);
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
/** One line: tier word, citation (parties), headline, detail, register link. */
|
|
51
|
+
export function formatRowLine(row) {
|
|
52
|
+
const who = row.parties ? ` (${row.parties}${row.year ? `, ${row.year}` : ""})` : "";
|
|
53
|
+
const times = row.occurrences && row.occurrences > 1 ? ` [cited ${row.occurrences} times]` : "";
|
|
54
|
+
const parts = [`- ${tierWord(row.tier)}: ${row.citation}${who}${times}.`];
|
|
55
|
+
if (row.tier === "white" && row.support) {
|
|
56
|
+
parts.push(supportText(row));
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
parts.push(sentence(row.headline));
|
|
60
|
+
if (row.detail)
|
|
61
|
+
parts.push(sentence(row.detail));
|
|
62
|
+
}
|
|
63
|
+
if (row.evidence?.url)
|
|
64
|
+
parts.push(`Register: ${row.evidence.url}`);
|
|
65
|
+
return parts.join(" ");
|
|
66
|
+
}
|
|
67
|
+
/** A full view of one row, for resolve_citation. */
|
|
68
|
+
export function formatRowDetail(row) {
|
|
69
|
+
const lines = [
|
|
70
|
+
`${tierWord(row.tier)}: ${row.citation}` + (row.parties ? ` (${row.parties}${row.year ? `, ${row.year}` : ""})` : ""),
|
|
71
|
+
sentence(row.headline),
|
|
72
|
+
];
|
|
73
|
+
if (row.detail)
|
|
74
|
+
lines.push(sentence(row.detail));
|
|
75
|
+
for (const note of row.notes ?? [])
|
|
76
|
+
lines.push(`Note: ${sentence(note)}`);
|
|
77
|
+
const e = row.evidence;
|
|
78
|
+
if (e) {
|
|
79
|
+
const facts = [];
|
|
80
|
+
if (e.register_name)
|
|
81
|
+
facts.push(`register name: ${e.register_name}`);
|
|
82
|
+
if (e.court)
|
|
83
|
+
facts.push(`court: ${e.court}`);
|
|
84
|
+
if (e.register_date)
|
|
85
|
+
facts.push(`date: ${e.register_date}`);
|
|
86
|
+
if (e.citations?.length)
|
|
87
|
+
facts.push(`citations: ${e.citations.join("; ")}`);
|
|
88
|
+
if (e.cite_points_to)
|
|
89
|
+
facts.push(`this citation points to: ${e.cite_points_to}`);
|
|
90
|
+
if (facts.length)
|
|
91
|
+
lines.push(`Evidence: ${facts.join("; ")}.`);
|
|
92
|
+
if (e.url)
|
|
93
|
+
lines.push(`Register: ${e.url}`);
|
|
94
|
+
}
|
|
95
|
+
return lines.join("\n");
|
|
96
|
+
}
|
|
97
|
+
function databaseIdsLine(rows) {
|
|
98
|
+
const cites = rows.map((r) => r.citation);
|
|
99
|
+
const n = rows.length;
|
|
100
|
+
return `- CANNOT VERIFY: ${n} Westlaw/Lexis identifier${n === 1 ? "" : "s"} (${cites.join("; ")}). ` +
|
|
101
|
+
"Open registers cannot resolve these; check them in Westlaw or Lexis.";
|
|
102
|
+
}
|
|
103
|
+
function supportText(row) {
|
|
104
|
+
const sup = row.support;
|
|
105
|
+
const parts = [sentence(sup.headline ?? `Support: ${sup.status}`)];
|
|
106
|
+
if (sup.confidence !== undefined)
|
|
107
|
+
parts.push(`(confidence ${sup.confidence.toFixed(2)}${sup.band ? `, ${sup.band}` : ""})`);
|
|
108
|
+
if (sup.passage && sup.status !== "confirmed")
|
|
109
|
+
parts.push(`Closest passage: "${clip(sup.passage, PASSAGE_CHARS)}"`);
|
|
110
|
+
return parts.join(" ");
|
|
111
|
+
}
|
|
112
|
+
function sentence(text) {
|
|
113
|
+
const t = text.trim();
|
|
114
|
+
return /[.!?]$/.test(t) ? t : `${t}.`;
|
|
115
|
+
}
|
|
116
|
+
function clip(text, max) {
|
|
117
|
+
const t = text.replace(/\s+/g, " ").trim();
|
|
118
|
+
return t.length <= max ? t : `${t.slice(0, max - 1)}…`;
|
|
119
|
+
}
|
|
120
|
+
function tierWord(tier) {
|
|
121
|
+
return TIER_WORD[tier] ?? String(tier).toUpperCase();
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ServerOptions } from "./server.js";
|
|
2
|
+
export interface HttpOptions extends ServerOptions {
|
|
3
|
+
port: number;
|
|
4
|
+
host?: string;
|
|
5
|
+
/** Hosts allowed in the Host header (DNS-rebinding protection). Defaults to localhost forms when binding to loopback. */
|
|
6
|
+
allowedHosts?: string[];
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Serves MCP over Streamable HTTP at /mcp: one McpServer and transport per session (initialize starts one,
|
|
10
|
+
* the Mcp-Session-Id header names it, DELETE ends it). GET /health answers 200 for load balancers.
|
|
11
|
+
*/
|
|
12
|
+
export declare function startHttp(options: HttpOptions): Promise<{
|
|
13
|
+
close(): Promise<void>;
|
|
14
|
+
url: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** The Host header values a loopback bind should accept; undefined (no protection) when bound to a real interface. */
|
|
17
|
+
export declare function loopbackHosts(host: string, port: number): string[] | undefined;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createServer as createHttpServer } from "node:http";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { createServer } from "./server.js";
|
|
5
|
+
/**
|
|
6
|
+
* Serves MCP over Streamable HTTP at /mcp: one McpServer and transport per session (initialize starts one,
|
|
7
|
+
* the Mcp-Session-Id header names it, DELETE ends it). GET /health answers 200 for load balancers.
|
|
8
|
+
*/
|
|
9
|
+
export function startHttp(options) {
|
|
10
|
+
const host = options.host ?? "127.0.0.1";
|
|
11
|
+
let port = options.port; // replaced by the bound port once listening (port 0 lets the OS pick)
|
|
12
|
+
const allowedHosts = () => options.allowedHosts ?? loopbackHosts(host, port);
|
|
13
|
+
const sessions = new Map();
|
|
14
|
+
async function handle(req, res) {
|
|
15
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
16
|
+
if (url.pathname === "/health") {
|
|
17
|
+
res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify({ ok: true, sessions: sessions.size }));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (url.pathname !== "/mcp") {
|
|
21
|
+
res.writeHead(404, { "content-type": "text/plain" }).end("not found; MCP is served at /mcp");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
25
|
+
const existing = typeof sessionId === "string" ? sessions.get(sessionId) : undefined;
|
|
26
|
+
if (existing) {
|
|
27
|
+
await existing.handleRequest(req, res);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (req.method !== "POST") {
|
|
31
|
+
res.writeHead(400, { "content-type": "application/json" }).end(JSON.stringify({ jsonrpc: "2.0", error: { code: -32000, message: "no session; POST initialize first" }, id: null }));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const hosts = allowedHosts();
|
|
35
|
+
const transport = new StreamableHTTPServerTransport({
|
|
36
|
+
sessionIdGenerator: () => randomUUID(),
|
|
37
|
+
onsessioninitialized: (id) => {
|
|
38
|
+
sessions.set(id, transport);
|
|
39
|
+
},
|
|
40
|
+
onsessionclosed: (id) => {
|
|
41
|
+
sessions.delete(id);
|
|
42
|
+
},
|
|
43
|
+
...(hosts ? { allowedHosts: hosts, enableDnsRebindingProtection: true } : {}),
|
|
44
|
+
});
|
|
45
|
+
transport.onclose = () => {
|
|
46
|
+
if (transport.sessionId)
|
|
47
|
+
sessions.delete(transport.sessionId);
|
|
48
|
+
};
|
|
49
|
+
const server = createServer(options);
|
|
50
|
+
await server.connect(transport);
|
|
51
|
+
await transport.handleRequest(req, res);
|
|
52
|
+
}
|
|
53
|
+
const httpServer = createHttpServer((req, res) => {
|
|
54
|
+
handle(req, res).catch((err) => {
|
|
55
|
+
console.error("proofread-mcp http:", err);
|
|
56
|
+
if (!res.headersSent)
|
|
57
|
+
res.writeHead(500, { "content-type": "text/plain" }).end("internal error");
|
|
58
|
+
else
|
|
59
|
+
res.end();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
httpServer.once("error", reject);
|
|
64
|
+
httpServer.listen(options.port, host, () => {
|
|
65
|
+
const address = httpServer.address();
|
|
66
|
+
if (typeof address === "object" && address)
|
|
67
|
+
port = address.port;
|
|
68
|
+
resolve({
|
|
69
|
+
url: `http://${host}:${port}/mcp`,
|
|
70
|
+
close: async () => {
|
|
71
|
+
await Promise.all([...sessions.values()].map((t) => t.close()));
|
|
72
|
+
await new Promise((done) => httpServer.close(() => done()));
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** The Host header values a loopback bind should accept; undefined (no protection) when bound to a real interface. */
|
|
79
|
+
export function loopbackHosts(host, port) {
|
|
80
|
+
if (host === "127.0.0.1" || host === "localhost")
|
|
81
|
+
return [`127.0.0.1:${port}`, `localhost:${port}`, "127.0.0.1", "localhost"];
|
|
82
|
+
if (host === "::1")
|
|
83
|
+
return [`[::1]:${port}`, `localhost:${port}`, "[::1]", "localhost"];
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=http.js.map
|
package/dist/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createServer, INSTRUCTIONS, SERVER_NAME, SERVER_VERSION } from "./server.js";
|
|
2
|
+
export { startHttp } from "./http.js";
|
|
3
|
+
export { createClient, verifyPath, MAX_BATCH_CITES, type Client, type VerifyOptions } from "./client.js";
|
|
4
|
+
export { configFromEnv, DEFAULT_BASE_URL, type Config } from "./config.js";
|
|
5
|
+
export { ProofreadError, explain } from "./errors.js";
|
|
6
|
+
export { formatReport, formatRowLine, formatRowDetail, TIER_WORD } from "./format.js";
|
|
7
|
+
export { formatResolve, formatResolveLine, formatResolveBatch, STATUS_WORD } from "./resolve_format.js";
|
|
8
|
+
export { tools } from "./tools/index.js";
|
|
9
|
+
export type { Report, Row, Summary, Tier, Coverage, ResolveResult, ResolveBatch, ResolveStatus, ResolveCase, ResolveCoverage } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createServer, INSTRUCTIONS, SERVER_NAME, SERVER_VERSION } from "./server.js";
|
|
2
|
+
export { startHttp } from "./http.js";
|
|
3
|
+
export { createClient, verifyPath, MAX_BATCH_CITES } from "./client.js";
|
|
4
|
+
export { configFromEnv, DEFAULT_BASE_URL } from "./config.js";
|
|
5
|
+
export { ProofreadError, explain } from "./errors.js";
|
|
6
|
+
export { formatReport, formatRowLine, formatRowDetail, TIER_WORD } from "./format.js";
|
|
7
|
+
export { formatResolve, formatResolveLine, formatResolveBatch, STATUS_WORD } from "./resolve_format.js";
|
|
8
|
+
export { tools } from "./tools/index.js";
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,eAAe,EAAmC,MAAM,aAAa,CAAC;AACzG,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAe,MAAM,aAAa,CAAC;AAC3E,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACxG,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/reports.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* The last few reports, in this process's memory only, so render_report can be called with a short id
|
|
4
|
+
* instead of the model echoing the whole report JSON. Nothing is written to disk; the map dies with the process.
|
|
5
|
+
*/
|
|
6
|
+
const MAX_REPORTS = 50;
|
|
7
|
+
const reports = new Map();
|
|
8
|
+
export function rememberReport(report) {
|
|
9
|
+
const id = `r_${randomBytes(4).toString("hex")}`;
|
|
10
|
+
reports.set(id, report);
|
|
11
|
+
while (reports.size > MAX_REPORTS) {
|
|
12
|
+
const oldest = reports.keys().next().value;
|
|
13
|
+
if (oldest === undefined)
|
|
14
|
+
break;
|
|
15
|
+
reports.delete(oldest);
|
|
16
|
+
}
|
|
17
|
+
return id;
|
|
18
|
+
}
|
|
19
|
+
export function recallReport(id) {
|
|
20
|
+
return reports.get(id);
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=reports.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reports.js","sourceRoot":"","sources":["../src/reports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C;;;GAGG;AACH,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;AAE1C,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,MAAM,EAAE,GAAG,KAAK,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;IACjD,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IACxB,OAAO,OAAO,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QAC3C,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM;QAChC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,EAAU;IACrC,OAAO,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ResolveBatch, ResolveResult, ResolveStatus } from "./types.js";
|
|
2
|
+
/** The words a user sees per register status. A missing entry is a register fact with a coverage qualifier, never a verdict on the author. */
|
|
3
|
+
export declare const STATUS_WORD: Record<ResolveStatus, string>;
|
|
4
|
+
/** The full answer for one citation: status, the case or the reason, the volume's coverage, then the coverage statement. */
|
|
5
|
+
export declare function formatResolve(r: ResolveResult): string;
|
|
6
|
+
/** One line per citation, for the batch tool. */
|
|
7
|
+
export declare function formatResolveLine(r: ResolveResult): string;
|
|
8
|
+
/** Counts by status, one line per citation, the coverage statement last. */
|
|
9
|
+
export declare function formatResolveBatch(batch: ResolveBatch): string;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/** The words a user sees per register status. A missing entry is a register fact with a coverage qualifier, never a verdict on the author. */
|
|
2
|
+
export const STATUS_WORD = {
|
|
3
|
+
found: "FOUND",
|
|
4
|
+
ambiguous: "AMBIGUOUS",
|
|
5
|
+
not_found: "NOT IN THE REGISTER",
|
|
6
|
+
unverifiable: "CANNOT VERIFY",
|
|
7
|
+
beyond_register: "CANNOT VERIFY",
|
|
8
|
+
known_cite: "KNOWN CITATION",
|
|
9
|
+
unresolvable: "CANNOT VERIFY",
|
|
10
|
+
unparsed: "NO CITATION RECOGNISED",
|
|
11
|
+
};
|
|
12
|
+
/** The full answer for one citation: status, the case or the reason, the volume's coverage, then the coverage statement. */
|
|
13
|
+
export function formatResolve(r) {
|
|
14
|
+
const lines = [headline(r)];
|
|
15
|
+
const c = r.case;
|
|
16
|
+
if (r.status === "found" && c) {
|
|
17
|
+
const others = (c.citations ?? []).filter((x) => x !== r.normalized && x !== caseCiteFor(r));
|
|
18
|
+
if (others.length)
|
|
19
|
+
lines.push(`Parallel citations: ${others.join("; ")}.`);
|
|
20
|
+
if (c.url)
|
|
21
|
+
lines.push(`Register: ${c.url}`);
|
|
22
|
+
}
|
|
23
|
+
if (r.status === "ambiguous") {
|
|
24
|
+
for (const cand of r.candidates)
|
|
25
|
+
lines.push(`- ${caseLine(cand)}${cand.url ? ` ${cand.url}` : ""}`);
|
|
26
|
+
}
|
|
27
|
+
const cov = describeCoverage(r.coverage);
|
|
28
|
+
if (cov)
|
|
29
|
+
lines.push(cov);
|
|
30
|
+
if (r.coverage_statement)
|
|
31
|
+
lines.push(`Coverage: ${r.coverage_statement}`);
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
|
34
|
+
/** One line per citation, for the batch tool. */
|
|
35
|
+
export function formatResolveLine(r) {
|
|
36
|
+
const cite = r.normalized ?? r.cite;
|
|
37
|
+
switch (r.status) {
|
|
38
|
+
case "found":
|
|
39
|
+
return `- FOUND: ${cite} = ${caseLine(r.case)}${r.match === "pincite" ? ` (pin cite inside ${caseCiteFor(r) ?? "the opinion"})` : ""}${r.case?.url ? ` ${r.case.url}` : ""}`;
|
|
40
|
+
case "ambiguous":
|
|
41
|
+
return `- AMBIGUOUS: ${cite} ${matches(r)}; best: ${r.case ? caseLine(r.case) : "none"}`;
|
|
42
|
+
case "not_found":
|
|
43
|
+
return `- NOT IN THE REGISTER: ${cite}; ${volumeHeld(r.coverage) ?? "the volume is held"} but nothing at this page (a register fact, not evidence about the citation)`;
|
|
44
|
+
case "known_cite":
|
|
45
|
+
return `- KNOWN CITATION: ${cite} is not held as an opinion, but other opinions cite it as ${knownAs(r)}`;
|
|
46
|
+
case "unresolvable":
|
|
47
|
+
return `- CANNOT VERIFY: ${cite} is a Westlaw/Lexis identifier; open registers cannot resolve it`;
|
|
48
|
+
case "beyond_register":
|
|
49
|
+
case "unverifiable":
|
|
50
|
+
return `- CANNOT VERIFY: ${cite}; the register cannot see this yet (${notSeen(r.coverage)})`;
|
|
51
|
+
case "unparsed":
|
|
52
|
+
return `- NO CITATION RECOGNISED: ${JSON.stringify(r.cite)}`;
|
|
53
|
+
default:
|
|
54
|
+
return `- ${String(r.status).toUpperCase()}: ${cite}`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Counts by status, one line per citation, the coverage statement last. */
|
|
58
|
+
export function formatResolveBatch(batch) {
|
|
59
|
+
const counts = new Map();
|
|
60
|
+
for (const r of batch.results) {
|
|
61
|
+
const word = STATUS_WORD[r.status] ?? String(r.status);
|
|
62
|
+
counts.set(word, (counts.get(word) ?? 0) + 1);
|
|
63
|
+
}
|
|
64
|
+
const n = batch.results.length;
|
|
65
|
+
const summary = [...counts.entries()].map(([w, k]) => `${k} ${w.toLowerCase()}`).join(", ");
|
|
66
|
+
const lines = [`${n} citation${n === 1 ? "" : "s"}: ${summary}.`];
|
|
67
|
+
for (const r of batch.results)
|
|
68
|
+
lines.push(formatResolveLine(r));
|
|
69
|
+
if (batch.elapsed_s !== undefined)
|
|
70
|
+
lines.push(`Elapsed: ${batch.elapsed_s} s.`);
|
|
71
|
+
lines.push(`Coverage: ${batch.coverage_statement}`);
|
|
72
|
+
return lines.join("\n");
|
|
73
|
+
}
|
|
74
|
+
function headline(r) {
|
|
75
|
+
const cite = r.normalized ?? r.cite;
|
|
76
|
+
const c = r.case;
|
|
77
|
+
switch (r.status) {
|
|
78
|
+
case "found":
|
|
79
|
+
return r.match === "pincite"
|
|
80
|
+
? `FOUND: ${cite} is a pin cite inside ${caseLine(c)}, reported at ${caseCiteFor(r) ?? "this reporter"}.`
|
|
81
|
+
: `FOUND: ${cite} is ${caseLine(c)}.`;
|
|
82
|
+
case "ambiguous":
|
|
83
|
+
return `AMBIGUOUS: ${cite} ${matches(r)}; the best match is ${c ? caseLine(c) : "unclear"}. Candidates:`;
|
|
84
|
+
case "not_found":
|
|
85
|
+
return `NOT IN THE REGISTER: ${cite}. The register has no case at this page. ` +
|
|
86
|
+
"A missing entry is a register fact with a coverage qualifier (below), not proof that the case does not exist; check the citation in another source.";
|
|
87
|
+
case "known_cite":
|
|
88
|
+
return `KNOWN CITATION: ${cite} is not held as an opinion, but other opinions cite it as ${knownAs(r)}. The register cannot show the opinion itself; check it in another source.`;
|
|
89
|
+
case "unresolvable":
|
|
90
|
+
return `CANNOT VERIFY: ${cite} is a Westlaw/Lexis identifier. Open registers cannot resolve it; check it in Westlaw or Lexis.`;
|
|
91
|
+
case "beyond_register":
|
|
92
|
+
case "unverifiable":
|
|
93
|
+
return `CANNOT VERIFY: ${cite}. The register cannot see this yet: ${notSeen(r.coverage)}. This says nothing about whether the case exists; check it in another source.`;
|
|
94
|
+
case "unparsed":
|
|
95
|
+
return `NO CITATION RECOGNISED in ${JSON.stringify(r.cite)}. Reporter citations look like '590 U.S. 644' (volume, reporter, page).`;
|
|
96
|
+
default:
|
|
97
|
+
return `${String(r.status).toUpperCase()}: ${cite}.`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function matches(r) {
|
|
101
|
+
const n = r.candidates.length;
|
|
102
|
+
return n > 1 ? `matches ${n} entries in the register` : "matches more than one entry in the register";
|
|
103
|
+
}
|
|
104
|
+
function caseLine(c) {
|
|
105
|
+
const when = [c.court, c.date].filter(Boolean).join(", ");
|
|
106
|
+
return when ? `${c.name} (${when})` : c.name;
|
|
107
|
+
}
|
|
108
|
+
/** The case's own citation in the reporter that was asked for (for a pin cite, the opinion's first page). */
|
|
109
|
+
function caseCiteFor(r) {
|
|
110
|
+
const reporter = r.coverage?.reporter;
|
|
111
|
+
if (!reporter || !r.case?.citations)
|
|
112
|
+
return undefined;
|
|
113
|
+
return r.case.citations.find((x) => x.includes(` ${reporter} `));
|
|
114
|
+
}
|
|
115
|
+
function knownAs(r) {
|
|
116
|
+
const k = r.known_as;
|
|
117
|
+
if (!k)
|
|
118
|
+
return "another case";
|
|
119
|
+
const bits = [k.court_hint, k.year !== undefined ? String(k.year) : undefined].filter(Boolean).join(", ");
|
|
120
|
+
const cited = k.n_citing !== undefined ? `, cited by ${k.n_citing} opinion${k.n_citing === 1 ? "" : "s"}` : "";
|
|
121
|
+
return `${k.name}${bits ? ` (${bits})` : ""}${cited}`;
|
|
122
|
+
}
|
|
123
|
+
function volumeHeld(cov) {
|
|
124
|
+
if (!cov?.reporter || !cov.volume)
|
|
125
|
+
return undefined;
|
|
126
|
+
const n = cov.volume_n !== undefined ? ` (${cov.volume_n} case${cov.volume_n === 1 ? "" : "s"})` : "";
|
|
127
|
+
if (cov.register_coverage === "volume_thin")
|
|
128
|
+
return `${cov.reporter} volume ${cov.volume} is only partly held${n}`;
|
|
129
|
+
if (cov.register_coverage === "volume_absent")
|
|
130
|
+
return `no part of ${cov.reporter} volume ${cov.volume} is held`;
|
|
131
|
+
return `${cov.reporter} volume ${cov.volume} is held${n}`;
|
|
132
|
+
}
|
|
133
|
+
function notSeen(cov) {
|
|
134
|
+
if (!cov?.reporter)
|
|
135
|
+
return "this reporter is not in the register";
|
|
136
|
+
if (cov.beyond_max && cov.max_volume !== undefined)
|
|
137
|
+
return `${cov.reporter} volume ${cov.volume} is newer than the register, which runs to volume ${cov.max_volume}`;
|
|
138
|
+
if (cov.volume)
|
|
139
|
+
return `no part of ${cov.reporter} volume ${cov.volume} is held`;
|
|
140
|
+
return `${cov.reporter} is not held`;
|
|
141
|
+
}
|
|
142
|
+
/** The coverage of the volume that was asked for, so a not_found inside a thin volume reads as the weak claim it is. */
|
|
143
|
+
function describeCoverage(cov) {
|
|
144
|
+
if (!cov?.reporter || !cov.volume || cov.register_coverage === "database_id")
|
|
145
|
+
return undefined;
|
|
146
|
+
const held = volumeHeld(cov);
|
|
147
|
+
const runs = cov.max_volume !== undefined ? `; the register runs to ${cov.reporter} volume ${cov.max_volume}` : "";
|
|
148
|
+
return held ? `Register coverage: ${held}${runs}.` : undefined;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=resolve_format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve_format.js","sourceRoot":"","sources":["../src/resolve_format.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type Client } from "./client.js";
|
|
3
|
+
import { type Config } from "./config.js";
|
|
4
|
+
export declare const SERVER_NAME = "proofread-mcp";
|
|
5
|
+
export declare const SERVER_VERSION = "0.1.0";
|
|
6
|
+
export declare const INSTRUCTIONS: string;
|
|
7
|
+
export interface ServerOptions {
|
|
8
|
+
config?: Config;
|
|
9
|
+
client?: Client;
|
|
10
|
+
}
|
|
11
|
+
/** Builds an McpServer with every tool registered. The client can be injected for tests. */
|
|
12
|
+
export declare function createServer(options?: ServerOptions): McpServer;
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { createClient } from "./client.js";
|
|
3
|
+
import { configFromEnv } from "./config.js";
|
|
4
|
+
import { tools } from "./tools/index.js";
|
|
5
|
+
export const SERVER_NAME = "proofread-mcp";
|
|
6
|
+
export const SERVER_VERSION = "0.1.0";
|
|
7
|
+
export const INSTRUCTIONS = "proofread.law checks US case citations against an open register of about 10 million court opinions. " +
|
|
8
|
+
"Tiers: red = check this (the register holds something concrete that disagrees), orange = cannot verify (nothing to check against; not evidence either way), " +
|
|
9
|
+
"green = found, white = deep check (does the opinion support the sentence; a review queue, not a verdict). " +
|
|
10
|
+
"Never call a red or orange row invented or untrue: say what was checked and what was found. " +
|
|
11
|
+
"Every result carries a coverage statement; repeat it when reporting to the user. " +
|
|
12
|
+
"Not covered: Westlaw/Lexis identifiers, statutes, regulations, secondary sources, whether a case is still good law.";
|
|
13
|
+
/** Builds an McpServer with every tool registered. The client can be injected for tests. */
|
|
14
|
+
export function createServer(options = {}) {
|
|
15
|
+
const config = options.config ?? configFromEnv();
|
|
16
|
+
const client = options.client ?? createClient(config);
|
|
17
|
+
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, { instructions: INSTRUCTIONS });
|
|
18
|
+
for (const tool of tools) {
|
|
19
|
+
server.registerTool(tool.name, { title: tool.title, description: tool.description, inputSchema: tool.inputSchema, ...(tool.annotations ? { annotations: tool.annotations } : {}) }, (args, extra) => tool.run(args, { client }, extra));
|
|
20
|
+
}
|
|
21
|
+
return server;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,YAAY,EAAe,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,aAAa,EAAe,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAEzC,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC;AAC3C,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC;AAEtC,MAAM,CAAC,MAAM,YAAY,GACvB,sGAAsG;IACtG,8JAA8J;IAC9J,4GAA4G;IAC5G,8FAA8F;IAC9F,mFAAmF;IACnF,qHAAqH,CAAC;AAOxH,4FAA4F;AAC5F,MAAM,UAAU,YAAY,CAAC,OAAO,GAAkB,EAAE;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,aAAa,EAAE,CAAC;IACjD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,CAAC,CAAC;IAC7G,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,CAAC,YAAY,CACjB,IAAI,CAAC,IAAI,EACT,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EACnJ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,CACnD,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/sse.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Report, Row } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reads a `POST /verify?deep=1` Server-Sent Events body into one report.
|
|
4
|
+
* Events: `report` (the full report, rows provisional), `row` (one row with `support` filled), `done` ({summary, elapsed_s}).
|
|
5
|
+
* Comment lines (`: keepalive`) are skipped. `onRow` fires as each row arrives.
|
|
6
|
+
*/
|
|
7
|
+
export declare function readSseReport(body: ReadableStream<Uint8Array>, onRow?: (row: Row, report: Report) => void): Promise<Report>;
|
|
8
|
+
interface SseEvent {
|
|
9
|
+
name: string;
|
|
10
|
+
data: string;
|
|
11
|
+
}
|
|
12
|
+
/** Splits an SSE byte stream into events. One event = the lines up to a blank line. */
|
|
13
|
+
export declare function sseEvents(body: ReadableStream<Uint8Array>): AsyncGenerator<SseEvent>;
|
|
14
|
+
export {};
|
package/dist/sse.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a `POST /verify?deep=1` Server-Sent Events body into one report.
|
|
3
|
+
* Events: `report` (the full report, rows provisional), `row` (one row with `support` filled), `done` ({summary, elapsed_s}).
|
|
4
|
+
* Comment lines (`: keepalive`) are skipped. `onRow` fires as each row arrives.
|
|
5
|
+
*/
|
|
6
|
+
export async function readSseReport(body, onRow) {
|
|
7
|
+
let report;
|
|
8
|
+
for await (const event of sseEvents(body)) {
|
|
9
|
+
if (event.name === "report") {
|
|
10
|
+
report = JSON.parse(event.data);
|
|
11
|
+
}
|
|
12
|
+
else if (event.name === "row" && report) {
|
|
13
|
+
const row = JSON.parse(event.data);
|
|
14
|
+
const at = report.rows.findIndex((r) => r.n === row.n);
|
|
15
|
+
if (at >= 0)
|
|
16
|
+
report.rows[at] = row;
|
|
17
|
+
else
|
|
18
|
+
report.rows.push(row);
|
|
19
|
+
onRow?.(row, report);
|
|
20
|
+
}
|
|
21
|
+
else if (event.name === "done" && report) {
|
|
22
|
+
const done = JSON.parse(event.data);
|
|
23
|
+
if (done.summary)
|
|
24
|
+
report.summary = { ...report.summary, ...done.summary };
|
|
25
|
+
if (done.elapsed_s !== undefined)
|
|
26
|
+
report.elapsed_s = done.elapsed_s;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (!report)
|
|
30
|
+
throw new Error("the event stream ended without a report");
|
|
31
|
+
return report;
|
|
32
|
+
}
|
|
33
|
+
/** Splits an SSE byte stream into events. One event = the lines up to a blank line. */
|
|
34
|
+
export async function* sseEvents(body) {
|
|
35
|
+
const decoder = new TextDecoder();
|
|
36
|
+
let buffer = "";
|
|
37
|
+
for await (const chunk of body) {
|
|
38
|
+
buffer += decoder.decode(chunk, { stream: true });
|
|
39
|
+
let cut;
|
|
40
|
+
while ((cut = buffer.search(/\r?\n\r?\n/)) >= 0) {
|
|
41
|
+
const block = buffer.slice(0, cut);
|
|
42
|
+
buffer = buffer.slice(cut).replace(/^\r?\n\r?\n/, "");
|
|
43
|
+
const event = parseBlock(block);
|
|
44
|
+
if (event)
|
|
45
|
+
yield event;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const last = parseBlock(buffer + decoder.decode());
|
|
49
|
+
if (last)
|
|
50
|
+
yield last;
|
|
51
|
+
}
|
|
52
|
+
function parseBlock(block) {
|
|
53
|
+
let name = "message";
|
|
54
|
+
const data = [];
|
|
55
|
+
for (const line of block.split(/\r?\n/)) {
|
|
56
|
+
if (line.startsWith(":") || line.trim() === "")
|
|
57
|
+
continue;
|
|
58
|
+
const colon = line.indexOf(":");
|
|
59
|
+
const field = colon < 0 ? line : line.slice(0, colon);
|
|
60
|
+
const value = colon < 0 ? "" : line.slice(colon + 1).replace(/^ /, "");
|
|
61
|
+
if (field === "event")
|
|
62
|
+
name = value;
|
|
63
|
+
else if (field === "data")
|
|
64
|
+
data.push(value);
|
|
65
|
+
}
|
|
66
|
+
return data.length ? { name, data: data.join("\n") } : undefined;
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=sse.js.map
|
package/dist/sse.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Report, Row } from "../types.js";
|
|
3
|
+
import { type ToolExtra } from "./tool.js";
|
|
4
|
+
export declare const checkCitations: import("./tool.js").ToolDef<{
|
|
5
|
+
text: z.ZodString;
|
|
6
|
+
deep: z.ZodOptional<z.ZodBoolean>;
|
|
7
|
+
}>;
|
|
8
|
+
/** Deep checks stream rows; each finished row becomes a progress notification when the client asked for progress. */
|
|
9
|
+
export declare function verifyOptions(deep: boolean | undefined, extra: ToolExtra): {
|
|
10
|
+
deep?: boolean;
|
|
11
|
+
onRow?: (row: Row, report: Report) => void;
|
|
12
|
+
signal: AbortSignal;
|
|
13
|
+
};
|