playwright-stealth-mcp-server 0.0.1 → 0.0.3
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 +25 -6
- package/package.json +1 -1
- package/shared/index.d.ts +2 -0
- package/shared/index.js +2 -0
- package/shared/playwright-client/playwright-client.integration-mock.js +1 -0
- package/shared/resources.d.ts +6 -0
- package/shared/resources.js +40 -0
- package/shared/server.js +10 -1
- package/shared/storage/factory.d.ts +7 -0
- package/shared/storage/factory.js +17 -0
- package/shared/storage/filesystem.d.ts +16 -0
- package/shared/storage/filesystem.js +126 -0
- package/shared/storage/index.d.ts +4 -0
- package/shared/storage/index.js +3 -0
- package/shared/storage/types.d.ts +29 -0
- package/shared/storage/types.js +4 -0
- package/shared/tools.js +57 -4
- package/shared/types.d.ts +1 -0
package/README.md
CHANGED
|
@@ -62,11 +62,13 @@ Add to your Claude Desktop config file:
|
|
|
62
62
|
|
|
63
63
|
### Environment Variables
|
|
64
64
|
|
|
65
|
-
| Variable
|
|
66
|
-
|
|
|
67
|
-
| `STEALTH_MODE`
|
|
68
|
-
| `HEADLESS`
|
|
69
|
-
| `TIMEOUT`
|
|
65
|
+
| Variable | Description | Default |
|
|
66
|
+
| ------------------------- | -------------------------------------------------------------------------- | ----------------------------- |
|
|
67
|
+
| `STEALTH_MODE` | Enable stealth mode with anti-detection measures | `false` |
|
|
68
|
+
| `HEADLESS` | Run browser in headless mode | `true` |
|
|
69
|
+
| `TIMEOUT` | Default timeout for Playwright actions (click, fill, etc.) in milliseconds | `30000` |
|
|
70
|
+
| `NAVIGATION_TIMEOUT` | Default timeout for page navigation (goto, reload, etc.) in milliseconds | `60000` |
|
|
71
|
+
| `SCREENSHOT_STORAGE_PATH` | Directory for storing screenshots | `/tmp/playwright-screenshots` |
|
|
70
72
|
|
|
71
73
|
## Available Tools
|
|
72
74
|
|
|
@@ -89,11 +91,19 @@ return title;
|
|
|
89
91
|
|
|
90
92
|
### `browser_screenshot`
|
|
91
93
|
|
|
92
|
-
Take a screenshot of the current page.
|
|
94
|
+
Take a screenshot of the current page. Screenshots are saved to filesystem storage and can be accessed later via MCP resources.
|
|
93
95
|
|
|
94
96
|
**Parameters:**
|
|
95
97
|
|
|
96
98
|
- `fullPage` (optional): Capture full scrollable page. Default: `false`
|
|
99
|
+
- `resultHandling` (optional): How to handle the result:
|
|
100
|
+
- `saveAndReturn` (default): Saves to storage AND returns inline base64 image
|
|
101
|
+
- `saveOnly`: Saves to storage and returns only the resource URI (more efficient for large screenshots)
|
|
102
|
+
|
|
103
|
+
**Returns:**
|
|
104
|
+
|
|
105
|
+
- With `saveAndReturn`: Inline base64 PNG image plus a `resource_link` with the file URI
|
|
106
|
+
- With `saveOnly`: Only a `resource_link` with the `file://` URI to the saved screenshot
|
|
97
107
|
|
|
98
108
|
### `browser_get_state`
|
|
99
109
|
|
|
@@ -103,6 +113,15 @@ Get the current browser state including URL, title, and configuration.
|
|
|
103
113
|
|
|
104
114
|
Close the browser session. A new browser will be launched on the next `browser_execute` call.
|
|
105
115
|
|
|
116
|
+
## MCP Resources
|
|
117
|
+
|
|
118
|
+
The server exposes saved screenshots as MCP resources. Clients can use:
|
|
119
|
+
|
|
120
|
+
- `resources/list`: List all saved screenshots with their URIs and metadata
|
|
121
|
+
- `resources/read`: Read a screenshot by its `file://` URI
|
|
122
|
+
|
|
123
|
+
This allows clients to access previously captured screenshots without needing to take new ones.
|
|
124
|
+
|
|
106
125
|
## Usage Examples
|
|
107
126
|
|
|
108
127
|
### Navigate and Extract Data
|
package/package.json
CHANGED
package/shared/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { createMCPServer, type IPlaywrightClient, type ClientFactory } from './server.js';
|
|
2
2
|
export { createRegisterTools } from './tools.js';
|
|
3
|
+
export { registerResources } from './resources.js';
|
|
3
4
|
export { logServerStart, logError, logWarning, logDebug } from './logging.js';
|
|
5
|
+
export * from './storage/index.js';
|
|
4
6
|
//# sourceMappingURL=index.d.ts.map
|
package/shared/index.js
CHANGED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import { ScreenshotStorageFactory } from './storage/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Register screenshot resources handlers to an MCP server
|
|
5
|
+
*/
|
|
6
|
+
export function registerResources(server) {
|
|
7
|
+
// Register resource list handler
|
|
8
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
9
|
+
const storage = await ScreenshotStorageFactory.create();
|
|
10
|
+
const resources = await storage.list();
|
|
11
|
+
return {
|
|
12
|
+
resources: resources.map((resource) => ({
|
|
13
|
+
uri: resource.uri,
|
|
14
|
+
name: resource.name,
|
|
15
|
+
description: resource.description,
|
|
16
|
+
mimeType: resource.mimeType,
|
|
17
|
+
})),
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
// Register resource read handler
|
|
21
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
22
|
+
const { uri } = request.params;
|
|
23
|
+
const storage = await ScreenshotStorageFactory.create();
|
|
24
|
+
try {
|
|
25
|
+
const content = await storage.read(uri);
|
|
26
|
+
return {
|
|
27
|
+
contents: [
|
|
28
|
+
{
|
|
29
|
+
uri: content.uri,
|
|
30
|
+
mimeType: content.mimeType,
|
|
31
|
+
blob: content.blob,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
package/shared/server.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { createRegisterTools } from './tools.js';
|
|
3
|
+
import { registerResources } from './resources.js';
|
|
3
4
|
/**
|
|
4
5
|
* Playwright client implementation with optional stealth mode
|
|
5
6
|
*/
|
|
@@ -44,6 +45,9 @@ export class PlaywrightClient {
|
|
|
44
45
|
: undefined,
|
|
45
46
|
});
|
|
46
47
|
this.page = await this.context.newPage();
|
|
48
|
+
// Apply timeout configuration to Playwright
|
|
49
|
+
this.page.setDefaultTimeout(this.config.timeout);
|
|
50
|
+
this.page.setDefaultNavigationTimeout(this.config.navigationTimeout);
|
|
47
51
|
// Capture console messages
|
|
48
52
|
this.page.on('console', (msg) => {
|
|
49
53
|
this.consoleMessages.push(`[${msg.type()}] ${msg.text()}`);
|
|
@@ -125,10 +129,11 @@ export function createMCPServer() {
|
|
|
125
129
|
const stealthMode = process.env.STEALTH_MODE === 'true';
|
|
126
130
|
const server = new Server({
|
|
127
131
|
name: 'playwright-stealth-mcp-server',
|
|
128
|
-
version: '0.0.
|
|
132
|
+
version: '0.0.3',
|
|
129
133
|
}, {
|
|
130
134
|
capabilities: {
|
|
131
135
|
tools: {},
|
|
136
|
+
resources: {},
|
|
132
137
|
},
|
|
133
138
|
});
|
|
134
139
|
// Track active client for cleanup
|
|
@@ -139,15 +144,19 @@ export function createMCPServer() {
|
|
|
139
144
|
(() => {
|
|
140
145
|
const headless = process.env.HEADLESS !== 'false';
|
|
141
146
|
const timeout = parseInt(process.env.TIMEOUT || '30000', 10);
|
|
147
|
+
const navigationTimeout = parseInt(process.env.NAVIGATION_TIMEOUT || '60000', 10);
|
|
142
148
|
activeClient = new PlaywrightClient({
|
|
143
149
|
stealthMode,
|
|
144
150
|
headless,
|
|
145
151
|
timeout,
|
|
152
|
+
navigationTimeout,
|
|
146
153
|
});
|
|
147
154
|
return activeClient;
|
|
148
155
|
});
|
|
149
156
|
const registerTools = createRegisterTools(factory);
|
|
150
157
|
registerTools(server);
|
|
158
|
+
// Register resources handlers for screenshot storage
|
|
159
|
+
registerResources(server);
|
|
151
160
|
};
|
|
152
161
|
const cleanup = async () => {
|
|
153
162
|
if (activeClient) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { FileSystemScreenshotStorage } from './filesystem.js';
|
|
2
|
+
export class ScreenshotStorageFactory {
|
|
3
|
+
static instance = null;
|
|
4
|
+
static async create() {
|
|
5
|
+
if (this.instance) {
|
|
6
|
+
return this.instance;
|
|
7
|
+
}
|
|
8
|
+
const rootDir = process.env.SCREENSHOT_STORAGE_PATH;
|
|
9
|
+
const fsStorage = new FileSystemScreenshotStorage(rootDir);
|
|
10
|
+
await fsStorage.init();
|
|
11
|
+
this.instance = fsStorage;
|
|
12
|
+
return this.instance;
|
|
13
|
+
}
|
|
14
|
+
static reset() {
|
|
15
|
+
this.instance = null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ScreenshotStorage, ScreenshotResourceData, ScreenshotResourceContent, ScreenshotMetadata } from './types.js';
|
|
2
|
+
export declare class FileSystemScreenshotStorage implements ScreenshotStorage {
|
|
3
|
+
private rootDir;
|
|
4
|
+
private initialized;
|
|
5
|
+
constructor(rootDir?: string);
|
|
6
|
+
init(): Promise<void>;
|
|
7
|
+
list(): Promise<ScreenshotResourceData[]>;
|
|
8
|
+
read(uri: string): Promise<ScreenshotResourceContent>;
|
|
9
|
+
write(base64Data: string, metadata: Omit<ScreenshotMetadata, 'timestamp'>): Promise<string>;
|
|
10
|
+
exists(uri: string): Promise<boolean>;
|
|
11
|
+
delete(uri: string): Promise<void>;
|
|
12
|
+
private fileExists;
|
|
13
|
+
private uriToFilePath;
|
|
14
|
+
private generateFileName;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=filesystem.d.ts.map
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
export class FileSystemScreenshotStorage {
|
|
5
|
+
rootDir;
|
|
6
|
+
initialized = false;
|
|
7
|
+
constructor(rootDir) {
|
|
8
|
+
this.rootDir = rootDir || path.join(os.tmpdir(), 'playwright-screenshots');
|
|
9
|
+
}
|
|
10
|
+
async init() {
|
|
11
|
+
if (this.initialized) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
await fs.mkdir(this.rootDir, { recursive: true });
|
|
15
|
+
this.initialized = true;
|
|
16
|
+
}
|
|
17
|
+
async list() {
|
|
18
|
+
await this.init();
|
|
19
|
+
const resources = [];
|
|
20
|
+
try {
|
|
21
|
+
const files = await fs.readdir(this.rootDir);
|
|
22
|
+
for (const file of files) {
|
|
23
|
+
if (file.endsWith('.png')) {
|
|
24
|
+
const metadataFile = file.replace('.png', '.json');
|
|
25
|
+
const filePath = path.join(this.rootDir, file);
|
|
26
|
+
const metadataPath = path.join(this.rootDir, metadataFile);
|
|
27
|
+
try {
|
|
28
|
+
const metadataContent = await fs.readFile(metadataPath, 'utf-8');
|
|
29
|
+
const metadata = JSON.parse(metadataContent);
|
|
30
|
+
const uri = `file://${filePath}`;
|
|
31
|
+
resources.push({
|
|
32
|
+
uri,
|
|
33
|
+
name: file,
|
|
34
|
+
description: metadata.pageUrl
|
|
35
|
+
? `Screenshot of ${metadata.pageUrl}`
|
|
36
|
+
: `Screenshot taken at ${metadata.timestamp}`,
|
|
37
|
+
mimeType: 'image/png',
|
|
38
|
+
metadata,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Ignore files without metadata
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Directory might not exist yet
|
|
49
|
+
}
|
|
50
|
+
// Sort by timestamp descending (most recent first)
|
|
51
|
+
resources.sort((a, b) => {
|
|
52
|
+
const timeA = new Date(a.metadata.timestamp).getTime();
|
|
53
|
+
const timeB = new Date(b.metadata.timestamp).getTime();
|
|
54
|
+
return timeB - timeA;
|
|
55
|
+
});
|
|
56
|
+
return resources;
|
|
57
|
+
}
|
|
58
|
+
async read(uri) {
|
|
59
|
+
const filePath = this.uriToFilePath(uri);
|
|
60
|
+
if (!(await this.fileExists(filePath))) {
|
|
61
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
62
|
+
}
|
|
63
|
+
const buffer = await fs.readFile(filePath);
|
|
64
|
+
const blob = buffer.toString('base64');
|
|
65
|
+
return {
|
|
66
|
+
uri,
|
|
67
|
+
mimeType: 'image/png',
|
|
68
|
+
blob,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
async write(base64Data, metadata) {
|
|
72
|
+
await this.init();
|
|
73
|
+
const timestamp = new Date().toISOString();
|
|
74
|
+
const fileName = this.generateFileName(timestamp);
|
|
75
|
+
const filePath = path.join(this.rootDir, fileName);
|
|
76
|
+
const metadataPath = path.join(this.rootDir, fileName.replace('.png', '.json'));
|
|
77
|
+
const fullMetadata = {
|
|
78
|
+
...metadata,
|
|
79
|
+
timestamp,
|
|
80
|
+
};
|
|
81
|
+
// Write the screenshot image
|
|
82
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
83
|
+
await fs.writeFile(filePath, buffer);
|
|
84
|
+
// Write the metadata
|
|
85
|
+
await fs.writeFile(metadataPath, JSON.stringify(fullMetadata, null, 2), 'utf-8');
|
|
86
|
+
return `file://${filePath}`;
|
|
87
|
+
}
|
|
88
|
+
async exists(uri) {
|
|
89
|
+
const filePath = this.uriToFilePath(uri);
|
|
90
|
+
return this.fileExists(filePath);
|
|
91
|
+
}
|
|
92
|
+
async delete(uri) {
|
|
93
|
+
const filePath = this.uriToFilePath(uri);
|
|
94
|
+
if (!(await this.fileExists(filePath))) {
|
|
95
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
96
|
+
}
|
|
97
|
+
await fs.unlink(filePath);
|
|
98
|
+
// Also delete metadata file if it exists
|
|
99
|
+
const metadataPath = filePath.replace('.png', '.json');
|
|
100
|
+
try {
|
|
101
|
+
await fs.unlink(metadataPath);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// Ignore if metadata file doesn't exist
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async fileExists(filePath) {
|
|
108
|
+
try {
|
|
109
|
+
await fs.access(filePath);
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
uriToFilePath(uri) {
|
|
117
|
+
if (uri.startsWith('file://')) {
|
|
118
|
+
return uri.substring(7);
|
|
119
|
+
}
|
|
120
|
+
throw new Error(`Invalid file URI: ${uri}`);
|
|
121
|
+
}
|
|
122
|
+
generateFileName(timestamp) {
|
|
123
|
+
const timestampPart = timestamp.replace(/[^0-9T-]/g, '').replace('T', '_');
|
|
124
|
+
return `screenshot_${timestampPart}.png`;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for screenshot resource storage
|
|
3
|
+
*/
|
|
4
|
+
export interface ScreenshotMetadata {
|
|
5
|
+
timestamp: string;
|
|
6
|
+
pageUrl?: string;
|
|
7
|
+
pageTitle?: string;
|
|
8
|
+
fullPage: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ScreenshotResourceData {
|
|
11
|
+
uri: string;
|
|
12
|
+
name: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
mimeType: string;
|
|
15
|
+
metadata: ScreenshotMetadata;
|
|
16
|
+
}
|
|
17
|
+
export interface ScreenshotResourceContent {
|
|
18
|
+
uri: string;
|
|
19
|
+
mimeType: string;
|
|
20
|
+
blob: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ScreenshotStorage {
|
|
23
|
+
list(): Promise<ScreenshotResourceData[]>;
|
|
24
|
+
read(uri: string): Promise<ScreenshotResourceContent>;
|
|
25
|
+
write(base64Data: string, metadata: Omit<ScreenshotMetadata, 'timestamp'>): Promise<string>;
|
|
26
|
+
exists(uri: string): Promise<boolean>;
|
|
27
|
+
delete(uri: string): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=types.d.ts.map
|
package/shared/tools.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import { ScreenshotStorageFactory } from './storage/index.js';
|
|
3
4
|
// =============================================================================
|
|
4
5
|
// TOOL SCHEMAS
|
|
5
6
|
// =============================================================================
|
|
@@ -9,6 +10,10 @@ const ExecuteSchema = z.object({
|
|
|
9
10
|
});
|
|
10
11
|
const ScreenshotSchema = z.object({
|
|
11
12
|
fullPage: z.boolean().optional().describe('Capture the full scrollable page. Default: false'),
|
|
13
|
+
resultHandling: z
|
|
14
|
+
.enum(['saveAndReturn', 'saveOnly'])
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("How to handle the screenshot result. 'saveAndReturn' (default) saves to storage and returns inline base64, 'saveOnly' saves to storage and returns only the resource URI"),
|
|
12
17
|
});
|
|
13
18
|
// =============================================================================
|
|
14
19
|
// TOOL DESCRIPTIONS
|
|
@@ -56,15 +61,23 @@ await page.click('button[type="submit"]');
|
|
|
56
61
|
**Note:** When STEALTH_MODE=true, the browser includes anti-detection measures to help bypass bot protection.`;
|
|
57
62
|
const SCREENSHOT_DESCRIPTION = `Take a screenshot of the current page.
|
|
58
63
|
|
|
59
|
-
Captures the visible viewport or full page as a PNG image
|
|
64
|
+
Captures the visible viewport or full page as a PNG image. Screenshots are saved to filesystem storage and can be accessed later via MCP resources.
|
|
65
|
+
|
|
66
|
+
**Parameters:**
|
|
67
|
+
- \`fullPage\`: Whether to capture the full scrollable page (default: false)
|
|
68
|
+
- \`resultHandling\`: How to handle the result:
|
|
69
|
+
- \`saveAndReturn\` (default): Saves to storage AND returns inline base64 image
|
|
70
|
+
- \`saveOnly\`: Saves to storage and returns only the resource URI (more efficient for large screenshots)
|
|
60
71
|
|
|
61
72
|
**Returns:**
|
|
62
|
-
-
|
|
73
|
+
- With \`saveAndReturn\`: Inline base64 PNG image data plus a resource_link to the saved file
|
|
74
|
+
- With \`saveOnly\`: A resource_link with the \`file://\` URI to the saved screenshot
|
|
63
75
|
|
|
64
76
|
**Use cases:**
|
|
65
77
|
- Verify page state after navigation
|
|
66
78
|
- Debug automation issues
|
|
67
|
-
- Capture visual content for analysis
|
|
79
|
+
- Capture visual content for analysis
|
|
80
|
+
- Store screenshots for later reference via MCP resources`;
|
|
68
81
|
const GET_STATE_DESCRIPTION = `Get the current browser state.
|
|
69
82
|
|
|
70
83
|
Returns information about the current browser session including the URL, page title, and whether a browser is open.
|
|
@@ -161,14 +174,47 @@ export function createRegisterTools(clientFactory) {
|
|
|
161
174
|
type: 'boolean',
|
|
162
175
|
description: 'Capture the full scrollable page. Default: false',
|
|
163
176
|
},
|
|
177
|
+
resultHandling: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
enum: ['saveAndReturn', 'saveOnly'],
|
|
180
|
+
description: "How to handle the screenshot result. 'saveAndReturn' (default) saves to storage and returns inline base64, 'saveOnly' saves to storage and returns only the resource URI",
|
|
181
|
+
},
|
|
164
182
|
},
|
|
165
183
|
},
|
|
166
184
|
handler: async (args) => {
|
|
167
185
|
try {
|
|
168
186
|
const validated = ScreenshotSchema.parse(args);
|
|
169
|
-
const
|
|
187
|
+
const client = getClient();
|
|
188
|
+
const base64 = await client.screenshot({
|
|
170
189
|
fullPage: validated.fullPage,
|
|
171
190
|
});
|
|
191
|
+
// Get page metadata for the screenshot
|
|
192
|
+
const state = await client.getState();
|
|
193
|
+
// Save to storage
|
|
194
|
+
const storage = await ScreenshotStorageFactory.create();
|
|
195
|
+
const uri = await storage.write(base64, {
|
|
196
|
+
pageUrl: state.currentUrl,
|
|
197
|
+
pageTitle: state.title,
|
|
198
|
+
fullPage: validated.fullPage ?? false,
|
|
199
|
+
});
|
|
200
|
+
const resultHandling = validated.resultHandling ?? 'saveAndReturn';
|
|
201
|
+
// Generate a name from the URI for the resource link
|
|
202
|
+
const fileName = uri.split('/').pop() || 'screenshot.png';
|
|
203
|
+
if (resultHandling === 'saveOnly') {
|
|
204
|
+
// Return only the resource link
|
|
205
|
+
return {
|
|
206
|
+
content: [
|
|
207
|
+
{
|
|
208
|
+
type: 'resource_link',
|
|
209
|
+
uri,
|
|
210
|
+
name: fileName,
|
|
211
|
+
description: `Screenshot saved to ${uri}`,
|
|
212
|
+
mimeType: 'image/png',
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
// Default: saveAndReturn - return both inline image and resource link
|
|
172
218
|
return {
|
|
173
219
|
content: [
|
|
174
220
|
{
|
|
@@ -176,6 +222,13 @@ export function createRegisterTools(clientFactory) {
|
|
|
176
222
|
data: base64,
|
|
177
223
|
mimeType: 'image/png',
|
|
178
224
|
},
|
|
225
|
+
{
|
|
226
|
+
type: 'resource_link',
|
|
227
|
+
uri,
|
|
228
|
+
name: fileName,
|
|
229
|
+
description: `Screenshot also saved to ${uri}`,
|
|
230
|
+
mimeType: 'image/png',
|
|
231
|
+
},
|
|
179
232
|
],
|
|
180
233
|
};
|
|
181
234
|
}
|