modality-ai 0.7.5 → 0.8.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/dist/index.js CHANGED
@@ -129458,7 +129458,7 @@ var setupStdioToHttpTools = async (client, mcpServer) => {
129458
129458
  return setupAITools(aiTools, mcpServer);
129459
129459
  };
129460
129460
  // src/mcp-oauth-provider.ts
129461
- import { createHash } from "node:crypto";
129461
+ import { createHash, randomBytes } from "node:crypto";
129462
129462
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
129463
129463
  import { homedir } from "node:os";
129464
129464
  import { join as join9 } from "node:path";
@@ -129474,11 +129474,16 @@ class CLIBrowserOAuthProvider {
129474
129474
  _serverIdentity;
129475
129475
  _noOpen;
129476
129476
  _cachePath;
129477
+ _callbackPath;
129478
+ _callbackHost;
129479
+ _state;
129480
+ _onAuthorizationUrl;
129477
129481
  _port;
129478
129482
  _server;
129479
129483
  _resolveCode;
129480
129484
  _rejectCode;
129481
129485
  _pendingCode;
129486
+ _codeSettled = false;
129482
129487
  _codeVerifier;
129483
129488
  _clientInfo;
129484
129489
  _tokens;
@@ -129486,6 +129491,10 @@ class CLIBrowserOAuthProvider {
129486
129491
  constructor(options = {}) {
129487
129492
  this._clientName = options.clientName ?? "mcp-cli";
129488
129493
  this._noOpen = options.noOpen ?? false;
129494
+ this._onAuthorizationUrl = options.onAuthorizationUrl;
129495
+ this._state = randomBytes(16).toString("hex");
129496
+ this._callbackPath = options.externalCallback?.path ?? "/callback";
129497
+ this._callbackHost = options.externalCallback?.host ?? "127.0.0.1";
129489
129498
  this._serverIdentity = options.serverUrl ? resolveServerIdentity(options.serverUrl) : null;
129490
129499
  if (options.serverUrl === null) {
129491
129500
  this._cachePath = null;
@@ -129505,11 +129514,15 @@ class CLIBrowserOAuthProvider {
129505
129514
  this._resolveCode = resolve2;
129506
129515
  this._rejectCode = reject3;
129507
129516
  });
129508
- this._server = Bun.serve({
129509
- port: options.callbackPort ?? 9876,
129510
- fetch: (req) => this._handleCallback(req)
129511
- });
129512
- this._port = this._server.port ?? 9876;
129517
+ if (options.externalCallback) {
129518
+ this._port = options.externalCallback.port;
129519
+ } else {
129520
+ this._server = Bun.serve({
129521
+ port: options.callbackPort ?? 9876,
129522
+ fetch: (req) => this._handleCallback(req)
129523
+ });
129524
+ this._port = this._server.port ?? 9876;
129525
+ }
129513
129526
  if (this._clientInfo) {
129514
129527
  const uris = this._clientInfo.redirect_uris ?? [];
129515
129528
  if (!uris.includes(this.redirectUrl)) {
@@ -129518,7 +129531,7 @@ class CLIBrowserOAuthProvider {
129518
129531
  }
129519
129532
  }
129520
129533
  get redirectUrl() {
129521
- return `http://127.0.0.1:${this._port}/callback`;
129534
+ return `http://${this._callbackHost}:${this._port}${this._callbackPath}`;
129522
129535
  }
129523
129536
  get clientMetadata() {
129524
129537
  const identity7 = this._serverIdentity;
@@ -129569,7 +129582,11 @@ class CLIBrowserOAuthProvider {
129569
129582
  params.set("client_secret", secret3);
129570
129583
  }
129571
129584
  };
129585
+ state() {
129586
+ return this._state;
129587
+ }
129572
129588
  async redirectToAuthorization(authorizationUrl) {
129589
+ this._onAuthorizationUrl?.(authorizationUrl);
129573
129590
  const url5 = authorizationUrl.toString();
129574
129591
  if (this._noOpen) {
129575
129592
  console.error(`
@@ -129592,6 +129609,15 @@ class CLIBrowserOAuthProvider {
129592
129609
  waitForCode() {
129593
129610
  return this._pendingCode;
129594
129611
  }
129612
+ get oauthState() {
129613
+ return this.state();
129614
+ }
129615
+ get callbackPath() {
129616
+ return this._callbackPath;
129617
+ }
129618
+ handleCallback(req) {
129619
+ return this._handleCallback(req);
129620
+ }
129595
129621
  getDiscoveryState() {
129596
129622
  return this._discoveryState;
129597
129623
  }
@@ -129600,7 +129626,7 @@ class CLIBrowserOAuthProvider {
129600
129626
  this._persistCache();
129601
129627
  }
129602
129628
  stop() {
129603
- this._server.stop(true);
129629
+ this._server?.stop(true);
129604
129630
  }
129605
129631
  _loadCache() {
129606
129632
  if (!this._cachePath)
@@ -129626,20 +129652,32 @@ class CLIBrowserOAuthProvider {
129626
129652
  }
129627
129653
  _handleCallback(req) {
129628
129654
  const url5 = new URL(req.url);
129629
- if (url5.pathname !== "/callback") {
129655
+ if (url5.pathname !== this._callbackPath) {
129630
129656
  return new Response("Not found", { status: 404 });
129631
129657
  }
129658
+ if (this._codeSettled) {
129659
+ return errorPage("already_completed", "Authorization code was already received.");
129660
+ }
129661
+ const returnedState = url5.searchParams.get("state");
129662
+ if (returnedState !== null && returnedState !== this._state) {
129663
+ this._codeSettled = true;
129664
+ this._rejectCode?.(new Error("OAuth state mismatch"));
129665
+ return errorPage("state_mismatch", "State parameter did not match.");
129666
+ }
129632
129667
  const error54 = url5.searchParams.get("error");
129633
129668
  if (error54) {
129634
129669
  const description = url5.searchParams.get("error_description") ?? "";
129670
+ this._codeSettled = true;
129635
129671
  this._rejectCode?.(new Error(`OAuth error: ${error54} — ${description}`));
129636
129672
  return errorPage(error54, description);
129637
129673
  }
129638
129674
  const code = url5.searchParams.get("code");
129639
129675
  if (!code) {
129676
+ this._codeSettled = true;
129640
129677
  this._rejectCode?.(new Error("OAuth callback missing authorization code"));
129641
129678
  return errorPage("no_code", "No authorization code received.");
129642
129679
  }
129680
+ this._codeSettled = true;
129643
129681
  this._resolveCode?.(code);
129644
129682
  return successPage();
129645
129683
  }
@@ -129686,6 +129724,9 @@ function successPage() {
129686
129724
  </html>`;
129687
129725
  return new Response(body, { status: 200, headers: { "Content-Type": "text/html" } });
129688
129726
  }
129727
+ function escapeHtml2(s) {
129728
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
129729
+ }
129689
129730
  function errorPage(error54, description) {
129690
129731
  const body = `<!DOCTYPE html>
129691
129732
  <html lang="en">
@@ -129723,7 +129764,7 @@ function errorPage(error54, description) {
129723
129764
  <div class="card">
129724
129765
  <span class="icon">❌</span>
129725
129766
  <h1>Authentication failed</h1>
129726
- <p><code>${error54}</code>${description ? `: ${description}` : ""}</p>
129767
+ <p><code>${escapeHtml2(error54)}</code>${description ? `: ${escapeHtml2(description)}` : ""}</p>
129727
129768
  </div>
129728
129769
  </body>
129729
129770
  </html>`;
@@ -11,8 +11,28 @@ interface CLIBrowserOAuthProviderOptions {
11
11
  /**
12
12
  * Port for the local callback server.
13
13
  * Defaults to 9876 for a stable redirect_uri across runs.
14
+ * Ignored when `externalCallback` is set.
14
15
  */
15
16
  callbackPort?: number;
17
+ /**
18
+ * Route the OAuth redirect through an existing HTTP server instead of
19
+ * starting a local Bun.serve. When set, the provider binds no port of its
20
+ * own — the host app must forward the redirect request to `handleCallback()`.
21
+ * `port`/`path`/`host` define the advertised redirect_uri and must match the
22
+ * host server (defaults: host "127.0.0.1", path "/callback").
23
+ * Correlate the inbound callback to this provider via `oauthState`.
24
+ */
25
+ externalCallback?: {
26
+ port: number;
27
+ path?: string;
28
+ host?: string;
29
+ };
30
+ /**
31
+ * Invoked with the full authorization URL just before the browser opens.
32
+ * Lets the host correlate the later callback to this provider (e.g. by
33
+ * reading the `state` query param, which equals `oauthState`).
34
+ */
35
+ onAuthorizationUrl?: (url: URL) => void;
16
36
  /**
17
37
  * Skip opening the system browser automatically.
18
38
  * When true the authorization URL is printed but not launched.
@@ -58,11 +78,16 @@ export declare class CLIBrowserOAuthProvider implements OAuthClientProvider {
58
78
  private readonly _serverIdentity;
59
79
  private readonly _noOpen;
60
80
  private readonly _cachePath;
81
+ private readonly _callbackPath;
82
+ private readonly _callbackHost;
83
+ private readonly _state;
84
+ private readonly _onAuthorizationUrl?;
61
85
  private _port;
62
- private _server;
86
+ private _server?;
63
87
  private _resolveCode?;
64
88
  private _rejectCode?;
65
89
  private _pendingCode;
90
+ private _codeSettled;
66
91
  private _codeVerifier?;
67
92
  private _clientInfo?;
68
93
  private _tokens?;
@@ -89,16 +114,36 @@ export declare class CLIBrowserOAuthProvider implements OAuthClientProvider {
89
114
  * auth-method string the registration response contained.
90
115
  */
91
116
  readonly addClientAuthentication: AddClientAuthentication;
117
+ /**
118
+ * CSRF `state` echoed back on the redirect. The SDK reads this via the
119
+ * optional `state()` hook and appends it to the authorization request; the
120
+ * callback validates it. Also used by host apps to correlate an external
121
+ * callback to this provider instance.
122
+ */
123
+ state(): string;
92
124
  redirectToAuthorization(authorizationUrl: URL): Promise<void>;
93
125
  /**
94
126
  * Resolves with the authorization code once the browser redirect completes.
95
127
  */
96
128
  waitForCode(): Promise<string>;
129
+ /** CSRF state for this flow — key an external callback route by this value. */
130
+ get oauthState(): string;
131
+ /** Advertised redirect path — the route an external host server must expose. */
132
+ get callbackPath(): string;
133
+ /**
134
+ * Handle an OAuth redirect request forwarded from an external host server
135
+ * (externalCallback mode). Resolves waitForCode() and returns the page to
136
+ * render in the browser tab.
137
+ */
138
+ handleCallback(req: Request): Response;
97
139
  /** Discovery state captured before registration — available even when registration fails. */
98
140
  getDiscoveryState(): OAuthDiscoveryState | undefined;
99
141
  /** Clear cached tokens only — forces browser re-auth on next run while keeping client registration. */
100
142
  clearCache(): void;
101
- /** Stop the local callback HTTP server. Call once auth is complete. */
143
+ /**
144
+ * Stop the local callback HTTP server. Call once auth is complete.
145
+ * No-op in externalCallback mode, where the host owns the server.
146
+ */
102
147
  stop(): void;
103
148
  private _loadCache;
104
149
  private _persistCache;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.7.5",
2
+ "version": "0.8.1",
3
3
  "name": "modality-ai",
4
4
  "repository": {
5
5
  "type": "git",