@attson/atwebpilot-mcp 0.0.81 → 0.0.83
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 +8 -4
- package/dist/index.js +170 -5
- package/dist/skill/SKILL.md +10 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
让 Codex 或 Claude Code 经一个本地 ws 中继驱动 atwebpilot 浏览器扩展操作网页(读 / 写 / 采)。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 给用户:安装
|
|
6
6
|
|
|
7
7
|
Codex:
|
|
8
8
|
|
|
9
|
+
npx -y @attson/atwebpilot-mcp --install-codex-skill
|
|
9
10
|
codex mcp add atwebpilot -- npx -y @attson/atwebpilot-mcp
|
|
10
11
|
|
|
12
|
+
第一条命令把正式 Skill 安装到用户级 `~/.agents/skills/atwebpilot-browser/`,让 Codex 在真实网页验收、截图、调试、读取或交互任务中主动发现 AtWebPilot;它不会修改任何项目的 `AGENTS.md`。内容相同时重复执行是幂等的;若目标被手工修改会拒绝覆盖,确认替换时显式加 `--force`。安装后新开 Codex 会话。
|
|
13
|
+
|
|
11
14
|
Claude Code:
|
|
12
15
|
|
|
13
16
|
claude mcp add atwebpilot --scope user -- npx -y @attson/atwebpilot-mcp
|
|
@@ -17,8 +20,8 @@ Claude Code:
|
|
|
17
20
|
**不需要手填端口。** 从 Plan 33 起是配对流程:
|
|
18
21
|
|
|
19
22
|
1. 会话启动时**不绑任何端口** —— 不碰网页的会话零副作用。
|
|
20
|
-
2. AI 第一次调 `
|
|
21
|
-
3.
|
|
23
|
+
2. AI 第一次调 `browser_start` 时才绑一个空闲端口,通过 MCP URL elicitation 请宿主打开配对页;不支持该能力的宿主回退到系统默认浏览器。`browser_start` 最多等待 45 秒。
|
|
24
|
+
3. 你在浏览器里点「允许」,扩展即接入;唯一 tab 可确定时同一次 `browser_start` 调用直接返回 ready session,多个候选时返回 `select_tab`,AI 带选中的 `tab_id` 再调用一次。无需让你回复「已连接」。只有等待超时才返回 `pairing_required + pair_url`;`pairing_status` 始终是立即返回的非阻塞状态检查。
|
|
22
25
|
4. 之后本机的会话都免确认(信任是安装级的);端口能复用时连配对页都不会弹。
|
|
23
26
|
|
|
24
27
|
若宿主和系统都未弹出浏览器,可在超时结果中打开 `pair_url`。授权成功只以扩展实际建立连接并发送 `HELLO` 为准;打开配对页本身不代表已连接。
|
|
@@ -30,6 +33,7 @@ Coordinator 查看已接入的会话、单独断开,或撤销信任。
|
|
|
30
33
|
|
|
31
34
|
- `ATWEBPILOT_WS_PORT`:固定端口(默认是自动选一个空闲端口并复用上次的)
|
|
32
35
|
- `ATWEBPILOT_WS_TOKEN`:要求扩展带 `bearer.<token>` 子协议
|
|
36
|
+
- `ATWEBPILOT_CODEX_SKILLS_DIR`:覆盖 Codex Skill 安装根目录(默认 `~/.agents/skills`)
|
|
33
37
|
|
|
34
38
|
## 给开发者:本地 monorepo
|
|
35
39
|
|
|
@@ -48,7 +52,7 @@ listen 端口的能力,所以方向只能如此。这也是配对页存在的
|
|
|
48
52
|
|
|
49
53
|
## 工具面
|
|
50
54
|
|
|
51
|
-
- 控制面
|
|
55
|
+
- 控制面 6 个:`browser_start / pairing_status / list_tabs / open_session / close_session / get_quota`。常规网页任务从 `browser_start` 开始;`list_tabs` / `open_session` 保留为底层兼容入口。
|
|
52
56
|
- 发现 1 个:`browser_discoverTools` —— 已知工具时直接传 `enable: [...]`,已知分组时直接传 `enableGroups: ["export", ...]`,也可用 `profile: "core" | "full"` 切换整套工具面;只有这些都未知时才不带参数读取目录。选中的工具会加入本进程的 `tools/list`(发送 `tools/list_changed`),并始终返回完整 schema,包括已经启用的工具。
|
|
53
57
|
- 同轮执行桥 1 个:`browser_call({session_id, tool, args})` —— 某些宿主虽已刷新 `tools/list`,却不会在当前模型轮次注入新工具。AI 可在 `discoverTools` 后立刻通过该桥执行,不需要让用户回复“继续”;下一轮看到原生工具后仍优先使用原生工具。
|
|
54
58
|
- 批量编排 1 个:`browser_runSteps({session_id, steps, return_bindings?})` —— 在步骤和参数已经明确时,把 1–20 个 safe/caution 工具合并为一次 MCP 调用;`${binding}` 可引用前序结果。整批先做工具支持、权限和配额预检,dangerous、脚本、截图、下载与 tab 生命周期操作保持单步。
|
package/dist/index.js
CHANGED
|
@@ -946,6 +946,20 @@ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprot
|
|
|
946
946
|
|
|
947
947
|
// src/control-tools.ts
|
|
948
948
|
var CONTROL_TOOLS = [
|
|
949
|
+
{
|
|
950
|
+
name: "browser_start",
|
|
951
|
+
description: "Primary entry point for a live web page in the user's existing Chrome: localhost, screenshots/UI smoke, authenticated interaction, or console/network debugging. Pairs if needed and returns a ready session or tab candidates.",
|
|
952
|
+
inputSchema: {
|
|
953
|
+
type: "object",
|
|
954
|
+
properties: {
|
|
955
|
+
tab_id: { type: "string", description: "Exact id from browser_start/list_tabs" },
|
|
956
|
+
url: { type: "string", description: "Exact URL of an open tab" },
|
|
957
|
+
capabilities: { type: "array", items: { type: "string" }, description: "Optional capability scope" },
|
|
958
|
+
idle_timeout_min: { type: "number", description: "Optional idle timeout in minutes" }
|
|
959
|
+
},
|
|
960
|
+
additionalProperties: false
|
|
961
|
+
}
|
|
962
|
+
},
|
|
949
963
|
{
|
|
950
964
|
name: "pairing_status",
|
|
951
965
|
description: "Start or check browser pairing without waiting. Returns connected or pairing_required with the pair URL.",
|
|
@@ -2389,6 +2403,53 @@ async function handleListTabs(deps, onPairingRequired, timeoutMs = LIST_TABS_PAI
|
|
|
2389
2403
|
}
|
|
2390
2404
|
return { status: "connected", tabs: worker.available_tabs };
|
|
2391
2405
|
}
|
|
2406
|
+
function selectionResult(tabs, message) {
|
|
2407
|
+
return { status: "select_tab", tabs, message };
|
|
2408
|
+
}
|
|
2409
|
+
async function handleBrowserStart(deps, args, onPairingRequired, timeoutMs = LIST_TABS_PAIRING_TIMEOUT_MS) {
|
|
2410
|
+
const listed = await handleListTabs(deps, onPairingRequired, timeoutMs);
|
|
2411
|
+
if (listed.status === "pairing_required") return listed;
|
|
2412
|
+
const tabId = typeof args.tab_id === "string" && args.tab_id.length > 0 ? args.tab_id : void 0;
|
|
2413
|
+
const url = typeof args.url === "string" && args.url.length > 0 ? args.url : void 0;
|
|
2414
|
+
let selected;
|
|
2415
|
+
if (tabId) {
|
|
2416
|
+
selected = listed.tabs.find((tab) => tab.tab_id === tabId);
|
|
2417
|
+
if (!selected) {
|
|
2418
|
+
throw new Error(`InvalidArgs: browser_start tab_id ${JSON.stringify(tabId)} is not available`);
|
|
2419
|
+
}
|
|
2420
|
+
if (url && selected.url !== url) {
|
|
2421
|
+
throw new Error("InvalidArgs: browser_start tab_id and url must identify the same tab");
|
|
2422
|
+
}
|
|
2423
|
+
} else if (url) {
|
|
2424
|
+
const matches = listed.tabs.filter((tab) => tab.url === url);
|
|
2425
|
+
if (matches.length === 1) {
|
|
2426
|
+
[selected] = matches;
|
|
2427
|
+
} else if (matches.length > 1) {
|
|
2428
|
+
return selectionResult(
|
|
2429
|
+
matches,
|
|
2430
|
+
"Multiple tabs have that exact URL. Choose one and call browser_start again with tab_id."
|
|
2431
|
+
);
|
|
2432
|
+
} else {
|
|
2433
|
+
return selectionResult(
|
|
2434
|
+
listed.tabs,
|
|
2435
|
+
"No exact URL match. Choose a tab and call browser_start again with tab_id."
|
|
2436
|
+
);
|
|
2437
|
+
}
|
|
2438
|
+
} else if (listed.tabs.length === 1) {
|
|
2439
|
+
[selected] = listed.tabs;
|
|
2440
|
+
} else {
|
|
2441
|
+
return selectionResult(
|
|
2442
|
+
listed.tabs,
|
|
2443
|
+
"Choose a tab and call browser_start again with tab_id."
|
|
2444
|
+
);
|
|
2445
|
+
}
|
|
2446
|
+
const opened = await handleOpenSession(deps, {
|
|
2447
|
+
tab_id: selected.tab_id,
|
|
2448
|
+
...Array.isArray(args.capabilities) ? { capabilities: args.capabilities } : {},
|
|
2449
|
+
...typeof args.idle_timeout_min === "number" ? { idle_timeout_min: args.idle_timeout_min } : {}
|
|
2450
|
+
});
|
|
2451
|
+
return { status: "ready", session_id: opened.session_id, tab: selected };
|
|
2452
|
+
}
|
|
2392
2453
|
async function handleOpenSession(deps, args) {
|
|
2393
2454
|
const { coordinator } = await deps.ensure();
|
|
2394
2455
|
const worker_id = singleWorkerId(coordinator, deps.pairUrl());
|
|
@@ -2792,9 +2853,9 @@ var SKILL_DESCRIPTION = "Strategy + scenarios + safety rails for driving the AtW
|
|
|
2792
2853
|
var TOPICS = {
|
|
2793
2854
|
quickstart: `# AtWebPilot quickstart
|
|
2794
2855
|
|
|
2795
|
-
1. Call
|
|
2796
|
-
2. If
|
|
2797
|
-
3.
|
|
2856
|
+
1. Call browser_start once. On first use it opens the pairing page and waits for authorization; when one tab is unambiguous it returns a ready session in the same call.
|
|
2857
|
+
2. If browser_start returns select_tab, call browser_start again with one returned tab_id. list_tabs and open_session remain lower-level compatibility tools.
|
|
2858
|
+
3. If browser_start times out and returns pairing_required, show pair_url and wait for browser approval. Use pairing_status for a later non-blocking status check; do not ask the user to reply just to continue.
|
|
2798
2859
|
4. For interaction use browser_getPageInfo then browser_takeSnapshot; for broad reading use browser_createPageIndex.
|
|
2799
2860
|
5. When 2-20 safe/caution steps and their arguments are already known, use browser_runSteps to execute them in one MCP call. Keep dangerous actions and steps needing intermediate inspection as separate calls.
|
|
2800
2861
|
6. Prefer purpose-built tools and verified results. Use browser_discoverTools only for capabilities outside the connected core surface. Its response always includes the selected schemas. If a newly enabled native tool is not visible in the current turn, call it immediately with browser_call; do not ask the user to send "continue".
|
|
@@ -2878,7 +2939,7 @@ var SKILL_TOOL = {
|
|
|
2878
2939
|
// package.json
|
|
2879
2940
|
var package_default = {
|
|
2880
2941
|
name: "@attson/atwebpilot-mcp",
|
|
2881
|
-
version: "0.0.
|
|
2942
|
+
version: "0.0.83",
|
|
2882
2943
|
license: "Apache-2.0",
|
|
2883
2944
|
description: "MCP server for the atwebpilot browser extension - let Codex or Claude Code drive your browser.",
|
|
2884
2945
|
homepage: "https://github.com/attson/atwebpilot#readme",
|
|
@@ -2987,6 +3048,7 @@ function createPairingPresenter(server, fallbackOpenUrl) {
|
|
|
2987
3048
|
}
|
|
2988
3049
|
|
|
2989
3050
|
// src/mcp-server.ts
|
|
3051
|
+
var MCP_INSTRUCTIONS = "Use AtWebPilot for tasks that require inspecting, testing, debugging, screenshotting, reading, or interacting with a live web page in the user's existing Chrome, including localhost and authenticated pages. Start with browser_start. When AtWebPilot is available, use it before probing or launching Playwright, Puppeteer, or Chrome remote debugging. Do not start the browser for pure code editing or static source analysis that does not require observing a live page.";
|
|
2990
3052
|
var ALL_BROWSER_TOOLS = generateBrowserTools("full");
|
|
2991
3053
|
var BROWSER_BY_NAME = new Map(ALL_BROWSER_TOOLS.map((t) => [t.name, t]));
|
|
2992
3054
|
function createToolState(mode) {
|
|
@@ -3102,6 +3164,11 @@ async function dispatchCall(deps, name, args, state = DEFAULT_STATE, onListChang
|
|
|
3102
3164
|
if (result.status === "connected") await revealBrowserSurface(state, onListChanged);
|
|
3103
3165
|
return ok(result);
|
|
3104
3166
|
}
|
|
3167
|
+
if (name === "browser_start") {
|
|
3168
|
+
const result = await handleBrowserStart(deps, args, onPairingRequired);
|
|
3169
|
+
if (result.status !== "pairing_required") await revealBrowserSurface(state, onListChanged);
|
|
3170
|
+
return ok(result);
|
|
3171
|
+
}
|
|
3105
3172
|
if (name === "open_session") {
|
|
3106
3173
|
const result = await handleOpenSession(deps, args);
|
|
3107
3174
|
await revealBrowserSurface(state, onListChanged);
|
|
@@ -3154,7 +3221,10 @@ async function revealBrowserSurface(state, onListChanged) {
|
|
|
3154
3221
|
function createMcpServer(deps, state = DEFAULT_STATE, options = {}) {
|
|
3155
3222
|
const server = new Server(
|
|
3156
3223
|
{ name: "atwebpilot-mcp", version: MCP_VERSION },
|
|
3157
|
-
{
|
|
3224
|
+
{
|
|
3225
|
+
capabilities: { tools: { listChanged: true }, logging: {} },
|
|
3226
|
+
instructions: MCP_INSTRUCTIONS
|
|
3227
|
+
}
|
|
3158
3228
|
);
|
|
3159
3229
|
const presentPairing = createPairingPresenter(server, options.fallbackOpenUrl);
|
|
3160
3230
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: buildToolList(deps, state) }));
|
|
@@ -3931,8 +4001,103 @@ async function defaultOpenUrl(url) {
|
|
|
3931
4001
|
throw new Error(errors.join("; ") || "no browser opener available");
|
|
3932
4002
|
}
|
|
3933
4003
|
|
|
4004
|
+
// src/skill-installer.ts
|
|
4005
|
+
import { access, mkdir, readFile as readFile2, rename, unlink, writeFile } from "fs/promises";
|
|
4006
|
+
import { homedir as homedir2 } from "os";
|
|
4007
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
4008
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
4009
|
+
var SKILL_DIRECTORY_NAME = "atwebpilot-browser";
|
|
4010
|
+
function isMissing(error) {
|
|
4011
|
+
return error?.code === "ENOENT";
|
|
4012
|
+
}
|
|
4013
|
+
async function atomicWrite(targetFile, content) {
|
|
4014
|
+
const temporaryFile = `${targetFile}.${process.pid}.${Date.now()}.tmp`;
|
|
4015
|
+
try {
|
|
4016
|
+
await writeFile(temporaryFile, content, { encoding: "utf8", mode: 420, flag: "wx" });
|
|
4017
|
+
await rename(temporaryFile, targetFile);
|
|
4018
|
+
} catch (error) {
|
|
4019
|
+
await unlink(temporaryFile).catch(() => void 0);
|
|
4020
|
+
throw error;
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
async function installCodexSkill(options) {
|
|
4024
|
+
let source;
|
|
4025
|
+
try {
|
|
4026
|
+
source = await readFile2(options.sourceFile, "utf8");
|
|
4027
|
+
} catch (error) {
|
|
4028
|
+
if (isMissing(error)) {
|
|
4029
|
+
throw new Error(`Skill source is missing: ${options.sourceFile}`);
|
|
4030
|
+
}
|
|
4031
|
+
throw error;
|
|
4032
|
+
}
|
|
4033
|
+
const targetDirectory = join3(options.skillsRoot, SKILL_DIRECTORY_NAME);
|
|
4034
|
+
const targetFile = join3(targetDirectory, "SKILL.md");
|
|
4035
|
+
let existing;
|
|
4036
|
+
try {
|
|
4037
|
+
existing = await readFile2(targetFile, "utf8");
|
|
4038
|
+
} catch (error) {
|
|
4039
|
+
if (!isMissing(error)) throw error;
|
|
4040
|
+
}
|
|
4041
|
+
if (existing === source) return { status: "unchanged", targetFile };
|
|
4042
|
+
if (existing !== void 0 && !options.force) {
|
|
4043
|
+
throw new Error(
|
|
4044
|
+
`Codex Skill already exists with different content at ${targetFile}; rerun with --force to overwrite it.`
|
|
4045
|
+
);
|
|
4046
|
+
}
|
|
4047
|
+
await mkdir(targetDirectory, { recursive: true });
|
|
4048
|
+
await atomicWrite(targetFile, source);
|
|
4049
|
+
return {
|
|
4050
|
+
status: existing === void 0 ? "installed" : "overwritten",
|
|
4051
|
+
targetFile
|
|
4052
|
+
};
|
|
4053
|
+
}
|
|
4054
|
+
function defaultCodexSkillsRoot(env = process.env, homeDirectory = homedir2()) {
|
|
4055
|
+
return env.ATWEBPILOT_CODEX_SKILLS_DIR || join3(homeDirectory, ".agents", "skills");
|
|
4056
|
+
}
|
|
4057
|
+
async function resolveBundledSkillFile(moduleUrl = import.meta.url) {
|
|
4058
|
+
const here = dirname2(fileURLToPath2(moduleUrl));
|
|
4059
|
+
const candidates = [
|
|
4060
|
+
join3(here, "skill", "SKILL.md"),
|
|
4061
|
+
join3(here, "../../../skill/SKILL.md")
|
|
4062
|
+
];
|
|
4063
|
+
for (const candidate of candidates) {
|
|
4064
|
+
try {
|
|
4065
|
+
await access(candidate);
|
|
4066
|
+
return candidate;
|
|
4067
|
+
} catch {
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
throw new Error(`Skill source is missing; checked: ${candidates.join(", ")}`);
|
|
4071
|
+
}
|
|
4072
|
+
async function runSkillInstallCommand(args, overrides = {}) {
|
|
4073
|
+
if (!args.includes("--install-codex-skill")) return { handled: false };
|
|
4074
|
+
const supported = /* @__PURE__ */ new Set(["--install-codex-skill", "--force"]);
|
|
4075
|
+
const unknown = args.filter((arg) => !supported.has(arg));
|
|
4076
|
+
if (unknown.length > 0) {
|
|
4077
|
+
throw new Error(`Unknown option for Skill installation: ${unknown.join(", ")}`);
|
|
4078
|
+
}
|
|
4079
|
+
const sourceFile = overrides.sourceFile ?? await resolveBundledSkillFile();
|
|
4080
|
+
const skillsRoot = overrides.skillsRoot ?? defaultCodexSkillsRoot();
|
|
4081
|
+
const result = await installCodexSkill({
|
|
4082
|
+
sourceFile,
|
|
4083
|
+
skillsRoot,
|
|
4084
|
+
force: args.includes("--force")
|
|
4085
|
+
});
|
|
4086
|
+
return { handled: true, result };
|
|
4087
|
+
}
|
|
4088
|
+
|
|
3934
4089
|
// src/index.ts
|
|
3935
4090
|
async function main() {
|
|
4091
|
+
const args = process.argv.slice(2);
|
|
4092
|
+
const install = await runSkillInstallCommand(args);
|
|
4093
|
+
if (install.handled) {
|
|
4094
|
+
process.stdout.write(`Codex Skill ${install.result.status}: ${install.result.targetFile}
|
|
4095
|
+
`);
|
|
4096
|
+
return;
|
|
4097
|
+
}
|
|
4098
|
+
if (args.length > 0) {
|
|
4099
|
+
throw new Error(`Unknown option: ${args.join(", ")}`);
|
|
4100
|
+
}
|
|
3936
4101
|
const explicitPort = process.env.ATWEBPILOT_WS_PORT ? Number(process.env.ATWEBPILOT_WS_PORT) : void 0;
|
|
3937
4102
|
const token = process.env.ATWEBPILOT_WS_TOKEN || void 0;
|
|
3938
4103
|
const clock = new DefaultClock();
|
package/dist/skill/SKILL.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: atwebpilot-browser
|
|
3
|
+
description: Use AtWebPilot when a task requires inspecting, testing, debugging, screenshotting, reading, or interacting with a live web page in the user's existing Chrome, including localhost and authenticated pages. Do not use it for pure code editing or static source analysis that does not require observing a live page.
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# atwebpilot-browser skill
|
|
2
7
|
|
|
3
8
|
A reusable strategy for agents that drive AtWebPilot's browser extension via
|
|
@@ -12,6 +17,10 @@ When this skill is loaded, you can drive any open tab through these tools
|
|
|
12
17
|
|
|
13
18
|
### Control plane
|
|
14
19
|
|
|
20
|
+
- `browser_start({ tab_id?, url? })` — primary entry point for live-page work.
|
|
21
|
+
It pairs when needed and returns a ready session when exactly one tab can be
|
|
22
|
+
selected safely. When it returns `select_tab`, call it again with one listed
|
|
23
|
+
`tab_id`; it never guesses between tabs.
|
|
15
24
|
- `pairing_status` — start/check pairing without waiting. When it reports
|
|
16
25
|
`pairing_required`, show `pair_url` once; use it only as a later status check.
|
|
17
26
|
- `list_tabs` — on first use, open the pairing page and wait up to 45 seconds
|
|
@@ -88,7 +97,7 @@ compatibility path; use the native tool once the host exposes it.
|
|
|
88
97
|
|
|
89
98
|
## Recommended flow
|
|
90
99
|
|
|
91
|
-
1. **先建立连接**:调一次 `
|
|
100
|
+
1. **先建立连接**:调一次 `browser_start`;首次配对时它会打开授权页并等待最多 45 秒,唯一 tab 可确定时同一次调用直接返回 ready session。返回 `select_tab` 时带 `tab_id` 再调一次;只有超时才会返回 `pairing_required`,此时展示 `pair_url`,稍后用非阻塞的 `pairing_status` 检查,不要轮询 `list_tabs`。
|
|
92
101
|
2. **探查先于操作**:每次进入新页面,先 `getPageInfo` 确认位置,再 `takeSnapshot`(要点击/填表)或 `createPageIndex`(要读内容/抽字段);`snapshotDOM` / `querySelector` 属于 legacy-dom 组,只在需要分析整页结构时通过 `browser_discoverTools` 启用。
|
|
93
102
|
3. **最少充分验证**:危险或不可逆操作保持单步;多个独立字段用 `fillForm`。工具已返回 `verified` 或明确后置状态时,不要再用 `runJS` 重复验证。
|
|
94
103
|
4. **dangerous 工具会被人工审核**:调用前用 `extractText` 给用户看上下文,让审批更顺。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@attson/atwebpilot-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.83",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "MCP server for the atwebpilot browser extension - let Codex or Claude Code drive your browser.",
|
|
6
6
|
"homepage": "https://github.com/attson/atwebpilot#readme",
|