mcpbrowser 0.2.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 +100 -0
- package/package.json +41 -0
- package/src/mcp-browser.js +249 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 cherchyk
|
|
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,100 @@
|
|
|
1
|
+
# MCPBrowser (MCP fetch tool with authenticated Chrome)
|
|
2
|
+
|
|
3
|
+
An MCP server that exposes an authenticated page fetch tool for GitHub Copilot. It drives your signed-in Chrome/Edge via DevTools, reusing your profile to read restricted pages.
|
|
4
|
+
|
|
5
|
+
## Quick Install
|
|
6
|
+
|
|
7
|
+
### Option 1: Install from GitHub (Recommended)
|
|
8
|
+
```bash
|
|
9
|
+
git clone https://github.com/cherchyk/MCPBrowser.git
|
|
10
|
+
cd MCPBrowser
|
|
11
|
+
npm install
|
|
12
|
+
copy .env.example .env # optional: set Chrome overrides
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Option 2: Install via npx (when published to npm)
|
|
16
|
+
```bash
|
|
17
|
+
npx mcpbrowser
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Prereqs
|
|
21
|
+
- Chrome or Edge installed.
|
|
22
|
+
- Node 18+.
|
|
23
|
+
|
|
24
|
+
## Run (automatic via Copilot)
|
|
25
|
+
- Add the MCP server entry to VS Code settings (`github.copilot.chat.modelContextProtocolServers`, see below). Copilot will start the server automatically when it needs the tool—no manual launch required.
|
|
26
|
+
- On first use, the server auto-launches Chrome/Edge with remote debugging if it cannot find an existing DevTools endpoint. Defaults: port `9222`, user data dir `%LOCALAPPDATA%/ChromeAuthProfile`. Override with `CHROME_PATH`, `CHROME_USER_DATA_DIR`, or `CHROME_REMOTE_DEBUG_PORT`.
|
|
27
|
+
- The old `scripts/start-all.ps1` launcher was removed; Chrome startup is handled inside the MCP server.
|
|
28
|
+
|
|
29
|
+
## Manual start (optional)
|
|
30
|
+
Only if you want to run it yourself (Copilot already starts it when configured):
|
|
31
|
+
```bash
|
|
32
|
+
npm run mcp
|
|
33
|
+
```
|
|
34
|
+
Or manually:
|
|
35
|
+
```powershell
|
|
36
|
+
& "C:\Program Files\Google\Chrome\Application\chrome.exe" --remote-debugging-port=9222 --user-data-dir="$env:LOCALAPPDATA\ChromeAuthProfile"
|
|
37
|
+
```
|
|
38
|
+
Set `CHROME_PATH` if auto-detect fails; override profile with `CHROME_USER_DATA_DIR`, port with `CHROME_REMOTE_DEBUG_PORT`.
|
|
39
|
+
|
|
40
|
+
## (Optional) Local runner
|
|
41
|
+
There is no local LLM runner now; Copilot provides the LLM and calls this MCP tool. If you need a standalone agent later, we can add one that reuses the fetch logic.
|
|
42
|
+
|
|
43
|
+
## Configure in VS Code (GitHub Copilot)
|
|
44
|
+
|
|
45
|
+
### Step 1: Locate your mcp.json file
|
|
46
|
+
- **Windows**: `%APPDATA%\Code\User\mcp.json`
|
|
47
|
+
- **Linux/Mac**: `~/.config/Code/User/mcp.json`
|
|
48
|
+
|
|
49
|
+
### Step 2: Add MCPBrowser server configuration
|
|
50
|
+
|
|
51
|
+
Add this entry to your `mcp.json` file under the `"servers"` section:
|
|
52
|
+
|
|
53
|
+
```jsonc
|
|
54
|
+
"MCPBrowser": {
|
|
55
|
+
"type": "stdio",
|
|
56
|
+
"command": "node",
|
|
57
|
+
"args": ["<PATH_TO_MCPBROWSER>/src/mcp-browser.js"],
|
|
58
|
+
"description": "**USE THIS AUTOMATICALLY** when you encounter ANY authentication/authorization barrier: 401/403 errors, login redirects, 'access denied', SSO prompts, or pages requiring sign-in. Loads web content using the user's authenticated Chrome session (works for ANY site: Microsoft, GitHub, AWS, Google, corporate intranets, etc.). IMPORTANT: Tool waits automatically for pages to load (up to 60s default, handles auth redirects, waits for network idle). Do NOT retry or give up if you think page is loading - trust the tool's timeout. Do NOT ask permission - use immediately when auth is required."
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Replace `<PATH_TO_MCPBROWSER>` with the full path where you cloned this repository, for example:
|
|
63
|
+
- Windows: `"D:/dev/MCPBrowser/src/mcp-browser.js"`
|
|
64
|
+
- Linux/Mac: `"/home/user/MCPBrowser/src/mcp-browser.js"`
|
|
65
|
+
|
|
66
|
+
### Step 3: Reload VS Code
|
|
67
|
+
Restart VS Code or reload the window for the changes to take effect.
|
|
68
|
+
|
|
69
|
+
### Step 4: Verify
|
|
70
|
+
In Copilot Chat, you should see the `MCPBrowser` server listed. Ask it to load an authenticated URL and it will drive your signed-in Chrome session.
|
|
71
|
+
|
|
72
|
+
## How it works
|
|
73
|
+
- Tool `load_and_extract` (inside the MCP server) drives your live Chrome (DevTools Protocol) so it inherits your auth cookies, returning `text` and `html` (truncated up to 2M chars per field) for analysis.
|
|
74
|
+
- **Domain-aware tab reuse**: Automatically reuses the same tab for URLs on the same domain, preserving authentication session. Different domains open new tabs.
|
|
75
|
+
- **Automatic page loading**: Waits for network idle (`networkidle0`) by default, ensuring JavaScript-heavy pages (SPAs, dashboards) fully load before returning content.
|
|
76
|
+
- **Automatic auth detection**: Detects ANY authentication redirect (domain changes, login/auth/sso/oauth URLs) and waits for you to complete sign-in, then returns to target page.
|
|
77
|
+
- **Universal compatibility**: Works with Microsoft, GitHub, AWS, Google, Okta, corporate SSO, or any authenticated site.
|
|
78
|
+
- **Smart timeouts**: 60s default for page load, 10 min for auth redirects. Tabs stay open indefinitely for reuse (no auto-close).
|
|
79
|
+
- GitHub Copilot's LLM invokes this tool via MCP; this repo itself does not run an LLM.
|
|
80
|
+
|
|
81
|
+
## Auth-assisted fetch flow
|
|
82
|
+
- Copilot can call with just the URL, or with no params if you set an env default (`DEFAULT_FETCH_URL` or `MCP_DEFAULT_FETCH_URL`). By default tabs stay open indefinitely for reuse (domain-aware).
|
|
83
|
+
- First call opens the tab and leaves it open so you can sign in. No extra params needed.
|
|
84
|
+
- After you sign in, call the same URL again; tab stays open for reuse. Set `keepPageOpen: false` to close immediately on success.
|
|
85
|
+
- Optional fields (`authWaitSelector`, `waitForSelector`, `waitForUrlPattern`, etc.) are available but not required.
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
- `.env`: optional overrides for `CHROME_WS_ENDPOINT`, `CHROME_REMOTE_DEBUG_HOST/PORT`, `CHROME_PATH`, `CHROME_USER_DATA_DIR`.
|
|
89
|
+
- To use a specific WS endpoint: set `CHROME_WS_ENDPOINT` from Chrome `chrome://version` DevTools JSON.
|
|
90
|
+
|
|
91
|
+
## Tips
|
|
92
|
+
- **Universal auth**: Works with ANY authenticated site (Microsoft, GitHub, AWS, Google, corporate intranets, SSO, OAuth, etc.)
|
|
93
|
+
- **No re-authentication needed**: Automatically reuses the same tab for URLs on the same domain, keeping your auth session alive across multiple page fetches
|
|
94
|
+
- **Automatic page loading**: Tool waits for pages to fully load (default 60s timeout, waits for network idle). Copilot should trust the tool and not retry manually.
|
|
95
|
+
- **Auth redirect handling**: Auto-detects auth redirects by monitoring domain changes and common login URL patterns (`/login`, `/auth`, `/signin`, `/sso`, `/oauth`, `/saml`)
|
|
96
|
+
- **Tabs stay open**: By default tabs remain open indefinitely for reuse. Set `keepPageOpen: false` to close immediately after successful fetch.
|
|
97
|
+
- **Smart domain switching**: When switching domains, automatically closes the old tab and opens a new one to prevent tab accumulation
|
|
98
|
+
- If you hit login pages, verify Chrome instance is signed in and the site opens there.
|
|
99
|
+
- Use a dedicated profile directory to avoid interfering with your daily Chrome.
|
|
100
|
+
- For heavy pages, add `waitForSelector` to ensure post-login content appears before extraction.
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcpbrowser",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "MCP server that loads authenticated web pages using Chrome DevTools Protocol",
|
|
6
|
+
"main": "src/mcp-browser.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"mcpbrowser": "src/mcp-browser.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"mcp": "node src/mcp-browser.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"mcp-server",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"chrome",
|
|
18
|
+
"puppeteer",
|
|
19
|
+
"authentication",
|
|
20
|
+
"web-scraping",
|
|
21
|
+
"github-copilot"
|
|
22
|
+
],
|
|
23
|
+
"author": "cherchyk",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/cherchyk/MCPBrowser.git"
|
|
28
|
+
},
|
|
29
|
+
"bugs": {
|
|
30
|
+
"url": "https://github.com/cherchyk/MCPBrowser/issues"
|
|
31
|
+
},
|
|
32
|
+
"homepage": "https://github.com/cherchyk/MCPBrowser#readme",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
35
|
+
"dotenv": "^16.4.5",
|
|
36
|
+
"puppeteer-core": "^23.4.1"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import puppeteer from "puppeteer-core";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
|
|
12
|
+
dotenv.config();
|
|
13
|
+
|
|
14
|
+
const chromeHost = process.env.CHROME_REMOTE_DEBUG_HOST || "127.0.0.1";
|
|
15
|
+
const chromePort = Number(process.env.CHROME_REMOTE_DEBUG_PORT || 9222);
|
|
16
|
+
const explicitWSEndpoint = process.env.CHROME_WS_ENDPOINT;
|
|
17
|
+
const userDataDir = process.env.CHROME_USER_DATA_DIR || path.join(os.homedir(), "AppData/Local/ChromeAuthProfile");
|
|
18
|
+
const chromePathEnv = process.env.CHROME_PATH;
|
|
19
|
+
const defaultChromePaths = [
|
|
20
|
+
"C:/Program Files/Google/Chrome/Application/chrome.exe",
|
|
21
|
+
"C:/Program Files (x86)/Google/Chrome/Application/chrome.exe",
|
|
22
|
+
"C:/Program Files/Microsoft/Edge/Application/msedge.exe",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
let cachedBrowser = null;
|
|
26
|
+
let lastKeptPage = null; // reuse the same tab when requested
|
|
27
|
+
|
|
28
|
+
async function devtoolsAvailable() {
|
|
29
|
+
try {
|
|
30
|
+
const url = `http://${chromeHost}:${chromePort}/json/version`;
|
|
31
|
+
const res = await fetch(url, { method: "GET" });
|
|
32
|
+
if (!res.ok) return false;
|
|
33
|
+
const data = await res.json();
|
|
34
|
+
return Boolean(data.webSocketDebuggerUrl);
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function findChromePath() {
|
|
41
|
+
if (chromePathEnv && existsSync(chromePathEnv)) return chromePathEnv;
|
|
42
|
+
return defaultChromePaths.find((p) => existsSync(p));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function launchChromeIfNeeded() {
|
|
46
|
+
if (explicitWSEndpoint) return; // user provided explicit endpoint; assume managed externally
|
|
47
|
+
if (await devtoolsAvailable()) return;
|
|
48
|
+
|
|
49
|
+
const chromePath = findChromePath();
|
|
50
|
+
if (!chromePath) {
|
|
51
|
+
throw new Error("Chrome/Edge not found. Set CHROME_PATH to your browser executable.");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const args = [`--remote-debugging-port=${chromePort}`, `--user-data-dir=${userDataDir}`];
|
|
55
|
+
const child = spawn(chromePath, args, { detached: true, stdio: "ignore" });
|
|
56
|
+
child.unref();
|
|
57
|
+
|
|
58
|
+
// Wait for DevTools to come up
|
|
59
|
+
const deadline = Date.now() + 20000;
|
|
60
|
+
while (Date.now() < deadline) {
|
|
61
|
+
if (await devtoolsAvailable()) return;
|
|
62
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
63
|
+
}
|
|
64
|
+
throw new Error("Chrome did not become available on DevTools port; check CHROME_PATH/port/profile.");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function resolveWSEndpoint() {
|
|
68
|
+
if (explicitWSEndpoint) return explicitWSEndpoint;
|
|
69
|
+
const url = `http://${chromeHost}:${chromePort}/json/version`;
|
|
70
|
+
const res = await fetch(url);
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
throw new Error(`Unable to reach Chrome devtools at ${url}: ${res.status}`);
|
|
73
|
+
}
|
|
74
|
+
const data = await res.json();
|
|
75
|
+
if (!data.webSocketDebuggerUrl) {
|
|
76
|
+
throw new Error("No webSocketDebuggerUrl in /json/version response");
|
|
77
|
+
}
|
|
78
|
+
return data.webSocketDebuggerUrl;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function getBrowser() {
|
|
82
|
+
await launchChromeIfNeeded();
|
|
83
|
+
if (cachedBrowser && cachedBrowser.isConnected()) return cachedBrowser;
|
|
84
|
+
const wsEndpoint = await resolveWSEndpoint();
|
|
85
|
+
cachedBrowser = await puppeteer.connect({
|
|
86
|
+
browserWSEndpoint: wsEndpoint,
|
|
87
|
+
defaultViewport: null,
|
|
88
|
+
});
|
|
89
|
+
cachedBrowser.on("disconnected", () => {
|
|
90
|
+
cachedBrowser = null;
|
|
91
|
+
lastKeptPage = null;
|
|
92
|
+
});
|
|
93
|
+
return cachedBrowser;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function fetchPage({
|
|
97
|
+
url,
|
|
98
|
+
keepPageOpen = true,
|
|
99
|
+
}) {
|
|
100
|
+
// Hardcoded smart defaults
|
|
101
|
+
const waitUntil = "networkidle0";
|
|
102
|
+
const timeoutMs = 60000;
|
|
103
|
+
const reuseLastKeptPage = true;
|
|
104
|
+
|
|
105
|
+
if (!url) {
|
|
106
|
+
throw new Error("url parameter is required");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const browser = await getBrowser();
|
|
110
|
+
let page = null;
|
|
111
|
+
|
|
112
|
+
// Smart tab reuse: only reuse if same domain (preserves auth within domain)
|
|
113
|
+
if (reuseLastKeptPage && lastKeptPage && !lastKeptPage.isClosed()) {
|
|
114
|
+
let newHostname;
|
|
115
|
+
try {
|
|
116
|
+
newHostname = new URL(url).hostname;
|
|
117
|
+
} catch {
|
|
118
|
+
throw new Error(`Invalid URL: ${url}`);
|
|
119
|
+
}
|
|
120
|
+
const currentUrl = lastKeptPage.url();
|
|
121
|
+
|
|
122
|
+
if (currentUrl) {
|
|
123
|
+
try {
|
|
124
|
+
const currentHostname = new URL(currentUrl).hostname;
|
|
125
|
+
// Reuse tab only if same domain (keeps auth session alive)
|
|
126
|
+
if (currentHostname === newHostname) {
|
|
127
|
+
page = lastKeptPage;
|
|
128
|
+
await page.bringToFront().catch(() => {});
|
|
129
|
+
} else {
|
|
130
|
+
// Different domain - close old tab and create new one
|
|
131
|
+
await lastKeptPage.close().catch(() => {});
|
|
132
|
+
lastKeptPage = null;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// If URL parsing fails, create new tab
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create new tab if no reuse
|
|
141
|
+
if (!page) {
|
|
142
|
+
page = await browser.newPage();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let shouldKeepOpen = keepPageOpen || page === lastKeptPage;
|
|
146
|
+
let wasSuccess = false;
|
|
147
|
+
try {
|
|
148
|
+
await page.goto(url, { waitUntil, timeout: timeoutMs });
|
|
149
|
+
|
|
150
|
+
// Extract content
|
|
151
|
+
const text = await page.evaluate(() => document.body?.innerText || "");
|
|
152
|
+
const html = await page.evaluate(() => document.documentElement?.outerHTML || "");
|
|
153
|
+
wasSuccess = true;
|
|
154
|
+
if (keepPageOpen && lastKeptPage !== page) {
|
|
155
|
+
// Close old kept page if we're keeping a different one
|
|
156
|
+
if (lastKeptPage && !lastKeptPage.isClosed()) {
|
|
157
|
+
await lastKeptPage.close().catch(() => {});
|
|
158
|
+
}
|
|
159
|
+
lastKeptPage = page;
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
success: true,
|
|
163
|
+
url: page.url(),
|
|
164
|
+
text: truncate(text, 2000000),
|
|
165
|
+
html: truncate(html, 2000000),
|
|
166
|
+
};
|
|
167
|
+
} catch (err) {
|
|
168
|
+
shouldKeepOpen = shouldKeepOpen || keepPageOpen;
|
|
169
|
+
const hint = shouldKeepOpen
|
|
170
|
+
? "Tab is left open. Complete sign-in there, then call load_and_extract again with just the URL."
|
|
171
|
+
: undefined;
|
|
172
|
+
return { success: false, error: err.message || String(err), pageKeptOpen: shouldKeepOpen, hint };
|
|
173
|
+
} finally {
|
|
174
|
+
if (!shouldKeepOpen && lastKeptPage === page) {
|
|
175
|
+
lastKeptPage = null;
|
|
176
|
+
}
|
|
177
|
+
if (!shouldKeepOpen) {
|
|
178
|
+
await page.close().catch(() => {});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function truncate(str, max) {
|
|
184
|
+
if (!str) return "";
|
|
185
|
+
return str.length > max ? `${str.slice(0, max)}... [truncated]` : str;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function main() {
|
|
189
|
+
const server = new Server({ name: "MCPBrowser", version: "0.2.0" }, { capabilities: { tools: {} } });
|
|
190
|
+
|
|
191
|
+
const tools = [
|
|
192
|
+
{
|
|
193
|
+
name: "load_and_extract",
|
|
194
|
+
description: "Fetch and extract content from authenticated web pages using a local Chrome/Edge browser via DevTools Protocol. Automatically detects auth redirects by waiting for network idle. Supports smart tab reuse within the same domain to preserve authentication sessions. Returns both plain text and HTML content.",
|
|
195
|
+
inputSchema: {
|
|
196
|
+
type: "object",
|
|
197
|
+
properties: {
|
|
198
|
+
url: { type: "string", description: "The URL to fetch" },
|
|
199
|
+
keepPageOpen: { type: "boolean", description: "Keep the tab open after fetching for manual auth or reuse (default: true)" },
|
|
200
|
+
},
|
|
201
|
+
required: ["url"],
|
|
202
|
+
additionalProperties: false,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
];
|
|
206
|
+
|
|
207
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
208
|
+
|
|
209
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
210
|
+
const { name, arguments: args } = request.params;
|
|
211
|
+
if (name !== "load_and_extract") {
|
|
212
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
213
|
+
}
|
|
214
|
+
const safeArgs = args || {};
|
|
215
|
+
const fallbackUrl = process.env.DEFAULT_FETCH_URL || process.env.MCP_DEFAULT_FETCH_URL;
|
|
216
|
+
if (!safeArgs.url) {
|
|
217
|
+
if (fallbackUrl) {
|
|
218
|
+
safeArgs.url = fallbackUrl;
|
|
219
|
+
} else {
|
|
220
|
+
return {
|
|
221
|
+
content: [
|
|
222
|
+
{
|
|
223
|
+
type: "text",
|
|
224
|
+
text: JSON.stringify({ success: false, error: "Missing url and no DEFAULT_FETCH_URL/MCP_DEFAULT_FETCH_URL configured" }),
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const result = await fetchPage(safeArgs);
|
|
232
|
+
return {
|
|
233
|
+
content: [
|
|
234
|
+
{
|
|
235
|
+
type: "text",
|
|
236
|
+
text: JSON.stringify(result),
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
};
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const transport = new StdioServerTransport();
|
|
243
|
+
await server.connect(transport);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
main().catch((err) => {
|
|
247
|
+
console.error(err);
|
|
248
|
+
process.exit(1);
|
|
249
|
+
});
|