modality-ai 0.7.4 → 0.8.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/dist/index.js CHANGED
@@ -126465,16 +126465,31 @@ class ModalityClientImpl {
126465
126465
  }
126466
126466
  }
126467
126467
  parseContent(toolResult) {
126468
- try {
126469
- const content = toolResult?.content?.[0];
126470
- if (content?.type === "text" && content.text) {
126471
- return JSON.parse(content.text);
126468
+ const blocks = Array.isArray(toolResult?.content) ? toolResult.content : [];
126469
+ const parsed = blocks.filter((b) => b?.type === "text" && b.text).map((b) => {
126470
+ try {
126471
+ return JSON.parse(b.text);
126472
+ } catch {
126473
+ return b.text;
126472
126474
  }
126473
- return content ?? toolResult;
126474
- } catch {
126475
- const content = toolResult?.content?.[0];
126476
- return content?.text ?? content ?? toolResult;
126475
+ });
126476
+ if (parsed.length === 0) {
126477
+ return toolResult?.content?.[0] ?? toolResult;
126477
126478
  }
126479
+ const objects = parsed.filter((p) => typeof p === "object" && p !== null && !Array.isArray(p));
126480
+ const extras = parsed.filter((p) => typeof p !== "object" || p === null || Array.isArray(p));
126481
+ if (objects.length === 0) {
126482
+ return parsed.length === 1 ? parsed[0] : parsed;
126483
+ }
126484
+ const merged = Object.assign({}, ...objects);
126485
+ if (extras.length > 0) {
126486
+ const key = objects.some((o) => ("_extra" in o)) ? "__extra" : "_extra";
126487
+ merged[key] = extras.length === 1 ? extras[0] : extras;
126488
+ }
126489
+ if (merged.success === undefined) {
126490
+ merged.success = toolResult?.isError !== true;
126491
+ }
126492
+ return merged;
126478
126493
  }
126479
126494
  }
126480
126495
  function http(url3, timeout, options) {
@@ -129443,7 +129458,7 @@ var setupStdioToHttpTools = async (client, mcpServer) => {
129443
129458
  return setupAITools(aiTools, mcpServer);
129444
129459
  };
129445
129460
  // src/mcp-oauth-provider.ts
129446
- import { createHash } from "node:crypto";
129461
+ import { createHash, randomBytes } from "node:crypto";
129447
129462
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
129448
129463
  import { homedir } from "node:os";
129449
129464
  import { join as join9 } from "node:path";
@@ -129459,6 +129474,10 @@ class CLIBrowserOAuthProvider {
129459
129474
  _serverIdentity;
129460
129475
  _noOpen;
129461
129476
  _cachePath;
129477
+ _callbackPath;
129478
+ _callbackHost;
129479
+ _state;
129480
+ _onAuthorizationUrl;
129462
129481
  _port;
129463
129482
  _server;
129464
129483
  _resolveCode;
@@ -129471,6 +129490,10 @@ class CLIBrowserOAuthProvider {
129471
129490
  constructor(options = {}) {
129472
129491
  this._clientName = options.clientName ?? "mcp-cli";
129473
129492
  this._noOpen = options.noOpen ?? false;
129493
+ this._onAuthorizationUrl = options.onAuthorizationUrl;
129494
+ this._state = randomBytes(16).toString("hex");
129495
+ this._callbackPath = options.externalCallback?.path ?? "/callback";
129496
+ this._callbackHost = options.externalCallback?.host ?? "127.0.0.1";
129474
129497
  this._serverIdentity = options.serverUrl ? resolveServerIdentity(options.serverUrl) : null;
129475
129498
  if (options.serverUrl === null) {
129476
129499
  this._cachePath = null;
@@ -129490,11 +129513,15 @@ class CLIBrowserOAuthProvider {
129490
129513
  this._resolveCode = resolve2;
129491
129514
  this._rejectCode = reject3;
129492
129515
  });
129493
- this._server = Bun.serve({
129494
- port: options.callbackPort ?? 9876,
129495
- fetch: (req) => this._handleCallback(req)
129496
- });
129497
- this._port = this._server.port ?? 9876;
129516
+ if (options.externalCallback) {
129517
+ this._port = options.externalCallback.port;
129518
+ } else {
129519
+ this._server = Bun.serve({
129520
+ port: options.callbackPort ?? 9876,
129521
+ fetch: (req) => this._handleCallback(req)
129522
+ });
129523
+ this._port = this._server.port ?? 9876;
129524
+ }
129498
129525
  if (this._clientInfo) {
129499
129526
  const uris = this._clientInfo.redirect_uris ?? [];
129500
129527
  if (!uris.includes(this.redirectUrl)) {
@@ -129503,7 +129530,7 @@ class CLIBrowserOAuthProvider {
129503
129530
  }
129504
129531
  }
129505
129532
  get redirectUrl() {
129506
- return `http://127.0.0.1:${this._port}/callback`;
129533
+ return `http://${this._callbackHost}:${this._port}${this._callbackPath}`;
129507
129534
  }
129508
129535
  get clientMetadata() {
129509
129536
  const identity7 = this._serverIdentity;
@@ -129554,7 +129581,11 @@ class CLIBrowserOAuthProvider {
129554
129581
  params.set("client_secret", secret3);
129555
129582
  }
129556
129583
  };
129584
+ state() {
129585
+ return this._state;
129586
+ }
129557
129587
  async redirectToAuthorization(authorizationUrl) {
129588
+ this._onAuthorizationUrl?.(authorizationUrl);
129558
129589
  const url5 = authorizationUrl.toString();
129559
129590
  if (this._noOpen) {
129560
129591
  console.error(`
@@ -129577,6 +129608,15 @@ class CLIBrowserOAuthProvider {
129577
129608
  waitForCode() {
129578
129609
  return this._pendingCode;
129579
129610
  }
129611
+ get oauthState() {
129612
+ return this._state;
129613
+ }
129614
+ get callbackPath() {
129615
+ return this._callbackPath;
129616
+ }
129617
+ handleCallback(req) {
129618
+ return this._handleCallback(req);
129619
+ }
129580
129620
  getDiscoveryState() {
129581
129621
  return this._discoveryState;
129582
129622
  }
@@ -129585,7 +129625,7 @@ class CLIBrowserOAuthProvider {
129585
129625
  this._persistCache();
129586
129626
  }
129587
129627
  stop() {
129588
- this._server.stop(true);
129628
+ this._server?.stop(true);
129589
129629
  }
129590
129630
  _loadCache() {
129591
129631
  if (!this._cachePath)
@@ -129611,9 +129651,14 @@ class CLIBrowserOAuthProvider {
129611
129651
  }
129612
129652
  _handleCallback(req) {
129613
129653
  const url5 = new URL(req.url);
129614
- if (url5.pathname !== "/callback") {
129654
+ if (url5.pathname !== this._callbackPath) {
129615
129655
  return new Response("Not found", { status: 404 });
129616
129656
  }
129657
+ const returnedState = url5.searchParams.get("state");
129658
+ if (returnedState !== null && returnedState !== this._state) {
129659
+ this._rejectCode?.(new Error("OAuth state mismatch"));
129660
+ return errorPage("state_mismatch", "State parameter did not match.");
129661
+ }
129617
129662
  const error54 = url5.searchParams.get("error");
129618
129663
  if (error54) {
129619
129664
  const description = url5.searchParams.get("error_description") ?? "";
@@ -47,6 +47,19 @@ declare class ModalityClientImpl {
47
47
  callStream(method: string, params?: any, callback?: (p: any) => void): ReadableStream;
48
48
  close(): Promise<void>;
49
49
  listTools(): Promise<ListToolsResult>;
50
+ /**
51
+ * Parse a CallToolResult into a plain value.
52
+ *
53
+ * modality-kit's `formatSuccessResponse(data, meta)` splits results across
54
+ * text blocks — block 0 is the envelope (`{message}`), blocks 1+ carry the
55
+ * payload — so ALL text blocks are parsed and object blocks shallow-merged
56
+ * (later blocks win on key collisions). Non-object blocks (e.g. the
57
+ * `DEBUG=1` "Response size" note) are collected under `_extra` (or
58
+ * `__extra` when the tool's payload already carries `_extra`).
59
+ *
60
+ * The merged object always carries `success`: derived from the MCP-level
61
+ * `isError` flag unless the tool set it explicitly.
62
+ */
50
63
  parseContent(toolResult: any): unknown;
51
64
  }
52
65
  export type ModalityClientInstance = ModalityClientImpl;
@@ -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,8 +78,12 @@ 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;
@@ -89,16 +113,36 @@ export declare class CLIBrowserOAuthProvider implements OAuthClientProvider {
89
113
  * auth-method string the registration response contained.
90
114
  */
91
115
  readonly addClientAuthentication: AddClientAuthentication;
116
+ /**
117
+ * CSRF `state` echoed back on the redirect. The SDK reads this via the
118
+ * optional `state()` hook and appends it to the authorization request; the
119
+ * callback validates it. Also used by host apps to correlate an external
120
+ * callback to this provider instance.
121
+ */
122
+ state(): string;
92
123
  redirectToAuthorization(authorizationUrl: URL): Promise<void>;
93
124
  /**
94
125
  * Resolves with the authorization code once the browser redirect completes.
95
126
  */
96
127
  waitForCode(): Promise<string>;
128
+ /** CSRF state for this flow — key an external callback route by this value. */
129
+ get oauthState(): string;
130
+ /** Advertised redirect path — the route an external host server must expose. */
131
+ get callbackPath(): string;
132
+ /**
133
+ * Handle an OAuth redirect request forwarded from an external host server
134
+ * (externalCallback mode). Resolves waitForCode() and returns the page to
135
+ * render in the browser tab.
136
+ */
137
+ handleCallback(req: Request): Response;
97
138
  /** Discovery state captured before registration — available even when registration fails. */
98
139
  getDiscoveryState(): OAuthDiscoveryState | undefined;
99
140
  /** Clear cached tokens only — forces browser re-auth on next run while keeping client registration. */
100
141
  clearCache(): void;
101
- /** Stop the local callback HTTP server. Call once auth is complete. */
142
+ /**
143
+ * Stop the local callback HTTP server. Call once auth is complete.
144
+ * No-op in externalCallback mode, where the host owns the server.
145
+ */
102
146
  stop(): void;
103
147
  private _loadCache;
104
148
  private _persistCache;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.7.4",
2
+ "version": "0.8.0",
3
3
  "name": "modality-ai",
4
4
  "repository": {
5
5
  "type": "git",