@browserstack/mcp-server 1.5.1-beta.2 → 2.0.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/README.md +54 -1
- package/capability/tm.capability-index.json +50419 -0
- package/dist/lib/instrumentation.d.ts +14 -1
- package/dist/lib/instrumentation.js +8 -1
- package/dist/server-factory.js +5 -0
- package/dist/tools/capability-registry/bind.d.ts +28 -0
- package/dist/tools/capability-registry/bind.js +371 -0
- package/dist/tools/capability-registry/config.d.ts +62 -0
- package/dist/tools/capability-registry/config.js +218 -0
- package/dist/tools/capability-registry/discovery.d.ts +44 -0
- package/dist/tools/capability-registry/discovery.js +99 -0
- package/dist/tools/capability-registry/egress.d.ts +44 -0
- package/dist/tools/capability-registry/egress.js +174 -0
- package/dist/tools/capability-registry/index-loader.d.ts +162 -0
- package/dist/tools/capability-registry/index-loader.js +523 -0
- package/dist/tools/capability-registry/redact.d.ts +34 -0
- package/dist/tools/capability-registry/redact.js +98 -0
- package/dist/tools/capability-registry/register.d.ts +48 -0
- package/dist/tools/capability-registry/register.js +708 -0
- package/dist/tools/capability-registry/resolve.d.ts +38 -0
- package/dist/tools/capability-registry/resolve.js +83 -0
- package/dist/tools/capability-registry/search.d.ts +170 -0
- package/dist/tools/capability-registry/search.js +709 -0
- package/dist/tools/capability-registry/types.d.ts +302 -0
- package/dist/tools/capability-registry/types.js +33 -0
- package/package.json +9 -3
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip the PII we can identify by SHAPE from agent-written intent text, before it is
|
|
3
|
+
* recorded as telemetry.
|
|
4
|
+
*
|
|
5
|
+
* Regex only, no model call: this runs on every capability search and write, so it must
|
|
6
|
+
* be deterministic, allocation-light and incapable of adding latency or a failure mode.
|
|
7
|
+
*
|
|
8
|
+
* WHAT THIS CANNOT DO, and must not be described as doing: a person's NAME has no shape.
|
|
9
|
+
* "assign this to Priya Sharma" is PII and passes through untouched, because no pattern
|
|
10
|
+
* separates a person from a project ("Payments Regression") without destroying the field's
|
|
11
|
+
* value. Physical addresses are the same. Redaction here REDUCES exposure; it does not
|
|
12
|
+
* eliminate it, and the sign-off should be read on those terms.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* The ceiling on any redacted field.
|
|
16
|
+
*
|
|
17
|
+
* The rules above catch PII by shape, and the header is explicit that a person's NAME has
|
|
18
|
+
* none — so the longer the recorded string, the more unshaped personal detail rides along
|
|
19
|
+
* with it. A cap does not make the field safe; it bounds how much escapes when redaction
|
|
20
|
+
* misses, which it will. 512 leaves an ordinary query or change summary intact.
|
|
21
|
+
*
|
|
22
|
+
* This existed, was removed, and the doc comment kept claiming it for two commits. Both
|
|
23
|
+
* fields it guards are free text an agent writes from what the user said.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MAX_LENGTH = 512;
|
|
26
|
+
/**
|
|
27
|
+
* Returns the text with shape-identifiable PII replaced, capped at `MAX_LENGTH`.
|
|
28
|
+
*
|
|
29
|
+
* FAILS CLOSED: any throw returns undefined, so the caller records no field rather than
|
|
30
|
+
* the raw value. Non-string and empty input yields undefined for the same reason.
|
|
31
|
+
*/
|
|
32
|
+
export declare function redact(text: unknown): string | undefined;
|
|
33
|
+
/** The rule names, for documenting what is covered. */
|
|
34
|
+
export declare const REDACTED_TYPES: string[];
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip the PII we can identify by SHAPE from agent-written intent text, before it is
|
|
3
|
+
* recorded as telemetry.
|
|
4
|
+
*
|
|
5
|
+
* Regex only, no model call: this runs on every capability search and write, so it must
|
|
6
|
+
* be deterministic, allocation-light and incapable of adding latency or a failure mode.
|
|
7
|
+
*
|
|
8
|
+
* WHAT THIS CANNOT DO, and must not be described as doing: a person's NAME has no shape.
|
|
9
|
+
* "assign this to Priya Sharma" is PII and passes through untouched, because no pattern
|
|
10
|
+
* separates a person from a project ("Payments Regression") without destroying the field's
|
|
11
|
+
* value. Physical addresses are the same. Redaction here REDUCES exposure; it does not
|
|
12
|
+
* eliminate it, and the sign-off should be read on those terms.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Ordered: earlier rules win. Every rule matches a SHAPE, never a word list — nothing here
|
|
16
|
+
* needs updating when a product renames a field or a new customer appears.
|
|
17
|
+
*/
|
|
18
|
+
const RULES = [
|
|
19
|
+
// Credentials inside a URL, before the email rule so `user:pass@host` is not
|
|
20
|
+
// half-matched as an address.
|
|
21
|
+
{
|
|
22
|
+
name: "url_credentials",
|
|
23
|
+
pattern: /\b(https?:\/\/)[^\s/@]+:[^\s/@]+@/gi,
|
|
24
|
+
replacement: "$1[redacted]@",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "email",
|
|
28
|
+
pattern: /\b[\w.%+-]+@[\w.-]+\.[a-z]{2,}\b/gi,
|
|
29
|
+
replacement: "[email]",
|
|
30
|
+
},
|
|
31
|
+
// JWT: three base64url segments.
|
|
32
|
+
{
|
|
33
|
+
name: "jwt",
|
|
34
|
+
pattern: /\beyJ[\w-]{8,}\.[\w-]{8,}\.[\w-]{8,}\b/g,
|
|
35
|
+
replacement: "[token]",
|
|
36
|
+
},
|
|
37
|
+
// A long opaque run — access keys, API keys, session ids. Requires BOTH a digit and a
|
|
38
|
+
// letter, so ordinary long words ("internationalisation") are never matched. This is
|
|
39
|
+
// the rule that catches a pasted credential regardless of how it was introduced, which
|
|
40
|
+
// is why no vocabulary of labels ("api_key", "bearer", ...) is hardcoded: guessing the
|
|
41
|
+
// wording a product uses is exactly the assumption that fails.
|
|
42
|
+
{
|
|
43
|
+
name: "opaque_token",
|
|
44
|
+
pattern: /\b(?=[A-Za-z0-9_-]*\d)(?=[A-Za-z0-9_-]*[A-Za-z])[A-Za-z0-9_-]{20,}\b/g,
|
|
45
|
+
replacement: "[token]",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "ipv4",
|
|
49
|
+
pattern: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g,
|
|
50
|
+
replacement: "[ip]",
|
|
51
|
+
},
|
|
52
|
+
// Phone: optional country prefix, then digit groups with separators. Requires a
|
|
53
|
+
// separator or a `+`, so a bare digit run falls to long_number instead.
|
|
54
|
+
{
|
|
55
|
+
name: "phone",
|
|
56
|
+
pattern: /(?:\+\d{1,3}[\s.-]?)?(?:\(\d{2,4}\)[\s.-]?)?\d{3,5}[\s.-]\d{3,5}(?:[\s.-]\d{2,5})?\b/g,
|
|
57
|
+
replacement: "[phone]",
|
|
58
|
+
},
|
|
59
|
+
// Long bare digit runs: account ids, card numbers, unseparated phone numbers.
|
|
60
|
+
{ name: "long_number", pattern: /\b\d{9,}\b/g, replacement: "[number]" },
|
|
61
|
+
];
|
|
62
|
+
/**
|
|
63
|
+
* The ceiling on any redacted field.
|
|
64
|
+
*
|
|
65
|
+
* The rules above catch PII by shape, and the header is explicit that a person's NAME has
|
|
66
|
+
* none — so the longer the recorded string, the more unshaped personal detail rides along
|
|
67
|
+
* with it. A cap does not make the field safe; it bounds how much escapes when redaction
|
|
68
|
+
* misses, which it will. 512 leaves an ordinary query or change summary intact.
|
|
69
|
+
*
|
|
70
|
+
* This existed, was removed, and the doc comment kept claiming it for two commits. Both
|
|
71
|
+
* fields it guards are free text an agent writes from what the user said.
|
|
72
|
+
*/
|
|
73
|
+
export const MAX_LENGTH = 512;
|
|
74
|
+
/**
|
|
75
|
+
* Returns the text with shape-identifiable PII replaced, capped at `MAX_LENGTH`.
|
|
76
|
+
*
|
|
77
|
+
* FAILS CLOSED: any throw returns undefined, so the caller records no field rather than
|
|
78
|
+
* the raw value. Non-string and empty input yields undefined for the same reason.
|
|
79
|
+
*/
|
|
80
|
+
export function redact(text) {
|
|
81
|
+
try {
|
|
82
|
+
if (typeof text !== "string")
|
|
83
|
+
return undefined;
|
|
84
|
+
let out = text.replace(/\s+/g, " ").trim();
|
|
85
|
+
if (!out)
|
|
86
|
+
return undefined;
|
|
87
|
+
for (const rule of RULES)
|
|
88
|
+
out = out.replace(rule.pattern, rule.replacement);
|
|
89
|
+
// Truncate AFTER redacting, so a value that would have been replaced cannot be split
|
|
90
|
+
// across the boundary and leave its first half in the clear.
|
|
91
|
+
return out.length > MAX_LENGTH ? `${out.slice(0, MAX_LENGTH)}…` : out;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** The rule names, for documenting what is covered. */
|
|
98
|
+
export const REDACTED_TYPES = RULES.map((r) => r.name);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The tool surface: four discovery tools plus ONE invoke tool.
|
|
3
|
+
*
|
|
4
|
+
* Discovery is deliberately two steps. `searchCapability` returns a shortlist — enough to
|
|
5
|
+
* CHOOSE — and `describeCapability` returns the contract for the one chosen. Parameters and
|
|
6
|
+
* response shapes are 86% of a full record and are needed once, not eight times: measured
|
|
7
|
+
* over eight queries, ~8.6k tokens a search becomes ~1.1k, and even describing every result
|
|
8
|
+
* still costs less than the single fat call did.
|
|
9
|
+
*
|
|
10
|
+
* ONE invoke tool means one set of MCP annotations, so they describe the whole surface
|
|
11
|
+
* honestly: it can write (not read-only) and it can never delete, because destructive
|
|
12
|
+
* endpoints are refused before binding. Write consent therefore rests on `user_permission`
|
|
13
|
+
* enforced HERE rather than on a client-side hint — which is the one thing a separate
|
|
14
|
+
* read/write tool pair was buying.
|
|
15
|
+
*/
|
|
16
|
+
import { McpServer, RegisteredTool } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
17
|
+
import { BrowserStackConfig } from "../../lib/types.js";
|
|
18
|
+
import { Credentials, Transport } from "./egress.js";
|
|
19
|
+
import { CapabilityRegistry } from "./index-loader.js";
|
|
20
|
+
export declare const PERMISSION_VALUES: readonly ["not_asked", "granted", "denied"];
|
|
21
|
+
/**
|
|
22
|
+
* The search-side twin of `user_permission`: did a human choose the product, or did you?
|
|
23
|
+
*
|
|
24
|
+
* No `denied`. A refused write is a thing the caller must not do; a refused product choice
|
|
25
|
+
* is not a state — the user either named one, in which case you search it, or has not been
|
|
26
|
+
* asked, in which case there is nothing to search yet.
|
|
27
|
+
*/
|
|
28
|
+
export declare const PRODUCT_CHOICE_VALUES: readonly ["not_asked", "user_confirmed"];
|
|
29
|
+
export interface RegistryDeps {
|
|
30
|
+
registry: CapabilityRegistry;
|
|
31
|
+
/**
|
|
32
|
+
* Per-product base URL. Never baked into the artifact — it is environment AND account
|
|
33
|
+
* specific: tm is region-sharded, so this is resolved per call, not once at startup.
|
|
34
|
+
*/
|
|
35
|
+
baseUrlFor: (product: string) => Promise<string>;
|
|
36
|
+
credentialsFor: () => Credentials;
|
|
37
|
+
transport?: Transport;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The tool-adder the server factory calls.
|
|
41
|
+
*
|
|
42
|
+
* Registers NOTHING when the artifact is absent or unreadable, rather than throwing: a
|
|
43
|
+
* missing index is a packaging problem, and taking the whole MCP server down with it would
|
|
44
|
+
* remove every other product's tools too. The reason is logged so it is not silent.
|
|
45
|
+
*/
|
|
46
|
+
export declare function addCapabilityRegistryToolsFromConfig(server: McpServer, config: BrowserStackConfig): Record<string, RegisteredTool>;
|
|
47
|
+
export declare function addCapabilityRegistryTools(server: McpServer, deps: RegistryDeps, config?: BrowserStackConfig): Record<string, RegisteredTool>;
|
|
48
|
+
export default addCapabilityRegistryToolsFromConfig;
|