@opencraw/mcp 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -37,6 +37,9 @@ a module of its choosing.
37
37
  credentials as `{{env.NAME}}` read from the server's environment. The `probe` and `run` tools then take an
38
38
  `access` argument naming a profile. The file and the credentials never pass through a tool call.
39
39
 
40
+ `OPENCRAW_PROFILES` is the directory of the persistent browser profiles recipes name in
41
+ `session.browserProfile` (default `.opencraw/profiles` in the server's working directory).
42
+
40
43
  ## Tools
41
44
 
42
45
  ### `probe`
@@ -90,6 +93,7 @@ Crawls the recipes at the given paths, or passed inline.
90
93
  | `dryRun?` | One record per input recipe instead of a full crawl — for checking a recipe under construction. |
91
94
  | `headed?`, `browserPath?`, `insecureTls?`, `userAgent?` | As in the cli. |
92
95
  | `access?` | The access profile for recipes that name none, from the server's `OPENCRAW_ACCESS` config. |
96
+ | `parallel?` | How many input recipes run at once (1 to 16, default 1). |
93
97
 
94
98
  Returns `{ report: CrawlReport, records?, truncated? }`. `records`/`truncated` are present only when `out`
95
99
  was not given, and `records` is capped at 50 even then.
@@ -105,6 +109,21 @@ call, for checking what already exists before writing a new recipe.
105
109
 
106
110
  Returns `{ outputs: [{ path, id }], inputs: [{ path, id, output, mode }], others: string[] }`.
107
111
 
112
+ ### `diff`
113
+
114
+ Compares two runs' JSON Lines files by record key: what was added, what was removed, and which fields changed,
115
+ before and after. For "what changed in this price list since last week" without re-reading it all.
116
+
117
+ | Input | Meaning |
118
+ |---|---|
119
+ | `previous`, `current` | The two runs' files (a `run`'s `out`). |
120
+ | `key?` | The fields that identify a record (the output recipe's key fields). Default: each line's `_key`, which a run with `append` writes. |
121
+ | `ignore?` | Fields not compared, such as a `scrapedAt` the crawl stamps. |
122
+
123
+ Returns `{ added, removed, changed, unchanged, shrunk?, summary, changes }`: `summary` is the report a person
124
+ reads, `changes` the first 200 changes (`removed`, `changed` with `fields: [{ field, before, after }]`, `added`),
125
+ and `shrunk` is set when the current run holds under half the records the previous one did.
126
+
108
127
  ## Building
109
128
 
110
129
  `nx build @opencraw/mcp`, `nx test @opencraw/mcp` (unit tests per tool), `nx run @opencraw/mcp:e2e`
package/dist/index.esm.js CHANGED
@@ -1,8 +1,60 @@
1
1
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
2
2
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { readRecipeFiles, probeUrl, loadForRun, loadPlugins, resolveAccess } from '@opencraw/cli';
3
+ import { readRecordsFile, diffRecords, memorySink, jsonLinesSink, createCrawler, RecipeSet, RecipeValidationError, RecipeBindingError, parseOutputRecipe, parseInputRecipe, bindRecipeSet } from '@opencraw/core';
4
+ import { diffReport, readRecipeFiles, probeUrl, loadForRun, loadPlugins, resolveAccess } from '@opencraw/cli';
4
5
  import { z } from 'zod';
5
- import { memorySink, jsonLinesSink, createCrawler, RecipeSet, RecipeValidationError, RecipeBindingError, parseOutputRecipe, parseInputRecipe, bindRecipeSet } from '@opencraw/core';
6
+
7
+ /** Changes returned at most; the counts cover all of them. */
8
+ const CHANGE_LIMIT = 200;
9
+ /** Input schema for the `diff` tool. */
10
+ const diffInputShape = {
11
+ previous: z.string().describe('The earlier run\'s JSON Lines file (a run\'s "out").'),
12
+ current: z.string().describe('The later run\'s JSON Lines file.'),
13
+ key: z.array(z.string()).optional().describe('The fields that identify a record (the output recipe\'s key fields). Default: each line\'s _key, which an appending run writes.'),
14
+ ignore: z.array(z.string()).optional().describe('Fields left out of the comparison, such as a scrapedAt the crawl stamps.')
15
+ };
16
+ /**
17
+ * The `diff` tool: compares two runs' records by key, so an agent watching a
18
+ * source can say what changed since last time instead of re-reading it all.
19
+ *
20
+ * @param args - The tool's parsed input.
21
+ * @returns The MCP tool result.
22
+ */
23
+ async function diffTool(args) {
24
+ try {
25
+ const [previous, current] = await Promise.all([readRecordsFile(args.previous), readRecordsFile(args.current)]);
26
+ const diff = diffRecords(previous, current, {
27
+ key: args.key,
28
+ ignore: args.ignore
29
+ });
30
+ const result = {
31
+ added: diff.added,
32
+ removed: diff.removed,
33
+ changed: diff.changed,
34
+ unchanged: diff.unchanged,
35
+ ...(diff.shrunk !== undefined && {
36
+ shrunk: diff.shrunk
37
+ }),
38
+ summary: diffReport(diff),
39
+ changes: diff.changes.slice(0, CHANGE_LIMIT)
40
+ };
41
+ return {
42
+ content: [{
43
+ type: 'text',
44
+ text: JSON.stringify(result)
45
+ }],
46
+ structuredContent: result
47
+ };
48
+ } catch (error) {
49
+ return {
50
+ content: [{
51
+ type: 'text',
52
+ text: error instanceof Error ? error.message : String(error)
53
+ }],
54
+ isError: true
55
+ };
56
+ }
57
+ }
6
58
 
7
59
  /** Input schema for the `list_recipes` tool. */
8
60
  const listRecipesInputShape = {
@@ -130,7 +182,8 @@ const runInputShape = {
130
182
  browserPath: z.string().optional().describe('A browser binary other than the one Playwright installed. Defaults to OPENCRAW_CHROMIUM in the server\'s environment.'),
131
183
  insecureTls: z.boolean().optional().describe("Accept an intercepting proxy's certificate. Defaults to OPENCRAW_INSECURE_TLS=1 in the server's environment."),
132
184
  userAgent: z.string().optional().describe('The user agent to send.'),
133
- access: z.string().optional().describe('An access profile (a proxy) from the server\'s access config file, OPENCRAW_ACCESS, used by every recipe that names none. The error for an unknown name lists the profiles.')
185
+ access: z.string().optional().describe('An access profile (a proxy) from the server\'s access config file, OPENCRAW_ACCESS, used by every recipe that names none. The error for an unknown name lists the profiles.'),
186
+ parallel: z.int().min(1).max(16).optional().describe('How many input recipes run at once. Default 1. Iterations inside a recipe follow its limits.concurrency.')
134
187
  };
135
188
  /**
136
189
  * The `run` tool: crawls the recipes in `paths` or `recipes` (exactly one
@@ -179,6 +232,8 @@ async function runTool(args) {
179
232
  access: serverFile('OPENCRAW_ACCESS'),
180
233
  accessProfile: args.access
181
234
  }),
235
+ profilesDir: serverFile('OPENCRAW_PROFILES'),
236
+ parallel: args.parallel,
182
237
  resume: args.resume,
183
238
  browser: {
184
239
  headless: args.headed !== true,
@@ -339,7 +394,7 @@ const SERVER_INFO = {
339
394
  };
340
395
  /**
341
396
  * Builds the MCP server: one tool per crawl primitive (`probe`, `validate`,
342
- * `run`, `list_recipes`), each a thin wrapper over `@opencraw/core` and
397
+ * `run`, `list_recipes`, `diff`), each a thin wrapper over `@opencraw/core` and
343
398
  * `@opencraw/cli`'s structured helpers. No transport is attached; `main`
344
399
  * connects it over stdio.
345
400
  *
@@ -363,6 +418,10 @@ function createServer() {
363
418
  description: 'List the recipe files in a directory, split by kind, with their ids. Check before writing a new recipe, or to see what an existing output recipe covers.',
364
419
  inputSchema: listRecipesInputShape
365
420
  }, args => listRecipesTool(args));
421
+ server.registerTool('diff', {
422
+ description: 'Compare two runs\' JSON Lines files by record key: what was added, removed, and changed (each field before and after). Use to report what changed in a source since the last crawl.',
423
+ inputSchema: diffInputShape
424
+ }, args => diffTool(args));
366
425
  return server;
367
426
  }
368
427
 
@@ -376,5 +435,5 @@ async function main() {
376
435
  await server.connect(new StdioServerTransport());
377
436
  }
378
437
 
379
- export { createServer, listRecipesInputShape, listRecipesTool, main, probeInputShape, probeTool, runInputShape, runTool, validateInputShape, validateTool };
438
+ export { createServer, diffInputShape, diffTool, listRecipesInputShape, listRecipesTool, main, probeInputShape, probeTool, runInputShape, runTool, validateInputShape, validateTool };
380
439
  //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1,42 @@
1
+ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { RecordChange } from '@opencraw/core';
3
+ import { z } from 'zod';
4
+ /** Input schema for the `diff` tool. */
5
+ export declare const diffInputShape: {
6
+ previous: z.ZodString;
7
+ current: z.ZodString;
8
+ key: z.ZodOptional<z.ZodArray<z.ZodString>>;
9
+ ignore: z.ZodOptional<z.ZodArray<z.ZodString>>;
10
+ };
11
+ interface DiffArgs {
12
+ previous: string;
13
+ current: string;
14
+ key?: string[];
15
+ ignore?: string[];
16
+ }
17
+ /** What the `diff` tool returns. */
18
+ export interface DiffResult {
19
+ added: number;
20
+ removed: number;
21
+ changed: number;
22
+ unchanged: number;
23
+ /** Set when the current run lost most of the previous run's records. */
24
+ shrunk?: {
25
+ previous: number;
26
+ current: number;
27
+ };
28
+ /** The report a person reads, one line per change. */
29
+ summary: string[];
30
+ /** The changes, up to 200: removed, changed (with each field's before and after), added. */
31
+ changes: RecordChange[];
32
+ }
33
+ /**
34
+ * The `diff` tool: compares two runs' records by key, so an agent watching a
35
+ * source can say what changed since last time instead of re-reading it all.
36
+ *
37
+ * @param args - The tool's parsed input.
38
+ * @returns The MCP tool result.
39
+ */
40
+ export declare function diffTool(args: DiffArgs): Promise<CallToolResult>;
41
+ export {};
42
+ //# sourceMappingURL=diff-tool.handler.d.ts.map
@@ -0,0 +1,3 @@
1
+ export { diffTool, diffInputShape } from './diff-tool.handler.js';
2
+ export type { DiffResult } from './diff-tool.handler.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -2,6 +2,8 @@ export { main } from './main.js';
2
2
  export { createServer } from './server/index.js';
3
3
  export { probeTool, probeInputShape } from './probe-tool/index.js';
4
4
  export { validateTool, validateInputShape } from './validate-tool/index.js';
5
+ export { diffTool, diffInputShape } from './diff-tool/index.js';
6
+ export type { DiffResult } from './diff-tool/index.js';
5
7
  export type { ValidateResult } from './validate-tool/index.js';
6
8
  export { runTool, runInputShape } from './run-tool/index.js';
7
9
  export type { RunResult } from './run-tool/index.js';
@@ -14,6 +14,7 @@ export declare const runInputShape: {
14
14
  insecureTls: z.ZodOptional<z.ZodBoolean>;
15
15
  userAgent: z.ZodOptional<z.ZodString>;
16
16
  access: z.ZodOptional<z.ZodString>;
17
+ parallel: z.ZodOptional<z.ZodInt>;
17
18
  paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
18
19
  recipes: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>]>>;
19
20
  };
@@ -28,6 +29,7 @@ interface RunArgs extends RecipeSourceArgs {
28
29
  insecureTls?: boolean;
29
30
  userAgent?: string;
30
31
  access?: string;
32
+ parallel?: number;
31
33
  }
32
34
  /** What the `run` tool returns. */
33
35
  export interface RunResult {
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  /**
3
3
  * Builds the MCP server: one tool per crawl primitive (`probe`, `validate`,
4
- * `run`, `list_recipes`), each a thin wrapper over `@opencraw/core` and
4
+ * `run`, `list_recipes`, `diff`), each a thin wrapper over `@opencraw/core` and
5
5
  * `@opencraw/cli`'s structured helpers. No transport is attached; `main`
6
6
  * connects it over stdio.
7
7
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencraw/mcp",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "main": "./dist/index.esm.js",
6
6
  "module": "./dist/index.esm.js",
@@ -24,8 +24,8 @@
24
24
  "!**/*.js.map"
25
25
  ],
26
26
  "dependencies": {
27
- "@opencraw/core": "^0.1.2",
28
- "@opencraw/cli": "^0.1.2",
27
+ "@opencraw/core": "^0.1.4",
28
+ "@opencraw/cli": "^0.1.4",
29
29
  "@modelcontextprotocol/sdk": "^1.30.1",
30
30
  "playwright": "^1.63.0",
31
31
  "zod": "^4.6.5"