@iamjameslennon/ddb-mcp 2.6.4 → 2.7.1
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 +59 -65
- package/dist/browser.d.ts +2 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +94 -9
- package/dist/browser.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +13 -2
- package/dist/index.js.map +1 -1
- package/dist/session-fetch.d.ts +1 -0
- package/dist/session-fetch.d.ts.map +1 -1
- package/dist/session-fetch.js +47 -3
- package/dist/session-fetch.js.map +1 -1
- package/dist/tools/character.d.ts.map +1 -1
- package/dist/tools/character.js +63 -5
- package/dist/tools/character.js.map +1 -1
- package/dist/tools/navigate.d.ts.map +1 -1
- package/dist/tools/navigate.js +43 -11
- package/dist/tools/navigate.js.map +1 -1
- package/package.json +6 -5
package/dist/tools/navigate.js
CHANGED
|
@@ -1,23 +1,41 @@
|
|
|
1
|
+
import { mkdirSync } from "fs";
|
|
1
2
|
import { homedir } from "os";
|
|
2
|
-
import { join } from "path";
|
|
3
|
+
import { dirname, join } from "path";
|
|
3
4
|
import { getPage } from "../browser.js";
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// Hosts the browser tools are allowed to navigate to. The wildcard form
|
|
6
|
+
// `*.dndbeyond.com` is intentionally NOT used here: every code path that
|
|
7
|
+
// drives the browser already targets www.dndbeyond.com specifically, and
|
|
8
|
+
// pinning the allowlist avoids trust spillover to other DDB subdomains
|
|
9
|
+
// (including any user-controlled ones DDB might host in future).
|
|
10
|
+
const ALLOWED_NAVIGATE_HOSTS = new Set([
|
|
11
|
+
"www.dndbeyond.com",
|
|
12
|
+
"dndbeyond.com", // apex redirects to www; accept so users can pass either form
|
|
13
|
+
]);
|
|
14
|
+
// Reject Playwright selector forms that go beyond CSS / text-locator matching.
|
|
15
|
+
// A prompt-injected DDB page could otherwise craft a selector that runs
|
|
16
|
+
// XPath, calls Playwright internals, pierces frames, or invokes an engine we
|
|
17
|
+
// haven't reviewed. Deny-list (not allow-list) because legitimate CSS is a
|
|
18
|
+
// regular-language we can't easily white-list; we instead exclude every
|
|
19
|
+
// known engine prefix that introduces non-CSS behavior. Engine prefixes are
|
|
20
|
+
// `<name>=` at the start of a selector chunk, plus `>>` for chaining.
|
|
7
21
|
function assertSafeSelector(selector) {
|
|
8
22
|
const lower = selector.toLowerCase();
|
|
9
23
|
if (lower.startsWith("xpath=") ||
|
|
10
24
|
lower.startsWith("xpath/") ||
|
|
25
|
+
lower.startsWith("id=") || // Playwright id-engine — bypasses CSS escaping
|
|
26
|
+
lower.startsWith("data-testid=") || // Use `[data-testid="..."]` (CSS) instead
|
|
27
|
+
lower.startsWith("internal:") || // Playwright internal engines (chromium, react, vue, etc.)
|
|
11
28
|
lower.includes("javascript:") ||
|
|
12
29
|
lower.startsWith(">>") ||
|
|
30
|
+
lower.includes(">>") || // Frame piercing / chaining anywhere in the selector
|
|
13
31
|
lower.includes("__playwright") ||
|
|
14
32
|
lower.includes("_eval")) {
|
|
15
|
-
throw new Error("Selector contains disallowed syntax. Use CSS selectors or
|
|
33
|
+
throw new Error("Selector contains disallowed syntax. Use CSS selectors or text-locator selectors only (e.g. 'button:has-text(\"…\")' or '[data-testid=\"…\"]').");
|
|
16
34
|
}
|
|
17
35
|
}
|
|
18
36
|
export async function navigate(context, url) {
|
|
19
37
|
const page = await getPage(context);
|
|
20
|
-
// Only allow D&D Beyond URLs — validate the actual hostname
|
|
38
|
+
// Only allow D&D Beyond URLs — validate the actual hostname.
|
|
21
39
|
let validatedUrl;
|
|
22
40
|
try {
|
|
23
41
|
validatedUrl = new URL(url);
|
|
@@ -25,9 +43,8 @@ export async function navigate(context, url) {
|
|
|
25
43
|
catch {
|
|
26
44
|
throw new Error("Invalid URL.");
|
|
27
45
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
throw new Error("Only D&D Beyond URLs (https://www.dndbeyond.com/...) are supported.");
|
|
46
|
+
if (validatedUrl.protocol !== "https:" || !ALLOWED_NAVIGATE_HOSTS.has(validatedUrl.hostname)) {
|
|
47
|
+
throw new Error("Only https://www.dndbeyond.com/ URLs are supported.");
|
|
31
48
|
}
|
|
32
49
|
await page.goto(url, { waitUntil: "networkidle", timeout: 30000 });
|
|
33
50
|
// Extract page text content and convert to readable markdown-ish format
|
|
@@ -39,7 +56,11 @@ export async function navigate(context, url) {
|
|
|
39
56
|
return main.innerText;
|
|
40
57
|
});
|
|
41
58
|
const truncated = content.length > 8000 ? content.slice(0, 8000) + "\n\n[Content truncated — use ddb_read_book or a more specific URL to get full content]" : content;
|
|
42
|
-
|
|
59
|
+
// Wrap scraped content in delimiters so callers can clearly separate
|
|
60
|
+
// trusted tool output from untrusted page content (potential prompt-
|
|
61
|
+
// injection surface). The page may contain user-authored text from DMs,
|
|
62
|
+
// forum posts, or campaign notes.
|
|
63
|
+
return `URL: ${url}\n\n<untrusted_dndbeyond_content>\n${truncated}\n</untrusted_dndbeyond_content>`;
|
|
43
64
|
}
|
|
44
65
|
export async function interact(context, action, selector, value) {
|
|
45
66
|
assertSafeSelector(selector);
|
|
@@ -63,6 +84,15 @@ export async function interact(context, action, selector, value) {
|
|
|
63
84
|
}
|
|
64
85
|
case "screenshot": {
|
|
65
86
|
const screenshotPath = join(homedir(), "Downloads", `ddb-screenshot-${Date.now()}.png`);
|
|
87
|
+
// ~/Downloads isn't guaranteed to exist on minimal Linux profiles.
|
|
88
|
+
try {
|
|
89
|
+
mkdirSync(dirname(screenshotPath), { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
const code = e.code;
|
|
93
|
+
throw new Error(`Cannot create ${dirname(screenshotPath)} (${code ?? "unknown"}). ` +
|
|
94
|
+
`Ensure ~/Downloads exists and is writable.`);
|
|
95
|
+
}
|
|
66
96
|
await page.screenshot({ path: screenshotPath, fullPage: false });
|
|
67
97
|
return `Screenshot saved to: ${screenshotPath}`;
|
|
68
98
|
}
|
|
@@ -79,6 +109,8 @@ export async function getCurrentPageContent(context) {
|
|
|
79
109
|
return main.innerText;
|
|
80
110
|
});
|
|
81
111
|
const truncated = content.length > 8000 ? content.slice(0, 8000) + "\n[truncated]" : content;
|
|
82
|
-
|
|
112
|
+
// See note in navigate() — wrap scraped content so untrusted page text is
|
|
113
|
+
// visibly separated from trusted tool output.
|
|
114
|
+
return `Current URL: ${url}\n\n<untrusted_dndbeyond_content>\n${truncated}\n</untrusted_dndbeyond_content>`;
|
|
83
115
|
}
|
|
84
116
|
//# sourceMappingURL=navigate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigate.js","sourceRoot":"","sources":["../../src/tools/navigate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"navigate.js","sourceRoot":"","sources":["../../src/tools/navigate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iamjameslennon/ddb-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "D&D Beyond MCP Server with Google Auth",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"ddb-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
8
11
|
"files": [
|
|
9
12
|
"dist/",
|
|
10
13
|
"README.md"
|
|
@@ -18,13 +21,11 @@
|
|
|
18
21
|
"lint": "eslint src/",
|
|
19
22
|
"typecheck": "tsc --noEmit",
|
|
20
23
|
"test": "vitest run",
|
|
21
|
-
"release": "node scripts/release.js"
|
|
22
|
-
"postinstall": "node -e \"if (process.env.npm_config_global) { require('child_process').execSync('npx playwright install chromium', {stdio:'inherit'}); }\"",
|
|
23
|
-
"preinstall": "node -e \"if (process.env.npm_command && process.env.npm_command !== 'ci' && !process.env.npm_config_global) { console.error('\\nERROR: Use npm ci instead of npm install.\\nThis project pins exact dependency versions via package-lock.json.\\nRunning npm install can silently upgrade packages and break things.\\n'); process.exit(1); }\""
|
|
24
|
+
"release": "node scripts/release.js"
|
|
24
25
|
},
|
|
25
26
|
"repository": {
|
|
26
27
|
"type": "git",
|
|
27
|
-
"url": "https://github.com/iamjameslennon/ddb-mcp"
|
|
28
|
+
"url": "git+https://github.com/iamjameslennon/ddb-mcp.git"
|
|
28
29
|
},
|
|
29
30
|
"engines": {
|
|
30
31
|
"node": ">=20"
|