nexus-ai-pro 2.20.2 → 2.22.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.
Files changed (233) hide show
  1. package/API_STABILITY.md +93 -0
  2. package/CHANGELOG.md +208 -1
  3. package/README.md +11 -6
  4. package/SECURITY.md +16 -2
  5. package/dist/adapter-kit/gather.d.ts +76 -0
  6. package/dist/adapter-kit/gather.js +147 -0
  7. package/dist/adapter-kit/index.d.ts +1 -0
  8. package/dist/adapter-kit/index.js +1 -0
  9. package/dist/adapter-kit/record.d.ts +3 -1
  10. package/dist/adapter-kit/record.js +20 -5
  11. package/dist/agent/openapi.d.ts +94 -8
  12. package/dist/agent/openapi.js +528 -32
  13. package/dist/cli/adapters.d.ts +4 -3
  14. package/dist/cli/adapters.js +71 -2
  15. package/dist/cli/doctor.js +3 -0
  16. package/dist/cli.js +3 -1
  17. package/dist/core/config-builder.d.ts +1 -0
  18. package/dist/core/config-builder.js +1 -0
  19. package/dist/core/defaults.d.ts +34 -0
  20. package/dist/core/defaults.js +39 -0
  21. package/dist/core/secure-stream.js +4 -1
  22. package/dist/decisions/completion.d.ts +10 -7
  23. package/dist/decisions/completion.js +82 -8
  24. package/dist/decisions/core.d.ts +88 -43
  25. package/dist/decisions/core.js +115 -28
  26. package/dist/decisions/errors.d.ts +25 -0
  27. package/dist/decisions/errors.js +12 -0
  28. package/dist/decisions/index.d.ts +6 -5
  29. package/dist/decisions/index.js +2 -1
  30. package/dist/decisions/integrations.d.ts +14 -5
  31. package/dist/decisions/integrations.js +16 -1
  32. package/dist/decisions/mock.d.ts +5 -3
  33. package/dist/decisions/mock.js +4 -1
  34. package/dist/decisions/pictures.d.ts +43 -0
  35. package/dist/decisions/pictures.js +103 -0
  36. package/dist/decisions/typesafe.d.ts +33 -5
  37. package/dist/decisions/typesafe.js +121 -8
  38. package/dist/doctor/index.d.ts +6 -9
  39. package/dist/doctor/index.js +41 -0
  40. package/dist/embeddings/adapters.d.ts +1 -1
  41. package/dist/embeddings/adapters.js +3 -3
  42. package/dist/embeddings/batch-google.d.ts +27 -8
  43. package/dist/embeddings/batch-google.js +112 -36
  44. package/dist/embeddings/batch-mock.js +1 -1
  45. package/dist/embeddings/batch.js +10 -4
  46. package/dist/embeddings/engine.js +14 -13
  47. package/dist/embeddings/index.d.ts +1 -1
  48. package/dist/embeddings/inputs.d.ts +26 -3
  49. package/dist/embeddings/inputs.js +84 -5
  50. package/dist/embeddings/mock.js +1 -1
  51. package/dist/embeddings/models.js +39 -4
  52. package/dist/loaders/csv.d.ts +23 -1
  53. package/dist/loaders/csv.js +125 -72
  54. package/dist/loaders/index.d.ts +7 -2
  55. package/dist/loaders/input.d.ts +6 -0
  56. package/dist/loaders/input.js +42 -1
  57. package/dist/loaders/json.d.ts +14 -3
  58. package/dist/loaders/json.js +171 -20
  59. package/dist/loaders/web.d.ts +55 -0
  60. package/dist/loaders/web.js +20 -1
  61. package/dist/mcp/client.d.ts +54 -5
  62. package/dist/mcp/client.js +171 -20
  63. package/dist/mcp/http.d.ts +45 -0
  64. package/dist/mcp/http.js +233 -0
  65. package/dist/mcp/index.d.ts +4 -2
  66. package/dist/mcp/index.js +3 -1
  67. package/dist/mcp/protocol.d.ts +107 -0
  68. package/dist/mcp/sampling.d.ts +41 -0
  69. package/dist/mcp/sampling.js +52 -0
  70. package/dist/mcp/server.d.ts +96 -14
  71. package/dist/mcp/server.js +274 -59
  72. package/dist/models/data.js +20 -1
  73. package/dist/operations/adapters.d.ts +1 -0
  74. package/dist/operations/adapters.js +3 -1
  75. package/dist/protocols/a2a-grpc.js +10 -3
  76. package/dist/protocols/a2a-signing.d.ts +65 -0
  77. package/dist/protocols/a2a-signing.js +133 -0
  78. package/dist/protocols/a2a.d.ts +14 -0
  79. package/dist/protocols/a2a.js +60 -5
  80. package/dist/providers/cohere.js +16 -2
  81. package/dist/providers/google.js +7 -1
  82. package/dist/providers/ollama.d.ts +1 -1
  83. package/dist/providers/ollama.js +35 -27
  84. package/dist/providers/openai.js +19 -2
  85. package/dist/rag/pinecone.d.ts +63 -2
  86. package/dist/rag/pinecone.js +80 -0
  87. package/dist/realtime/conversation.js +18 -0
  88. package/dist/realtime/gemini-live.transport.d.ts +28 -3
  89. package/dist/realtime/gemini-live.transport.js +170 -32
  90. package/dist/realtime/session.d.ts +2 -0
  91. package/dist/realtime/session.js +19 -4
  92. package/dist/realtime/types.d.ts +35 -3
  93. package/dist/security/output-guard.js +4 -1
  94. package/dist/server/auth.d.ts +263 -2
  95. package/dist/server/auth.js +432 -5
  96. package/dist/server/deployments.d.ts +3 -2
  97. package/dist/server/deployments.js +6 -1
  98. package/dist/telephony/index.d.ts +1 -1
  99. package/dist/telephony/manager.d.ts +8 -1
  100. package/dist/telephony/manager.js +13 -0
  101. package/dist/telephony/providers/telnyx.d.ts +18 -5
  102. package/dist/telephony/providers/telnyx.js +44 -7
  103. package/dist/telephony/providers/twilio.d.ts +3 -1
  104. package/dist/telephony/providers/twilio.js +16 -0
  105. package/dist/tracing/stores.d.ts +2 -0
  106. package/dist/tracing/stores.js +1 -0
  107. package/dist/tracing/tracer.d.ts +5 -2
  108. package/dist/tracing/tracer.js +6 -3
  109. package/dist/types/embeddings.d.ts +47 -5
  110. package/dist/types/messages.d.ts +7 -0
  111. package/dist/types/principal.d.ts +1 -1
  112. package/dist/types/response.d.ts +5 -0
  113. package/dist/types/telephony.d.ts +38 -0
  114. package/dist/types/tracing.d.ts +6 -0
  115. package/dist/version.d.ts +1 -1
  116. package/dist/version.js +1 -1
  117. package/dist/voice/providers/google.d.ts +44 -0
  118. package/dist/voice/providers/google.js +215 -0
  119. package/dist-cjs/adapter-kit/gather.d.ts +76 -0
  120. package/dist-cjs/adapter-kit/gather.js +152 -0
  121. package/dist-cjs/adapter-kit/index.d.ts +1 -0
  122. package/dist-cjs/adapter-kit/index.js +4 -1
  123. package/dist-cjs/adapter-kit/record.d.ts +3 -1
  124. package/dist-cjs/adapter-kit/record.js +20 -5
  125. package/dist-cjs/agent/openapi.d.ts +94 -8
  126. package/dist-cjs/agent/openapi.js +530 -32
  127. package/dist-cjs/cli/adapters.d.ts +4 -3
  128. package/dist-cjs/cli/adapters.js +70 -1
  129. package/dist-cjs/cli/doctor.js +3 -0
  130. package/dist-cjs/core/config-builder.d.ts +1 -0
  131. package/dist-cjs/core/config-builder.js +5 -1
  132. package/dist-cjs/core/defaults.d.ts +34 -0
  133. package/dist-cjs/core/defaults.js +44 -0
  134. package/dist-cjs/core/secure-stream.js +4 -1
  135. package/dist-cjs/decisions/completion.d.ts +10 -7
  136. package/dist-cjs/decisions/completion.js +81 -7
  137. package/dist-cjs/decisions/core.d.ts +88 -43
  138. package/dist-cjs/decisions/core.js +134 -46
  139. package/dist-cjs/decisions/errors.d.ts +25 -0
  140. package/dist-cjs/decisions/errors.js +16 -0
  141. package/dist-cjs/decisions/index.d.ts +6 -5
  142. package/dist-cjs/decisions/index.js +5 -1
  143. package/dist-cjs/decisions/integrations.d.ts +14 -5
  144. package/dist-cjs/decisions/integrations.js +16 -1
  145. package/dist-cjs/decisions/mock.d.ts +5 -3
  146. package/dist-cjs/decisions/mock.js +4 -1
  147. package/dist-cjs/decisions/pictures.d.ts +43 -0
  148. package/dist-cjs/decisions/pictures.js +109 -0
  149. package/dist-cjs/decisions/typesafe.d.ts +33 -5
  150. package/dist-cjs/decisions/typesafe.js +126 -13
  151. package/dist-cjs/doctor/index.d.ts +6 -9
  152. package/dist-cjs/doctor/index.js +41 -0
  153. package/dist-cjs/embeddings/adapters.d.ts +1 -1
  154. package/dist-cjs/embeddings/adapters.js +3 -3
  155. package/dist-cjs/embeddings/batch-google.d.ts +27 -8
  156. package/dist-cjs/embeddings/batch-google.js +112 -36
  157. package/dist-cjs/embeddings/batch-mock.js +1 -1
  158. package/dist-cjs/embeddings/batch.js +9 -3
  159. package/dist-cjs/embeddings/engine.js +13 -12
  160. package/dist-cjs/embeddings/index.d.ts +1 -1
  161. package/dist-cjs/embeddings/inputs.d.ts +26 -3
  162. package/dist-cjs/embeddings/inputs.js +89 -5
  163. package/dist-cjs/embeddings/mock.js +1 -1
  164. package/dist-cjs/embeddings/models.js +39 -4
  165. package/dist-cjs/loaders/csv.d.ts +23 -1
  166. package/dist-cjs/loaders/csv.js +126 -71
  167. package/dist-cjs/loaders/index.d.ts +7 -2
  168. package/dist-cjs/loaders/input.d.ts +6 -0
  169. package/dist-cjs/loaders/input.js +43 -1
  170. package/dist-cjs/loaders/json.d.ts +14 -3
  171. package/dist-cjs/loaders/json.js +170 -19
  172. package/dist-cjs/loaders/web.d.ts +55 -0
  173. package/dist-cjs/loaders/web.js +21 -1
  174. package/dist-cjs/mcp/client.d.ts +54 -5
  175. package/dist-cjs/mcp/client.js +171 -20
  176. package/dist-cjs/mcp/http.d.ts +45 -0
  177. package/dist-cjs/mcp/http.js +236 -0
  178. package/dist-cjs/mcp/index.d.ts +4 -2
  179. package/dist-cjs/mcp/index.js +5 -1
  180. package/dist-cjs/mcp/protocol.d.ts +107 -0
  181. package/dist-cjs/mcp/sampling.d.ts +41 -0
  182. package/dist-cjs/mcp/sampling.js +55 -0
  183. package/dist-cjs/mcp/server.d.ts +96 -14
  184. package/dist-cjs/mcp/server.js +273 -58
  185. package/dist-cjs/models/data.js +20 -1
  186. package/dist-cjs/operations/adapters.d.ts +1 -0
  187. package/dist-cjs/operations/adapters.js +3 -1
  188. package/dist-cjs/protocols/a2a-grpc.js +10 -3
  189. package/dist-cjs/protocols/a2a-signing.d.ts +65 -0
  190. package/dist-cjs/protocols/a2a-signing.js +140 -0
  191. package/dist-cjs/protocols/a2a.d.ts +14 -0
  192. package/dist-cjs/protocols/a2a.js +65 -6
  193. package/dist-cjs/providers/cohere.js +16 -2
  194. package/dist-cjs/providers/google.js +7 -1
  195. package/dist-cjs/providers/ollama.d.ts +1 -1
  196. package/dist-cjs/providers/ollama.js +35 -27
  197. package/dist-cjs/providers/openai.js +19 -2
  198. package/dist-cjs/rag/pinecone.d.ts +63 -2
  199. package/dist-cjs/rag/pinecone.js +81 -0
  200. package/dist-cjs/realtime/conversation.js +18 -0
  201. package/dist-cjs/realtime/gemini-live.transport.d.ts +28 -3
  202. package/dist-cjs/realtime/gemini-live.transport.js +170 -32
  203. package/dist-cjs/realtime/session.d.ts +2 -0
  204. package/dist-cjs/realtime/session.js +19 -4
  205. package/dist-cjs/realtime/types.d.ts +35 -3
  206. package/dist-cjs/security/output-guard.js +4 -1
  207. package/dist-cjs/server/auth.d.ts +263 -2
  208. package/dist-cjs/server/auth.js +437 -5
  209. package/dist-cjs/server/deployments.d.ts +3 -2
  210. package/dist-cjs/server/deployments.js +6 -1
  211. package/dist-cjs/telephony/index.d.ts +1 -1
  212. package/dist-cjs/telephony/manager.d.ts +8 -1
  213. package/dist-cjs/telephony/manager.js +13 -0
  214. package/dist-cjs/telephony/providers/telnyx.d.ts +18 -5
  215. package/dist-cjs/telephony/providers/telnyx.js +44 -7
  216. package/dist-cjs/telephony/providers/twilio.d.ts +3 -1
  217. package/dist-cjs/telephony/providers/twilio.js +16 -0
  218. package/dist-cjs/tracing/stores.d.ts +2 -0
  219. package/dist-cjs/tracing/stores.js +1 -0
  220. package/dist-cjs/tracing/tracer.d.ts +5 -2
  221. package/dist-cjs/tracing/tracer.js +6 -3
  222. package/dist-cjs/types/embeddings.d.ts +47 -5
  223. package/dist-cjs/types/messages.d.ts +7 -0
  224. package/dist-cjs/types/principal.d.ts +1 -1
  225. package/dist-cjs/types/response.d.ts +5 -0
  226. package/dist-cjs/types/telephony.d.ts +38 -0
  227. package/dist-cjs/types/tracing.d.ts +6 -0
  228. package/dist-cjs/version.d.ts +1 -1
  229. package/dist-cjs/version.js +1 -1
  230. package/dist-cjs/voice/providers/google.d.ts +44 -0
  231. package/dist-cjs/voice/providers/google.js +252 -0
  232. package/llms.txt +1 -1
  233. package/package.json +17 -1
