@howells/fieldwork-mcp 0.0.0-stage → 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Howells
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 CHANGED
@@ -1,3 +1,90 @@
1
- # Temporary Holding Version
1
+ # Fieldwork MCP server
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Web search, page reading, value extraction, site maps, crawls, research and document parsing as tools for any MCP client, across Exa, Firecrawl, Tavily, Parallel and Perplexity. It serves the tools from [`@howells/fieldwork-sdk`](https://www.npmjs.com/package/@howells/fieldwork-sdk) over stdio.
4
+
5
+ ```sh
6
+ npx -y @howells/fieldwork-mcp --provider firecrawl
7
+ ```
8
+
9
+ ## Choosing providers
10
+
11
+ Use one provider for everything it offers:
12
+
13
+ ```sh
14
+ fieldwork-mcp --provider tavily
15
+ ```
16
+
17
+ Or one provider per capability. A capability without a provider has no tool, and nothing falls back to another provider:
18
+
19
+ ```sh
20
+ fieldwork-mcp --search parallel --read firecrawl --product firecrawl --research exa
21
+ ```
22
+
23
+ Capabilities: `search`, `read`, `extract`, `product`, `map`, `crawl`, `research`, `act`, `parse`. A provider that lacks the capability you route to it stops the server at start-up with an error.
24
+
25
+ Each provider reads its key from the environment: `EXA_API_KEY`, `FIRECRAWL_API_KEY`, `TAVILY_API_KEY`, `PARALLEL_API_KEY` or `PERPLEXITY_API_KEY`.
26
+
27
+ | Option | Effect |
28
+ | --- | --- |
29
+ | `--allow-act` | Adds `run_browser_script`, which runs code in a remote browser. Off by default. |
30
+ | `--max-characters <n>` | The long text one result may return. Default 20,000. |
31
+ | `--perplexity-model <id>` | The model Perplexity research runs on. |
32
+
33
+ ## Client configuration
34
+
35
+ Claude Code:
36
+
37
+ ```sh
38
+ claude mcp add fieldwork -e FIRECRAWL_API_KEY=… -- npx -y @howells/fieldwork-mcp --provider firecrawl
39
+ ```
40
+
41
+ Claude Desktop, Cursor and other clients that take a JSON file:
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "fieldwork": {
47
+ "command": "npx",
48
+ "args": [
49
+ "-y",
50
+ "@howells/fieldwork-mcp",
51
+ "--search",
52
+ "parallel",
53
+ "--read",
54
+ "firecrawl"
55
+ ],
56
+ "env": { "PARALLEL_API_KEY": "…", "FIRECRAWL_API_KEY": "…" }
57
+ }
58
+ }
59
+ }
60
+ ```
61
+
62
+ ## Tools
63
+
64
+ | Tool | What it does |
65
+ | --- | --- |
66
+ | `search_web` | Ranked pages for a query. |
67
+ | `read_page` | One page as Markdown. |
68
+ | `extract_values` | The values a JSON Schema asks for, from one page. |
69
+ | `read_product` | A product page's variants, from its structured data. |
70
+ | `map_site` | A site's URLs, without reading them. |
71
+ | `crawl_site` | The text of many pages from one site. |
72
+ | `research_question` | An answer with sources, from a job of many steps. |
73
+ | `parse_document` | A PDF, DOCX, XLSX, PPTX, HTML or CSV file as Markdown. |
74
+ | `run_browser_script` | A script run in a remote browser. Only with `--allow-act`. |
75
+
76
+ Every tool's input schema lists only the options its provider supports. Results come back as `structuredContent`. Failures come back as results with `isError: true` and a JSON body: `code`, `message`, `retryable`, `retryAfter`, and `jobId` for a job that may still be running. Pass a `jobId` back as `resume` to collect the job instead of starting another.
77
+
78
+ ## Embedding
79
+
80
+ ```ts
81
+ import { Fieldwork } from "@howells/fieldwork-sdk";
82
+ import { firecrawl } from "@howells/fieldwork-sdk/firecrawl";
83
+ import { createFieldworkServer } from "@howells/fieldwork-mcp";
84
+
85
+ const server = createFieldworkServer(new Fieldwork({ adapter: firecrawl() }));
86
+ ```
87
+
88
+ `server` is an `McpServer` from `@modelcontextprotocol/server`; connect it to any transport.
89
+
90
+ Node 22 or later.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {}
package/dist/cli.js ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { a as createFieldworkServer, i as parseArgs, n as USAGE, r as fieldworkFor } from "./config-CVsHltSO.js";
3
+ import { createRequire } from "node:module";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
5
+ // src/cli.ts
6
+ const { version } = createRequire(import.meta.url)("../package.json");
7
+ try {
8
+ const config = parseArgs(process.argv.slice(2));
9
+ if (config.help) process.stderr.write(`${USAGE}\n`);
10
+ else if (config.version) process.stderr.write(`${version}\n`);
11
+ else {
12
+ const web = fieldworkFor(config);
13
+ await createFieldworkServer(web, {
14
+ version,
15
+ maxCharacters: config.maxCharacters,
16
+ capabilities: config.allowAct ? Object.entries(web.capabilities).filter(([, offered]) => offered).map(([capability]) => capability) : void 0
17
+ }).connect(new StdioServerTransport());
18
+ }
19
+ } catch (error) {
20
+ process.stderr.write(`fieldwork-mcp: ${error instanceof Error ? error.message : String(error)}\n`);
21
+ process.exitCode = 1;
22
+ }
23
+ export {};
@@ -0,0 +1,156 @@
1
+ import { toolError, toolsFor } from "@howells/fieldwork-sdk/tools";
2
+ import { McpServer } from "@modelcontextprotocol/server";
3
+ import { CAPABILITIES, Fieldwork, FieldworkError, router } from "@howells/fieldwork-sdk";
4
+ import { exa } from "@howells/fieldwork-sdk/exa";
5
+ import { firecrawl } from "@howells/fieldwork-sdk/firecrawl";
6
+ import { parallel } from "@howells/fieldwork-sdk/parallel";
7
+ import { perplexity } from "@howells/fieldwork-sdk/perplexity";
8
+ import { tavily } from "@howells/fieldwork-sdk/tavily";
9
+ // src/server.ts
10
+ /**
11
+ * Advertises a Fieldwork JSON Schema to MCP clients and passes every input
12
+ * through: the tool's own `execute` checks it, and its errors name the field,
13
+ * the problem and the inputs the provider accepts.
14
+ */
15
+ function schemaOf(schema) {
16
+ return { "~standard": {
17
+ version: 1,
18
+ vendor: "fieldwork",
19
+ validate: (value) => ({ value }),
20
+ jsonSchema: {
21
+ input: () => schema,
22
+ output: () => schema
23
+ }
24
+ } };
25
+ }
26
+ /**
27
+ * An MCP server with one tool per capability the Fieldwork instance offers.
28
+ * Tool errors come back as results with `isError: true` and a structured
29
+ * body, so a model can read the code and decide what to do next.
30
+ */
31
+ function createFieldworkServer(web, options = {}) {
32
+ const server = new McpServer({
33
+ name: "fieldwork",
34
+ title: "Fieldwork",
35
+ version: options.version ?? "0.0.0"
36
+ });
37
+ for (const tool of toolsFor(web, options)) server.registerTool(tool.name, {
38
+ title: tool.annotations.title,
39
+ description: tool.description,
40
+ inputSchema: schemaOf(tool.inputSchema),
41
+ outputSchema: schemaOf(tool.outputSchema),
42
+ annotations: tool.annotations,
43
+ _meta: {
44
+ "fieldwork/provider": tool.provider,
45
+ "fieldwork/capability": tool.capability
46
+ }
47
+ }, async (input, context) => {
48
+ try {
49
+ const result = await tool.execute(input, { signal: context.mcpReq.signal });
50
+ return {
51
+ content: [{
52
+ type: "text",
53
+ text: JSON.stringify(result)
54
+ }],
55
+ structuredContent: result
56
+ };
57
+ } catch (error) {
58
+ const body = toolError(error);
59
+ return {
60
+ isError: true,
61
+ content: [{
62
+ type: "text",
63
+ text: JSON.stringify(body)
64
+ }]
65
+ };
66
+ }
67
+ });
68
+ return server;
69
+ }
70
+ // src/config.ts
71
+ const PROVIDERS = [
72
+ "exa",
73
+ "firecrawl",
74
+ "tavily",
75
+ "parallel",
76
+ "perplexity"
77
+ ];
78
+ const USAGE = `Usage: fieldwork-mcp [options]
79
+
80
+ Serves Fieldwork's tools over MCP on stdin and stdout.
81
+
82
+ Choose providers with one of:
83
+ --provider <name> Every capability the provider offers.
84
+ --<capability> <name> One provider per capability, for example
85
+ --search parallel --read firecrawl.
86
+ Capabilities: ${CAPABILITIES.join(", ")}.
87
+
88
+ Providers: ${PROVIDERS.join(", ")}.
89
+ Keys come from EXA_API_KEY, FIRECRAWL_API_KEY, TAVILY_API_KEY,
90
+ PARALLEL_API_KEY and PERPLEXITY_API_KEY.
91
+
92
+ Options:
93
+ --allow-act Offer run_browser_script, which runs code in a
94
+ remote browser. Off unless given.
95
+ --max-characters <n> Long text one result may return. Default 20000.
96
+ --perplexity-model <id> The model Perplexity research runs on.
97
+ --help Show this text.
98
+ --version Show the version.`;
99
+ function invalid(message) {
100
+ return new FieldworkError("InvalidRequest", `${message}\n\n${USAGE}`);
101
+ }
102
+ function provider(value, flag) {
103
+ const found = PROVIDERS.find((name) => name === value);
104
+ if (!found) throw invalid(`${flag} needs one of ${PROVIDERS.join(", ")}`);
105
+ return found;
106
+ }
107
+ /** Reads the command line. Configuration is flags only; the environment holds keys. */
108
+ function parseArgs(argv) {
109
+ const config = {
110
+ routes: {},
111
+ allowAct: false,
112
+ help: false,
113
+ version: false
114
+ };
115
+ const args = [...argv];
116
+ while (args.length > 0) {
117
+ const flag = args.shift() ?? "";
118
+ const name = flag.replace(/^--/u, "");
119
+ if (flag === "--help" || flag === "-h") config.help = true;
120
+ else if (flag === "--version") config.version = true;
121
+ else if (flag === "--allow-act") config.allowAct = true;
122
+ else if (flag === "--provider") config.provider = provider(args.shift(), flag);
123
+ else if (flag === "--perplexity-model") config.perplexityModel = args.shift();
124
+ else if (flag === "--max-characters") {
125
+ const value = Number(args.shift());
126
+ if (!Number.isInteger(value) || value < 1) throw invalid("--max-characters needs a whole number above 0");
127
+ config.maxCharacters = value;
128
+ } else if (CAPABILITIES.includes(name)) config.routes[name] = provider(args.shift(), flag);
129
+ else throw invalid(`Unknown option ${flag}`);
130
+ }
131
+ return config;
132
+ }
133
+ function adapterFor(name, config) {
134
+ return {
135
+ exa: () => exa(),
136
+ firecrawl: () => firecrawl(),
137
+ tavily: () => tavily(),
138
+ parallel: () => parallel(),
139
+ perplexity: () => perplexity({ model: config.perplexityModel })
140
+ }[name]();
141
+ }
142
+ /** Builds the Fieldwork instance the configuration names. */
143
+ function fieldworkFor(config) {
144
+ const routed = Object.entries(config.routes);
145
+ if (config.provider && routed.length > 0) throw invalid("Use --provider or per-capability options, not both");
146
+ if (config.provider) return new Fieldwork({ adapter: adapterFor(config.provider, config) });
147
+ if (routed.length === 0) throw invalid("Choose a provider");
148
+ const adapters = /* @__PURE__ */ new Map();
149
+ const routes = Object.fromEntries(routed.map(([capability, name]) => {
150
+ const adapter = adapters.get(name) ?? adapterFor(name, config);
151
+ adapters.set(name, adapter);
152
+ return [capability, adapter];
153
+ }));
154
+ return new Fieldwork({ adapter: router(routes) });
155
+ }
156
+ export { createFieldworkServer as a, parseArgs as i, USAGE as n, fieldworkFor as r, PROVIDERS as t };
@@ -0,0 +1,31 @@
1
+ import { ToolsOptions } from "@howells/fieldwork-sdk/tools";
2
+ import { McpServer } from "@modelcontextprotocol/server";
3
+ import { AnyAdapter, Capability, Fieldwork } from "@howells/fieldwork-sdk";
4
+ interface FieldworkServerOptions extends ToolsOptions {
5
+ /** Reported to clients as the server's version. */
6
+ version?: string;
7
+ }
8
+ /**
9
+ * An MCP server with one tool per capability the Fieldwork instance offers.
10
+ * Tool errors come back as results with `isError: true` and a structured
11
+ * body, so a model can read the code and decide what to do next.
12
+ */
13
+ export declare function createFieldworkServer(web: Fieldwork<AnyAdapter>, options?: FieldworkServerOptions): McpServer;
14
+ export declare const PROVIDERS: readonly ["exa", "firecrawl", "tavily", "parallel", "perplexity"];
15
+ type Provider = (typeof PROVIDERS)[number];
16
+ interface ServerConfig {
17
+ /** One provider for every capability it offers. */
18
+ provider?: Provider;
19
+ /** A provider per capability, which builds a router. */
20
+ routes: Partial<Record<Capability, Provider>>;
21
+ allowAct: boolean;
22
+ maxCharacters?: number;
23
+ perplexityModel?: string;
24
+ help: boolean;
25
+ version: boolean;
26
+ }
27
+ /** Reads the command line. Configuration is flags only; the environment holds keys. */
28
+ export declare function parseArgs(argv: readonly string[]): ServerConfig;
29
+ /** Builds the Fieldwork instance the configuration names. */
30
+ export declare function fieldworkFor(config: ServerConfig): Fieldwork<AnyAdapter>;
31
+ export type { FieldworkServerOptions, ServerConfig };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { t as PROVIDERS, a as createFieldworkServer, r as fieldworkFor, i as parseArgs } from "./config-CVsHltSO.js";
package/package.json CHANGED
@@ -1,6 +1,67 @@
1
1
  {
2
2
  "name": "@howells/fieldwork-mcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "An MCP server for Fieldwork: web search, page reading, extraction, crawling, research and document parsing across Exa, Firecrawl, Tavily, Parallel and Perplexity, as tools for any MCP client.",
5
+ "keywords": [
6
+ "agents",
7
+ "exa",
8
+ "fieldwork",
9
+ "firecrawl",
10
+ "mcp",
11
+ "model-context-protocol",
12
+ "parallel",
13
+ "perplexity",
14
+ "tavily",
15
+ "web-search"
16
+ ],
17
+ "license": "MIT",
18
+ "author": "Daniel Howells",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/howells/fieldwork.git",
22
+ "directory": "packages/fieldwork-mcp"
23
+ },
24
+ "bin": {
25
+ "fieldwork-mcp": "./dist/cli.js"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "type": "module",
33
+ "sideEffects": false,
34
+ "types": "./dist/index.d.ts",
35
+ "exports": {
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "import": "./dist/index.js"
39
+ }
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "@modelcontextprotocol/server": "^2.4.0",
46
+ "@howells/fieldwork-sdk": "^0.2.0"
47
+ },
48
+ "devDependencies": {
49
+ "@howells/lint": "^3.5.1",
50
+ "@howells/typescript-config": "^0.1.8",
51
+ "@modelcontextprotocol/client": "^2.4.0",
52
+ "@types/node": "^26.6.2",
53
+ "tsdown": "^0.23.0",
54
+ "typescript": "7.0.2",
55
+ "vitest": "^5.0.1"
56
+ },
57
+ "engines": {
58
+ "node": ">=22.12.0"
59
+ },
60
+ "scripts": {
61
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsdown",
62
+ "lint": "howells-check .",
63
+ "lint:fix": "howells-fix .",
64
+ "test": "vitest run",
65
+ "typecheck": "tsc --noEmit"
66
+ }
6
67
  }