@browserstack/mcp-server 1.4.0-beta.3 → 1.5.0-beta.10
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/capability/loadtesting.capability-index.json +1792 -0
- package/capability/tm.capability-index.json +20094 -0
- package/dist/config.d.ts +1 -4
- package/dist/config.js +2 -23
- package/dist/index.js +2 -5
- package/dist/server-factory.js +5 -5
- package/dist/tools/accessibility.js +2 -5
- package/dist/tools/capability-registry/bind.d.ts +29 -0
- package/dist/tools/capability-registry/bind.js +134 -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 +128 -0
- package/dist/tools/capability-registry/index-loader.d.ts +133 -0
- package/dist/tools/capability-registry/index-loader.js +369 -0
- package/dist/tools/capability-registry/register.d.ts +34 -0
- package/dist/tools/capability-registry/register.js +396 -0
- package/dist/tools/capability-registry/resolve.d.ts +38 -0
- package/dist/tools/capability-registry/resolve.js +45 -0
- package/dist/tools/capability-registry/search.d.ts +97 -0
- package/dist/tools/capability-registry/search.js +527 -0
- package/dist/tools/capability-registry/types.d.ts +232 -0
- package/dist/tools/capability-registry/types.js +33 -0
- package/dist/tools/get-failure-logs.js +1 -3
- package/dist/tools/rca-agent.js +2 -5
- package/dist/tools/selfheal.js +2 -5
- package/dist/tools/testmanagement.js +15 -37
- package/package.json +3 -2
- package/dist/tools/ask-browserstack/central-oauth.d.ts +0 -120
- package/dist/tools/ask-browserstack/central-oauth.js +0 -277
- package/dist/tools/ask-browserstack/config.d.ts +0 -102
- package/dist/tools/ask-browserstack/config.js +0 -140
- package/dist/tools/ask-browserstack/egress.d.ts +0 -34
- package/dist/tools/ask-browserstack/egress.js +0 -31
- package/dist/tools/ask-browserstack/register.d.ts +0 -61
- package/dist/tools/ask-browserstack/register.js +0 -416
- package/dist/tools/ask-browserstack/relay.d.ts +0 -201
- package/dist/tools/ask-browserstack/relay.js +0 -577
- package/dist/tools/ask-browserstack/stream.d.ts +0 -116
- package/dist/tools/ask-browserstack/stream.js +0 -236
- package/dist/tools/ask-browserstack/types.d.ts +0 -196
- package/dist/tools/ask-browserstack/types.js +0 -14
- package/dist/tools/tool-handoff.d.ts +0 -62
- package/dist/tools/tool-handoff.js +0 -75
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of the index artifact, which is the contract with the export pipeline.
|
|
3
|
+
*
|
|
4
|
+
* The artifact is generated by the capability-registry build and transformed into its
|
|
5
|
+
* released envelope by the export pipeline's merge step. It contains ONLY data that has
|
|
6
|
+
* already passed that side's outbound boundary. Nothing here is parsed from an OpenAPI spec
|
|
7
|
+
* at runtime: if this half parsed specs it would become the boundary, and every gate (route
|
|
8
|
+
* lint, vocabulary rule, intent lint, discovery denylist) would have to be reimplemented and
|
|
9
|
+
* re-tested here. Reading a pre-projected index means the internal data is not in the
|
|
10
|
+
* package at all.
|
|
11
|
+
*
|
|
12
|
+
* ONE FILE PER PRODUCT. The released artifact is `capability/<product>.capability-index.json`,
|
|
13
|
+
* with the
|
|
14
|
+
* product object at the top level under its own name. The plural `products` wrapper belonged
|
|
15
|
+
* to the pre-release shape and implied multi-product files that never existed.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The envelope's format marker, for FUTURE shape changes.
|
|
19
|
+
*
|
|
20
|
+
* BOTH the released and the pre-release shapes claim 1, so this number cannot distinguish
|
|
21
|
+
* them — `readIndexFile` shape-detects on the `products` wrapper instead. The check is still
|
|
22
|
+
* worth keeping: it is what refuses a shape the generator has announced but this build does
|
|
23
|
+
* not read.
|
|
24
|
+
*/
|
|
25
|
+
export declare const SUPPORTED_SCHEMA_VERSION = 1;
|
|
26
|
+
/** Envelope keys, i.e. everything at the top level that is NOT the product object. */
|
|
27
|
+
export declare const ENVELOPE_KEYS: readonly ["schema_version", "version", "build_id", "harness_commit", "products"];
|
|
28
|
+
export type Mode = "read" | "write" | "destructive";
|
|
29
|
+
/** One parameter, under the name the OpenAPI spec itself gives it. */
|
|
30
|
+
export interface WireParam {
|
|
31
|
+
name: string;
|
|
32
|
+
type: string;
|
|
33
|
+
required?: true;
|
|
34
|
+
values?: unknown[];
|
|
35
|
+
example?: unknown;
|
|
36
|
+
description?: string;
|
|
37
|
+
/** Field names/types one level inside an array item or nested object. */
|
|
38
|
+
fields?: {
|
|
39
|
+
name: string;
|
|
40
|
+
type: string;
|
|
41
|
+
required?: true;
|
|
42
|
+
}[];
|
|
43
|
+
/**
|
|
44
|
+
* Where a body field sits in the JSON, when that differs from its name. Published
|
|
45
|
+
* because the nesting is not guessable and getting it wrong fails silently — tm's folder
|
|
46
|
+
* create really wants `{folder: {name}}` while the spec's flat `{name}` is what a reader
|
|
47
|
+
* would assume.
|
|
48
|
+
*/
|
|
49
|
+
json_path?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A reference into a product-level lookup table.
|
|
53
|
+
*
|
|
54
|
+
* Named components are stored ONCE, already dereferenced and allOf-flattened at build time,
|
|
55
|
+
* so resolution is a single dict lookup rather than a recursive $ref walk at runtime.
|
|
56
|
+
*/
|
|
57
|
+
export interface ComponentRef {
|
|
58
|
+
$response?: string;
|
|
59
|
+
$schema?: string;
|
|
60
|
+
}
|
|
61
|
+
/** A JSON Schema fragment, or a reference to a named one. */
|
|
62
|
+
export type SchemaNode = ComponentRef & Record<string, unknown>;
|
|
63
|
+
/** One declared response, or a reference to a named one. */
|
|
64
|
+
export interface ResponseDoc extends ComponentRef {
|
|
65
|
+
description?: string;
|
|
66
|
+
schema?: SchemaNode;
|
|
67
|
+
}
|
|
68
|
+
/** A capability, keyed by the endpoint it exposes. There is deliberately no name. */
|
|
69
|
+
export interface Capability {
|
|
70
|
+
/**
|
|
71
|
+
* The caller-facing handle — the snake-cased `operationId`, unique within its product.
|
|
72
|
+
*
|
|
73
|
+
* This is what `invokeCapability` takes, and what `entities[*].capabilities[]` lists. It
|
|
74
|
+
* is stable where a route is not: `/edit` becoming `/edit-v2` must not break a caller
|
|
75
|
+
* holding a handle.
|
|
76
|
+
*
|
|
77
|
+
* OPTIONAL because not every product publishes it yet — loadtesting's index carries none.
|
|
78
|
+
* For those, the endpoint remains the handle, so nothing may assume this is present.
|
|
79
|
+
*/
|
|
80
|
+
name?: string;
|
|
81
|
+
method: string;
|
|
82
|
+
path: string;
|
|
83
|
+
mode: Mode;
|
|
84
|
+
entity: string;
|
|
85
|
+
path_params?: WireParam[];
|
|
86
|
+
query?: WireParam[];
|
|
87
|
+
body?: WireParam[];
|
|
88
|
+
intent?: string;
|
|
89
|
+
/**
|
|
90
|
+
* How to call the endpoint correctly.
|
|
91
|
+
*
|
|
92
|
+
* ABSENT from the released artifact — the export dropped it, and the shape-change note
|
|
93
|
+
* does not say whether that was intended. Kept optional and still scored by search so the
|
|
94
|
+
* server works either way; nothing may depend on it being present.
|
|
95
|
+
*/
|
|
96
|
+
guidance?: string[];
|
|
97
|
+
/** Allowlisted row fields. Absent when `shape` is "discovered". */
|
|
98
|
+
returns?: string[];
|
|
99
|
+
/** "discovered" when the product declares no response schema for this operation. */
|
|
100
|
+
shape?: "discovered";
|
|
101
|
+
requires?: string[];
|
|
102
|
+
paginated?: boolean;
|
|
103
|
+
/** The largest page the operation declares. */
|
|
104
|
+
max_page_size?: number;
|
|
105
|
+
/**
|
|
106
|
+
* Declared responses by status code, values possibly `{$response: "Name"}` references.
|
|
107
|
+
*
|
|
108
|
+
* ADDITIVE and not yet emitted: no capability in the current export carries it. Absence
|
|
109
|
+
* means "no response schema available", never an error. Resolve with `resolveComponent`.
|
|
110
|
+
*/
|
|
111
|
+
responses?: Record<string, ResponseDoc>;
|
|
112
|
+
}
|
|
113
|
+
export interface EntityDoc {
|
|
114
|
+
title?: string;
|
|
115
|
+
aliases?: string[];
|
|
116
|
+
id_convention?: string;
|
|
117
|
+
parents?: string[];
|
|
118
|
+
relations?: {
|
|
119
|
+
entity?: string;
|
|
120
|
+
via?: string;
|
|
121
|
+
}[];
|
|
122
|
+
/**
|
|
123
|
+
* What a caller gets wrong about this ENTITY, as opposed to one operation.
|
|
124
|
+
*
|
|
125
|
+
* The entity-wide half of guidance: "a run's `id` is the display string, take `uuid`"
|
|
126
|
+
* holds for every capability returning a run, so it is stated once here rather than
|
|
127
|
+
* copied onto each capability's `guidance` — which would repeat across ~20 records and,
|
|
128
|
+
* since guidance is a search haystack, dilute ranking with boilerplate.
|
|
129
|
+
*
|
|
130
|
+
* Filtered at build time and often absent: of tm's 155 authored facts, 73 are publishable
|
|
131
|
+
* as written and the rest name routes, HTTP verbs or wire-shaped parameters. Absent means
|
|
132
|
+
* "none passed", never an error.
|
|
133
|
+
*/
|
|
134
|
+
key_facts?: string[];
|
|
135
|
+
[key: string]: unknown;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Paging controls, keyed "METHOD /path".
|
|
139
|
+
*
|
|
140
|
+
* NOT USED BY THIS SERVER: it performs one request and returns the response, so paging
|
|
141
|
+
* belongs to the caller and the page parameters are published with the endpoint's other
|
|
142
|
+
* query parameters. The field is still emitted by the build, so it stays described here
|
|
143
|
+
* rather than silently ignored — a consumer that DOES page can use it.
|
|
144
|
+
*/
|
|
145
|
+
export interface PagingRule {
|
|
146
|
+
page?: string;
|
|
147
|
+
size?: string;
|
|
148
|
+
/** The largest page the operation declares. Absent when the spec states no maximum. */
|
|
149
|
+
max?: number;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* How a product wants the caller's credentials presented.
|
|
153
|
+
*
|
|
154
|
+
* The OpenAPI `securityScheme` vocabulary, verbatim — `type` / `in` / `name` / `scheme` —
|
|
155
|
+
* because the spec already describes this and inventing a parallel taxonomy would mean
|
|
156
|
+
* translating between two of them forever. ONE scheme per product, not OpenAPI's list of
|
|
157
|
+
* alternatives: this server holds a username and an access key and nothing else, so
|
|
158
|
+
* "the first alternative we can satisfy" only ever had one answer.
|
|
159
|
+
*
|
|
160
|
+
* `template` is what makes it a contract rather than a guess — a product taking
|
|
161
|
+
* `{username}_{access_key}` is expressible instead of being a special case in the server.
|
|
162
|
+
* It names placeholders, never values: the credential itself stays in config, and the
|
|
163
|
+
* harness's `{ env: … }` value sources must NOT cross the boundary into the artifact.
|
|
164
|
+
*/
|
|
165
|
+
export interface AuthScheme {
|
|
166
|
+
/** "apiKey" puts the rendered template in a header; "http" with scheme "basic" encodes it. */
|
|
167
|
+
type: "apiKey" | "http";
|
|
168
|
+
/** apiKey only. Header is the sole supported location — see `egress.authHeaders`. */
|
|
169
|
+
in?: "header" | "cookie" | "query";
|
|
170
|
+
/** apiKey only: the header name, e.g. "Api-Token". */
|
|
171
|
+
name?: string;
|
|
172
|
+
/** http only. */
|
|
173
|
+
scheme?: string;
|
|
174
|
+
/** Defaults to `{username}:{access_key}`. */
|
|
175
|
+
template?: string;
|
|
176
|
+
}
|
|
177
|
+
export interface ProductIndex {
|
|
178
|
+
summary: string;
|
|
179
|
+
/**
|
|
180
|
+
* How to authenticate to this product. Absent means the historical default: the caller's
|
|
181
|
+
* credentials as `Api-Token: {username}:{access_key}`, which is what every shipped index
|
|
182
|
+
* relies on today.
|
|
183
|
+
*/
|
|
184
|
+
auth?: AuthScheme;
|
|
185
|
+
/**
|
|
186
|
+
* The single host this product is served from, when it has one.
|
|
187
|
+
*
|
|
188
|
+
* A default, not the last word: config overrides it, and for a region-sharded product
|
|
189
|
+
* (one declaring `base_urls`) account discovery outranks it and this is the fallback.
|
|
190
|
+
*/
|
|
191
|
+
base_url?: string;
|
|
192
|
+
/**
|
|
193
|
+
* Candidate regional hosts, in probe order, for a product whose host depends on the
|
|
194
|
+
* ACCOUNT rather than the deployment.
|
|
195
|
+
*
|
|
196
|
+
* Declaring these makes the product region-sharded: the server asks each in turn with the
|
|
197
|
+
* caller's credentials and keeps the one that answers. A single fixed `base_url` cannot
|
|
198
|
+
* express this — it would send every account outside the default region to the wrong host,
|
|
199
|
+
* a failure invisible to anyone testing from inside that region.
|
|
200
|
+
*/
|
|
201
|
+
base_urls?: string[];
|
|
202
|
+
/**
|
|
203
|
+
* The endpoint to probe the candidates with. Derived from the capabilities when absent
|
|
204
|
+
* (see `discovery.probePath`); declare it when the derived choice would be wrong.
|
|
205
|
+
*/
|
|
206
|
+
probe_path?: string;
|
|
207
|
+
capabilities: Capability[];
|
|
208
|
+
entities: Record<string, EntityDoc>;
|
|
209
|
+
paging?: Record<string, PagingRule>;
|
|
210
|
+
/** Named responses, referenced as `{$response: "Name"}`. Additive; not yet emitted. */
|
|
211
|
+
responses?: Record<string, ResponseDoc>;
|
|
212
|
+
/** Named schemas, referenced as `{$schema: "Name"}`. Additive; not yet emitted. */
|
|
213
|
+
schemas?: Record<string, SchemaNode>;
|
|
214
|
+
}
|
|
215
|
+
/** Where one product's data came from. Logging and cache-busting only. */
|
|
216
|
+
export interface Provenance {
|
|
217
|
+
/** `<commit>_<UTC timestamp>` in the released shape. */
|
|
218
|
+
build_id: string;
|
|
219
|
+
/** Dotted content-generation counter, e.g. "1.2". Absent in the pre-release shape. */
|
|
220
|
+
version?: string;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The merged, in-memory model: every loaded file's product under its own name.
|
|
224
|
+
*
|
|
225
|
+
* This is the server's own structure, not the file format — one file carries one product,
|
|
226
|
+
* and the registry holds all of them so a caller can search across products.
|
|
227
|
+
*/
|
|
228
|
+
export interface RegistryIndex {
|
|
229
|
+
schema_version: number;
|
|
230
|
+
build_id: string;
|
|
231
|
+
products: Record<string, ProductIndex>;
|
|
232
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of the index artifact, which is the contract with the export pipeline.
|
|
3
|
+
*
|
|
4
|
+
* The artifact is generated by the capability-registry build and transformed into its
|
|
5
|
+
* released envelope by the export pipeline's merge step. It contains ONLY data that has
|
|
6
|
+
* already passed that side's outbound boundary. Nothing here is parsed from an OpenAPI spec
|
|
7
|
+
* at runtime: if this half parsed specs it would become the boundary, and every gate (route
|
|
8
|
+
* lint, vocabulary rule, intent lint, discovery denylist) would have to be reimplemented and
|
|
9
|
+
* re-tested here. Reading a pre-projected index means the internal data is not in the
|
|
10
|
+
* package at all.
|
|
11
|
+
*
|
|
12
|
+
* ONE FILE PER PRODUCT. The released artifact is `capability/<product>.capability-index.json`,
|
|
13
|
+
* with the
|
|
14
|
+
* product object at the top level under its own name. The plural `products` wrapper belonged
|
|
15
|
+
* to the pre-release shape and implied multi-product files that never existed.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* The envelope's format marker, for FUTURE shape changes.
|
|
19
|
+
*
|
|
20
|
+
* BOTH the released and the pre-release shapes claim 1, so this number cannot distinguish
|
|
21
|
+
* them — `readIndexFile` shape-detects on the `products` wrapper instead. The check is still
|
|
22
|
+
* worth keeping: it is what refuses a shape the generator has announced but this build does
|
|
23
|
+
* not read.
|
|
24
|
+
*/
|
|
25
|
+
export const SUPPORTED_SCHEMA_VERSION = 1;
|
|
26
|
+
/** Envelope keys, i.e. everything at the top level that is NOT the product object. */
|
|
27
|
+
export const ENVELOPE_KEYS = [
|
|
28
|
+
"schema_version",
|
|
29
|
+
"version",
|
|
30
|
+
"build_id",
|
|
31
|
+
"harness_commit",
|
|
32
|
+
"products",
|
|
33
|
+
];
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
3
|
-
import { NEEDS_SESSION_ID } from "./tool-handoff.js";
|
|
4
3
|
import { retrieveNetworkFailures, retrieveSessionFailures, retrieveConsoleFailures, } from "./failurelogs-utils/automate.js";
|
|
5
4
|
import { retrieveDeviceLogs, retrieveAppiumLogs, retrieveCrashLogs, } from "./failurelogs-utils/app-automate.js";
|
|
6
5
|
import { AppAutomateLogType, AutomateLogType, SessionType, } from "../lib/constants.js";
|
|
@@ -99,8 +98,7 @@ export async function getFailureLogs(args, config) {
|
|
|
99
98
|
// Register tool with the MCP server
|
|
100
99
|
export default function registerGetFailureLogs(server, config) {
|
|
101
100
|
const tools = {};
|
|
102
|
-
tools.getFailureLogs = server.tool("getFailureLogs", "Fetch various types of logs from a BrowserStack session. Supports both automate and app-automate sessions."
|
|
103
|
-
NEEDS_SESSION_ID, {
|
|
101
|
+
tools.getFailureLogs = server.tool("getFailureLogs", "Fetch various types of logs from a BrowserStack session. Supports both automate and app-automate sessions.", {
|
|
104
102
|
sessionType: z
|
|
105
103
|
.enum([SessionType.Automate, SessionType.AppAutomate])
|
|
106
104
|
.describe("Type of BrowserStack session. Must be explicitly provided by the user."),
|
package/dist/tools/rca-agent.js
CHANGED
|
@@ -7,7 +7,6 @@ import { getRCAData } from "./rca-agent-utils/rca-data.js";
|
|
|
7
7
|
import { formatRCAData } from "./rca-agent-utils/format-rca.js";
|
|
8
8
|
import { handleMCPError } from "../lib/utils.js";
|
|
9
9
|
import { trackMCP } from "../index.js";
|
|
10
|
-
import { NEEDS_BUILD_ID, NEEDS_TEST_IDS } from "./tool-handoff.js";
|
|
11
10
|
import { FETCH_RCA_PARAMS, GET_BUILD_ID_PARAMS, LIST_TEST_IDS_PARAMS, } from "./rca-agent-utils/constants.js";
|
|
12
11
|
// Tool function to fetch build ID
|
|
13
12
|
export async function getBuildIdTool(args, config) {
|
|
@@ -131,8 +130,7 @@ export async function listTestIdsTool(args, config) {
|
|
|
131
130
|
}
|
|
132
131
|
export default function addRCATools(server, config) {
|
|
133
132
|
const tools = {};
|
|
134
|
-
tools.fetchRCA = server.tool("fetchRCA", "Fetch AI Root Cause Analysis for the current user's failed BrowserStack Automate/App-Automate tests. Suggests fixes only; never auto-apply, require explicit user approval."
|
|
135
|
-
NEEDS_TEST_IDS, FETCH_RCA_PARAMS, {
|
|
133
|
+
tools.fetchRCA = server.tool("fetchRCA", "Fetch AI Root Cause Analysis for the current user's failed BrowserStack Automate/App-Automate tests. Suggests fixes only; never auto-apply, require explicit user approval.", FETCH_RCA_PARAMS, {
|
|
136
134
|
title: "Fetch Root Cause Analysis",
|
|
137
135
|
readOnlyHint: true,
|
|
138
136
|
openWorldHint: false,
|
|
@@ -177,8 +175,7 @@ export default function addRCATools(server, config) {
|
|
|
177
175
|
return handleMCPError("listBuildId", server, config, error);
|
|
178
176
|
}
|
|
179
177
|
});
|
|
180
|
-
tools.listTestIds = server.tool("listTestIds", "List all tests of a BrowserStack build (each with its status); optional status filter."
|
|
181
|
-
NEEDS_BUILD_ID, LIST_TEST_IDS_PARAMS, {
|
|
178
|
+
tools.listTestIds = server.tool("listTestIds", "List all tests of a BrowserStack build (each with its status); optional status filter.", LIST_TEST_IDS_PARAMS, {
|
|
182
179
|
title: "List Test IDs",
|
|
183
180
|
readOnlyHint: true,
|
|
184
181
|
openWorldHint: false,
|
package/dist/tools/selfheal.js
CHANGED
|
@@ -3,7 +3,6 @@ import { getSelfHealSelectors, fetchSelfHealingReportByBuild, } from "./selfheal
|
|
|
3
3
|
import { fetchTestCodeForSessions, formatTestCodeAsContext, describeTestCodeFetchIssues, } from "./selfheal-utils/fetch-test-code.js";
|
|
4
4
|
import logger from "../logger.js";
|
|
5
5
|
import { trackMCP } from "../lib/instrumentation.js";
|
|
6
|
-
import { NEEDS_SESSION_ID } from "./tool-handoff.js";
|
|
7
6
|
// Local helper: returns the server-configured BrowserStack credentials, or
|
|
8
7
|
// null when either is missing. Lives here because the self-heal tools need
|
|
9
8
|
// to degrade gracefully — `getBrowserStackAuth` throws, which is wrong for
|
|
@@ -431,8 +430,7 @@ export default function addSelfHealTools(server, config) {
|
|
|
431
430
|
"the run. Provide exactly one of `sessionId` (single Automate / " +
|
|
432
431
|
"App-Automate session) or `buildUuid` (full self-healing report for a " +
|
|
433
432
|
"build). Pass the returned locator pairs to `prepareSelfHealingPlan` " +
|
|
434
|
-
"to plan edits."
|
|
435
|
-
NEEDS_SESSION_ID, {
|
|
433
|
+
"to plan edits.", {
|
|
436
434
|
sessionId: z
|
|
437
435
|
.string()
|
|
438
436
|
.describe("Session ID. Mutually exclusive with buildUuid.")
|
|
@@ -501,8 +499,7 @@ export default function addSelfHealTools(server, config) {
|
|
|
501
499
|
"[...]}`, the raw report `{healing_logs: [...]}` (with " +
|
|
502
500
|
"`healed_selectors` aliasing `locators`), and snake_case keys " +
|
|
503
501
|
"(`session_id`, `original_locator`, `healed_locator`, " +
|
|
504
|
-
"`healing_thought`)."
|
|
505
|
-
NEEDS_SESSION_ID, {
|
|
502
|
+
"`healing_thought`).", {
|
|
506
503
|
sessions: sessionsFieldSchema.describe("Sessions to plan edits for. See tool description for accepted shapes."),
|
|
507
504
|
}, {
|
|
508
505
|
title: "Prepare Self-Healing Plan",
|
|
@@ -19,7 +19,6 @@ import { getTestPlan, GetTestPlanSchema, } from "./testmanagement-utils/get-test
|
|
|
19
19
|
import { listSubTestPlans, ListSubTestPlansSchema, } from "./testmanagement-utils/list-sub-testplans.js";
|
|
20
20
|
import { getSubTestPlan, GetSubTestPlanSchema, } from "./testmanagement-utils/get-sub-testplan.js";
|
|
21
21
|
import { elicitCredentialsIfSupported } from "../lib/elicit-credentials.js";
|
|
22
|
-
import { NEEDS_PROJECT_ID, NEEDS_TEST_PLAN_ID, PLAN_WRITES_VIA_AGENT, PROJECT_ID_ONLY_FOR_FOLDER, } from "./tool-handoff.js";
|
|
23
22
|
//TODO: Moving the traceMCP and catch block to the parent(server) function
|
|
24
23
|
/**
|
|
25
24
|
* Wrapper to call createProjectOrFolder util.
|
|
@@ -433,40 +432,35 @@ export async function getSubTestPlanTool(args, config, server) {
|
|
|
433
432
|
*/
|
|
434
433
|
export default function addTestManagementTools(server, config) {
|
|
435
434
|
const tools = {};
|
|
436
|
-
tools.createProjectOrFolder = server.tool("createProjectOrFolder", "Create a project and/or folder in BrowserStack Test Management."
|
|
437
|
-
PROJECT_ID_ONLY_FOR_FOLDER, CreateProjFoldSchema.shape, {
|
|
435
|
+
tools.createProjectOrFolder = server.tool("createProjectOrFolder", "Create a project and/or folder in BrowserStack Test Management.", CreateProjFoldSchema.shape, {
|
|
438
436
|
title: "Create Project or Folder",
|
|
439
437
|
readOnlyHint: false,
|
|
440
438
|
openWorldHint: false,
|
|
441
439
|
destructiveHint: false,
|
|
442
440
|
idempotentHint: false,
|
|
443
441
|
}, (args) => createProjectOrFolderTool(args, config, server));
|
|
444
|
-
tools.createTestCase = server.tool("createTestCase", "Use this tool to create a test case in BrowserStack Test Management."
|
|
445
|
-
NEEDS_PROJECT_ID, CreateTestCaseSchema.shape, {
|
|
442
|
+
tools.createTestCase = server.tool("createTestCase", "Use this tool to create a test case in BrowserStack Test Management.", CreateTestCaseSchema.shape, {
|
|
446
443
|
title: "Create Test Case",
|
|
447
444
|
readOnlyHint: false,
|
|
448
445
|
openWorldHint: false,
|
|
449
446
|
destructiveHint: false,
|
|
450
447
|
idempotentHint: false,
|
|
451
448
|
}, (args) => createTestCaseTool(args, config, server));
|
|
452
|
-
tools.updateTestCase = server.tool("updateTestCase", "Update an existing test case in BrowserStack Test Management. Any subset of the following fields may be changed: name, description, preconditions, test_case_steps, owner, priority, case_type, automation_status, status, tags, issues, custom_fields. Only the supplied fields are modified."
|
|
453
|
-
NEEDS_PROJECT_ID, UpdateTestCaseSchema.shape, {
|
|
449
|
+
tools.updateTestCase = server.tool("updateTestCase", "Update an existing test case in BrowserStack Test Management. Any subset of the following fields may be changed: name, description, preconditions, test_case_steps, owner, priority, case_type, automation_status, status, tags, issues, custom_fields. Only the supplied fields are modified.", UpdateTestCaseSchema.shape, {
|
|
454
450
|
title: "Update Test Case",
|
|
455
451
|
readOnlyHint: false,
|
|
456
452
|
openWorldHint: false,
|
|
457
453
|
destructiveHint: true,
|
|
458
454
|
idempotentHint: true,
|
|
459
455
|
}, (args) => updateTestCaseTool(args, config, server));
|
|
460
|
-
tools.listTestCases = server.tool("listTestCases", "List test cases in a project, optionally scoped to a specific folder. Omit folder_id to list all test cases in the project; provide folder_id (discoverable via listFolders) to list only that folder's cases. Supports filters: case_type, priority, pagination."
|
|
461
|
-
NEEDS_PROJECT_ID, ListTestCasesSchema.shape, {
|
|
456
|
+
tools.listTestCases = server.tool("listTestCases", "List test cases in a project, optionally scoped to a specific folder. Omit folder_id to list all test cases in the project; provide folder_id (discoverable via listFolders) to list only that folder's cases. Supports filters: case_type, priority, pagination.", ListTestCasesSchema.shape, {
|
|
462
457
|
title: "List Test Cases",
|
|
463
458
|
readOnlyHint: true,
|
|
464
459
|
openWorldHint: false,
|
|
465
460
|
destructiveHint: false,
|
|
466
461
|
idempotentHint: true,
|
|
467
462
|
}, (args) => listTestCasesTool(args, config, server));
|
|
468
|
-
tools.listFolders = server.tool("listFolders", "List folders in a BrowserStack Test Management project, returning each folder's id and name (plus case counts and sub-folder counts). Pass parent_id to list sub-folders under a specific folder instead of top-level folders."
|
|
469
|
-
NEEDS_PROJECT_ID, ListFoldersSchema.shape, {
|
|
463
|
+
tools.listFolders = server.tool("listFolders", "List folders in a BrowserStack Test Management project, returning each folder's id and name (plus case counts and sub-folder counts). Pass parent_id to list sub-folders under a specific folder instead of top-level folders.", ListFoldersSchema.shape, {
|
|
470
464
|
title: "List Folders",
|
|
471
465
|
readOnlyHint: true,
|
|
472
466
|
openWorldHint: false,
|
|
@@ -480,39 +474,35 @@ export default function addTestManagementTools(server, config) {
|
|
|
480
474
|
destructiveHint: false,
|
|
481
475
|
idempotentHint: true,
|
|
482
476
|
}, (args) => listTemplatesTool(args, config, server));
|
|
483
|
-
tools.createTestRun = server.tool("createTestRun", "Create a test run in BrowserStack Test Management."
|
|
477
|
+
tools.createTestRun = server.tool("createTestRun", "Create a test run in BrowserStack Test Management.", CreateTestRunSchema.shape, {
|
|
484
478
|
title: "Create Test Run",
|
|
485
479
|
readOnlyHint: false,
|
|
486
480
|
openWorldHint: false,
|
|
487
481
|
destructiveHint: false,
|
|
488
482
|
idempotentHint: false,
|
|
489
483
|
}, (args) => createTestRunTool(args, config, server));
|
|
490
|
-
tools.listTestRuns = server.tool("listTestRuns", "List test runs in a project with optional filters (date ranges, assignee, state, etc.)"
|
|
491
|
-
NEEDS_PROJECT_ID, ListTestRunsSchema.shape, {
|
|
484
|
+
tools.listTestRuns = server.tool("listTestRuns", "List test runs in a project with optional filters (date ranges, assignee, state, etc.)", ListTestRunsSchema.shape, {
|
|
492
485
|
title: "List Test Runs",
|
|
493
486
|
readOnlyHint: true,
|
|
494
487
|
openWorldHint: false,
|
|
495
488
|
destructiveHint: false,
|
|
496
489
|
idempotentHint: true,
|
|
497
490
|
}, (args) => listTestRunsTool(args, config, server));
|
|
498
|
-
tools.updateTestRun = server.tool("updateTestRun", "Update a test run's metadata and/or add test cases to it."
|
|
499
|
-
NEEDS_PROJECT_ID, UpdateTestRunSchema.shape, {
|
|
491
|
+
tools.updateTestRun = server.tool("updateTestRun", "Update a test run's metadata and/or add test cases to it.", UpdateTestRunSchema.shape, {
|
|
500
492
|
title: "Update Test Run",
|
|
501
493
|
readOnlyHint: false,
|
|
502
494
|
openWorldHint: false,
|
|
503
495
|
destructiveHint: true,
|
|
504
496
|
idempotentHint: true,
|
|
505
497
|
}, (args) => updateTestRunTool(args, config, server));
|
|
506
|
-
tools.addTestResult = server.tool("addTestResult", "Add a test result to a specific test run via BrowserStack Test Management API."
|
|
507
|
-
NEEDS_PROJECT_ID, AddTestResultSchema.shape, {
|
|
498
|
+
tools.addTestResult = server.tool("addTestResult", "Add a test result to a specific test run via BrowserStack Test Management API.", AddTestResultSchema.shape, {
|
|
508
499
|
title: "Add Test Result",
|
|
509
500
|
readOnlyHint: false,
|
|
510
501
|
openWorldHint: false,
|
|
511
502
|
destructiveHint: false,
|
|
512
503
|
idempotentHint: false,
|
|
513
504
|
}, (args) => addTestResultTool(args, config, server));
|
|
514
|
-
tools.uploadProductRequirementFile = server.tool("uploadProductRequirementFile", "Upload files (e.g., PDRs, PDFs) to BrowserStack Test Management and retrieve a file mapping ID. This is utilized for generating test cases from files and is part of the Test Case Generator AI Agent in BrowserStack."
|
|
515
|
-
NEEDS_PROJECT_ID, UploadFileSchema.shape, {
|
|
505
|
+
tools.uploadProductRequirementFile = server.tool("uploadProductRequirementFile", "Upload files (e.g., PDRs, PDFs) to BrowserStack Test Management and retrieve a file mapping ID. This is utilized for generating test cases from files and is part of the Test Case Generator AI Agent in BrowserStack.", UploadFileSchema.shape, {
|
|
516
506
|
title: "Upload Product Requirement File",
|
|
517
507
|
readOnlyHint: false,
|
|
518
508
|
openWorldHint: false,
|
|
@@ -526,47 +516,35 @@ export default function addTestManagementTools(server, config) {
|
|
|
526
516
|
destructiveHint: false,
|
|
527
517
|
idempotentHint: false,
|
|
528
518
|
}, (args, context) => createTestCasesFromFileTool(args, context, config, server));
|
|
529
|
-
tools.createLCASteps = server.tool("createLCASteps", "Generate Low Code Automation (LCA) steps for a test case in BrowserStack Test Management using the Low Code Automation Agent."
|
|
530
|
-
NEEDS_PROJECT_ID, CreateLCAStepsSchema.shape, {
|
|
519
|
+
tools.createLCASteps = server.tool("createLCASteps", "Generate Low Code Automation (LCA) steps for a test case in BrowserStack Test Management using the Low Code Automation Agent.", CreateLCAStepsSchema.shape, {
|
|
531
520
|
title: "Create LCA Steps",
|
|
532
521
|
readOnlyHint: false,
|
|
533
522
|
openWorldHint: false,
|
|
534
523
|
destructiveHint: false,
|
|
535
524
|
idempotentHint: false,
|
|
536
525
|
}, (args, context) => createLCAStepsTool(args, context, config, server));
|
|
537
|
-
tools.listTestPlans = server.tool("listTestPlans", "List test plans in a BrowserStack Test Management project. Returns each plan's identifier (TP-*), name, status, description, dates, and active/closed test-run counts. Supports pagination."
|
|
538
|
-
NEEDS_PROJECT_ID +
|
|
539
|
-
PLAN_WRITES_VIA_AGENT, ListTestPlansSchema.shape, {
|
|
526
|
+
tools.listTestPlans = server.tool("listTestPlans", "List test plans in a BrowserStack Test Management project. Returns each plan's identifier (TP-*), name, status, description, dates, and active/closed test-run counts. Supports pagination.", ListTestPlansSchema.shape, {
|
|
540
527
|
title: "List Test Plans",
|
|
541
528
|
readOnlyHint: true,
|
|
542
529
|
openWorldHint: false,
|
|
543
530
|
destructiveHint: false,
|
|
544
531
|
idempotentHint: true,
|
|
545
532
|
}, (args) => listTestPlansTool(args, config, server));
|
|
546
|
-
tools.getTestPlan = server.tool("getTestPlan", "Fetch a test plan by identifier (TP-*) from BrowserStack Test Management. Returns plan metadata, the full list of linked test runs, total test-case count across runs, and a status summary — suitable for generating test documentation or QA status reports."
|
|
547
|
-
NEEDS_PROJECT_ID +
|
|
548
|
-
NEEDS_TEST_PLAN_ID +
|
|
549
|
-
PLAN_WRITES_VIA_AGENT, GetTestPlanSchema.shape, {
|
|
533
|
+
tools.getTestPlan = server.tool("getTestPlan", "Fetch a test plan by identifier (TP-*) from BrowserStack Test Management. Returns plan metadata, the full list of linked test runs, total test-case count across runs, and a status summary — suitable for generating test documentation or QA status reports.", GetTestPlanSchema.shape, {
|
|
550
534
|
title: "Get Test Plan",
|
|
551
535
|
readOnlyHint: true,
|
|
552
536
|
openWorldHint: false,
|
|
553
537
|
destructiveHint: false,
|
|
554
538
|
idempotentHint: true,
|
|
555
539
|
}, (args) => getTestPlanTool(args, config, server));
|
|
556
|
-
tools.listSubTestPlans = server.tool("listSubTestPlans", "List sub-test-plans under a parent test plan (TP-*) in a Test Management project. Supports pagination."
|
|
557
|
-
NEEDS_PROJECT_ID +
|
|
558
|
-
NEEDS_TEST_PLAN_ID +
|
|
559
|
-
PLAN_WRITES_VIA_AGENT, ListSubTestPlansSchema.shape, {
|
|
540
|
+
tools.listSubTestPlans = server.tool("listSubTestPlans", "List sub-test-plans under a parent test plan (TP-*) in a Test Management project. Supports pagination.", ListSubTestPlansSchema.shape, {
|
|
560
541
|
title: "List Sub Test Plans",
|
|
561
542
|
readOnlyHint: true,
|
|
562
543
|
openWorldHint: false,
|
|
563
544
|
destructiveHint: false,
|
|
564
545
|
idempotentHint: true,
|
|
565
546
|
}, (args) => listSubTestPlansTool(args, config, server));
|
|
566
|
-
tools.getSubTestPlan = server.tool("getSubTestPlan", "Fetch a sub-test-plan (STP-*) under a parent plan (TP-*). Returns metadata and linked test runs."
|
|
567
|
-
NEEDS_PROJECT_ID +
|
|
568
|
-
NEEDS_TEST_PLAN_ID +
|
|
569
|
-
PLAN_WRITES_VIA_AGENT, GetSubTestPlanSchema.shape, {
|
|
547
|
+
tools.getSubTestPlan = server.tool("getSubTestPlan", "Fetch a sub-test-plan (STP-*) under a parent plan (TP-*). Returns metadata and linked test runs.", GetSubTestPlanSchema.shape, {
|
|
570
548
|
title: "Get Sub Test Plan",
|
|
571
549
|
readOnlyHint: true,
|
|
572
550
|
openWorldHint: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@browserstack/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0-beta.10",
|
|
4
4
|
"description": "BrowserStack's Official MCP Server",
|
|
5
5
|
"mcpName": "io.github.browserstack/mcp-server",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,8 @@
|
|
|
21
21
|
"browserstack-mcp-server": "dist/index.js"
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
|
-
"dist"
|
|
24
|
+
"dist",
|
|
25
|
+
"capability"
|
|
25
26
|
],
|
|
26
27
|
"keywords": [
|
|
27
28
|
"mcp",
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mint a BrowserStack central-OAuth JWT from the caller's username and access key.
|
|
3
|
-
*
|
|
4
|
-
* This replaces a shared delegation token, and the upgrade is not cosmetic.
|
|
5
|
-
* `validate_delegation_token` refuses any token without `user.user_id`/`user.group_id`, so
|
|
6
|
-
* what we mint here is USER-ATTESTED: Atlas sets `principal_verified=True`, takes the acting
|
|
7
|
-
* user from signed claims rather than from anything we put in the request body, and reuses
|
|
8
|
-
* this same JWT as its `egress_token` — so the product call a human approves runs as that
|
|
9
|
-
* human, not as a shared service account.
|
|
10
|
-
*
|
|
11
|
-
* SECRET HYGIENE IS THE WHOLE POINT OF THIS MODULE, and Atlas's `central_oauth.py` learned
|
|
12
|
-
* it the hard way: "The body can echo the credential back on some errors, so it is NOT
|
|
13
|
-
* logged or raised — only the status." Neither the access key nor the minted token is ever
|
|
14
|
-
* logged, returned, or put in an error message. Only a status code is.
|
|
15
|
-
*/
|
|
16
|
-
import { Credentials } from "./egress.js";
|
|
17
|
-
/**
|
|
18
|
-
* BOTH PARTS ARE REQUIRED, AND THERE IS NO FALLBACK TO ANOTHER SCOPE.
|
|
19
|
-
*
|
|
20
|
-
* `oauth_user_profile` stays because it is what makes the pair obtainable through the
|
|
21
|
-
* username+access_key flow at all. `ai_agent_notify` is what Atlas matches on
|
|
22
|
-
* (`delegation.required_scope`, checked as exact membership of the token's `scopes` claim in
|
|
23
|
-
* `web/oauth.py`); both halves move together with Atlas.
|
|
24
|
-
*
|
|
25
|
-
* THIS SCOPE MAY SIMPLY NOT BE ISSUABLE TO US, and the reasons are worth stating rather than
|
|
26
|
-
* discovering. From the merged `browserstack/railsApp#175367` (2026-08-24):
|
|
27
|
-
*
|
|
28
|
-
* - `ai_agent_notify` is documented there as CLIENT_ID/SECRET auth, and
|
|
29
|
-
* `USERNAME_ACCESS_KEY_ONLY_SCOPES` remains only `user_management, oauth_user_profile`.
|
|
30
|
-
* We are on the username+access_key flow, which those restrictions are not written for.
|
|
31
|
-
* - It is additionally covered by a new
|
|
32
|
-
* `APP_REGISTERED_SCOPE_REQUIRED = %w[ai_agent ai_agent_notify]` gate, requiring the
|
|
33
|
-
* calling APPLICATION to be registered for it — though that gate sits in the
|
|
34
|
-
* `client_id + client_secret` path, not ours.
|
|
35
|
-
* - railsApp defines it as the PRODUCT -> AGENT direction: "a product reporting progress
|
|
36
|
-
* back to an AI agent for work the agent dispatched." We use it in the opposite
|
|
37
|
-
* direction, as an agent -> Atlas inbound credential.
|
|
38
|
-
* - `central_ai_s2s`, which this replaces, was deliberately EXCLUDED from that new gate.
|
|
39
|
-
*
|
|
40
|
-
* So this is strictly more restricted than what it replaces. If the endpoint refuses it, that
|
|
41
|
-
* is a PROVISIONING problem — the scope is not available to this credential type or this
|
|
42
|
-
* application — and it is reported as one, naming the scope. It is never retried with a
|
|
43
|
-
* different scope: a silent downgrade to a different authorization is exactly the kind of
|
|
44
|
-
* thing nobody notices until it matters.
|
|
45
|
-
*/
|
|
46
|
-
export declare const CENTRAL_SCOPE = "oauth_user_profile ai_agent_notify";
|
|
47
|
-
/** What we ask for. The endpoint clamps to its own maximum, so the response wins. */
|
|
48
|
-
export declare const REQUESTED_EXPIRES_IN = 3600;
|
|
49
|
-
/**
|
|
50
|
-
* Treat a token as stale this long before it actually expires.
|
|
51
|
-
*
|
|
52
|
-
* NOT the usual small skew. This token is not merely used to open the request — Atlas holds
|
|
53
|
-
* it for the life of the run and re-uses it for product egress, so it has to outlive the
|
|
54
|
-
* whole call, and our own `/agent` budget is already 330s. Handing out a token with 61
|
|
55
|
-
* seconds left would mean a human approves a write and the egress that follows fails on an
|
|
56
|
-
* expired credential, which is the exact mid-flight expiry this cache exists to prevent.
|
|
57
|
-
*/
|
|
58
|
-
export declare const REFRESH_SKEW_MS: number;
|
|
59
|
-
/** The token endpoint gets its own, much shorter budget than `/agent`. */
|
|
60
|
-
export declare const TOKEN_TIMEOUT_MS = 15000;
|
|
61
|
-
export interface TokenResponse {
|
|
62
|
-
status: number;
|
|
63
|
-
body: unknown;
|
|
64
|
-
/** Only when there was no response at all to speak for itself. */
|
|
65
|
-
error?: string;
|
|
66
|
-
}
|
|
67
|
-
export type TokenTransport = (url: string, form: Record<string, string>) => Promise<TokenResponse>;
|
|
68
|
-
/**
|
|
69
|
-
* Was this refusal about the SCOPE or about the CREDENTIAL?
|
|
70
|
-
*
|
|
71
|
-
* The two need completely different fixes — provisioning versus a password — so collapsing
|
|
72
|
-
* them into one message sends someone to the wrong place entirely. Our form has five fields
|
|
73
|
-
* and four of them are constants, so a refusal of the REQUEST (as opposed to the caller) can
|
|
74
|
-
* only really be about the scope.
|
|
75
|
-
*
|
|
76
|
-
* Nothing from the body is ever surfaced; the code is used to classify and then discarded.
|
|
77
|
-
*/
|
|
78
|
-
export declare function refusalIsAboutScope(status: number, body: unknown): boolean;
|
|
79
|
-
/**
|
|
80
|
-
* The ways authentication can fail, kept apart because a user cannot act on them otherwise.
|
|
81
|
-
*
|
|
82
|
-
* `scope refused` is a provisioning problem; `rejected` is "your credentials are wrong";
|
|
83
|
-
* `unreachable` is "auth is down". A fourth — Atlas refusing a token we minted successfully —
|
|
84
|
-
* is a server misconfiguration and lives in `relay.ts`, because it is discovered from
|
|
85
|
-
* `/agent`. Four different fixes, so four different sentences.
|
|
86
|
-
*/
|
|
87
|
-
export declare const AUTH_SCOPE_REFUSED_DETAIL: (status: number) => string;
|
|
88
|
-
export declare const AUTH_REJECTED_DETAIL: (status: number) => string;
|
|
89
|
-
export declare const AUTH_UNREACHABLE_DETAIL: string;
|
|
90
|
-
/**
|
|
91
|
-
* A 5xx from auth: their service is down, not your password.
|
|
92
|
-
*
|
|
93
|
-
* Split out because routing 5xx to `AUTH_REJECTED_DETAIL` actively misdirects the reader,
|
|
94
|
-
* and did: a preprod outage returned 503 and the tool answered "Your BrowserStack
|
|
95
|
-
* credentials were rejected … Check BROWSERSTACK_USERNAME and BROWSERSTACK_ACCESS_KEY",
|
|
96
|
-
* sending someone to audit env vars that had worked minutes earlier. The status alone
|
|
97
|
-
* settles it — OAuth2 says a bad client is 401/403 and a bad request is 400, so nothing in
|
|
98
|
-
* the 5xx range is ever a statement about the caller.
|
|
99
|
-
*/
|
|
100
|
-
export declare const AUTH_SERVER_ERROR_DETAIL: (status: number) => string;
|
|
101
|
-
export declare const AUTH_UNUSABLE_DETAIL: (status: number) => string;
|
|
102
|
-
/** Drop every cached token. For tests, and for a credential rotation. */
|
|
103
|
-
export declare function resetTokenCache(): void;
|
|
104
|
-
/**
|
|
105
|
-
* The token endpoint, through `apiClient` per rules/security.md — no bare `fetch`.
|
|
106
|
-
*
|
|
107
|
-
* `raise_error: false` keeps the status-first contract this transport has always had: the
|
|
108
|
-
* caller distinguishes a 400 scope refusal from a 401 rejection from an unreachable host,
|
|
109
|
-
* so a thrown AxiosError on any non-2xx would destroy the only signal it reads.
|
|
110
|
-
*/
|
|
111
|
-
export declare function fetchTokenTransport(timeoutMs?: number): TokenTransport;
|
|
112
|
-
/** The exact form body of the `client_credentials` grant. */
|
|
113
|
-
export declare function mintForm(credentials: Credentials): Record<string, string>;
|
|
114
|
-
/**
|
|
115
|
-
* Return a valid token, minting one only when the cache has nothing fresh.
|
|
116
|
-
*
|
|
117
|
-
* Minting per tool call would add a round trip to every request and make the token endpoint
|
|
118
|
-
* a hot dependency of the whole surface.
|
|
119
|
-
*/
|
|
120
|
-
export declare function mintCentralToken(url: string, credentials: Credentials, transport: TokenTransport, now?: number): Promise<string>;
|