@@ -28,7 +28,62 @@ export interface WebLoaderOptions extends SafeFetchPolicy {
28
28
  onError?: 'throw' | 'skip';
29
29
  /** Called for each page skipped, with the reason. */
30
30
  onSkip?: (url: string, reason: Error) => void;
31
+ /**
32
+ * Renders a page whose content a script builds, with a browser of the application's own, such as
33
+ * `browserRenderer()` over Playwright's: given the page fetched, it returns the HTML the browser ends
34
+ * with. Called for an HTML page that runs a script and whose text, as fetched, is shorter than
35
+ * `renderBelow`. The fetch has checked the page's URL first; the browser fetches what the page
36
+ * loads under its own policy.
37
+ */
38
+ render?: WebPageRenderer;
39
+ /** Renders a page whose fetched text is shorter than this many characters. Defaults to 200; `Infinity` renders every page that runs a script. */
40
+ renderBelow?: number;
41
+ }
42
+ /** A page as fetched, which a renderer is given. */
43
+ export interface WebPageToRender {
44
+ /** Its URL, after redirects. */
45
+ url: string;
46
+ /** The HTML the server sent. */
47
+ html: string;
48
+ }
49
+ /**
50
+ * Renders a page with a browser: the HTML it ends with once the page's scripts have run, which is
51
+ * what a page built by script holds.
52
+ */
53
+ export type WebPageRenderer = (page: WebPageToRender, signal: AbortSignal) => Promise<string> | string;
54
+ /** What `browserRenderer()` drives: a Playwright or Puppeteer browser, or anything shaped like one. */
55
+ export interface RenderingBrowser {
56
+ /** Opens a page. */
57
+ newPage(): Promise<{
58
+ /** Goes to a URL and waits as told. */
59
+ goto(url: string, options?: {
60
+ waitUntil?: string;
61
+ timeout?: number;
62
+ }): Promise<unknown>;
63
+ /** The HTML the page holds now. */
64
+ content(): Promise<string>;
65
+ /** Closes it. */
66
+ close(): Promise<unknown>;
67
+ }>;
68
+ }
69
+ /** Options for `browserRenderer()`. */
70
+ export interface BrowserRendererOptions {
71
+ /** When the page counts as rendered. Defaults to `networkidle`, as Playwright names it. */
72
+ waitUntil?: string;
73
+ /** Time allowed per page, in milliseconds. Defaults to 30 seconds. */
74
+ timeoutMs?: number;
31
75
  }
