wolfpack-mcp 1.0.96 → 1.0.97
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/dist/browserTools.js +146 -0
- package/dist/client.js +12 -0
- package/dist/index.js +9 -0
- package/package.json +1 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
function text(data) {
|
|
3
|
+
return JSON.stringify(data, null, 2);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Browser control for chatbot agents (#2288): see and drive the page the chat
|
|
7
|
+
* widget is embedded in.
|
|
8
|
+
*
|
|
9
|
+
* Gated by the `browser_control` capability, which the backend grants only to a
|
|
10
|
+
* key bound to a chat session — a scheduled run or a coder never sees these.
|
|
11
|
+
* The backend gates every call on the visitor's own grant as well, so a tool
|
|
12
|
+
* called out of turn is refused there rather than here.
|
|
13
|
+
*/
|
|
14
|
+
export const BROWSER_TOOLS = [
|
|
15
|
+
{
|
|
16
|
+
name: 'browser_request_control',
|
|
17
|
+
description: 'Ask the person you are chatting with for permission to drive their browser. ' +
|
|
18
|
+
'They see your reason in the conversation with Allow and Deny buttons, and this call waits for their answer. ' +
|
|
19
|
+
'You MUST call this and be granted control before any other browser tool will work — there is no way around it, ' +
|
|
20
|
+
'and asking repeatedly after a refusal is not acceptable. ' +
|
|
21
|
+
'Ask only when driving the page is genuinely the best way to help; explaining what to click is usually better.',
|
|
22
|
+
inputSchema: {
|
|
23
|
+
type: 'object',
|
|
24
|
+
properties: {
|
|
25
|
+
reason: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'What you want to do on their page, in one plain sentence — this is what they read before deciding (e.g. "Fill in the booking form with the dates we agreed").',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
required: ['reason'],
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
name: 'browser_snapshot',
|
|
35
|
+
description: 'See the page: its URL, title, visible text, and the interactive elements you can act on. ' +
|
|
36
|
+
'Each element comes back with a "ref" — pass that ref to browser_click or browser_type. ' +
|
|
37
|
+
'Refs are only valid until the page changes, so take a fresh snapshot after every action.',
|
|
38
|
+
inputSchema: { type: 'object', properties: {} },
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'browser_click',
|
|
42
|
+
description: 'Click an element on the page. The visitor sees the pointer travel to it before it is clicked.',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
ref: { type: 'string', description: 'The ref of the element, from browser_snapshot' },
|
|
47
|
+
},
|
|
48
|
+
required: ['ref'],
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
name: 'browser_type',
|
|
53
|
+
description: 'Type text into an input, textarea or select on the page, replacing whatever it holds.',
|
|
54
|
+
inputSchema: {
|
|
55
|
+
type: 'object',
|
|
56
|
+
properties: {
|
|
57
|
+
ref: { type: 'string', description: 'The ref of the field, from browser_snapshot' },
|
|
58
|
+
text: { type: 'string', description: 'The text to enter' },
|
|
59
|
+
},
|
|
60
|
+
required: ['ref', 'text'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'browser_scroll',
|
|
65
|
+
description: 'Scroll the page vertically to bring more of it into view.',
|
|
66
|
+
inputSchema: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
properties: {
|
|
69
|
+
deltaY: {
|
|
70
|
+
type: 'number',
|
|
71
|
+
description: 'Pixels to scroll by — positive scrolls down, negative up',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
required: ['deltaY'],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'browser_release_control',
|
|
79
|
+
description: 'Hand the browser back when you are done, so the visitor stops seeing the "agent in control" frame on their screen. ' +
|
|
80
|
+
'Always do this once the task is finished. They can also stop you themselves at any time.',
|
|
81
|
+
inputSchema: { type: 'object', properties: {} },
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
const RequestControlSchema = z.object({ reason: z.string() });
|
|
85
|
+
const ClickSchema = z.object({ ref: z.string() });
|
|
86
|
+
const TypeSchema = z.object({ ref: z.string(), text: z.string() });
|
|
87
|
+
const ScrollSchema = z.object({ deltaY: z.coerce.number() });
|
|
88
|
+
/** A refused or failed command is reported as a tool error, so the agent reads
|
|
89
|
+
* it as something that did not happen rather than as a result. */
|
|
90
|
+
function commandOutcome(result) {
|
|
91
|
+
if (!result.ok) {
|
|
92
|
+
return {
|
|
93
|
+
content: [{ type: 'text', text: result.error ?? 'The command failed' }],
|
|
94
|
+
isError: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return { content: [{ type: 'text', text: text(result.result ?? { done: true }) }] };
|
|
98
|
+
}
|
|
99
|
+
export async function handleBrowserTool(name, args, client) {
|
|
100
|
+
switch (name) {
|
|
101
|
+
case 'browser_request_control': {
|
|
102
|
+
const { reason } = RequestControlSchema.parse(args);
|
|
103
|
+
const { outcome } = await client.requestBrowserControl(reason);
|
|
104
|
+
if (outcome === 'granted') {
|
|
105
|
+
return {
|
|
106
|
+
content: [
|
|
107
|
+
{
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: 'Granted. Take a browser_snapshot to see the page, and call browser_release_control when you are done.',
|
|
110
|
+
},
|
|
111
|
+
],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: 'text',
|
|
118
|
+
text: outcome === 'denied'
|
|
119
|
+
? 'The visitor declined. Do not ask again unless they bring it up — help them another way.'
|
|
120
|
+
: 'The visitor did not answer. Carry on without the browser.',
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
isError: true,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
case 'browser_snapshot':
|
|
127
|
+
return commandOutcome(await client.runBrowserCommand({ action: 'snapshot' }));
|
|
128
|
+
case 'browser_click': {
|
|
129
|
+
const { ref } = ClickSchema.parse(args);
|
|
130
|
+
return commandOutcome(await client.runBrowserCommand({ action: 'click', ref }));
|
|
131
|
+
}
|
|
132
|
+
case 'browser_type': {
|
|
133
|
+
const parsed = TypeSchema.parse(args);
|
|
134
|
+
return commandOutcome(await client.runBrowserCommand({ action: 'type', ...parsed }));
|
|
135
|
+
}
|
|
136
|
+
case 'browser_scroll': {
|
|
137
|
+
const { deltaY } = ScrollSchema.parse(args);
|
|
138
|
+
return commandOutcome(await client.runBrowserCommand({ action: 'scroll', deltaY }));
|
|
139
|
+
}
|
|
140
|
+
case 'browser_release_control':
|
|
141
|
+
await client.releaseBrowserControl();
|
|
142
|
+
return { content: [{ type: 'text', text: 'Browser control handed back to the visitor.' }] };
|
|
143
|
+
default:
|
|
144
|
+
throw new Error(`Unknown browser tool: ${name}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
package/dist/client.js
CHANGED
|
@@ -981,6 +981,18 @@ export class WolfpackClient {
|
|
|
981
981
|
async saveMemory(key, content) {
|
|
982
982
|
return this.api.put(`/self/memories/${encodeURIComponent(key)}`, { content });
|
|
983
983
|
}
|
|
984
|
+
// ─── Browser control (#2288) ───────────────────────────────────────────────
|
|
985
|
+
// No chat is named on any of these: the backend derives it from the session
|
|
986
|
+
// this key is bound to, so the agent cannot address another visitor's page.
|
|
987
|
+
async requestBrowserControl(reason) {
|
|
988
|
+
return this.api.post('/browser/request', { reason });
|
|
989
|
+
}
|
|
990
|
+
async runBrowserCommand(command) {
|
|
991
|
+
return this.api.post('/browser/command', command);
|
|
992
|
+
}
|
|
993
|
+
async releaseBrowserControl() {
|
|
994
|
+
await this.api.post('/browser/release', {});
|
|
995
|
+
}
|
|
984
996
|
close() {
|
|
985
997
|
// No cleanup needed for API client
|
|
986
998
|
}
|
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { validateConfig, config } from './config.js';
|
|
|
12
12
|
import { AGENT_BUILDER_TOOLS, handleAgentBuilderTool } from './agentBuilderTools.js';
|
|
13
13
|
import { PROCEDURE_TOOLS, handleProcedureTool } from './procedureTools.js';
|
|
14
14
|
import { AGENT_SELF_TOOLS, AGENT_MEMORY_TOOLS, handleAgentSelfTool } from './agentSelfTools.js';
|
|
15
|
+
import { BROWSER_TOOLS, handleBrowserTool } from './browserTools.js';
|
|
15
16
|
import { resolveRadarItemId } from './resolveRadarItemId.js';
|
|
16
17
|
import { SERVER_INSTRUCTIONS } from './serverInstructions.js';
|
|
17
18
|
import { fetch as proxyFetch } from './proxyFetch.js';
|
|
@@ -2467,6 +2468,7 @@ class WolfpackMCPServer {
|
|
|
2467
2468
|
...(this.capabilities.includes('agent_self') ? AGENT_SELF_TOOLS : []),
|
|
2468
2469
|
...(this.capabilities.includes('agent_memory') ? AGENT_MEMORY_TOOLS : []),
|
|
2469
2470
|
...(this.capabilities.includes('agent_builder') ? AGENT_BUILDER_TOOLS : []),
|
|
2471
|
+
...(this.capabilities.includes('browser_control') ? BROWSER_TOOLS : []),
|
|
2470
2472
|
],
|
|
2471
2473
|
};
|
|
2472
2474
|
});
|
|
@@ -3537,6 +3539,13 @@ class WolfpackMCPServer {
|
|
|
3537
3539
|
return handleAgentSelfTool(name, args, this.client);
|
|
3538
3540
|
}
|
|
3539
3541
|
}
|
|
3542
|
+
// Check browser control tools (#2288 — chat sessions only)
|
|
3543
|
+
if (this.capabilities.includes('browser_control')) {
|
|
3544
|
+
const browserToolNames = BROWSER_TOOLS.map((t) => t.name);
|
|
3545
|
+
if (browserToolNames.includes(name)) {
|
|
3546
|
+
return handleBrowserTool(name, args, this.client);
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3540
3549
|
// Check agent builder tools
|
|
3541
3550
|
if (this.capabilities.includes('agent_builder')) {
|
|
3542
3551
|
return handleAgentBuilderTool(name, args, this.client);
|