@browsa/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 +96 -0
- package/dist/server.js +251 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 neout
|
|
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,96 @@
|
|
|
1
|
+
# @browsa/mcp
|
|
2
|
+
|
|
3
|
+
**Drive anti-detect browsers from Claude Desktop, Cursor, Cline, and any MCP-compatible client.**
|
|
4
|
+
|
|
5
|
+
MCP (Model Context Protocol) server for [Browsa](https://browsa.io) — turn any LLM into a real-browser agent with one config line. The agent gets a freshly-spawned macOS-Chrome-fingerprinted Chromium with built-in CAPTCHA solving, human-shaped behavioral pacing, and a live noVNC stream so you can watch it work.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@browsa/mcp)
|
|
8
|
+
|
|
9
|
+
## What this is
|
|
10
|
+
|
|
11
|
+
Most "AI agent" setups stop at the LLM. We give the LLM a real browser. Through MCP, Claude can now:
|
|
12
|
+
- Navigate, fill forms, click, scroll, scrape
|
|
13
|
+
- Pass through Cloudflare Turnstile, hCaptcha, reCAPTCHA, ALTCHA
|
|
14
|
+
- Run from a residential US/UK/DE/etc. exit IP
|
|
15
|
+
- Stay logged in across calls via persistent identities
|
|
16
|
+
|
|
17
|
+
No code from you — just configure Claude Desktop (or Cursor / Cline / Zed) once, and ask in natural language.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
### Claude Desktop
|
|
22
|
+
|
|
23
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"browsa": {
|
|
29
|
+
"command": "npx",
|
|
30
|
+
"args": ["-y", "@browsa/mcp"],
|
|
31
|
+
"env": {
|
|
32
|
+
"BROWSA_API_KEY": "agt_live_...",
|
|
33
|
+
"BROWSA_LLM_API_KEY": "sk-ant-..."
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Restart Claude Desktop. Ask: *"Go to news.ycombinator.com and tell me the top story title."*
|
|
41
|
+
|
|
42
|
+
### Cursor
|
|
43
|
+
|
|
44
|
+
`~/.cursor/mcp.json`:
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"mcpServers": {
|
|
49
|
+
"browsa": {
|
|
50
|
+
"command": "npx",
|
|
51
|
+
"args": ["-y", "@browsa/mcp"],
|
|
52
|
+
"env": { "BROWSA_API_KEY": "agt_live_...", "BROWSA_LLM_API_KEY": "sk-ant-..." }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Cline / Continue / Zed
|
|
59
|
+
|
|
60
|
+
Any MCP-spec-compliant client. Point at `npx -y @browsa/mcp` and set the two env vars.
|
|
61
|
+
|
|
62
|
+
## Required env
|
|
63
|
+
|
|
64
|
+
| Var | Required | What |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `BROWSA_API_KEY` | yes | Your `agt_live_*` key from [browsa.io → Keys](https://browsa.io/dashboard/keys) |
|
|
67
|
+
| `BROWSA_LLM_API_KEY` | recommended | Default LLM provider key (e.g. Anthropic). Without this, Claude must pass it on every `run_task` call |
|
|
68
|
+
| `NEOUT_LLM` | no | Default LLM. Defaults to `claude-opus-4-7` |
|
|
69
|
+
| `NEOUT_COUNTRY` | no | Default egress country (e.g. `US`) |
|
|
70
|
+
| `NEOUT_BASE_URL` | no | Override prod URL (staging / self-hosted) |
|
|
71
|
+
|
|
72
|
+
## Tools exposed
|
|
73
|
+
|
|
74
|
+
| Tool | What |
|
|
75
|
+
|---|---|
|
|
76
|
+
| `run_task` | Run a browser-driven task. Returns final result + live noVNC URL. |
|
|
77
|
+
| `get_job` | Poll a long-running task. Optional server-side `wait_seconds`. |
|
|
78
|
+
| `cancel_job` | Cancel a running task. |
|
|
79
|
+
| `respond_to_job` | Answer an `input_required` prompt (CAPTCHA, decision). |
|
|
80
|
+
| `credits` | Show credit balance + top-up packs. |
|
|
81
|
+
| `list_webhooks` / `create_webhook` / `delete_webhook` | Webhook CRUD. |
|
|
82
|
+
| `list_identities` | List persistent browser identities (use one via `profile_id` to reuse warmed cookies). |
|
|
83
|
+
|
|
84
|
+
## Why MCP-first matters
|
|
85
|
+
|
|
86
|
+
Most agent platforms gate distribution behind "write code → deploy → call our API." We gate it behind "paste config → ask Claude." For an AI agent platform, **the prospect's first 30 seconds is the make-or-break moment.** With this MCP server, the first 30 seconds is a working browser doing what they asked.
|
|
87
|
+
|
|
88
|
+
## Source + Issues
|
|
89
|
+
|
|
90
|
+
- Source: <https://github.com/mohasaaid/neout/tree/main/sdks/@browsa/mcp>
|
|
91
|
+
- Issues: <https://github.com/mohasaaid/neout/issues>
|
|
92
|
+
- API reference: <https://browsa.io/docs>
|
|
93
|
+
|
|
94
|
+
## License
|
|
95
|
+
|
|
96
|
+
MIT.
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/server.ts
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import {
|
|
7
|
+
CallToolRequestSchema,
|
|
8
|
+
ListToolsRequestSchema
|
|
9
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { Client, NeoutError } from "browsa";
|
|
11
|
+
var API_KEY = process.env.BROWSA_API_KEY;
|
|
12
|
+
var BASE_URL = process.env.NEOUT_BASE_URL;
|
|
13
|
+
var DEFAULT_LLM = process.env.NEOUT_LLM ?? "claude-opus-4-7";
|
|
14
|
+
var DEFAULT_LLM_KEY = process.env.BROWSA_LLM_API_KEY;
|
|
15
|
+
var DEFAULT_COUNTRY = process.env.NEOUT_COUNTRY;
|
|
16
|
+
if (!API_KEY) {
|
|
17
|
+
console.error("[browsa-mcp] BROWSA_API_KEY env var is required");
|
|
18
|
+
console.error(" Mint one at https://browsa.io/dashboard/keys");
|
|
19
|
+
process.exit(2);
|
|
20
|
+
}
|
|
21
|
+
var client = new Client({
|
|
22
|
+
apiKey: API_KEY,
|
|
23
|
+
...BASE_URL ? { baseUrl: BASE_URL } : {}
|
|
24
|
+
});
|
|
25
|
+
var server = new Server(
|
|
26
|
+
{ name: "browsa", version: "0.1.0" },
|
|
27
|
+
{ capabilities: { tools: {} } }
|
|
28
|
+
);
|
|
29
|
+
var tools = [
|
|
30
|
+
{
|
|
31
|
+
name: "run_task",
|
|
32
|
+
description: "Run a browser-driven AI task on a fresh anti-detect Chromium burner. Returns the final result, total steps, and a live noVNC URL the user can open to watch the browser drive in real time. Use this when the user asks for anything web-shaped: scrape, fill, click, extract, login, place an order, search, summarize a page, etc. The browser is a real macOS-Chrome-fingerprinted Chromium with built-in CAPTCHA solving (Turnstile, hCaptcha, reCAPTCHA, ALTCHA, FunCaptcha) and behavioral humanization. Default timeout: 10 minutes.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
type: "object",
|
|
35
|
+
required: ["task"],
|
|
36
|
+
properties: {
|
|
37
|
+
task: {
|
|
38
|
+
type: "string",
|
|
39
|
+
description: "Plain-English instructions for the browser agent. Be specific about what to extract and the desired output shape (JSON, list, etc.). Example: 'Go to news.ycombinator.com and return the top 3 story titles + URLs as JSON.'"
|
|
40
|
+
},
|
|
41
|
+
llm: {
|
|
42
|
+
type: "string",
|
|
43
|
+
description: `LLM identifier (default: ${DEFAULT_LLM}). Common: claude-opus-4-7, claude-sonnet-4-6, gpt-4o.`
|
|
44
|
+
},
|
|
45
|
+
llm_api_key: {
|
|
46
|
+
type: "string",
|
|
47
|
+
description: "LLM provider API key (sk-ant-... / sk-...). Required if not set via BROWSA_LLM_API_KEY env."
|
|
48
|
+
},
|
|
49
|
+
country: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: `2-letter egress country (e.g. US, GB, DE). Default: ${DEFAULT_COUNTRY ?? "auto"}.`
|
|
52
|
+
},
|
|
53
|
+
max_steps: { type: "integer", description: "Max agent steps. Default 25. Increase for complex multi-page tasks." },
|
|
54
|
+
initial_url: { type: "string", description: "Optional pre-navigation URL (saves the first agent step)." },
|
|
55
|
+
ja3_profile: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "TLS handshake spoof profile (e.g. macos_chrome_137). Useful for TLS-fingerprinted sites."
|
|
58
|
+
},
|
|
59
|
+
profile_id: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Persistent identity UUID. If set, reuses an existing browser identity (cookies, history, fingerprint stay sticky). Omit for a fresh burner."
|
|
62
|
+
},
|
|
63
|
+
timeout_minutes: {
|
|
64
|
+
type: "integer",
|
|
65
|
+
description: "Max wall-clock minutes to wait before returning. Default 10.",
|
|
66
|
+
minimum: 1,
|
|
67
|
+
maximum: 30
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "get_job",
|
|
74
|
+
description: "Fetch the current state of a previously-submitted job by UUID. Returns status (queued|running|completed|failed|cancelled|input_required), step count, final_result text if terminal, and the live_url for the noVNC stream. Cheap \u2014 call repeatedly to poll a long-running job started by run_task.",
|
|
75
|
+
inputSchema: {
|
|
76
|
+
type: "object",
|
|
77
|
+
required: ["job_id"],
|
|
78
|
+
properties: {
|
|
79
|
+
job_id: { type: "string", description: "Job UUID returned by run_task or list_jobs." },
|
|
80
|
+
wait_seconds: {
|
|
81
|
+
type: "integer",
|
|
82
|
+
description: "If set (1-120), block server-side until the job reaches a terminal state or N seconds elapse.",
|
|
83
|
+
minimum: 0,
|
|
84
|
+
maximum: 120
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "cancel_job",
|
|
91
|
+
description: "Cancel a running job. Idempotent \u2014 returns success even if the job already terminated.",
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: "object",
|
|
94
|
+
required: ["job_id"],
|
|
95
|
+
properties: { job_id: { type: "string", description: "Job UUID." } }
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: "respond_to_job",
|
|
100
|
+
description: "Answer an input_required prompt (CAPTCHA solution, human decision point). Flips the job back to running.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
required: ["job_id", "response"],
|
|
104
|
+
properties: {
|
|
105
|
+
job_id: { type: "string" },
|
|
106
|
+
response: { type: "string", description: "Your answer to the agent's question." }
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: "credits",
|
|
112
|
+
description: "Get the current credit balance + available top-up packs.",
|
|
113
|
+
inputSchema: { type: "object", properties: {} }
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "list_webhooks",
|
|
117
|
+
description: "List webhook subscriptions for this workspace.",
|
|
118
|
+
inputSchema: { type: "object", properties: {} }
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "create_webhook",
|
|
122
|
+
description: "Register a callback URL the platform will POST lifecycle events to (HMAC-SHA256 signed via X-Agents-Signature). Use the verify helper from browsa/webhooks in your receiver.",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: "object",
|
|
125
|
+
required: ["url"],
|
|
126
|
+
properties: {
|
|
127
|
+
url: { type: "string", description: "HTTPS endpoint you control." },
|
|
128
|
+
events: {
|
|
129
|
+
type: "array",
|
|
130
|
+
items: { type: "string" },
|
|
131
|
+
description: "Event filter. Omit/empty for ALL events. Examples: job.completed, job.failed, agent.started."
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "delete_webhook",
|
|
138
|
+
description: "Delete (disable) a webhook subscription by id.",
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: "object",
|
|
141
|
+
required: ["webhook_id"],
|
|
142
|
+
properties: { webhook_id: { type: "string" } }
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
name: "list_identities",
|
|
147
|
+
description: "List persistent browser identities (long-lived cookies/history/fingerprint, billed monthly). Use one in run_task via profile_id to reuse a warmed-up session \u2014 critical for sites that fingerprint cookie age, session continuity, or behavioral history (Akamai, Cloudflare-strict, DataDome).",
|
|
148
|
+
inputSchema: { type: "object", properties: {} }
|
|
149
|
+
}
|
|
150
|
+
];
|
|
151
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
152
|
+
function ok(payload) {
|
|
153
|
+
return {
|
|
154
|
+
content: [{ type: "text", text: typeof payload === "string" ? payload : JSON.stringify(payload, null, 2) }]
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function fail(message, extra) {
|
|
158
|
+
const body = extra ? { error: message, ...extra } : { error: message };
|
|
159
|
+
return {
|
|
160
|
+
isError: true,
|
|
161
|
+
content: [{ type: "text", text: JSON.stringify(body, null, 2) }]
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
165
|
+
const name = req.params.name;
|
|
166
|
+
const args = req.params.arguments ?? {};
|
|
167
|
+
try {
|
|
168
|
+
switch (name) {
|
|
169
|
+
case "run_task": {
|
|
170
|
+
const llm = args.llm ?? DEFAULT_LLM;
|
|
171
|
+
const llmKey = args.llm_api_key ?? DEFAULT_LLM_KEY;
|
|
172
|
+
if (!llmKey) {
|
|
173
|
+
return fail(
|
|
174
|
+
"llm_api_key is required (or set BROWSA_LLM_API_KEY in the MCP env). Most users add their Anthropic key once via the env and never pass it per-call."
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
const job = await client.runTask({
|
|
178
|
+
task: args.task,
|
|
179
|
+
llm,
|
|
180
|
+
llmApiKey: llmKey,
|
|
181
|
+
country: args.country ?? DEFAULT_COUNTRY,
|
|
182
|
+
maxSteps: args.max_steps,
|
|
183
|
+
initialUrl: args.initial_url,
|
|
184
|
+
ja3Profile: args.ja3_profile,
|
|
185
|
+
profileId: args.profile_id,
|
|
186
|
+
timeoutMs: (args.timeout_minutes ?? 10) * 6e4
|
|
187
|
+
});
|
|
188
|
+
return ok({
|
|
189
|
+
job_id: job.id,
|
|
190
|
+
status: job.status,
|
|
191
|
+
steps: `${job.stepCount}/${job.progressTotal}`,
|
|
192
|
+
live_url: job.liveUrl,
|
|
193
|
+
credits_charged: job.creditsCharged,
|
|
194
|
+
final_result: job.finalResult,
|
|
195
|
+
error: job.errorMessage
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
case "get_job": {
|
|
199
|
+
const job = await client.getJob(args.job_id, { wait: args.wait_seconds ?? 0 });
|
|
200
|
+
return ok({
|
|
201
|
+
job_id: job.id,
|
|
202
|
+
status: job.status,
|
|
203
|
+
steps: `${job.stepCount}/${job.progressTotal}`,
|
|
204
|
+
live_url: job.liveUrl,
|
|
205
|
+
input_question: job.inputQuestion,
|
|
206
|
+
final_result: job.finalResult,
|
|
207
|
+
error: job.errorMessage
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
case "cancel_job": {
|
|
211
|
+
await client.cancelJob(args.job_id);
|
|
212
|
+
return ok({ cancelled: args.job_id });
|
|
213
|
+
}
|
|
214
|
+
case "respond_to_job": {
|
|
215
|
+
const job = await client.respondToJob(args.job_id, args.response);
|
|
216
|
+
return ok({ job_id: job.id, status: job.status });
|
|
217
|
+
}
|
|
218
|
+
case "credits":
|
|
219
|
+
return ok(await client.credits());
|
|
220
|
+
case "list_webhooks":
|
|
221
|
+
return ok(await client.listWebhooks());
|
|
222
|
+
case "create_webhook":
|
|
223
|
+
return ok(await client.createWebhook({ url: args.url, events: args.events }));
|
|
224
|
+
case "delete_webhook":
|
|
225
|
+
await client.deleteWebhook(args.webhook_id);
|
|
226
|
+
return ok({ deleted: args.webhook_id });
|
|
227
|
+
case "list_identities":
|
|
228
|
+
return ok(await client.listIdentities());
|
|
229
|
+
default:
|
|
230
|
+
return fail(`unknown tool: ${name}`);
|
|
231
|
+
}
|
|
232
|
+
} catch (e) {
|
|
233
|
+
if (e instanceof NeoutError) {
|
|
234
|
+
return fail(e.message, {
|
|
235
|
+
code: e.code,
|
|
236
|
+
status: e.status,
|
|
237
|
+
request_id: e.requestId
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return fail(e.message || String(e));
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
async function main() {
|
|
244
|
+
const transport = new StdioServerTransport();
|
|
245
|
+
await server.connect(transport);
|
|
246
|
+
console.error("[browsa-mcp] ready (tools=" + tools.length + ")");
|
|
247
|
+
}
|
|
248
|
+
main().catch((e) => {
|
|
249
|
+
console.error("[browsa-mcp] fatal:", e);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@browsa/mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Browsa \u2014 drive anti-detect cloud browsers from Claude Desktop, Cursor, Cline, and any MCP-compatible client",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Browsa <support@browsa.io>",
|
|
7
|
+
"homepage": "https://browsa.io",
|
|
8
|
+
"bugs": {
|
|
9
|
+
"email": "support@browsa.io"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"mcp",
|
|
13
|
+
"model-context-protocol",
|
|
14
|
+
"claude",
|
|
15
|
+
"cursor",
|
|
16
|
+
"anthropic",
|
|
17
|
+
"browser-automation",
|
|
18
|
+
"anti-detect",
|
|
19
|
+
"ai-agents",
|
|
20
|
+
"browser-use"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=18"
|
|
24
|
+
},
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "./dist/server.js",
|
|
27
|
+
"bin": {
|
|
28
|
+
"browsa-mcp": "./dist/server.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsup src/server.ts --format esm --target node18 --clean --shims",
|
|
37
|
+
"test": "node --test test/*.test.mjs",
|
|
38
|
+
"prepublishOnly": "npm run build && npm test",
|
|
39
|
+
"start": "node dist/server.js"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
43
|
+
"browsa": "^0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20",
|
|
47
|
+
"tsup": "^8",
|
|
48
|
+
"typescript": "^5"
|
|
49
|
+
}
|
|
50
|
+
}
|