@modelprofile.com/browser-runtime 1.0.1 → 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/ts/errors.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type TBrowserRuntimeErrorCode =
2
2
  | 'ABORTED'
3
3
  | 'AUTHORIZATION_DENIED'
4
+ | 'ATTACHMENT_CONFLICT'
4
5
  | 'BUSY'
5
6
  | 'CAPABILITY_EXPIRED'
6
7
  | 'CAPABILITY_INVALID'
@@ -14,24 +15,29 @@ export type TBrowserRuntimeErrorCode =
14
15
  | 'NOT_RUNNING'
15
16
  | 'PROTOCOL_ERROR'
16
17
  | 'QUOTA_EXCEEDED'
18
+ | 'RESOURCE_NOT_FOUND'
19
+ | 'RESOURCE_RETIRED'
17
20
  | 'TIMEOUT';
18
21
 
19
22
  const publicMessages: Record<TBrowserRuntimeErrorCode, string> = {
20
23
  ABORTED: 'The operation was aborted.',
21
24
  AUTHORIZATION_DENIED: 'Capability authorization was denied.',
22
- BUSY: 'The browser project is busy.',
25
+ ATTACHMENT_CONFLICT: 'The attachment binding conflicts with the current revision.',
26
+ BUSY: 'The browser resource is busy.',
23
27
  CAPABILITY_EXPIRED: 'The capability has expired.',
24
28
  CAPABILITY_INVALID: 'The capability is invalid.',
25
29
  CAPABILITY_REVOKED: 'The capability has been revoked.',
26
30
  CONFINEMENT_FAILED: 'Browser confinement could not be confirmed.',
27
31
  EGRESS_DENIED: 'The egress target was denied.',
28
- FENCED: 'The browser project is permanently fenced.',
32
+ FENCED: 'The browser resource is permanently fenced.',
29
33
  FRAME_TOO_LARGE: 'The browser frame exceeded its limit.',
30
34
  INVALID_INPUT: 'The request is invalid.',
31
35
  LOCKED: 'The browser runtime is already locked.',
32
36
  NOT_RUNNING: 'The browser runtime is not running.',
33
37
  PROTOCOL_ERROR: 'The framed protocol request is invalid.',
34
38
  QUOTA_EXCEEDED: 'A browser runtime quota was exceeded.',
39
+ RESOURCE_NOT_FOUND: 'The browser resource is not registered.',
40
+ RESOURCE_RETIRED: 'The browser resource has been retired.',
35
41
  TIMEOUT: 'The operation timed out.',
36
42
  };
37
43
 
package/ts/index.ts CHANGED
@@ -10,37 +10,48 @@ export { BrowserRuntime, BrowserRuntimeLease } from './classes.runtime.js';
10
10
  export { BrowserRuntimeError } from './errors.js';
11
11
  export { createBrowserRuntimeMcpHttpHandler } from './mcp.js';
12
12
 
13
- export type { IAcquireBrowserRuntimeLeaseOptions } from './classes.runtime.js';
13
+ export type { TAcquireBrowserRuntimeLeaseOptions } from './classes.runtime.js';
14
14
  export type { TBrowserRuntimeErrorCode } from './errors.js';
