@mcp-web/tools 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 +201 -0
- package/dist/base.d.ts +58 -0
- package/dist/base.d.ts.map +1 -0
- package/dist/base.js +54 -0
- package/dist/base.js.map +1 -0
- package/dist/dom/index.d.ts +25 -0
- package/dist/dom/index.d.ts.map +1 -0
- package/dist/dom/index.js +53 -0
- package/dist/dom/index.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/python/index.d.ts +39 -0
- package/dist/python/index.d.ts.map +1 -0
- package/dist/python/index.js +178 -0
- package/dist/python/index.js.map +1 -0
- package/dist/screenshot/index.d.ts +30 -0
- package/dist/screenshot/index.d.ts.map +1 -0
- package/dist/screenshot/index.js +89 -0
- package/dist/screenshot/index.js.map +1 -0
- package/package.json +40 -0
- package/src/base.ts +70 -0
- package/src/dom/index.ts +69 -0
- package/src/index.ts +1 -0
- package/src/python/index.ts +231 -0
- package/src/screenshot/index.ts +105 -0
- package/tsconfig.json +12 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Options as HtmlToImageOptions } from 'html-to-image/lib/types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { BaseTool } from '../base.js';
|
|
4
|
+
interface Options extends HtmlToImageOptions {
|
|
5
|
+
name?: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
format?: 'png' | 'jpeg' | 'webp';
|
|
8
|
+
elementSelector?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const TakeScreenshotInputSchema: z.ZodObject<{
|
|
11
|
+
elementSelector: z.ZodOptional<z.ZodString>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
type TakeScreenshotInput = z.infer<typeof TakeScreenshotInputSchema>;
|
|
14
|
+
/**
|
|
15
|
+
* Returns the screenshot as a data URL string (e.g., "data:image/png;base64,...").
|
|
16
|
+
* The bridge detects data URL strings and converts them into native MCP ImageContent
|
|
17
|
+
* blocks, so Claude receives the image efficiently rather than as raw base64 text.
|
|
18
|
+
*/
|
|
19
|
+
declare const TakeScreenshotOutputSchema: z.ZodString;
|
|
20
|
+
export declare class ScreenshotTool extends BaseTool<typeof TakeScreenshotInputSchema, typeof TakeScreenshotOutputSchema> {
|
|
21
|
+
#private;
|
|
22
|
+
constructor(options: Options);
|
|
23
|
+
get name(): string;
|
|
24
|
+
get description(): string;
|
|
25
|
+
get inputSchema(): typeof TakeScreenshotInputSchema;
|
|
26
|
+
get outputSchema(): typeof TakeScreenshotOutputSchema;
|
|
27
|
+
get handler(): (params?: TakeScreenshotInput) => Promise<string>;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/screenshot/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,UAAU,OAAQ,SAAQ,kBAAkB;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IACjC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,QAAA,MAAM,yBAAyB;;iBAE7B,CAAC;AACH,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAErE;;;;GAIG;AACH,QAAA,MAAM,0BAA0B,aAEW,CAAC;AA2C5C,qBAAa,cAAe,SAAQ,QAAQ,CAC1C,OAAO,yBAAyB,EAChC,OAAO,0BAA0B,CAClC;;gBAOa,OAAO,EAAE,OAAO;IAQ5B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,IAAI,WAAW,IAAI,OAAO,yBAAyB,CAElD;IAED,IAAI,YAAY,IAAI,OAAO,0BAA0B,CAEpD;IAED,IAAI,OAAO,IAAI,CAAC,MAAM,CAAC,EAAE,mBAAmB,KAAK,OAAO,CAAC,MAAM,CAAC,CAE/D;CACF"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _ScreenshotTool_name, _ScreenshotTool_description, _ScreenshotTool_inputSchema, _ScreenshotTool_outputSchema, _ScreenshotTool_handler;
|
|
13
|
+
import { toCanvas, toJpeg, toPng } from 'html-to-image';
|
|
14
|
+
import { z } from 'zod';
|
|
15
|
+
import { BaseTool } from '../base.js';
|
|
16
|
+
const TakeScreenshotInputSchema = z.object({
|
|
17
|
+
elementSelector: z.string().optional().describe('CSS selector to query'),
|
|
18
|
+
});
|
|
19
|
+
/**
|
|
20
|
+
* Returns the screenshot as a data URL string (e.g., "data:image/png;base64,...").
|
|
21
|
+
* The bridge detects data URL strings and converts them into native MCP ImageContent
|
|
22
|
+
* blocks, so Claude receives the image efficiently rather than as raw base64 text.
|
|
23
|
+
*/
|
|
24
|
+
const TakeScreenshotOutputSchema = z
|
|
25
|
+
.string()
|
|
26
|
+
.describe('The screenshot as a data URL');
|
|
27
|
+
const createScreenshot = (options) => {
|
|
28
|
+
return async (params) => {
|
|
29
|
+
return takeScreenshot(params || {}, options);
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
async function takeScreenshot(params, options) {
|
|
33
|
+
const parsedParams = TakeScreenshotInputSchema.safeParse(params);
|
|
34
|
+
if (!parsedParams.success) {
|
|
35
|
+
throw new Error('Invalid parameters');
|
|
36
|
+
}
|
|
37
|
+
const { elementSelector } = parsedParams.data;
|
|
38
|
+
const { format } = options;
|
|
39
|
+
const element = elementSelector
|
|
40
|
+
? document.querySelector(elementSelector) || document.body
|
|
41
|
+
: document.body;
|
|
42
|
+
try {
|
|
43
|
+
if (format === 'png') {
|
|
44
|
+
return await toPng(element, options);
|
|
45
|
+
}
|
|
46
|
+
else if (format === 'jpeg') {
|
|
47
|
+
return await toJpeg(element, options);
|
|
48
|
+
}
|
|
49
|
+
else if (format === 'webp') {
|
|
50
|
+
// html-to-image doesn't have direct webp support, convert from canvas
|
|
51
|
+
const canvas = await toCanvas(element, options);
|
|
52
|
+
return canvas.toDataURL('image/webp', options.quality);
|
|
53
|
+
}
|
|
54
|
+
return await toPng(element, options);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
throw new Error(`Failed to take screenshot: ${error instanceof Error ? error.message : String(error)}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export class ScreenshotTool extends BaseTool {
|
|
61
|
+
constructor(options) {
|
|
62
|
+
super();
|
|
63
|
+
_ScreenshotTool_name.set(this, void 0);
|
|
64
|
+
_ScreenshotTool_description.set(this, void 0);
|
|
65
|
+
_ScreenshotTool_inputSchema.set(this, TakeScreenshotInputSchema);
|
|
66
|
+
_ScreenshotTool_outputSchema.set(this, TakeScreenshotOutputSchema);
|
|
67
|
+
_ScreenshotTool_handler.set(this, void 0);
|
|
68
|
+
__classPrivateFieldSet(this, _ScreenshotTool_name, options.name || 'screenshot', "f");
|
|
69
|
+
__classPrivateFieldSet(this, _ScreenshotTool_description, options.description || 'Take a screenshot of the web page', "f");
|
|
70
|
+
__classPrivateFieldSet(this, _ScreenshotTool_handler, createScreenshot(options), "f");
|
|
71
|
+
}
|
|
72
|
+
get name() {
|
|
73
|
+
return __classPrivateFieldGet(this, _ScreenshotTool_name, "f");
|
|
74
|
+
}
|
|
75
|
+
get description() {
|
|
76
|
+
return __classPrivateFieldGet(this, _ScreenshotTool_description, "f");
|
|
77
|
+
}
|
|
78
|
+
get inputSchema() {
|
|
79
|
+
return __classPrivateFieldGet(this, _ScreenshotTool_inputSchema, "f");
|
|
80
|
+
}
|
|
81
|
+
get outputSchema() {
|
|
82
|
+
return __classPrivateFieldGet(this, _ScreenshotTool_outputSchema, "f");
|
|
83
|
+
}
|
|
84
|
+
get handler() {
|
|
85
|
+
return __classPrivateFieldGet(this, _ScreenshotTool_handler, "f");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
_ScreenshotTool_name = new WeakMap(), _ScreenshotTool_description = new WeakMap(), _ScreenshotTool_inputSchema = new WeakMap(), _ScreenshotTool_outputSchema = new WeakMap(), _ScreenshotTool_handler = new WeakMap();
|
|
89
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/screenshot/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mcp-web/tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP Web tools collection",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./python": {
|
|
14
|
+
"import": "./dist/python/index.js",
|
|
15
|
+
"types": "./dist/python/index.d.ts"
|
|
16
|
+
},
|
|
17
|
+
"./dom": {
|
|
18
|
+
"import": "./dist/dom/index.js",
|
|
19
|
+
"types": "./dist/dom/index.d.ts"
|
|
20
|
+
},
|
|
21
|
+
"./screenshot": {
|
|
22
|
+
"import": "./dist/screenshot/index.js",
|
|
23
|
+
"types": "./dist/screenshot/index.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"html-to-image": "^1.11.13",
|
|
28
|
+
"uuid": "^11.0.7",
|
|
29
|
+
"zod": "~4.1.12",
|
|
30
|
+
"@mcp-web/types": "0.1.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/uuid": "^10.0.0",
|
|
34
|
+
"typescript": "~5.9.3"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsc",
|
|
38
|
+
"clean": "rm -rf dist"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/base.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ToolDefinition } from '@mcp-web/types';
|
|
2
|
+
import type { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Abstract base class for creating reusable MCP tools.
|
|
6
|
+
*
|
|
7
|
+
* Extend this class to create tools that can be shared across projects.
|
|
8
|
+
* Subclasses must implement all abstract properties: name, description,
|
|
9
|
+
* inputSchema, outputSchema, and handler.
|
|
10
|
+
*
|
|
11
|
+
* @typeParam TInput - Zod schema type for tool input validation
|
|
12
|
+
* @typeParam TOutput - Zod schema type for tool output validation
|
|
13
|
+
*
|
|
14
|
+
* @example Creating a custom tool
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { BaseTool } from '@mcp-web/tools';
|
|
17
|
+
* import { z } from 'zod';
|
|
18
|
+
*
|
|
19
|
+
* const InputSchema = z.object({
|
|
20
|
+
* query: z.string().describe('Search query'),
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* const OutputSchema = z.object({
|
|
24
|
+
* results: z.array(z.string()),
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* class SearchTool extends BaseTool<typeof InputSchema, typeof OutputSchema> {
|
|
28
|
+
* get name() { return 'search'; }
|
|
29
|
+
* get description() { return 'Search for items'; }
|
|
30
|
+
* get inputSchema() { return InputSchema; }
|
|
31
|
+
* get outputSchema() { return OutputSchema; }
|
|
32
|
+
* get handler() {
|
|
33
|
+
* return ({ query }) => ({ results: ['item1', 'item2'] });
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
*
|
|
37
|
+
* // Use the tool
|
|
38
|
+
* const searchTool = new SearchTool();
|
|
39
|
+
* mcp.addTool(searchTool.definition);
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export abstract class BaseTool<
|
|
43
|
+
TInput extends z.ZodObject,
|
|
44
|
+
TOutput extends z.ZodType,
|
|
45
|
+
> {
|
|
46
|
+
/** Unique name for the tool. */
|
|
47
|
+
abstract get name(): string;
|
|
48
|
+
/** Description of what the tool does (shown to AI). */
|
|
49
|
+
abstract get description(): string;
|
|
50
|
+
/** Zod schema for validating input parameters. */
|
|
51
|
+
abstract get inputSchema(): TInput | undefined;
|
|
52
|
+
/** Zod schema for validating output values. */
|
|
53
|
+
abstract get outputSchema(): TOutput;
|
|
54
|
+
/** Function that executes the tool logic. */
|
|
55
|
+
abstract get handler(): (params: z.infer<TInput>) => z.infer<TOutput> | Promise<z.infer<TOutput>>;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns the tool definition for registration with MCPWeb.
|
|
59
|
+
* @returns Tool definition object compatible with MCPWeb.addTool()
|
|
60
|
+
*/
|
|
61
|
+
get definition(): ToolDefinition {
|
|
62
|
+
return {
|
|
63
|
+
name: this.name,
|
|
64
|
+
description: this.description,
|
|
65
|
+
handler: this.handler,
|
|
66
|
+
inputSchema: this.inputSchema,
|
|
67
|
+
outputSchema: this.outputSchema
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
package/src/dom/index.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { BaseTool } from '../base.js';
|
|
3
|
+
|
|
4
|
+
const DOMElementSchema = z.object({
|
|
5
|
+
tagName: z.string(),
|
|
6
|
+
id: z.string(),
|
|
7
|
+
className: z.string(),
|
|
8
|
+
textContent: z.string().nullable(),
|
|
9
|
+
attributes: z.record(z.string(), z.string())
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
type DOMElement = z.infer<typeof DOMElementSchema>;
|
|
13
|
+
|
|
14
|
+
const GetDOMElementsInputSchema = z.object({
|
|
15
|
+
selector: z.string().default('body').describe('CSS selector to query')
|
|
16
|
+
});
|
|
17
|
+
type GetDOMElementsInput = z.infer<typeof GetDOMElementsInputSchema>;
|
|
18
|
+
|
|
19
|
+
const GetDOMElementsOutputSchema = z.object({
|
|
20
|
+
elements: z.array(DOMElementSchema)
|
|
21
|
+
});
|
|
22
|
+
type GetDOMElementsOutput = z.infer<typeof GetDOMElementsOutputSchema>;
|
|
23
|
+
|
|
24
|
+
function getDOMElements(params: GetDOMElementsInput): GetDOMElementsOutput {
|
|
25
|
+
const parsedParams = GetDOMElementsInputSchema.safeParse(params);
|
|
26
|
+
|
|
27
|
+
if (!parsedParams.success) {
|
|
28
|
+
throw new Error(`DOMQueryTool: Invalid input parameters: ${parsedParams.error.message}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const { selector } = parsedParams.data;
|
|
32
|
+
|
|
33
|
+
const elements = document.querySelectorAll(selector);
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
elements: Array.from(elements).map((el) => ({
|
|
37
|
+
tagName: el.tagName,
|
|
38
|
+
id: el.id,
|
|
39
|
+
className: el.className,
|
|
40
|
+
textContent: el.textContent?.trim() || null,
|
|
41
|
+
attributes: Array.from(el.attributes).reduce((acc, attr) => {
|
|
42
|
+
acc[attr.name] = attr.value;
|
|
43
|
+
return acc;
|
|
44
|
+
}, {} as Record<string, string>)
|
|
45
|
+
} satisfies DOMElement))
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class DOMQueryTool extends BaseTool<typeof GetDOMElementsInputSchema, typeof GetDOMElementsOutputSchema> {
|
|
50
|
+
get name(): string {
|
|
51
|
+
return 'dom-query';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
get description(): string {
|
|
55
|
+
return 'Query the DOM for elements';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
get inputSchema(): typeof GetDOMElementsInputSchema {
|
|
59
|
+
return GetDOMElementsInputSchema;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
get outputSchema(): typeof GetDOMElementsOutputSchema {
|
|
63
|
+
return GetDOMElementsOutputSchema;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
get handler(): (params: GetDOMElementsInput) => GetDOMElementsOutput {
|
|
67
|
+
return getDOMElements;
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './base.js';
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { ToolDefinition } from '@mcp-web/types';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { BaseTool } from '../base.js';
|
|
5
|
+
|
|
6
|
+
interface Options {
|
|
7
|
+
/** The name of the tool. */
|
|
8
|
+
name?: string;
|
|
9
|
+
/** The description of the tool. */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** The default packages to load on init. Can be useful if you expect many calls to use the same packages. E.g., `['numpy', 'pandas']`. */
|
|
12
|
+
defaultPackages?: string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const RunPythonInputSchema = z.object({
|
|
16
|
+
script: z.string().describe('Python script to run')
|
|
17
|
+
});
|
|
18
|
+
type RunPythonInput = z.infer<typeof RunPythonInputSchema>;
|
|
19
|
+
|
|
20
|
+
const RunPythonOutputSchema = z.object({ result: z.string().describe('The result of the Python script') });
|
|
21
|
+
type RunPythonOutput = z.infer<typeof RunPythonOutputSchema>;
|
|
22
|
+
|
|
23
|
+
const runPythonJsonSchema = z.toJSONSchema(RunPythonInputSchema, { target: "draft-7" });
|
|
24
|
+
|
|
25
|
+
export interface WorkerMessagePackages {
|
|
26
|
+
packages: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WorkerMessageRunPython {
|
|
30
|
+
id: string;
|
|
31
|
+
script: string;
|
|
32
|
+
datasets: Record<string, unknown>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type WorkerMessage = WorkerMessagePackages | WorkerMessageRunPython;
|
|
36
|
+
|
|
37
|
+
interface WorkerResponseSuccess {
|
|
38
|
+
id: string;
|
|
39
|
+
result: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface WorkerResponseError {
|
|
43
|
+
id: string;
|
|
44
|
+
error: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
type WorkerResponse = WorkerResponseSuccess | WorkerResponseError;
|
|
48
|
+
|
|
49
|
+
const workerCode = /* js */`
|
|
50
|
+
import { loadPyodide } from "https://cdn.jsdelivr.net/pyodide/v0.28.1/full/pyodide.mjs";
|
|
51
|
+
|
|
52
|
+
const TIMEOUT = 30000;
|
|
53
|
+
|
|
54
|
+
const state = {
|
|
55
|
+
pyodide: null,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
async function initPyodide() {
|
|
59
|
+
const pyodide = await loadPyodide();
|
|
60
|
+
state.pyodide = pyodide;
|
|
61
|
+
self.postMessage({ init: true });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function loadPackages(packages) {
|
|
65
|
+
if (!state.pyodide) {
|
|
66
|
+
self.postMessage({ error: 'Pyodide not initialized' });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const pyodide = state.pyodide;
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await pyodide.loadPackage(packages);
|
|
74
|
+
self.postMessage({ packagesLoaded: true });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
self.postMessage({ error });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function runPython(id, script, datasets) {
|
|
81
|
+
if (!state.pyodide) {
|
|
82
|
+
self.postMessage({ id, error: 'Pyodide not initialized' });
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const pyodide = state.pyodide;
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await pyodide.loadPackagesFromImports(script);
|
|
90
|
+
|
|
91
|
+
for (const [key, value] of Object.entries(datasets)) {
|
|
92
|
+
pyodide.globals.set(key, value);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let result = '';
|
|
96
|
+
|
|
97
|
+
const pythonExecution = pyodide.runPythonAsync(script).then((r) => {
|
|
98
|
+
result = r?.toString() ?? '';
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const timeout = new Promise((_, reject) => {
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
reject(new Error("Python execution timed out"));
|
|
104
|
+
}, TIMEOUT);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
await Promise.race([pythonExecution, timeout]);
|
|
108
|
+
self.postMessage({ id, result });
|
|
109
|
+
} catch (error) {
|
|
110
|
+
self.postMessage({ id, error: error instanceof Error ? error.message : String(error) });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
self.onmessage = async (event) => {
|
|
115
|
+
if ('packages' in event.data) {
|
|
116
|
+
await loadPackages(event.data.packages);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { id, script, datasets } = event.data;
|
|
121
|
+
await runPython(id, script, datasets);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
initPyodide();
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
const createWorker = (options: Options) => {
|
|
128
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
129
|
+
const worker = new Worker(URL.createObjectURL(blob), { type: "module" });
|
|
130
|
+
|
|
131
|
+
if (options.defaultPackages?.length) {
|
|
132
|
+
worker.postMessage({ packages: options.defaultPackages } satisfies WorkerMessagePackages);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return worker;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const getPromiseAndResolveReject = <T>() => {
|
|
139
|
+
let resolve: (value: T | PromiseLike<T>) => void;
|
|
140
|
+
let reject: (error: Error) => void;
|
|
141
|
+
const promise = new Promise<T>((res, rej) => {
|
|
142
|
+
resolve = res;
|
|
143
|
+
reject = rej;
|
|
144
|
+
});
|
|
145
|
+
// @ts-expect-error: We know that the resolve and reject functions are defined
|
|
146
|
+
return { promise, resolve, reject };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const createRunPython = (
|
|
150
|
+
worker: Worker,
|
|
151
|
+
getDatasets: () => Record<string, unknown> | Promise<Record<string, unknown>>
|
|
152
|
+
) => async (params: RunPythonInput): Promise<RunPythonOutput> => {
|
|
153
|
+
const parsedParams = RunPythonInputSchema.safeParse(params);
|
|
154
|
+
|
|
155
|
+
if (!parsedParams.success) {
|
|
156
|
+
throw new Error(`PythonTool: Invalid input parameters: ${parsedParams.error.message}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const { script } = parsedParams.data;
|
|
160
|
+
|
|
161
|
+
const { promise, resolve, reject } = getPromiseAndResolveReject<RunPythonOutput>();
|
|
162
|
+
const executionId = uuidv4();
|
|
163
|
+
|
|
164
|
+
worker.addEventListener("message", function listener(event: MessageEvent<WorkerResponse>) {
|
|
165
|
+
// Ignore messages from other executions
|
|
166
|
+
if (event.data.id !== executionId) {
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// This listener is done so remove it.
|
|
171
|
+
worker.removeEventListener("message", listener);
|
|
172
|
+
|
|
173
|
+
if ('error' in event.data) {
|
|
174
|
+
reject(new Error(event.data.error));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
resolve({ result: event.data.result });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
worker.postMessage(
|
|
182
|
+
{
|
|
183
|
+
id: executionId,
|
|
184
|
+
script,
|
|
185
|
+
datasets: await getDatasets()
|
|
186
|
+
} satisfies WorkerMessageRunPython
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
return promise;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export class PythonTool extends BaseTool<typeof RunPythonInputSchema, typeof RunPythonOutputSchema> {
|
|
193
|
+
#name: string;
|
|
194
|
+
#description: string;
|
|
195
|
+
#inputSchema = RunPythonInputSchema;
|
|
196
|
+
#outputSchema = RunPythonOutputSchema;
|
|
197
|
+
#handler: (params: RunPythonInput) => Promise<RunPythonOutput>;
|
|
198
|
+
#worker: Worker;
|
|
199
|
+
|
|
200
|
+
constructor(getDatasets: () => Record<string, unknown>, options: Options) {
|
|
201
|
+
super();
|
|
202
|
+
this.#name = options.name || 'python';
|
|
203
|
+
this.#description = options.description || 'Run Python code';
|
|
204
|
+
this.#worker = createWorker(options);
|
|
205
|
+
this.#handler = createRunPython(this.#worker, getDatasets);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
get name(): string {
|
|
209
|
+
return this.#name;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
get description(): string {
|
|
213
|
+
return this.#description;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
get inputSchema(): typeof RunPythonInputSchema {
|
|
217
|
+
return this.#inputSchema;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
get outputSchema(): typeof RunPythonOutputSchema {
|
|
221
|
+
return this.#outputSchema;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
get handler(): (params: RunPythonInput) => Promise<RunPythonOutput> {
|
|
225
|
+
return this.#handler;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
destroy() {
|
|
229
|
+
this.#worker.terminate();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { toCanvas, toJpeg, toPng } from 'html-to-image';
|
|
2
|
+
import type { Options as HtmlToImageOptions } from 'html-to-image/lib/types';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { BaseTool } from '../base.js';
|
|
5
|
+
|
|
6
|
+
interface Options extends HtmlToImageOptions {
|
|
7
|
+
name?: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
format?: 'png' | 'jpeg' | 'webp';
|
|
10
|
+
elementSelector?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const TakeScreenshotInputSchema = z.object({
|
|
14
|
+
elementSelector: z.string().optional().describe('CSS selector to query'),
|
|
15
|
+
});
|
|
16
|
+
type TakeScreenshotInput = z.infer<typeof TakeScreenshotInputSchema>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Returns the screenshot as a data URL string (e.g., "data:image/png;base64,...").
|
|
20
|
+
* The bridge detects data URL strings and converts them into native MCP ImageContent
|
|
21
|
+
* blocks, so Claude receives the image efficiently rather than as raw base64 text.
|
|
22
|
+
*/
|
|
23
|
+
const TakeScreenshotOutputSchema = z
|
|
24
|
+
.string()
|
|
25
|
+
.describe('The screenshot as a data URL');
|
|
26
|
+
|
|
27
|
+
const createScreenshot = (options: Options) => {
|
|
28
|
+
return async (params?: TakeScreenshotInput): Promise<string> => {
|
|
29
|
+
return takeScreenshot(params || {}, options);
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
async function takeScreenshot(
|
|
34
|
+
params: TakeScreenshotInput,
|
|
35
|
+
options: Options,
|
|
36
|
+
): Promise<string> {
|
|
37
|
+
const parsedParams = TakeScreenshotInputSchema.safeParse(params);
|
|
38
|
+
|
|
39
|
+
if (!parsedParams.success) {
|
|
40
|
+
throw new Error('Invalid parameters');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const { elementSelector } = parsedParams.data;
|
|
44
|
+
const { format } = options;
|
|
45
|
+
|
|
46
|
+
const element: HTMLElement = elementSelector
|
|
47
|
+
? document.querySelector(elementSelector) || document.body
|
|
48
|
+
: document.body;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
if (format === 'png') {
|
|
52
|
+
return await toPng(element, options);
|
|
53
|
+
} else if (format === 'jpeg') {
|
|
54
|
+
return await toJpeg(element, options);
|
|
55
|
+
} else if (format === 'webp') {
|
|
56
|
+
// html-to-image doesn't have direct webp support, convert from canvas
|
|
57
|
+
const canvas = await toCanvas(element, options);
|
|
58
|
+
return canvas.toDataURL('image/webp', options.quality);
|
|
59
|
+
}
|
|
60
|
+
return await toPng(element, options);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Failed to take screenshot: ${error instanceof Error ? error.message : String(error)}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class ScreenshotTool extends BaseTool<
|
|
69
|
+
typeof TakeScreenshotInputSchema,
|
|
70
|
+
typeof TakeScreenshotOutputSchema
|
|
71
|
+
> {
|
|
72
|
+
#name: string;
|
|
73
|
+
#description: string;
|
|
74
|
+
#inputSchema = TakeScreenshotInputSchema;
|
|
75
|
+
#outputSchema = TakeScreenshotOutputSchema;
|
|
76
|
+
#handler: (params?: TakeScreenshotInput) => Promise<string>;
|
|
77
|
+
|
|
78
|
+
constructor(options: Options) {
|
|
79
|
+
super();
|
|
80
|
+
this.#name = options.name || 'screenshot';
|
|
81
|
+
this.#description =
|
|
82
|
+
options.description || 'Take a screenshot of the web page';
|
|
83
|
+
this.#handler = createScreenshot(options);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
get name(): string {
|
|
87
|
+
return this.#name;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
get description(): string {
|
|
91
|
+
return this.#description;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
get inputSchema(): typeof TakeScreenshotInputSchema {
|
|
95
|
+
return this.#inputSchema;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
get outputSchema(): typeof TakeScreenshotOutputSchema {
|
|
99
|
+
return this.#outputSchema;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get handler(): (params?: TakeScreenshotInput) => Promise<string> {
|
|
103
|
+
return this.#handler;
|
|
104
|
+
}
|
|
105
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"outDir": "dist",
|
|
5
|
+
"declaration": true,
|
|
6
|
+
"declarationMap": true,
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleResolution": "bundler"
|
|
9
|
+
},
|
|
10
|
+
"include": ["src/**/*"],
|
|
11
|
+
"exclude": ["dist", "node_modules"]
|
|
12
|
+
}
|