@howells/fieldwork-mcp 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +128 -2
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +24 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +1 -0
- package/dist/server-BjeNI2GT.js +187 -0
- package/package.json +64 -3
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,129 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Fieldwork MCP server
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
|
|
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 brave --read firecrawl --product firecrawl --research exa \
|
|
21
|
+
--monitor firecrawl --browser browserbase --allow-monitor --allow-browser
|
|
22
|
+
```
|
|
23
|
+
|
|
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.
|
|
25
|
+
|
|
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
|
|
42
|
+
|
|
43
|
+
| Option | Effect |
|
|
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`. |
|
|
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. |
|
|
50
|
+
| `--max-characters <n>` | The long text one result may return. Default 20,000. |
|
|
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.
|
|
54
|
+
|
|
55
|
+
## Client configuration
|
|
56
|
+
|
|
57
|
+
Claude Code:
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
claude mcp add fieldwork -e FIRECRAWL_API_KEY=… -- npx -y @howells/fieldwork-mcp --provider firecrawl
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Claude Desktop, Cursor and other clients that take a JSON file:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"fieldwork": {
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": [
|
|
71
|
+
"-y",
|
|
72
|
+
"@howells/fieldwork-mcp",
|
|
73
|
+
"--search",
|
|
74
|
+
"parallel",
|
|
75
|
+
"--read",
|
|
76
|
+
"firecrawl"
|
|
77
|
+
],
|
|
78
|
+
"env": { "PARALLEL_API_KEY": "…", "FIRECRAWL_API_KEY": "…" }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Tools
|
|
85
|
+
|
|
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.
|
|
114
|
+
|
|
115
|
+
## Embedding
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { Fieldwork } from "@howells/fieldwork-sdk";
|
|
119
|
+
import { firecrawl } from "@howells/fieldwork-sdk/firecrawl";
|
|
120
|
+
import { createFieldworkServer } from "@howells/fieldwork-mcp";
|
|
121
|
+
|
|
122
|
+
const server = createFieldworkServer(new Fieldwork({ adapter: firecrawl() }), {
|
|
123
|
+
allow: ["monitor"],
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
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.
|
|
128
|
+
|
|
129
|
+
Node 22 or later.
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as parseArgs, i as fieldworkFor, r as USAGE, t as createFieldworkServer } from "./server-BjeNI2GT.js";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
5
|
+
// src/cli.ts
|
|
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
|
+
};
|
|
11
|
+
try {
|
|
12
|
+
const config = parseArgs(process.argv.slice(2));
|
|
13
|
+
if (config.help) process.stderr.write(`${USAGE}\n`);
|
|
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());
|
|
20
|
+
} catch (error) {
|
|
21
|
+
process.stderr.write(`fieldwork-mcp: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
22
|
+
process.exitCode = 1;
|
|
23
|
+
}
|
|
24
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { AnyAdapter, Capability, Fieldwork } from "@howells/fieldwork-sdk";
|
|
2
|
+
import { ToolsOptions } from "@howells/fieldwork-sdk/tools";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
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, 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.
|
|
13
|
+
*/
|
|
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"];
|
|
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>>;
|
|
19
|
+
interface ServerConfig {
|
|
20
|
+
/** Capabilities left out by default that the flags ask for. */
|
|
21
|
+
allow: Capability[];
|
|
22
|
+
help: boolean;
|
|
23
|
+
maxCharacters?: number;
|
|
24
|
+
/** One provider for every capability it offers. */
|
|
25
|
+
provider?: Provider;
|
|
26
|
+
/** A provider per capability, which builds a router. */
|
|
27
|
+
routes: Partial<Record<Capability, Provider>>;
|
|
28
|
+
version: boolean;
|
|
29
|
+
}
|
|
30
|
+
/** Reads the command line. Configuration is flags only; the environment holds keys. */
|
|
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
ADDED
|
@@ -0,0 +1 @@
|
|
|
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,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@howells/fieldwork-mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
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
|
+
"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.3.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@howells/lint": "^3.9.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
|
}
|