15
15
  export type {
16
16
  IAttachTrustedFramedPeerOptions,
17
+ IApplyBrowserAttachmentBindingRequest,
18
+ IBrowserAttachmentBinding,
17
19
  IBrowserActionStateResult,
18
20
  IBrowserArtifactMetadata,
19
21
  IBrowserArtifactStoreOptions,
20
- IBrowserCapabilityAuthorizationRequest,
21
- IBrowserCapabilityDescriptor,
22
+ IBrowserMcpAuthenticatedBinding,
23
+ TBrowserCapabilityAuthorizationRequest,
24
+ TBrowserCapabilityDescriptor,
22
25
  IBrowserClickAction,
23
26
  IBrowserEgressConnectionOptions,
24
27
  IBrowserEgressProxyOptions,
25
28
  IBrowserEgressProxyStats,
29
+ IBrowserFlexCapabilityBinding,
30
+ IBrowserFlexSessionId,
26
31
  IBrowserFillAction,
27
32
  IBrowserNavigateAction,
28
33
  IBrowserObservationResult,
34
+ IBrowserOpenCodeSessionId,
29
35
  IBrowserPressAction,
30
- IBrowserRuntimeAuditEvent,
36
+ TBrowserRuntimeAuditEvent,
31
37
  IBrowserRuntimeFlexToolProviderOptions,
32
38
  IBrowserRuntimeFrameSubscription,
33
39
  IBrowserRuntimeFramedClientOptions,
34
40
  IBrowserRuntimeMcpOptions,
35
41
  IBrowserRuntimeOperationOptions,
36
42
  IBrowserRuntimeOptions,
43
+ IBrowserResourceKey,
44
+ IBrowserResourceRegistration,
37
45
  IBrowserRuntimeState,
38
46
  IBrowserRuntimeTabState,
39
47
  IBrowserScreenshotAction,
40
48
  IBrowserScreenshotResult,
41
49
  IBrowserSnapshotAction,
42
- IIssueBrowserCapabilityRequest,
43
- IIssuedBrowserCapability,
50
+ ICreateBrowserResourceRequest,
51
+ TIssueBrowserCapabilityRequest,
52
+ TIssuedBrowserCapability,
53
+ TQualifiedBrowserSessionId,
54
+ IRegisterBrowserResourceRequest,
44
55
  IResolvedBrowserFlexCapability,
45
56
  TBrowserActorRole,
46
57
  TBrowserAgentAction,
@@ -48,5 +59,6 @@ export type {
48
59
  TBrowserCapabilitySource,
49
60
  TBrowserDnsResolver,
50
61
  TBrowserEgressConnector,
62
+ TBrowserHarnessId,
51
63
  TBrowserRuntimeEvent,
52
64
  } from './interfaces.js';
package/ts/interfaces.ts CHANGED
@@ -1,49 +1,119 @@
1
1
  import type * as plugins from './plugins.js';
2
+ import type { BrowserEgressProxy } from './classes.egressproxy.js';
3
+ import type { BrowserRuntimeFramedClient } from './classes.framed.js';
2
4
 
3
5
  export type TBrowserActorRole = 'human' | 'agent';
4
6
  export type TBrowserCapabilitySource = 'human' | 'flex' | 'mcp';
7
+ export type TBrowserHarnessId = 'opencode' | 'flex';
5
8
 
6
- export interface IBrowserCapabilityAuthorizationRequest {
9
+ export interface IBrowserOpenCodeSessionId {
10
+ harnessId: 'opencode';
11
+ nativeId: string;
12
+ }
13
+
14
+ export interface IBrowserFlexSessionId {
15
+ harnessId: 'flex';
16
+ nativeId: string;
17
+ }
18
+
19
+ export type TQualifiedBrowserSessionId = IBrowserOpenCodeSessionId | IBrowserFlexSessionId;
20
+
21
+ export interface IBrowserResourceKey {
22
+ projectId: string;
23
+ browserResourceId: string;
24
+ }
25
+
26
+ export interface IBrowserAttachmentBinding {
27
+ attachmentAuthorityId: string;
28
+ attachmentRevision: number;
29
+ sessionId: TQualifiedBrowserSessionId | null;
30
+ }
31
+
32
+ export interface ICreateBrowserResourceRequest {
7
33
  projectId: string;
34
+ attachmentBinding: IBrowserAttachmentBinding;
35
+ }
36
+
37
+ export interface IRegisterBrowserResourceRequest extends IBrowserResourceKey {
38
+ attachmentBinding: IBrowserAttachmentBinding;
39
+ }
40
+
41
+ export interface IApplyBrowserAttachmentBindingRequest extends IBrowserResourceKey {
42
+ attachmentBinding: IBrowserAttachmentBinding;
43
+ }
44
+
45
+ export interface IBrowserResourceRegistration extends IBrowserResourceKey {
46
+ registeredAt: number;
47
+ attachmentBinding: IBrowserAttachmentBinding;
48
+ incarnationStatus: 'absent' | 'running' | 'fenced';
49
+ incarnationGeneration: number;
50
+ retirementPending: boolean;
51
+ }
52
+
53
+ export interface IBrowserCapabilityBindingBase extends IBrowserResourceKey {
54
+ attachmentAuthorityId: string;
55
+ attachmentRevision: number;
8
56
  actorId: string;
9
- role: TBrowserActorRole;
10
57
  peerId: string;
11
- source: TBrowserCapabilitySource;
12
- scopeId?: string;
13
- sessionId?: string;
14
58
  }
15
59
 
16
- export interface IIssueBrowserCapabilityRequest extends IBrowserCapabilityAuthorizationRequest {
60
+ export interface IBrowserHumanCapabilityBinding extends IBrowserCapabilityBindingBase {
61
+ role: 'human';
62
+ source: 'human';
63
+ sessionId?: never;
64
+ scopeId?: never;
65
+ channelId?: never;
66
+ }
67
+
68
+ export interface IBrowserFlexCapabilityBinding extends IBrowserCapabilityBindingBase {
69
+ role: 'agent';
70
+ source: 'flex';
71
+ sessionId: IBrowserFlexSessionId;
72
+ scopeId: string;
73
+ channelId: string;
74
+ }
75
+
76
+ export interface IBrowserMcpCapabilityBinding extends IBrowserCapabilityBindingBase {
77
+ role: 'agent';
78
+ source: 'mcp';
79
+ sessionId: IBrowserOpenCodeSessionId;
80
+ scopeId?: never;
81
+ channelId?: never;
82
+ }
83
+
84
+ export type TBrowserCapabilityAuthorizationRequest =
85
+ | IBrowserHumanCapabilityBinding
86
+ | IBrowserFlexCapabilityBinding
87
+ | IBrowserMcpCapabilityBinding;
88
+
89
+ export type TIssueBrowserCapabilityRequest = TBrowserCapabilityAuthorizationRequest & {
17
90
  expiresInMs?: number;
18
91
  disconnect?: (signal: AbortSignal) => Promise<void> | void;
19
- }
92
+ };
20
93
 
21
- export interface IBrowserCapabilityDescriptor extends IBrowserCapabilityAuthorizationRequest {
94
+ export type TBrowserCapabilityDescriptor = TBrowserCapabilityAuthorizationRequest & {
22
95
  capabilityId: string;
23
96
  expiresAt: number;
24
- }
97
+ };
25
98
 
26
- export interface IIssuedBrowserCapability extends IBrowserCapabilityDescriptor {
99
+ export type TIssuedBrowserCapability = TBrowserCapabilityDescriptor & {
27
100
  capabilityToken: string;
28
- }
101
+ };
29
102
 
30
103
  export interface IResolvedBrowserFlexCapability {
31
104
  capabilityToken: string;
32
105
  }
33
106
 
34
- export interface IBrowserRuntimeAuditEvent {
107
+ export type TBrowserRuntimeAuditEvent = TBrowserCapabilityAuthorizationRequest & {
35
108
  operationId: string;
36
109
  capabilityId: string;
37
- projectId: string;
38
- actorId: string;
39
- role: TBrowserActorRole;
40
- source: TBrowserCapabilitySource;
110
+ leaseId: string;
41
111
  action: string;
42
112
  phase: 'completed' | 'failed';
43
113
  startedAt: number;
44
114
  finishedAt: number;
45
115
  errorCode?: string;
46
- }
116
+ };
47
117
 
48
118
  export interface ILiveBrowserSessionLike {
49
119
  start(options?: plugins.smartpuppeteer.ILiveBrowserOperationOptions): Promise<void>;
@@ -120,9 +190,9 @@ export interface ILiveBrowserSessionLike {
120
190
  ): Promise<void>;
121
191
  }
122
192
 
123
- export interface IBrowserConfinementProbeContext {
193
+ export interface IBrowserConfinementProbeContext extends IBrowserResourceKey {
124
194
  session: ILiveBrowserSessionLike;
125
- proxy: import('./classes.egressproxy.js').BrowserEgressProxy;
195
+ proxy: BrowserEgressProxy;
126
196
  signal: AbortSignal;
127
197
  }
128
198
 
@@ -134,27 +204,31 @@ export interface IBrowserRuntimeEnvironment {
134
204
  export interface IBrowserRuntimeOptions {
135
205
  runtimeDirectory: string;
136
206
  authorizeCapability(
137
- request: IBrowserCapabilityAuthorizationRequest,
207
+ request: TBrowserCapabilityAuthorizationRequest,
138
208
  signal: AbortSignal,
139
209
  ): Promise<boolean> | boolean;
140
- audit?(event: IBrowserRuntimeAuditEvent, signal: AbortSignal): Promise<void> | void;
141
- maxProjectSlots?: number;
210
+ audit?(event: TBrowserRuntimeAuditEvent, signal: AbortSignal): Promise<void> | void;
211
+ maxResources?: number;
212
+ maxResourcesPerProject?: number;
213
+ maxRunningResources?: number;
214
+ maxRunningResourcesPerProject?: number;
142
215
  maxCapabilities?: number;
216
+ maxCapabilitiesPerResource?: number;
143
217
  maxCapabilitiesPerProject?: number;
144
218
  capabilityDefaultTtlMs?: number;
145
219
  capabilityMaximumTtlMs?: number;
146
220
  authorizationTimeoutMs?: number;
147
- maxTabsPerProject?: number;
221
+ maxTabsPerResource?: number;
148
222
  operationTimeoutMs?: number;
149
223
  quiescenceTimeoutMs?: number;
150
224
  terminationGraceMs?: number;
151
225
  terminationForceMs?: number;
152
226
  auditTimeoutMs?: number;
153
227
  confinementProbeTimeoutMs?: number;
154
- idleRetirementMs?: number;
228
+ idleTerminationMs?: number;
155
229
  maxFrameBytes?: number;
156
230
  frameAcknowledgementTimeoutMs?: number;
157
- egress?: Omit<IBrowserEgressProxyOptions, 'projectId'>;
231
+ egress?: Omit<IBrowserEgressProxyOptions, 'projectId' | 'browserResourceId'>;
158
232
  artifacts?: Omit<IBrowserArtifactStoreOptions, 'rootDirectory'>;
159
233
  }
160
234
 
@@ -186,9 +260,8 @@ export interface IBrowserRuntimeState {
186
260
  };
187
261
  }
188
262
 
189
- export interface IBrowserArtifactMetadata {
263
+ export interface IBrowserArtifactMetadata extends IBrowserResourceKey {
190
264
  artifactId: string;
191
- projectId: string;
192
265
  mimeType: string;
193
266
  size: number;
194
267
  createdAt: number;
@@ -198,6 +271,8 @@ export interface IBrowserArtifactMetadata {
198
271
  export interface IBrowserArtifactStoreOptions {
199
272
  rootDirectory: string;
200
273
  maxFileBytes?: number;
274
+ maxArtifactsPerResource?: number;
275
+ maxResourceBytes?: number;
201
276
  maxArtifactsPerProject?: number;
202
277
  maxProjectBytes?: number;
203
278
  maxProjects?: number;
@@ -315,8 +390,7 @@ export type TBrowserEgressConnector = (
315
390
  options: IBrowserEgressConnectionOptions,
316
391
  ) => plugins.net.Socket;
317
392
 
318
- export interface IBrowserEgressProxyOptions {
319
- projectId: string;
393
+ export interface IBrowserEgressProxyOptions extends IBrowserResourceKey {
320
394
  allowedPorts?: number[];
321
395
  maxConnections?: number;
322
396
  maxActiveRequests?: number;
@@ -340,17 +414,24 @@ export interface IBrowserEgressProxyStats {
340
414
  activeConnections: number;
341
415
  }
342
416
 
343
- export interface IAttachTrustedFramedPeerOptions {
344
- peerId: string;
345
- scopeId: string;
346
- sessionId: string;
417
+ export interface IAttachTrustedFramedPeerOptions
418
+ extends Omit<
419
+ IBrowserFlexCapabilityBinding,
420
+ 'role' | 'source'
421
+ > {
422
+ role: 'agent';
423
+ source: 'flex';
347
424
  readable: plugins.stream.Readable;
348
425
  writable: plugins.stream.Writable;
349
426
  }
350
427
 
351
- export interface IBrowserRuntimeFramedClientOptions {
352
- scopeId: string;
353
- sessionId: string;
428
+ export interface IBrowserRuntimeFramedClientOptions
429
+ extends Omit<
430
+ IBrowserFlexCapabilityBinding,
431
+ 'role' | 'source'
432
+ > {
433
+ role: 'agent';
434
+ source: 'flex';
354
435
  readable: plugins.stream.Readable;
355
436
  writable: plugins.stream.Writable;
356
437
  maxFrameBytes?: number;
@@ -358,8 +439,16 @@ export interface IBrowserRuntimeFramedClientOptions {
358
439
  requestTimeoutMs?: number;
359
440
  }
360
441
 
442
+ export interface IBrowserMcpAuthenticatedBinding extends IBrowserCapabilityBindingBase {
443
+ role: 'agent';
444
+ source: 'mcp';
445
+ sessionId: IBrowserOpenCodeSessionId;
446
+ scopeId?: never;
447
+ channelId?: never;
448
+ }
449
+
361
450
  export interface IBrowserRuntimeMcpOptions {
362
- authenticateMcpRequest(request: Request): Promise<{ peerId: string }>;
451
+ authenticateMcpRequest(request: Request): Promise<IBrowserMcpAuthenticatedBinding>;
363
452
  trustedOrigins?: string[];
364
453
  allowedHosts?: string[];
365
454
  allowMissingOrigin?: boolean;
@@ -368,7 +457,7 @@ export interface IBrowserRuntimeMcpOptions {
368
457
  }
369
458
 
370
459
  export interface IBrowserRuntimeFlexToolProviderOptions<TScope> {
371
- client: import('./classes.framed.js').BrowserRuntimeFramedClient;
460
+ client: BrowserRuntimeFramedClient;
372
461
  resolveCapability(
373
462
  context: plugins.flexharness.IFlexToolProviderContext<TScope>,
374
463
  ): Promise<IResolvedBrowserFlexCapability> | IResolvedBrowserFlexCapability;
@@ -1,6 +1,7 @@
1
1
  import type * as plugins from './plugins.js';
2
2
  import type {
3
3
  IBrowserConfinementProbeContext,
4
+ IBrowserRuntimeFramedClientOptions,
4
5
  IBrowserRuntimeOptions,
5
6
  IBrowserRuntimeEnvironment,
6
7
  ILiveBrowserSessionLike,
@@ -15,4 +16,15 @@ export interface IBrowserRuntimeTestingOptions extends IBrowserRuntimeOptions {
15
16
  options: plugins.smartpuppeteer.ILiveBrowserSessionOptions,
16
17
  ): ILiveBrowserSessionLike;
17
18
  confinementProbe(context: IBrowserConfinementProbeContext): Promise<void>;
19
+ beforeLeasePublication?(): Promise<void>;
20
+ beforeIdleTermination?(): Promise<void>;
21
+ beforeFrameFailureTermination?(): Promise<void>;
22
+ afterFrameFailureFence?(): Promise<void>;
23
+ beforeFramedPeerLeasePublication?(): Promise<void>;
24
+ }
25
+
26
+ export interface IBrowserRuntimeFramedClientTestingOptions
27
+ extends IBrowserRuntimeFramedClientOptions {
28
+ [browserRuntimeTesting]: true;
29
+ afterAcquireResponse?(): Promise<void>;
18
30
  }
package/ts/mcp.ts CHANGED
@@ -2,7 +2,10 @@ import * as plugins from './plugins.js';
2
2
  import type { BrowserRuntime, BrowserRuntimeLease } from './classes.runtime.js';
3
3
  import { BrowserRuntimeError } from './errors.js';
4
4
  import { commitinfo } from './00_commitinfo_data.js';
5
- import type { IBrowserRuntimeMcpOptions } from './interfaces.js';
5
+ import type {
6
+ IBrowserMcpAuthenticatedBinding,
7
+ IBrowserRuntimeMcpOptions,
8
+ } from './interfaces.js';
6
9
  import { validateAgentActionResult } from './actions.js';
7
10
  import {
8
11
  validateBoundedString,
@@ -51,19 +54,17 @@ export const createBrowserRuntimeMcpHttpHandler = (
51
54
  context: plugins.smartmcp.ISmartMcpToolContext,
52
55
  action: (lease: BrowserRuntimeLease, signal: AbortSignal) => Promise<T>,
53
56
  ): Promise<T> => {
54
- const peerId = validateBoundedString(context.authInfo?.clientId, 'peerId', 1, 128);
57
+ const binding = parseMcpAuthBinding(context.authInfo?.scopes);
55
58
  const capabilityToken = parseBearerToken(context.request);
56
59
  const signal = AbortSignal.any([context.extra.signal, context.request.signal]);
57
60
  signal.throwIfAborted();
58
- const descriptor = runtime.authorizeCapability(capabilityToken, peerId, 'agent', 'mcp');
61
+ const descriptor = runtime.authorizeCapability(capabilityToken, binding);
59
62
  if (descriptor.capabilityId !== context.authInfo?.token) {
60
63
  throw new BrowserRuntimeError('CAPABILITY_INVALID');
61
64
  }
62
65
  const lease = await runtime.acquireLease({
63
66
  capabilityToken,
64
- peerId,
65
- expectedRole: 'agent',
66
- expectedSource: 'mcp',
67
+ ...binding,
67
68
  signal,
68
69
  });
69
70
  try {
@@ -203,22 +204,16 @@ export const createBrowserRuntimeMcpHttpHandler = (
203
204
  maxToolResultBytes,
204
205
  redactError: () => 'Browser tool execution failed.',
205
206
  authorize: async ({ request }) => {
206
- let peerId: string;
207
207
  let capabilityToken: string;
208
208
  try {
209
209
  const authentication = await options.authenticateMcpRequest(request);
210
- peerId = validateBoundedString(authentication?.peerId, 'peerId', 1, 128);
210
+ const binding = validateMcpBinding(authentication);
211
211
  capabilityToken = parseBearerToken(request);
212
- const descriptor = runtime.authorizeCapability(
213
- capabilityToken,
214
- peerId,
215
- 'agent',
216
- 'mcp',
217
- );
212
+ const descriptor = runtime.authorizeCapability(capabilityToken, binding);
218
213
  return {
219
214
  token: descriptor.capabilityId,
220
- clientId: peerId,
221
- scopes: ['browser'],
215
+ clientId: binding.peerId,
216
+ scopes: [serializeMcpAuthBinding(binding)],
222
217
  expiresAt: descriptor.expiresAt,
223
218
  };
224
219
  } catch {
@@ -228,3 +223,59 @@ export const createBrowserRuntimeMcpHttpHandler = (
228
223
  tools,
229
224
  });
230
225
  };
226
+
227
+ const validateMcpBinding = (value: unknown): IBrowserMcpAuthenticatedBinding => {
228
+ if (!value || typeof value !== 'object') throw new BrowserRuntimeError('CAPABILITY_INVALID');
229
+ const binding = value as Record<string, unknown>;
230
+ if (binding.role !== 'agent' || binding.source !== 'mcp') {
231
+ throw new BrowserRuntimeError('CAPABILITY_INVALID');
232
+ }
233
+ const session = binding.sessionId as Record<string, unknown> | undefined;
234
+ if (session?.harnessId !== 'opencode') throw new BrowserRuntimeError('CAPABILITY_INVALID');
235
+ return {
236
+ projectId: validateBoundedString(binding.projectId, 'projectId', 1, 256),
237
+ browserResourceId: validateBoundedString(
238
+ binding.browserResourceId,
239
+ 'browserResourceId',
240
+ 16,
241
+ 256,
242
+ ),
243
+ attachmentAuthorityId: validateBoundedString(
244
+ binding.attachmentAuthorityId,
245
+ 'attachmentAuthorityId',
246
+ 1,
247
+ 256,
248
+ ),
249
+ attachmentRevision: validateInteger(
250
+ binding.attachmentRevision,
251
+ 'attachmentRevision',
252
+ 0,
253
+ Number.MAX_SAFE_INTEGER,
254
+ ),
255
+ actorId: validateBoundedString(binding.actorId, 'actorId', 1, 256),
256
+ role: 'agent',
257
+ peerId: validateBoundedString(binding.peerId, 'peerId', 1, 256),
258
+ source: 'mcp',
259
+ sessionId: {
260
+ harnessId: 'opencode',
261
+ nativeId: validateBoundedString(session.nativeId, 'sessionId.nativeId', 1, 256),
262
+ },
263
+ };
264
+ };
265
+
266
+ const serializeMcpAuthBinding = (
267
+ binding: IBrowserMcpAuthenticatedBinding,
268
+ ): string => plugins.Buffer.from(JSON.stringify(binding), 'utf8').toString('base64url');
269
+
270
+ const parseMcpAuthBinding = (scopes: string[] | undefined): IBrowserMcpAuthenticatedBinding => {
271
+ if (!Array.isArray(scopes) || scopes.length !== 1) {
272
+ throw new BrowserRuntimeError('CAPABILITY_INVALID');
273
+ }
274
+ try {
275
+ return validateMcpBinding(JSON.parse(
276
+ plugins.Buffer.from(scopes[0]!, 'base64url').toString('utf8'),
277
+ ));
278
+ } catch {
279
+ throw new BrowserRuntimeError('CAPABILITY_INVALID');
280
+ }
281
+ };