@howells/fieldwork-mcp 0.1.0 → 0.2.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Fieldwork MCP server
2
2
 
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.
3
+ Web search, image search, page reading, value extraction, site maps, crawls, answers, research, entity discovery, decisions, document parsing, remote browsers, monitors and account usage as tools for any MCP client, across Exa, Firecrawl, Tavily, Parallel, Perplexity, Brave, SerpApi and Browserbase. It serves the tools from [`@howells/fieldwork-sdk`](https://www.npmjs.com/package/@howells/fieldwork-sdk) over stdio.
4
4
 
5
5
  ```sh
6
6
  npx -y @howells/fieldwork-mcp --provider firecrawl
@@ -17,18 +17,40 @@ fieldwork-mcp --provider tavily
17
17
  Or one provider per capability. A capability without a provider has no tool, and nothing falls back to another provider:
18
18
 
19
19
  ```sh
20
- fieldwork-mcp --search parallel --read firecrawl --product firecrawl --research exa
20
+ fieldwork-mcp --search brave --read firecrawl --product firecrawl --research exa \
21
+ --monitor firecrawl --browser browserbase --allow-monitor --allow-browser
21
22
  ```
22
23
 
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
+ Capabilities: `search`, `images`, `read`, `readMany`, `extract`, `product`, `map`, `crawl`, `answer`, `research`, `discover`, `decide`, `act`, `browser`, `parse`, `monitor`, `usage`. A provider that lacks the capability you route to it stops the server at start-up with an error.
24
25
 
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
+ ### Providers
27
+
28
+ Each provider reads its key from the environment.
29
+
30
+ | Provider | Key | Capabilities |
31
+ | --- | --- | --- |
32
+ | `exa` | `EXA_API_KEY` | `search`, `read`, `readMany`, `extract`, `answer`, `research`, `monitor` |
33
+ | `firecrawl` | `FIRECRAWL_API_KEY` | `search`, `images`, `read`, `readMany`, `extract`, `product`, `map`, `crawl`, `research`, `act`, `browser`, `parse`, `monitor`, `usage` |
34
+ | `tavily` | `TAVILY_API_KEY` | `search`, `images`, `read`, `readMany`, `map`, `crawl`, `answer`, `research`, `usage` |
35
+ | `parallel` | `PARALLEL_API_KEY` | `search`, `images`, `read`, `readMany`, `extract`, `answer`, `research`, `discover`, `decide`, `monitor` |
36
+ | `perplexity` | `PERPLEXITY_API_KEY` | `search`, `answer`, `research`, `decide` |
37
+ | `brave` | `BRAVE_API_KEY` | `search`, `images`, `answer` |
38
+ | `serpapi` | `SERPAPI_API_KEY` | `search`, `images`, `usage` |
39
+ | `browserbase` | `BROWSERBASE_API_KEY`, and optionally `BROWSERBASE_PROJECT_ID` | `search`, `read`, `browser`, `usage` |
40
+
41
+ ## Options
26
42
 
27
43
  | Option | Effect |
28
44
  | --- | --- |
45
+ | `--provider <name>` | One provider for every capability it offers. |
46
+ | `--<capability> <name>` | One provider for one capability, for example `--monitor firecrawl` or `--browser browserbase`. |
29
47
  | `--allow-act` | Adds `run_browser_script`, which runs code in a remote browser. Off by default. |
48
+ | `--allow-browser` | Adds `open_browser` and `close_browser`, which keep a remote browser open until it is closed or expires. Off by default. |
49
+ | `--allow-monitor` | Adds `create_monitor`, `get_monitor`, `list_monitors`, `delete_monitor` and `list_monitor_events`. A monitor keeps running, and billing, until it is deleted. Off by default. |
30
50
  | `--max-characters <n>` | The long text one result may return. Default 20,000. |
31
- | `--perplexity-model <id>` | The model Perplexity research runs on. |
51
+ | `--help`, `--version` | Show the usage text or the version. |
52
+
53
+ The three `--allow-*` options are separate on purpose. Each tool group starts remote browsers or creates a lasting, billed resource, so a client gets it only when you ask.
32
54
 
33
55
  ## Client configuration
34
56
 
@@ -61,19 +83,34 @@ Claude Desktop, Cursor and other clients that take a JSON file:
61
83
 
62
84
  ## Tools
63
85
 
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.
86
+ Each tool serves one capability, or one method of `browser` or `monitor`. The last column lists the providers that offer it.
87
+
88
+ | Tool | What it does | Providers |
89
+ | --- | --- | --- |
90
+ | `search_web` | Ranked pages for a query. | all eight |
91
+ | `search_images` | Images for a query, with the page each appears on. | Firecrawl, Tavily, Parallel, Brave, SerpApi |
92
+ | `read_page` | One page as Markdown. | Exa, Firecrawl, Tavily, Parallel, Browserbase |
93
+ | `read_pages` | Several pages in one call; pages that fail come back in `failed`. | Exa, Firecrawl, Tavily, Parallel |
94
+ | `extract_values` | The values a JSON Schema asks for, from one page. | Exa, Firecrawl, Parallel |
95
+ | `read_product` | A product page's variants, from its structured data. | Firecrawl |
96
+ | `map_site` | A site's URLs, without reading them. | Firecrawl, Tavily |
97
+ | `crawl_site` | The text of many pages from one site. | Firecrawl, Tavily |
98
+ | `answer_question` | A short answer with its sources, in one call. | Exa, Tavily, Parallel, Perplexity, Brave |
99
+ | `research_question` | An answer with sources, from a job of many steps. | Exa, Firecrawl, Tavily, Parallel, Perplexity |
100
+ | `find_entities` | Entities of one kind that meet every condition you list. | Parallel |
101
+ | `make_decisions` | Yes or no, choice and score answers about a context, with probabilities. | Parallel, Perplexity |
102
+ | `parse_document` | A PDF, DOCX, XLSX, PPTX, HTML or CSV file as Markdown. | Firecrawl |
103
+ | `check_usage` | Credits used and remaining. | Firecrawl, Tavily, SerpApi, Browserbase |
104
+ | `run_browser_script` | A script run in a remote browser. Only with `--allow-act`. | Firecrawl |
105
+ | `open_browser` | A remote browser to drive over CDP. Only with `--allow-browser`. | Firecrawl, Browserbase |
106
+ | `close_browser` | Closes a browser opened with `open_browser`. Only with `--allow-browser`. | Firecrawl, Browserbase |
107
+ | `create_monitor` | A monitor that checks the web on a schedule. Only with `--allow-monitor`. | Exa, Firecrawl, Parallel |
108
+ | `get_monitor`, `list_monitors`, `delete_monitor` | Read, list and delete monitors. Only with `--allow-monitor`. | Exa, Firecrawl, Parallel |
109
+ | `list_monitor_events` | What a monitor found. Only with `--allow-monitor`. | Exa, Firecrawl, Parallel |
110
+
111
+ Every tool's input schema lists only the options its provider supports, so a client is never offered an option that would be ignored. Results come back as `structuredContent`, without the provider's own response. Lists come back as an object (`list_monitors` returns `{ "monitors": [...] }`), and `delete_monitor` and `close_browser` return `{ "deleted": true, "id": … }` and `{ "closed": true, "id": … }`.
112
+
113
+ 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
114
 
78
115
  ## Embedding
79
116
 
@@ -82,9 +119,11 @@ import { Fieldwork } from "@howells/fieldwork-sdk";
82
119
  import { firecrawl } from "@howells/fieldwork-sdk/firecrawl";
83
120
  import { createFieldworkServer } from "@howells/fieldwork-mcp";
84
121
 
85
- const server = createFieldworkServer(new Fieldwork({ adapter: firecrawl() }));
122
+ const server = createFieldworkServer(new Fieldwork({ adapter: firecrawl() }), {
123
+ allow: ["monitor"],
124
+ });
86
125
  ```
87
126
 
88
- `server` is an `McpServer` from `@modelcontextprotocol/server`; connect it to any transport.
127
+ `allow` names capabilities that are left out by default (`act`, `browser`, `monitor`) to include as well. `server` is an `McpServer` from `@modelcontextprotocol/server`; connect it to any transport.
89
128
 
90
129
  Node 22 or later.
package/dist/cli.js CHANGED
@@ -1,21 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { a as createFieldworkServer, i as parseArgs, n as USAGE, r as fieldworkFor } from "./config-CVsHltSO.js";
2
+ import { a as parseArgs, i as fieldworkFor, r as USAGE, t as createFieldworkServer } from "./server-BjeNI2GT.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
5
5
  // src/cli.ts
6
- const { version } = createRequire(import.meta.url)("../package.json");
6
+ const hasVersion = (value) => typeof value === "object" && value !== null && "version" in value && typeof value.version === "string";
7
+ const readVersion = () => {
8
+ const manifest = createRequire(import.meta.url)("../package.json");
9
+ return hasVersion(manifest) ? manifest.version : "0.0.0";
10
+ };
7
11
  try {
8
12
  const config = parseArgs(process.argv.slice(2));
9
13
  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
- }
14
+ else if (config.version) process.stderr.write(`${readVersion()}\n`);
15
+ else await createFieldworkServer(fieldworkFor(config), {
16
+ allow: config.allow,
17
+ maxCharacters: config.maxCharacters,
18
+ version: readVersion()
19
+ }).connect(new StdioServerTransport());
19
20
  } catch (error) {
20
21
  process.stderr.write(`fieldwork-mcp: ${error instanceof Error ? error.message : String(error)}\n`);
21
22
  process.exitCode = 1;
package/dist/index.d.ts CHANGED
@@ -1,31 +1,37 @@
1
+ import { AnyAdapter, Capability, Fieldwork } from "@howells/fieldwork-sdk";
1
2
  import { ToolsOptions } from "@howells/fieldwork-sdk/tools";
2
3
  import { McpServer } from "@modelcontextprotocol/server";
3
- import { AnyAdapter, Capability, Fieldwork } from "@howells/fieldwork-sdk";
4
4
  interface FieldworkServerOptions extends ToolsOptions {
5
5
  /** Reported to clients as the server's version. */
6
6
  version?: string;
7
7
  }
8
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.
9
+ * An MCP server with one tool per capability, or per method of a group
10
+ * capability, the Fieldwork instance offers. Tool errors come back as results
11
+ * with `isError: true` and a structured body, so a model can read the code
12
+ * and decide what to do next.
12
13
  */
13
- export declare function createFieldworkServer(web: Fieldwork<AnyAdapter>, options?: FieldworkServerOptions): McpServer;
14
- export declare const PROVIDERS: readonly ["exa", "firecrawl", "tavily", "parallel", "perplexity"];
14
+ export declare const createFieldworkServer: (web: Fieldwork<AnyAdapter>, options?: FieldworkServerOptions) => McpServer;
15
+ export declare const PROVIDERS: readonly ["exa", "firecrawl", "tavily", "parallel", "perplexity", "brave", "serpapi", "browserbase"];
15
16
  type Provider = (typeof PROVIDERS)[number];
17
+ /** API keys given directly; a provider without one reads its key from the environment. */
18
+ type ApiKeys = Partial<Record<Provider, string>>;
16
19
  interface ServerConfig {
20
+ /** Capabilities left out by default that the flags ask for. */
21
+ allow: Capability[];
22
+ help: boolean;
23
+ maxCharacters?: number;
17
24
  /** One provider for every capability it offers. */
18
25
  provider?: Provider;
19
26
  /** A provider per capability, which builds a router. */
20
27
  routes: Partial<Record<Capability, Provider>>;
21
- allowAct: boolean;
22
- maxCharacters?: number;
23
- perplexityModel?: string;
24
- help: boolean;
25
28
  version: boolean;
26
29
  }
27
30
  /** 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 };
31
+ export declare const parseArgs: (argv: readonly string[]) => ServerConfig;
32
+ /**
33
+ * Builds the Fieldwork instance the configuration names. A route to a
34
+ * provider that lacks the capability throws `Unsupported`.
35
+ */
36
+ export declare const fieldworkFor: (config: ServerConfig, keys?: ApiKeys) => Fieldwork<AnyAdapter>;
37
+ export type { ApiKeys, FieldworkServerOptions, Provider, ServerConfig };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export { t as PROVIDERS, a as createFieldworkServer, r as fieldworkFor, i as parseArgs } from "./config-CVsHltSO.js";
1
+ export { n as PROVIDERS, t as createFieldworkServer, i as fieldworkFor, a as parseArgs } from "./server-BjeNI2GT.js";
@@ -0,0 +1,187 @@
1
+ import { CAPABILITIES, Fieldwork, FieldworkError, runtimeRouter } from "@howells/fieldwork-sdk";
2
+ import { brave } from "@howells/fieldwork-sdk/brave";
3
+ import { browserbase } from "@howells/fieldwork-sdk/browserbase";
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 { serpapi } from "@howells/fieldwork-sdk/serpapi";
9
+ import { tavily } from "@howells/fieldwork-sdk/tavily";
10
+ import { toolError, toolsFor } from "@howells/fieldwork-sdk/tools";
11
+ import { McpServer } from "@modelcontextprotocol/server";
12
+ // src/config.ts
13
+ const PROVIDERS = [
14
+ "exa",
15
+ "firecrawl",
16
+ "tavily",
17
+ "parallel",
18
+ "perplexity",
19
+ "brave",
20
+ "serpapi",
21
+ "browserbase"
22
+ ];
23
+ const USAGE = `Usage: fieldwork-mcp [options]
24
+
25
+ Serves Fieldwork's tools over MCP on stdin and stdout.
26
+
27
+ Choose providers with one of:
28
+ --provider <name> Every capability the provider offers.
29
+ --<capability> <name> One provider per capability, for example
30
+ --search parallel --read firecrawl
31
+ --monitor firecrawl --browser browserbase.
32
+ Capabilities: ${CAPABILITIES.join(", ")}.
33
+
34
+ Providers: ${PROVIDERS.join(", ")}.
35
+ Keys come from EXA_API_KEY, FIRECRAWL_API_KEY, TAVILY_API_KEY,
36
+ PARALLEL_API_KEY, PERPLEXITY_API_KEY, BRAVE_API_KEY, SERPAPI_API_KEY and
37
+ BROWSERBASE_API_KEY. Browserbase also reads BROWSERBASE_PROJECT_ID, which is
38
+ optional.
39
+
40
+ Tools that start remote browsers or create lasting, billed resources are off
41
+ unless you ask for them:
42
+ --allow-act Offer run_browser_script, which runs code in a
43
+ remote browser.
44
+ --allow-browser Offer open_browser and close_browser, which
45
+ keep a remote browser open until it is closed or
46
+ expires.
47
+ --allow-monitor Offer create_monitor, get_monitor,
48
+ list_monitors, delete_monitor and
49
+ list_monitor_events. A monitor keeps running and
50
+ billing until it is deleted.
51
+
52
+ Other options:
53
+ --max-characters <n> Long text one result may return. Default 20000.
54
+ --help Show this text.
55
+ --version Show the version.`;
56
+ const ALLOW_FLAGS = /* @__PURE__ */ new Map([
57
+ ["--allow-act", "act"],
58
+ ["--allow-browser", "browser"],
59
+ ["--allow-monitor", "monitor"]
60
+ ]);
61
+ const invalid = (message) => new FieldworkError("InvalidRequest", `${message}\n\n${USAGE}`);
62
+ const providerFrom = (value, flag) => {
63
+ const found = PROVIDERS.find((name) => name === value);
64
+ if (found === void 0) throw invalid(`${flag} needs one of ${PROVIDERS.join(", ")}`);
65
+ return found;
66
+ };
67
+ /** Applies one flag to the configuration, taking its value from `args` when it has one. */
68
+ const readFlag = (config, flag, args) => {
69
+ const allowed = ALLOW_FLAGS.get(flag);
70
+ const capability = CAPABILITIES.find((name) => `--${name}` === flag);
71
+ if (flag === "--help" || flag === "-h") config.help = true;
72
+ else if (flag === "--version") config.version = true;
73
+ else if (allowed !== void 0) config.allow = [.../* @__PURE__ */ new Set([...config.allow, allowed])];
74
+ else if (flag === "--provider") config.provider = providerFrom(args.shift(), flag);
75
+ else if (flag === "--max-characters") {
76
+ const value = Number(args.shift());
77
+ if (!Number.isInteger(value) || value < 1) throw invalid("--max-characters needs a whole number above 0");
78
+ config.maxCharacters = value;
79
+ } else if (capability === void 0) throw invalid(`Unknown option ${flag}`);
80
+ else config.routes[capability] = providerFrom(args.shift(), flag);
81
+ };
82
+ /** Reads the command line. Configuration is flags only; the environment holds keys. */
83
+ const parseArgs = (argv) => {
84
+ const config = {
85
+ allow: [],
86
+ help: false,
87
+ routes: {},
88
+ version: false
89
+ };
90
+ const args = [...argv];
91
+ while (args.length > 0) readFlag(config, args.shift() ?? "", args);
92
+ return config;
93
+ };
94
+ const FACTORIES = {
95
+ brave: (apiKey) => brave({ apiKey }),
96
+ browserbase: (apiKey) => browserbase({ apiKey }),
97
+ exa: (apiKey) => exa({ apiKey }),
98
+ firecrawl: (apiKey) => firecrawl({ apiKey }),
99
+ parallel: (apiKey) => parallel({ apiKey }),
100
+ perplexity: (apiKey) => perplexity({ apiKey }),
101
+ serpapi: (apiKey) => serpapi({ apiKey }),
102
+ tavily: (apiKey) => tavily({ apiKey })
103
+ };
104
+ const adapterFor = (name, keys) => FACTORIES[name](keys[name]);
105
+ /**
106
+ * Builds the Fieldwork instance the configuration names. A route to a
107
+ * provider that lacks the capability throws `Unsupported`.
108
+ */
109
+ const fieldworkFor = (config, keys = {}) => {
110
+ const routed = CAPABILITIES.filter((capability) => config.routes[capability] !== void 0);
111
+ if (config.provider !== void 0 && routed.length > 0) throw invalid("Use --provider or per-capability options, not both");
112
+ if (config.provider !== void 0) return new Fieldwork({ adapter: adapterFor(config.provider, keys) });
113
+ if (routed.length === 0) throw invalid("Choose a provider");
114
+ const adapters = /* @__PURE__ */ new Map();
115
+ const routes = {};
116
+ for (const capability of routed) {
117
+ const name = config.routes[capability];
118
+ if (name !== void 0) {
119
+ const adapter = adapters.get(name) ?? adapterFor(name, keys);
120
+ adapters.set(name, adapter);
121
+ routes[capability] = adapter;
122
+ }
123
+ }
124
+ return new Fieldwork({ adapter: runtimeRouter(routes) });
125
+ };
126
+ // src/server.ts
127
+ const isArguments = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
128
+ /**
129
+ * Advertises a Fieldwork JSON Schema to MCP clients and passes every input
130
+ * object through: the tool's own `execute` checks it, and its errors name the
131
+ * field, the problem and the inputs the provider accepts.
132
+ */
133
+ const schemaOf = (schema) => ({ "~standard": {
134
+ jsonSchema: {
135
+ input: () => schema,
136
+ output: () => schema
137
+ },
138
+ validate: (value) => isArguments(value) ? { value } : { issues: [{ message: "input must be a JSON object" }] },
139
+ vendor: "fieldwork",
140
+ version: 1
141
+ } });
142
+ /**
143
+ * An MCP server with one tool per capability, or per method of a group
144
+ * capability, the Fieldwork instance offers. Tool errors come back as results
145
+ * with `isError: true` and a structured body, so a model can read the code
146
+ * and decide what to do next.
147
+ */
148
+ const createFieldworkServer = (web, options = {}) => {
149
+ const server = new McpServer({
150
+ name: "fieldwork",
151
+ title: "Fieldwork",
152
+ version: options.version ?? "0.0.0"
153
+ });
154
+ for (const tool of toolsFor(web, options)) server.registerTool(tool.name, {
155
+ _meta: {
156
+ "fieldwork/capability": tool.capability,
157
+ "fieldwork/method": tool.method,
158
+ "fieldwork/provider": tool.provider
159
+ },
160
+ annotations: tool.annotations,
161
+ description: tool.description,
162
+ inputSchema: schemaOf(tool.inputSchema),
163
+ outputSchema: schemaOf(tool.outputSchema),
164
+ title: tool.annotations.title
165
+ }, async (input, context) => {
166
+ try {
167
+ const result = await tool.execute(input, { signal: context.mcpReq.signal });
168
+ return {
169
+ content: [{
170
+ text: JSON.stringify(result),
171
+ type: "text"
172
+ }],
173
+ structuredContent: result
174
+ };
175
+ } catch (error) {
176
+ return {
177
+ content: [{
178
+ text: JSON.stringify(toolError(error)),
179
+ type: "text"
180
+ }],
181
+ isError: true
182
+ };
183
+ }
184
+ });
185
+ return server;
186
+ };
187
+ export { parseArgs as a, fieldworkFor as i, PROVIDERS as n, USAGE as r, createFieldworkServer as t };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@howells/fieldwork-mcp",
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.",
3
+ "version": "0.2.0",
4
+ "description": "An MCP server for Fieldwork: web and image search, page reading, extraction, product data, crawling, answers, research, entity discovery, decisions, monitors, remote browsers and document parsing across Exa, Firecrawl, Tavily, Parallel, Perplexity, Brave, SerpApi and Browserbase, as tools for any MCP client.",
5
5
  "keywords": [
6
6
  "agents",
7
7
  "exa",
@@ -43,10 +43,10 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@modelcontextprotocol/server": "^2.4.0",
46
- "@howells/fieldwork-sdk": "^0.2.0"
46
+ "@howells/fieldwork-sdk": "^0.3.0"
47
47
  },
48
48
  "devDependencies": {
49
- "@howells/lint": "^3.5.1",
49
+ "@howells/lint": "^3.9.1",
50
50
  "@howells/typescript-config": "^0.1.8",
51
51
  "@modelcontextprotocol/client": "^2.4.0",
52
52
  "@types/node": "^26.6.2",
@@ -1,156 +0,0 @@
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 };