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/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Data Alchemy Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# proofread-mcp
|
|
2
|
+
|
|
3
|
+
An MCP server for [proofread.law](https://proofread.law). It lets Claude Desktop, Claude Code, Cursor and the OpenAI Agents SDK check US case citations before a draft is filed.
|
|
4
|
+
|
|
5
|
+
proofread.law checks each citation against an open register of about 10 million court opinions (CourtListener bulk data). Every result says what was checked, what was found, and what the register cannot see.
|
|
6
|
+
|
|
7
|
+
## What it does
|
|
8
|
+
|
|
9
|
+
Six tools:
|
|
10
|
+
|
|
11
|
+
| Tool | Input | What comes back |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `check_citations` | text, `deep` (optional) | The coverage statement, counts per tier, one line per row that needs a human, the number of citations found, a report id |
|
|
14
|
+
| `check_document` | path to a `.pdf`, `.docx` or `.txt` (up to 10 MB), `deep` (optional) | The same, for a file on disk |
|
|
15
|
+
| `resolve_citation` | one citation string | The register's answer for that citation: found (case, court, date, parallel citations, link), ambiguous (candidates), not in the register, cannot verify, known citation, or no citation recognised; the coverage of that volume; the coverage statement |
|
|
16
|
+
| `resolve_citations` | a list of up to 500 citation strings | Counts by status, one line per citation in input order, the coverage statement |
|
|
17
|
+
| `coverage` | nothing | The coverage statement and the storage notice |
|
|
18
|
+
| `render_report` | a report id from a previous check, or the full report JSON | A markdown diligence report |
|
|
19
|
+
|
|
20
|
+
`check_citations` and `check_document` read prose: they compare the case name and any quotation with the register. `resolve_citation` and `resolve_citations` look the citation string up in the register (the `/v1/resolve` API) and tell you which case sits there; they do not compare it with the name you have.
|
|
21
|
+
|
|
22
|
+
Tiers, in the words the tools use:
|
|
23
|
+
|
|
24
|
+
| Tier | Word | Meaning |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| red | check this | The register holds something concrete that disagrees: a different case at that citation, a volume or page that does not match, quoted words not in the opinion |
|
|
27
|
+
| orange | cannot verify | Nothing to check against: a Westlaw or Lexis identifier, a volume newer than the register, a reporter the register holds only in part. Not evidence either way |
|
|
28
|
+
| green | found | The citation resolves to a case whose caption matches |
|
|
29
|
+
| white | deep check | With `deep: true` only: whether the opinion supports the sentence it is cited for. A review queue, not a verdict |
|
|
30
|
+
|
|
31
|
+
What it cannot do: resolve Westlaw (WL) or Lexis identifiers, check statutes, regulations or secondary sources, or say whether a case is still good law. Those limits are stated in every tool description and in the coverage statement that comes with every result.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
Needs Node 20 or newer. No install step is required; `npx` fetches it.
|
|
36
|
+
|
|
37
|
+
Until the package is on npm, clone this repository, run `npm install && npm run build`, and use `node /absolute/path/to/proofread-mcp/dist/cli.js` wherever the snippets below say `npx -y proofread-mcp`.
|
|
38
|
+
|
|
39
|
+
### Claude Desktop
|
|
40
|
+
|
|
41
|
+
Edit `claude_desktop_config.json` (Settings, Developer, Edit Config):
|
|
42
|
+
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"mcpServers": {
|
|
46
|
+
"proofread": {
|
|
47
|
+
"command": "npx",
|
|
48
|
+
"args": ["-y", "proofread-mcp"],
|
|
49
|
+
"env": {
|
|
50
|
+
"PROOFREAD_API_KEY": "pl_..."
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Leave out `env` to use the free tier.
|
|
58
|
+
|
|
59
|
+
### Claude Code
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
claude mcp add proofread -- npx -y proofread-mcp
|
|
63
|
+
# with a Firm key:
|
|
64
|
+
claude mcp add proofread -e PROOFREAD_API_KEY=pl_... -- npx -y proofread-mcp
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Cursor
|
|
68
|
+
|
|
69
|
+
Settings, MCP, Add new global MCP server, or write `.cursor/mcp.json` in the project:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"proofread": {
|
|
75
|
+
"command": "npx",
|
|
76
|
+
"args": ["-y", "proofread-mcp"],
|
|
77
|
+
"env": { "PROOFREAD_API_KEY": "pl_..." }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### OpenAI Agents SDK (over HTTP)
|
|
84
|
+
|
|
85
|
+
Start the server with the HTTP transport:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
PROOFREAD_API_KEY=pl_... npx proofread-mcp --http --port 3333
|
|
89
|
+
# MCP endpoint: http://127.0.0.1:3333/mcp health: http://127.0.0.1:3333/health
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Then connect from the Agents SDK:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from agents import Agent, Runner
|
|
96
|
+
from agents.mcp import MCPServerStreamableHttp
|
|
97
|
+
|
|
98
|
+
async with MCPServerStreamableHttp(params={"url": "http://127.0.0.1:3333/mcp"}) as proofread:
|
|
99
|
+
agent = Agent(name="Drafting assistant", instructions="Check every case citation before you rely on it.", mcp_servers=[proofread])
|
|
100
|
+
result = await Runner.run(agent, "Check the citations in this paragraph: ...")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { Agent, run, MCPServerStreamableHttp } from "@openai/agents";
|
|
105
|
+
|
|
106
|
+
const proofread = new MCPServerStreamableHttp({ url: "http://127.0.0.1:3333/mcp", name: "proofread" });
|
|
107
|
+
await proofread.connect();
|
|
108
|
+
const agent = new Agent({ name: "Drafting assistant", mcpServers: [proofread] });
|
|
109
|
+
const result = await run(agent, "Check the citations in this paragraph: ...");
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The HTTP server binds to 127.0.0.1 by default. To expose it on a network use `--host 0.0.0.0` and put it behind something that adds authentication; the server has none of its own.
|
|
113
|
+
|
|
114
|
+
### Any MCP client
|
|
115
|
+
|
|
116
|
+
stdio: run `proofread-mcp`. Streamable HTTP: run `proofread-mcp --http --port 3333` and point the client at `/mcp`.
|
|
117
|
+
|
|
118
|
+
## Environment variables
|
|
119
|
+
|
|
120
|
+
| Variable | Default | Meaning |
|
|
121
|
+
|---|---|---|
|
|
122
|
+
| `PROOFREAD_API_KEY` | unset | A Firm plan API key (`pl_...`), sent as `Authorization: Bearer`. Without it the free tier applies |
|
|
123
|
+
| `PROOFREAD_API` | `https://proofread.law` | Base URL, for a self-hosted or test instance |
|
|
124
|
+
|
|
125
|
+
## Free tier
|
|
126
|
+
|
|
127
|
+
Without a key, per IP address and per month:
|
|
128
|
+
|
|
129
|
+
| Tools | Quota |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `check_citations`, `check_document` | 20 checks, of which 3 may be deep checks |
|
|
132
|
+
| `resolve_citation`, `resolve_citations` | 1,000 resolves (each citation in a list counts as one) |
|
|
133
|
+
| `coverage`, `render_report` | free, not counted |
|
|
134
|
+
|
|
135
|
+
There is also a limit of 20 requests an hour per IP. When a limit is reached the tool returns a plain message with the retry time or the upgrade link; nothing is thrown at the protocol level.
|
|
136
|
+
|
|
137
|
+
`.docx` upload and unlimited checks need a paid plan. See [proofread.law/pricing](https://proofread.law/pricing).
|
|
138
|
+
|
|
139
|
+
## Privacy
|
|
140
|
+
|
|
141
|
+
- The text or file goes to proofread.law, which runs on its own machine, not a cloud provider's API. It is processed in memory and discarded when the report is returned. Only counts (citations, tiers, timing) are logged, never text.
|
|
142
|
+
- A citation string the local register cannot resolve may be looked up in the CourtListener citation API. Only the citation string leaves, never a party name or prose.
|
|
143
|
+
- Deep check (`deep: true`) is opt-in. In that mode the clause before each citation (up to 700 characters) is sent to a model judge, together with the cited opinion. That is the only mode in which any of the document's prose leaves proofread.law.
|
|
144
|
+
- This server stores nothing on disk. It keeps the last 50 reports in memory so `render_report` can be called with a short id; they are gone when the process exits.
|
|
145
|
+
|
|
146
|
+
## The coverage caveat
|
|
147
|
+
|
|
148
|
+
Every result starts with the coverage statement, for example:
|
|
149
|
+
|
|
150
|
+
> Checked against 10.1 M cases (CourtListener bulk data 2026-06-30, last refreshed 2026-09-19); federal appellate 2019 to 2023 is 10 to 15% incomplete; Westlaw/Lexis identifiers are not resolvable; statutes, regulations and secondary sources are not checked.
|
|
151
|
+
|
|
152
|
+
Read it. A citation that is not in the register is a register fact with a coverage qualifier, not proof that the case does not exist. A red row says "check this"; the tools say what was checked and what was found, never that a case is invented.
|
|
153
|
+
|
|
154
|
+
## Example
|
|
155
|
+
|
|
156
|
+
Input:
|
|
157
|
+
|
|
158
|
+
> Title VII forbids discrimination because of sexual orientation. Bostock v. Clayton County, 509 U.S. 644 (2020).
|
|
159
|
+
|
|
160
|
+
`check_citations` returns (real output, review instance, 2026-09-20):
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
Coverage: Checked against 10.1 M cases (CourtListener bulk data 2026-06-30, last refreshed 2026-09-19); federal appellate 2019 to 2023 is 10 to 15% incomplete; Westlaw/Lexis identifiers are not resolvable; statutes, regulations and secondary sources are not checked.
|
|
164
|
+
Summary: 1 citation, 1 row. Check this (red): 1. Cannot verify (orange): 0. Found (green): 0.
|
|
165
|
+
Flagged rows:
|
|
166
|
+
- CHECK THIS: 509 U.S. 644 (Bostock v. Clayton County). Register has Bostock v. Clayton County at 590 U.S. 644. Check the volume. In the register, 509 U.S. 644 is Shaw v. Reno. The case named in the document exists; this citation does not point to it. Register: https://www.courtlistener.com/opinion/4760997/bostock-v-clayton-county/
|
|
167
|
+
Found: 0 rows resolved to a case in the register.
|
|
168
|
+
Report id: r_1ff74051 (give it to render_report for a markdown report). Elapsed: 0.03 s.
|
|
169
|
+
Storage: Nothing you submit is stored. The document is processed in memory and discarded when this report is returned; only counts (citations, tiers, timing) are logged, never text.
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm install
|
|
176
|
+
npm run build # tsc -> dist/
|
|
177
|
+
npm test # vitest, mocked fetch, no network
|
|
178
|
+
LIVE=1 npm test -- test/live.test.ts # three live calls against proofread.law (counts against the free tier)
|
|
179
|
+
node scripts/smoke-stdio.mjs # spawn the stdio server, initialize, tools/list, four live tool calls
|
|
180
|
+
node scripts/smoke-stdio.mjs --offline # the same without network
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Layout: `src/client.ts` is the typed HTTP client (`/verify`, `/render`, `/api/coverage`, `/v1/resolve` single and batch), `src/format.ts` the compact formatter for checks, `src/resolve_format.ts` the one for register answers, `src/tools/<name>.ts` one file per tool, `src/server.ts` registers them, `src/cli.ts` picks the transport. The remaining register routes (`/v1/extract`, `/v1/case/{id}`, `/v1/coverage` per reporter) slot in the same way: one method on the client, one file under `src/tools/`.
|
|
184
|
+
|
|
185
|
+
## Publishing
|
|
186
|
+
|
|
187
|
+
See [RELEASE.md](RELEASE.md). The package is not on npm yet and the repository is private until the owner makes it public.
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT, Data Alchemy Labs.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { startHttp } from "./http.js";
|
|
4
|
+
import { createServer, SERVER_VERSION } from "./server.js";
|
|
5
|
+
const HELP = `proofread-mcp ${SERVER_VERSION}: MCP server for proofread.law
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
proofread-mcp stdio transport (for Claude Desktop, Claude Code, Cursor)
|
|
9
|
+
proofread-mcp --http Streamable HTTP at http://127.0.0.1:3333/mcp
|
|
10
|
+
proofread-mcp --http --port N --host H
|
|
11
|
+
|
|
12
|
+
Environment:
|
|
13
|
+
PROOFREAD_API_KEY Firm plan API key (pl_...). Without it the free tier applies.
|
|
14
|
+
PROOFREAD_API Base URL (default https://proofread.law).
|
|
15
|
+
`;
|
|
16
|
+
export function parseArgs(argv) {
|
|
17
|
+
const args = { http: false, port: 3333, host: "127.0.0.1", help: false };
|
|
18
|
+
for (let i = 0; i < argv.length; i++) {
|
|
19
|
+
const a = argv[i];
|
|
20
|
+
if (a === "--http")
|
|
21
|
+
args.http = true;
|
|
22
|
+
else if (a === "--port")
|
|
23
|
+
args.port = Number(argv[++i]);
|
|
24
|
+
else if (a?.startsWith("--port="))
|
|
25
|
+
args.port = Number(a.slice(7));
|
|
26
|
+
else if (a === "--host")
|
|
27
|
+
args.host = String(argv[++i]);
|
|
28
|
+
else if (a?.startsWith("--host="))
|
|
29
|
+
args.host = a.slice(7);
|
|
30
|
+
else if (a === "--help" || a === "-h")
|
|
31
|
+
args.help = true;
|
|
32
|
+
else
|
|
33
|
+
throw new Error(`unknown argument ${a}`);
|
|
34
|
+
}
|
|
35
|
+
if (!Number.isInteger(args.port) || args.port < 0 || args.port > 65535)
|
|
36
|
+
throw new Error("--port must be 0..65535");
|
|
37
|
+
return args;
|
|
38
|
+
}
|
|
39
|
+
async function main() {
|
|
40
|
+
let args;
|
|
41
|
+
try {
|
|
42
|
+
args = parseArgs(process.argv.slice(2));
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
console.error(String(err instanceof Error ? err.message : err));
|
|
46
|
+
console.error(HELP);
|
|
47
|
+
process.exit(2);
|
|
48
|
+
}
|
|
49
|
+
if (args.help) {
|
|
50
|
+
console.log(HELP);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (args.http) {
|
|
54
|
+
const { url } = await startHttp({ port: args.port, host: args.host });
|
|
55
|
+
console.error(`proofread-mcp listening on ${url}`); // stderr: stdout stays clean in both modes
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const server = createServer();
|
|
59
|
+
await server.connect(new StdioServerTransport());
|
|
60
|
+
}
|
|
61
|
+
main().catch((err) => {
|
|
62
|
+
console.error("proofread-mcp:", err instanceof Error ? err.message : err);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
65
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Config } from "./config.js";
|
|
2
|
+
import type { Coverage, Report, ResolveBatch, ResolveResult, Row } from "./types.js";
|
|
3
|
+
export declare const USER_AGENT = "proofread-mcp/0.1.0 (+https://github.com/Data-Alchemy-Labs/proofread-mcp)";
|
|
4
|
+
export declare const MAX_UPLOAD_BYTES: number;
|
|
5
|
+
export type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
6
|
+
export interface VerifyOptions {
|
|
7
|
+
/** Run the support check (opt-in; the clause before each citation is sent to the model judge). */
|
|
8
|
+
deep?: boolean;
|
|
9
|
+
/** Deep mode only: when given, the client reads the SSE stream and reports each finished row. Without it, one JSON is requested. */
|
|
10
|
+
onRow?: (row: Row, report: Report) => void;
|
|
11
|
+
signal?: AbortSignal;
|
|
12
|
+
}
|
|
13
|
+
export declare const MAX_BATCH_CITES = 500;
|
|
14
|
+
export interface Client {
|
|
15
|
+
verifyText(text: string, options?: VerifyOptions): Promise<Report>;
|
|
16
|
+
verifyFile(bytes: Uint8Array, filename: string, options?: VerifyOptions): Promise<Report>;
|
|
17
|
+
/** One citation string against the register (GET /v1/resolve). Counts against the resolve quota, not the check quota. */
|
|
18
|
+
resolveV1(cite: string, signal?: AbortSignal): Promise<ResolveResult>;
|
|
19
|
+
/** Up to 500 citation strings in one call (POST /v1/resolve); results come back in input order. */
|
|
20
|
+
resolveBatch(cites: string[], signal?: AbortSignal): Promise<ResolveBatch>;
|
|
21
|
+
renderMarkdown(report: Report, signal?: AbortSignal): Promise<string>;
|
|
22
|
+
coverage(signal?: AbortSignal): Promise<Coverage>;
|
|
23
|
+
}
|
|
24
|
+
export declare function createClient(config: Config, fetchImpl?: FetchLike): Client;
|
|
25
|
+
/** `?deep=1` streams SSE by default; `&stream=0` asks for one JSON after all checks. A default check has no query. */
|
|
26
|
+
export declare function verifyPath(deep: boolean, stream: boolean): string;
|
|
27
|
+
/** AbortSignal.any arrived in Node 20.3; older 20.x gets the same behaviour by hand. */
|
|
28
|
+
export declare function anySignal(signals: AbortSignal[]): AbortSignal;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { ProofreadError } from "./errors.js";
|
|
2
|
+
import { readSseReport } from "./sse.js";
|
|
3
|
+
export const USER_AGENT = "proofread-mcp/0.1.0 (+https://github.com/Data-Alchemy-Labs/proofread-mcp)";
|
|
4
|
+
export const MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
5
|
+
/** A default check answers in seconds; a deep check runs 1 to 2 s per citation, four in parallel, up to 15 minutes server-side. */
|
|
6
|
+
const DEFAULT_TIMEOUT_MS = 120_000;
|
|
7
|
+
const DEEP_TIMEOUT_MS = 16 * 60_000;
|
|
8
|
+
export const MAX_BATCH_CITES = 500;
|
|
9
|
+
export function createClient(config, fetchImpl = globalThis.fetch) {
|
|
10
|
+
const headers = (extra = {}) => ({
|
|
11
|
+
"User-Agent": USER_AGENT,
|
|
12
|
+
Accept: "application/json, text/event-stream",
|
|
13
|
+
...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),
|
|
14
|
+
...extra,
|
|
15
|
+
});
|
|
16
|
+
async function call(path, init, timeoutMs, signal) {
|
|
17
|
+
const url = config.baseUrl + path;
|
|
18
|
+
let res;
|
|
19
|
+
try {
|
|
20
|
+
res = await fetchImpl(url, { ...init, signal: withTimeout(timeoutMs, signal) });
|
|
21
|
+
}
|
|
22
|
+
catch (cause) {
|
|
23
|
+
throw ProofreadError.fromFetchFailure(config.baseUrl, cause);
|
|
24
|
+
}
|
|
25
|
+
if (!res.ok)
|
|
26
|
+
throw ProofreadError.fromBody(res.status, await bodyAsJson(res));
|
|
27
|
+
return res;
|
|
28
|
+
}
|
|
29
|
+
async function verify(body, contentType, options) {
|
|
30
|
+
const deep = options.deep === true;
|
|
31
|
+
const path = verifyPath(deep, deep && options.onRow !== undefined);
|
|
32
|
+
const res = await call(path, { method: "POST", headers: headers(contentType ? { "Content-Type": contentType } : {}), body }, deep ? DEEP_TIMEOUT_MS : DEFAULT_TIMEOUT_MS, options.signal);
|
|
33
|
+
return readReport(res, options.onRow);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
verifyText: (text, options = {}) => verify(JSON.stringify({ text }), "application/json", options),
|
|
37
|
+
verifyFile(bytes, filename, options = {}) {
|
|
38
|
+
if (bytes.byteLength > MAX_UPLOAD_BYTES) {
|
|
39
|
+
throw new ProofreadError(413, "too_large", `${bytes.byteLength} bytes`);
|
|
40
|
+
}
|
|
41
|
+
const form = new FormData();
|
|
42
|
+
form.append("file", new Blob([new Uint8Array(bytes)]), filename);
|
|
43
|
+
return verify(form, undefined, options); // fetch sets the multipart boundary
|
|
44
|
+
},
|
|
45
|
+
async resolveV1(cite, signal) {
|
|
46
|
+
const res = await call(`/v1/resolve?cite=${encodeURIComponent(cite)}`, { method: "GET", headers: headers() }, DEFAULT_TIMEOUT_MS, signal);
|
|
47
|
+
return (await res.json());
|
|
48
|
+
},
|
|
49
|
+
async resolveBatch(cites, signal) {
|
|
50
|
+
if (cites.length > MAX_BATCH_CITES)
|
|
51
|
+
throw new ProofreadError(400, "too_many", `${cites.length} citations; the cap is ${MAX_BATCH_CITES} per call`);
|
|
52
|
+
const res = await call("/v1/resolve", { method: "POST", headers: headers({ "Content-Type": "application/json" }), body: JSON.stringify({ cites }) }, DEFAULT_TIMEOUT_MS, signal);
|
|
53
|
+
return (await res.json());
|
|
54
|
+
},
|
|
55
|
+
async renderMarkdown(report, signal) {
|
|
56
|
+
const res = await call("/render?format=md", { method: "POST", headers: headers({ "Content-Type": "application/json", Accept: "text/markdown" }),
|
|
57
|
+
body: JSON.stringify(report) }, DEFAULT_TIMEOUT_MS, signal);
|
|
58
|
+
return res.text();
|
|
59
|
+
},
|
|
60
|
+
async coverage(signal) {
|
|
61
|
+
const res = await call("/api/coverage", { method: "GET", headers: headers() }, DEFAULT_TIMEOUT_MS, signal);
|
|
62
|
+
return (await res.json());
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** `?deep=1` streams SSE by default; `&stream=0` asks for one JSON after all checks. A default check has no query. */
|
|
67
|
+
export function verifyPath(deep, stream) {
|
|
68
|
+
if (!deep)
|
|
69
|
+
return "/verify";
|
|
70
|
+
return stream ? "/verify?deep=1" : "/verify?deep=1&stream=0";
|
|
71
|
+
}
|
|
72
|
+
async function readReport(res, onRow) {
|
|
73
|
+
const type = res.headers.get("content-type") ?? "";
|
|
74
|
+
if (type.startsWith("text/event-stream")) {
|
|
75
|
+
if (!res.body)
|
|
76
|
+
throw new ProofreadError(0, "empty_stream", "the event stream had no body");
|
|
77
|
+
return readSseReport(res.body, onRow);
|
|
78
|
+
}
|
|
79
|
+
return (await res.json());
|
|
80
|
+
}
|
|
81
|
+
async function bodyAsJson(res) {
|
|
82
|
+
try {
|
|
83
|
+
return await res.json();
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function withTimeout(ms, signal) {
|
|
90
|
+
const timeout = AbortSignal.timeout(ms);
|
|
91
|
+
return signal ? anySignal([signal, timeout]) : timeout;
|
|
92
|
+
}
|
|
93
|
+
/** AbortSignal.any arrived in Node 20.3; older 20.x gets the same behaviour by hand. */
|
|
94
|
+
export function anySignal(signals) {
|
|
95
|
+
if (typeof AbortSignal.any === "function")
|
|
96
|
+
return AbortSignal.any(signals);
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
for (const s of signals) {
|
|
99
|
+
if (s.aborted) {
|
|
100
|
+
controller.abort(s.reason);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
s.addEventListener("abort", () => controller.abort(s.reason), { once: true });
|
|
104
|
+
}
|
|
105
|
+
return controller.signal;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
apiKey?: string;
|
|
4
|
+
}
|
|
5
|
+
export declare const DEFAULT_BASE_URL = "https://proofread.law";
|
|
6
|
+
/** Reads PROOFREAD_API (base URL) and PROOFREAD_API_KEY (Firm plan key) from the environment. */
|
|
7
|
+
export declare function configFromEnv(env?: NodeJS.ProcessEnv): Config;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export const DEFAULT_BASE_URL = "https://proofread.law";
|
|
2
|
+
/** Reads PROOFREAD_API (base URL) and PROOFREAD_API_KEY (Firm plan key) from the environment. */
|
|
3
|
+
export function configFromEnv(env = process.env) {
|
|
4
|
+
const baseUrl = (env.PROOFREAD_API ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
5
|
+
const apiKey = env.PROOFREAD_API_KEY?.trim();
|
|
6
|
+
return apiKey ? { baseUrl, apiKey } : { baseUrl };
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAExD,iGAAiG;AACjG,MAAM,UAAU,aAAa,CAAC,GAAG,GAAsB,OAAO,CAAC,GAAG;IAChE,MAAM,OAAO,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,gBAAgB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5E,MAAM,MAAM,GAAG,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,CAAC;IAC7C,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;AACpD,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** An error answered by proofread.law, or a failure to reach it. `status` is 0 when no HTTP answer arrived. */
|
|
2
|
+
export declare class ProofreadError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly info: Record<string, unknown>;
|
|
6
|
+
constructor(status: number, code: string, message: string, info?: Record<string, unknown>);
|
|
7
|
+
static fromBody(status: number, body: unknown): ProofreadError;
|
|
8
|
+
static network(baseUrl: string, cause: unknown): ProofreadError;
|
|
9
|
+
/** fetch rejected: our timeout fired, the MCP client cancelled, or the host was not reachable. */
|
|
10
|
+
static fromFetchFailure(baseUrl: string, cause: unknown): ProofreadError;
|
|
11
|
+
}
|
|
12
|
+
/** One plain sentence a model can act on. Wording follows PRODUCT.md: what was checked, what was found, what to do next. */
|
|
13
|
+
export declare function explain(err: unknown): string;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** An error answered by proofread.law, or a failure to reach it. `status` is 0 when no HTTP answer arrived. */
|
|
2
|
+
export class ProofreadError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
info;
|
|
6
|
+
constructor(status, code, message, info = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "ProofreadError";
|
|
9
|
+
this.status = status;
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.info = info;
|
|
12
|
+
}
|
|
13
|
+
static fromBody(status, body) {
|
|
14
|
+
const err = body?.error;
|
|
15
|
+
if (err && typeof err.code === "string") {
|
|
16
|
+
const { code, message, ...info } = err;
|
|
17
|
+
return new ProofreadError(status, code, message ?? code, info);
|
|
18
|
+
}
|
|
19
|
+
return new ProofreadError(status, `http_${status}`, `proofread.law answered HTTP ${status}`);
|
|
20
|
+
}
|
|
21
|
+
static network(baseUrl, cause) {
|
|
22
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
23
|
+
return new ProofreadError(0, "network", `Could not reach ${baseUrl}: ${detail}`);
|
|
24
|
+
}
|
|
25
|
+
/** fetch rejected: our timeout fired, the MCP client cancelled, or the host was not reachable. */
|
|
26
|
+
static fromFetchFailure(baseUrl, cause) {
|
|
27
|
+
const name = cause instanceof Error ? cause.name : "";
|
|
28
|
+
if (name === "TimeoutError")
|
|
29
|
+
return new ProofreadError(0, "timeout", `no answer from ${baseUrl} in time`);
|
|
30
|
+
if (name === "AbortError")
|
|
31
|
+
return new ProofreadError(0, "cancelled", "the request was cancelled by the client");
|
|
32
|
+
return ProofreadError.network(baseUrl, cause);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** One plain sentence a model can act on. Wording follows PRODUCT.md: what was checked, what was found, what to do next. */
|
|
36
|
+
export function explain(err) {
|
|
37
|
+
if (!(err instanceof ProofreadError)) {
|
|
38
|
+
return `Unexpected error: ${err instanceof Error ? err.message : String(err)}`;
|
|
39
|
+
}
|
|
40
|
+
const i = err.info;
|
|
41
|
+
switch (err.code) {
|
|
42
|
+
case "network":
|
|
43
|
+
case "cancelled":
|
|
44
|
+
return err.message;
|
|
45
|
+
case "plan_required":
|
|
46
|
+
return `proofread.law: this needs the ${String(i.plan ?? "paid")} plan` +
|
|
47
|
+
(i.feature ? ` (${String(i.feature)})` : "") + ". " +
|
|
48
|
+
(i.upgrade ? `Upgrade at ${String(i.upgrade)}. ` : "") +
|
|
49
|
+
"A Firm API key goes in PROOFREAD_API_KEY.";
|
|
50
|
+
case "rate_limited":
|
|
51
|
+
return `proofread.law rate limit: ${err.message}.` +
|
|
52
|
+
(i.retry_after ? ` Retry after ${String(i.retry_after)} s.` : "");
|
|
53
|
+
case "quota_exceeded":
|
|
54
|
+
return `proofread.law monthly allowance used` +
|
|
55
|
+
(i.used !== undefined && i.limit !== undefined ? ` (${String(i.used)} of ${String(i.limit)})` : "") +
|
|
56
|
+
"." + (i.upgrade ? ` Upgrade at ${String(i.upgrade)}.` : "") +
|
|
57
|
+
" A Firm API key in PROOFREAD_API_KEY lifts the free-tier limits.";
|
|
58
|
+
case "bad_key":
|
|
59
|
+
return "proofread.law rejected the API key in PROOFREAD_API_KEY (unknown or revoked).";
|
|
60
|
+
case "missing_cite":
|
|
61
|
+
case "too_many":
|
|
62
|
+
return `proofread.law: ${err.message}.`;
|
|
63
|
+
case "too_large":
|
|
64
|
+
return `proofread.law: the input is too large (${err.message}); the cap is 10 MB.`;
|
|
65
|
+
case "unreadable":
|
|
66
|
+
case "unparseable":
|
|
67
|
+
return `proofread.law could not read the file: ${err.message}. Scanned PDFs without a text layer, encrypted PDFs and legacy .doc are not supported.`;
|
|
68
|
+
case "deep_budget_exhausted":
|
|
69
|
+
return "proofread.law: the deep-check budget for today is spent. Run the check without deep=true; the default check still works.";
|
|
70
|
+
case "timeout":
|
|
71
|
+
return `proofread.law timed out: ${err.message}. Try a shorter text or split the document.`;
|
|
72
|
+
default:
|
|
73
|
+
return `proofread.law error ${err.code} (HTTP ${err.status}): ${err.message}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Report, Row, Tier } from "./types.js";
|
|
2
|
+
/** The words a user sees per tier (PRODUCT.md): say what was checked and what was found, never a verdict on the author. */
|
|
3
|
+
export declare const TIER_WORD: Record<Tier, string>;
|
|
4
|
+
/**
|
|
5
|
+
* A compact, model-friendly view of a report: the coverage statement first, then the counts,
|
|
6
|
+
* then one line per row that needs a human (red, orange and, in deep mode, white), then the count of found rows.
|
|
7
|
+
* Westlaw/Lexis identifiers are collapsed into one line, as on the site.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatReport(report: Report, reportId?: string): string;
|
|
10
|
+
/** One line: tier word, citation (parties), headline, detail, register link. */
|
|
11
|
+
export declare function formatRowLine(row: Row): string;
|
|
12
|
+
/** A full view of one row, for resolve_citation. */
|
|
13
|
+
export declare function formatRowDetail(row: Row): string;
|