76
+ /**
77
+ * A renderer over a browser of the application's own, such as Playwright's `chromium.launch()`: each
78
+ * page is opened, waited on, read, and closed. The package depends on no browser; the browser fetches
79
+ * the page and what it loads under its own network policy, not the loader's.
80
+ *
81
+ * ```ts
82
+ * const browser = await chromium.launch();
83
+ * for await (const page of loadWebPages(urls, { render: browserRenderer(browser) })) store(page);
84
+ * ```
85
+ */
86
+ export declare function browserRenderer(browser: RenderingBrowser, options?: BrowserRendererOptions): WebPageRenderer;
32
87
  /** Raised when a page cannot be loaded: a status that is not 2xx, or a type that is not text. */
33
88
  export declare class WebLoaderError extends Error {
34
89
  /** The page. */
@@ -1,5 +1,19 @@
1
1
  import { parseUrl, safeFetch } from '../utils/safe-fetch.js';
2
2
  import { htmlToText } from './html.js';
3
+ export function browserRenderer(browser, options = {}) {
4
+ return async (page, signal) => {
5
+ signal.throwIfAborted();
6
+ const tab = await browser.newPage();
7
+ try {
8
+ await tab.goto(page.url, { waitUntil: options.waitUntil ?? 'networkidle', timeout: options.timeoutMs ?? 30_000 });
9
+ signal.throwIfAborted();
10
+ return await tab.content();
11
+ }
12
+ finally {
13
+ await tab.close().catch(() => undefined);
14
+ }
15
+ };
16
+ }
3
17
  const MAX_SITEMAP_BYTES = 50 * 1024 * 1024;
4
18
  export class WebLoaderError extends Error {
5
19
  url;
@@ -84,7 +98,12 @@ async function loadPage(url, options) {
84
98
  const body = new TextDecoder().decode(response.bytes);
85
99
  const metadata = { ...options.metadata, url: response.url };
86
100
  if (type === 'text/html' || type === 'application/xhtml+xml' || (!type && /^\s*</.test(body))) {
87
- const page = htmlToText(body, { baseUrl: response.url, mainContent: options.mainContent });
101
+ let page = htmlToText(body, { baseUrl: response.url, mainContent: options.mainContent });
102
+ if (options.render && page.text.length < (options.renderBelow ?? 200) && /<script\b/i.test(body)) {
103
+ const html = await options.render({ url: response.url, html: body }, options.signal ?? new AbortController().signal);
104
+ page = htmlToText(html, { baseUrl: response.url, mainContent: options.mainContent });
105
+ metadata.rendered = true;
106
+ }
88
107
  if (page.title)
89
108
  metadata.title = page.title;
90
109
  if (page.description)
@@ -1,5 +1,5 @@
1
1
  import type { ToolDefinition } from '../types/messages.js';
2
- import { type McpTransport } from './protocol.js';
2
+ import { type McpElicitationRequest, type McpElicitationResult, type McpRoot, type McpSamplingRequest, type McpSamplingResult, type McpTransport } from './protocol.js';
3
3
  /**
4
4
  * What a server says a tool does, as MCP's tool annotations: hints, which a client trusts only as far
5
5
  * as it trusts the server. A hint left out takes the specification's default.
@@ -99,6 +99,26 @@ export interface McpClientOptions {
99
99
  * `reopen` a new stream.
100
100
  */
101
101
  reconnect?: boolean;
102
+ /**
103
+ * Answers a server's `sampling/createMessage` with this client's model, so a server's tool can ask
104
+ * for a completion: `samplingFrom(ai, { model })` is one. Declared to the server when given;
105
+ * without it, a server's request for one is refused.
106
+ */
107
+ sampling?: (request: McpSamplingRequest, context: {
108
+ signal: AbortSignal;
109
+ }) => McpSamplingResult | Promise<McpSamplingResult>;
110
+ /**
111
+ * Answers a server's `elicitation/create`, a question for the person using this client, with the
112
+ * form of its answer. Declared to the server when given; without it, a request is refused.
113
+ */
114
+ elicitation?: (request: McpElicitationRequest, context: {
115
+ signal: AbortSignal;
116
+ }) => McpElicitationResult | Promise<McpElicitationResult>;
117
+ /**
118
+ * The roots a server may work within, answered to its `roots/list`. Declared to the server when
119
+ * given; `rootsChanged()` tells it they changed.
120
+ */
121
+ roots?: () => McpRoot[] | Promise<McpRoot[]>;
102
122
  }
103
123
  /**
104
124
  * Talks to an MCP server.
@@ -119,6 +139,10 @@ export declare class McpClient {
119
139
  private serverInfo;
120
140
  /** Who hears that the server's tools changed. */
121
141
  private readonly toolsChanged;
142
+ /** Who hears each of the server's notifications, by method. */
143
+ private readonly listeners;
144
+ /** Requests the server is waiting on this client for, by id, so a cancellation stops them. */
145
+ private readonly answering;
122
146
  private stopped;
123
147
  constructor(transport: McpTransport, options?: McpClientOptions);
124
148
  /**
@@ -126,6 +150,22 @@ export declare class McpClient {
126
150
  * `listTools()` returned. Returns a function that stops hearing.
127
151
  */
128
152
  onToolsChanged(handler: () => void): () => void;
153
+ /** Hears each `notifications/prompts/list_changed`: the server's prompts changed. Returns a function that stops hearing. */
154
+ onPromptsChanged(handler: () => void): () => void;
155
+ /** Hears each `notifications/resources/list_changed`: the server's resources changed. Returns a function that stops hearing. */
156
+ onResourcesChanged(handler: () => void): () => void;
157
+ /**
158
+ * Hears each `notifications/resources/updated`, for a resource this client subscribed to with
159
+ * `subscribeResource()`: its URI. Returns a function that stops hearing.
160
+ */
161
+ onResourceUpdated(handler: (uri: string) => void): () => void;
162
+ /** Asks the server to say when a resource changes, by `onResourceUpdated()`. */
163
+ subscribeResource(uri: string): Promise<void>;
164
+ /** Stops hearing that a resource changed. */
165
+ unsubscribeResource(uri: string): Promise<void>;
166
+ /** Tells the server this client's roots changed, so it lists them again. */
167
+ rootsChanged(): Promise<void>;
168
+ private listen;
129
169
  /**
130
170
  * Performs the handshake and returns the server's name and version. Called automatically by the
131
171
  * first request that needs it; requests made together share one handshake.
@@ -174,6 +214,8 @@ export declare class McpClient {
174
214
  private request;
175
215
  private notify;
176
216
  private receive;
217
+ /** Answers one request the server sent, with its result or why not. */
218
+ private answer;
177
219
  private settle;
178
220
  private failAll;
179
221
  }
@@ -218,12 +260,19 @@ export interface HttpClientOptions {
218
260
  headers?: Record<string, string>;
219
261
  /** Replaces the global `fetch`. */
220
262
  fetch?: typeof globalThis.fetch;
263
+ /**
264
+ * Holds a `GET` stream open once the server names a session, for what the server sends of its own
265
+ * accord: notifications that its tools or resources changed, and requests outside any call. A server
266
+ * that answers it with 405 does not offer one, and none is opened. Off by default.
267
+ */
268
+ listen?: boolean;
221
269
  }
222
270
  /**
223
- * Speaks to an MCP server over HTTP, one POST per message.
224
- *
225
- * Server-initiated streaming is not implemented: this covers request and response, which is what
226
- * calling a tool needs.
271
+ * Speaks to an MCP server over streamable HTTP, MCP's transport since 2025-03-26: a `POST` per
272
+ * message, answered as JSON or as a stream of events the transport reads as they come, so a server
273
+ * may report progress and ask this client for something in the middle of a call. With `listen`, a
274
+ * `GET` stream carries what the server sends of its own accord. It keeps the session the server
275
+ * names, sends the protocol version on every request, and ends the session when it closes.
227
276
  */
228
277
  export declare function createHttpTransport(options: HttpClientOptions): McpTransport;
229
278
  /** Connects to a remote MCP server over HTTP with Server-Sent Events, the transport before streamable HTTP. */
@@ -16,6 +16,8 @@ export class McpClient {
16
16
  connecting;
17
17
  serverInfo = {};
18
18
  toolsChanged = new Set();
19
+ listeners = new Map();
20
+ answering = new Map();
19
21
  stopped = false;
20
22
  constructor(transport, options = {}) {
21
23
  this.transport = transport;
@@ -33,6 +35,38 @@ export class McpClient {
33
35
  this.toolsChanged.delete(handler);
34
36
  };
35
37
  }
38
+ onPromptsChanged(handler) {
39
+ return this.listen('notifications/prompts/list_changed', () => handler());
40
+ }
41
+ onResourcesChanged(handler) {
42
+ return this.listen('notifications/resources/list_changed', () => handler());
43
+ }
44
+ onResourceUpdated(handler) {
45
+ return this.listen('notifications/resources/updated', (params) => handler(String(params.uri ?? '')));
46
+ }
47
+ async subscribeResource(uri) {
48
+ await this.connect();
49
+ await this.request('resources/subscribe', { uri });
50
+ }
51
+ async unsubscribeResource(uri) {
52
+ await this.connect();
53
+ await this.request('resources/unsubscribe', { uri });
54
+ }
55
+ async rootsChanged() {
56
+ await this.connect();
57
+ await this.notify('notifications/roots/list_changed');
58
+ }
59
+ listen(method, handler) {
60
+ let handlers = this.listeners.get(method);
61
+ if (!handlers) {
62
+ handlers = new Set();
63
+ this.listeners.set(method, handlers);
64
+ }
65
+ handlers.add(handler);
66
+ return () => {
67
+ handlers.delete(handler);
68
+ };
69
+ }
36
70
  async connect() {
37
71
  if (this.initialized)
38
72
  return this.serverInfo;
@@ -45,7 +79,14 @@ export class McpClient {
45
79
  await this.transport.start?.();
46
80
  const result = (await this.request('initialize', {
47
81
  protocolVersion: MCP_PROTOCOL_VERSION,
48
- capabilities: { tools: {}, resources: {}, prompts: {} },
82
+ capabilities: {
83
+ tools: {},
84
+ resources: {},
85
+ prompts: {},
86
+ ...(this.options.sampling ? { sampling: {} } : {}),
87
+ ...(this.options.elicitation ? { elicitation: {} } : {}),
88
+ ...(this.options.roots ? { roots: { listChanged: true } } : {}),
89
+ },
49
90
  clientInfo: this.options.clientInfo ?? { name: 'nexus-ai-pro', version: '1.x' },
50
91
  }));
51
92
  this.initialized = true;
@@ -164,6 +205,22 @@ export class McpClient {
164
205
  }
165
206
  return;
166
207
  }
208
+ if (isNotification(message) && message.method === 'notifications/cancelled') {
209
+ const requestId = message.params?.requestId;
210
+ if (requestId !== undefined)
211
+ this.answering.get(requestId)?.abort(new McpError('The server cancelled its request'));
212
+ return;
213
+ }
214
+ if (isNotification(message) && this.listeners.has(message.method)) {
215
+ for (const handler of this.listeners.get(message.method) ?? []) {
216
+ try {
217
+ handler((message.params ?? {}));
218
+ }
219
+ catch {
220
+ }
221
+ }
222
+ return;
223
+ }
167
224
  if (isNotification(message) && message.method === 'notifications/progress') {
168
225
  const params = (message.params ?? {});
169
226
  const report = this.progress.get(String(params.progressToken));
@@ -174,12 +231,49 @@ export class McpClient {
174
231
  });
175
232
  return;
176
233
  }
177
- if (isRequest(message)) {
178
- void this.transport.send({
234
+ if (isRequest(message))
235
+ void this.answer(message.id, message.method, message.params);
236
+ }
237
+ async answer(id, method, params) {
238
+ const controller = new AbortController();
239
+ this.answering.set(id, controller);
240
+ const handlers = {
241
+ ping: () => ({}),
242
+ 'sampling/createMessage': this.options.sampling
243
+ ? () => this.options.sampling(params, {
244
+ signal: controller.signal,
245
+ })
246
+ : undefined,
247
+ 'elicitation/create': this.options.elicitation
248
+ ? () => this.options.elicitation(params, { signal: controller.signal })
249
+ : undefined,
250
+ 'roots/list': this.options.roots
251
+ ? async () => ({ roots: await this.options.roots() })
252
+ : undefined,
253
+ };
254
+ const handler = Object.hasOwn(handlers, method) ? handlers[method] : undefined;
255
+ try {
256
+ if (!handler)
257
+ throw new McpError(`This client does not implement "${method}"`, -32_601);
258
+ const result = await handler();
259
+ if (controller.signal.aborted)
260
+ return;
261
+ await this.transport.send({ jsonrpc: '2.0', id, result });
262
+ }
263
+ catch (error) {
264
+ if (controller.signal.aborted)
265
+ return;
266
+ await Promise.resolve(this.transport.send({
179
267
  jsonrpc: '2.0',
180
- id: message.id,
181
- error: { code: -32_601, message: `This client does not implement "${message.method}"` },
182
- });
268
+ id,
269
+ error: {
270
+ code: error instanceof McpError ? error.code : -32_603,
271
+ message: error instanceof Error ? error.message : String(error),
272
+ },
273
+ })).catch(() => undefined);
274
+ }
275
+ finally {
276
+ this.answering.delete(id);
183
277
  }
184
278
  }
185
279
  settle(id, result, error) {
@@ -303,39 +397,96 @@ export function createHttpTransport(options) {
303
397
  const fetchImplementation = options.fetch ?? globalThis.fetch;
304
398
  let onMessage = () => undefined;
305
399
  let sessionId;
400
+ let listening;
401
+ let refused = false;
402
+ const deliver = (data) => {
403
+ try {
404
+ onMessage(JSON.parse(data));
405
+ }
406
+ catch {
407
+ }
408
+ };
409
+ const headers = () => ({
410
+ 'mcp-protocol-version': MCP_PROTOCOL_VERSION,
411
+ ...(sessionId ? { 'mcp-session-id': sessionId } : {}),
412
+ ...options.headers,
413
+ });
414
+ const listen = () => {
415
+ if (!options.listen || !sessionId || listening || refused)
416
+ return;
417
+ const own = new AbortController();
418
+ listening = own;
419
+ void (async () => {
420
+ try {
421
+ const response = await fetchImplementation(options.url, {
422
+ method: 'GET',
423
+ headers: { accept: 'text/event-stream', ...headers() },
424
+ signal: own.signal,
425
+ });
426
+ if (response.status === 405)
427
+ refused = true;
428
+ if (!response.ok || !response.body) {
429
+ await response.body?.cancel().catch(() => undefined);
430
+ return;
431
+ }
432
+ await readEvents(response.body.getReader(), (_name, data) => deliver(data));
433
+ }
434
+ catch {
435
+ }
436
+ finally {
437
+ if (listening === own)
438
+ listening = undefined;
439
+ }
440
+ })();
441
+ };
306
442
  return {
307
443
  async send(message) {
308
444
  const response = await fetchImplementation(options.url, {
309
445
  method: 'POST',
310
- headers: {
311
- 'content-type': 'application/json',
312
- accept: 'application/json, text/event-stream',
313
- ...(sessionId ? { 'mcp-session-id': sessionId } : {}),
314
- ...options.headers,
315
- },
446
+ headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', ...headers() },
316
447
  body: JSON.stringify(message),
317
448
  });
318
449
  sessionId = response.headers.get('mcp-session-id') ?? sessionId;
319
- if (!response.ok)
450
+ if (response.status === 404 && sessionId) {
451
+ sessionId = undefined;
452
+ await response.body?.cancel().catch(() => undefined);
453
+ throw new McpError('The MCP server ended the session');
454
+ }
455
+ if (!response.ok) {
456
+ await response.body?.cancel().catch(() => undefined);
320
457
  throw new McpError(`MCP HTTP transport failed with ${response.status}`);
321
- if (response.status === 202)
458
+ }
459
+ listen();
460
+ if (response.status === 202 || !response.body)
461
+ return;
462
+ if ((response.headers.get('content-type') ?? '').includes('text/event-stream')) {
463
+ void readEvents(response.body.getReader(), (_name, data) => deliver(data)).catch(() => undefined);
322
464
  return;
465
+ }
323
466
  const body = await response.text();
324
467
  if (!body.trim())
325
468
  return;
326
469
  for (const payload of body.startsWith('event:') || body.startsWith('data:') ? sseData(body) : [body]) {
327
- try {
328
- onMessage(JSON.parse(payload));
329
- }
330
- catch {
331
- }
470
+ deliver(payload);
332
471
  }
333
472
  },
334
473
  onMessage(handler) {
335
474
  onMessage = handler;
336
475
  },
337
- close() {
476
+ async close() {
477
+ listening?.abort();
478
+ listening = undefined;
479
+ const ending = sessionId;
338
480
  sessionId = undefined;
481
+ refused = false;
482
+ if (ending) {
483
+ await fetchImplementation(options.url, {
484
+ method: 'DELETE',
485
+ headers: { ...options.headers, 'mcp-protocol-version': MCP_PROTOCOL_VERSION, 'mcp-session-id': ending },
486
+ })
487
+ .then((response) => response.body?.cancel())
488
+ .catch(() => undefined);
489
+ }
339
490
  },
340
491
  };
341
492
  }
@@ -0,0 +1,45 @@
1
+ import type { McpServer } from './server.js';
2
+ /** Options for `mcpHttpHandler()`. */
3
+ export interface McpHttpOptions {
4
+ /**
5
+ * Origins allowed to call it from a browser, beside the endpoint's own: a request whose `Origin`
6
+ * header names any other is refused with 403, so a web page cannot reach a server on this machine
7
+ * through the person's browser.
8
+ */
9
+ allowedOrigins?: readonly string[];
10
+ /**
11
+ * Answers each request as JSON, after the server finishes, rather than as a stream of events. What
12
+ * the server sends meanwhile then waits for the session's `GET` stream.
13
+ */
14
+ json?: boolean;
15
+ /** How long a session lasts without a request, in milliseconds. Defaults to 30 minutes. */
16
+ sessionTtlMs?: number;
17
+ /** The most sessions held at once: a new one past it is refused with 503. Defaults to 1,000. */
18
+ maxSessions?: number;
19
+ /** Makes a new session's id. Defaults to a random UUID. */
20
+ sessionId?: () => string;
21
+ }
22
+ /** A Web handler serving an MCP server over streamable HTTP, and what it holds. */
23
+ export interface McpHttpHandler {
24
+ /** Answers one HTTP request: a `POST` of a message, a `GET` stream, or a `DELETE` of a session. */
25
+ (request: Request): Promise<Response>;
26
+ /** How many sessions are open. */
27
+ sessions(): number;
28
+ /** Ends every session. */
29
+ close(): Promise<void>;
30
+ }
31
+ /**
32
+ * Serves an MCP server over streamable HTTP, MCP's transport since 2025-03-26: each client that sends
33
+ * `initialize` gets a session, named in the `Mcp-Session-Id` header it sends from then on. A request is
34
+ * answered as a stream of events, which carries the server's progress and its questions for the
35
+ * client, such as a tool's `context.mcp.createMessage()`, before the answer; with `json`, as JSON. A
36
+ * notification or an answer the client sends is taken with 202. A `GET` holds the session's own stream,
37
+ * for what the server sends of its own accord, and a `DELETE` ends the session. A request from a web
38
+ * page on another origin is refused, unless `allowedOrigins` names it.
39
+ *
40
+ * ```ts
41
+ * const handle = mcpHttpHandler(new McpServer({ tools: [search] }));
42
+ * Bun.serve({ fetch: handle }); // or any server that speaks Request and Response
43
+ * ```
44
+ */
45
+ export declare function mcpHttpHandler(server: McpServer, options?: McpHttpOptions): McpHttpHandler;