nexus-ai-pro 2.16.0 → 2.17.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/API_STABILITY.md +22 -0
- package/CHANGELOG.md +54 -1
- package/README.md +6 -4
- package/dist/agent/capabilities.d.ts +3 -0
- package/dist/mcp/client.d.ts +62 -3
- package/dist/mcp/client.js +154 -16
- package/dist/mcp/index.d.ts +2 -2
- package/dist/mcp/index.js +2 -2
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.js +24 -5
- package/dist/protocols/acp-editor.d.ts +16 -2
- package/dist/protocols/acp-editor.js +21 -9
- package/dist/protocols/acp.d.ts +8 -0
- package/dist/protocols/acp.js +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/video/google.d.ts +42 -11
- package/dist/video/google.js +249 -20
- package/dist/video/index.d.ts +49 -6
- package/dist/video/index.js +91 -15
- package/dist/video/mock.js +10 -1
- package/dist/video/runway.d.ts +8 -1
- package/dist/video/runway.js +68 -2
- package/dist-cjs/agent/capabilities.d.ts +3 -0
- package/dist-cjs/mcp/client.d.ts +62 -3
- package/dist-cjs/mcp/client.js +156 -16
- package/dist-cjs/mcp/index.d.ts +2 -2
- package/dist-cjs/mcp/index.js +4 -1
- package/dist-cjs/mcp/server.d.ts +12 -0
- package/dist-cjs/mcp/server.js +25 -5
- package/dist-cjs/protocols/acp-editor.d.ts +16 -2
- package/dist-cjs/protocols/acp-editor.js +20 -8
- package/dist-cjs/protocols/acp.d.ts +8 -0
- package/dist-cjs/protocols/acp.js +1 -0
- package/dist-cjs/version.d.ts +1 -1
- package/dist-cjs/version.js +1 -1
- package/dist-cjs/video/google.d.ts +42 -11
- package/dist-cjs/video/google.js +248 -19
- package/dist-cjs/video/index.d.ts +49 -6
- package/dist-cjs/video/index.js +91 -15
- package/dist-cjs/video/mock.js +10 -1
- package/dist-cjs/video/runway.d.ts +8 -1
- package/dist-cjs/video/runway.js +68 -2
- package/package.json +1 -1
package/API_STABILITY.md
CHANGED
|
@@ -41,6 +41,28 @@ Both are covered by this policy:
|
|
|
41
41
|
Deep imports into `dist`, `dist-cjs`, or `src` are not supported. Import only from `nexus-ai-pro` or
|
|
42
42
|
one of its explicit subpaths.
|
|
43
43
|
|
|
44
|
+
## 2.17 stage (2.17.0)
|
|
45
|
+
|
|
46
|
+
Everything 2.17 adds is stable from the start and additive, unless this section says otherwise.
|
|
47
|
+
|
|
48
|
+
New and stable:
|
|
49
|
+
- in `nexus-ai-pro/mcp`: `createSseTransport()`, `SseClientOptions`, `mcpToolCapabilities()`,
|
|
50
|
+
`mcpToolAnnotations()`, `McpToolAnnotations`, `McpToolsOptions`, and `McpToolDescriptor.annotations`;
|
|
51
|
+
the capability kinds `mcp.read`, `mcp.write`, and `mcp.destructive`;
|
|
52
|
+
- in `nexus-ai-pro/protocols/acp/editor`: `AcpEditorOptions.mcpToolCapabilities` and
|
|
53
|
+
`AcpEditor.mcpCapabilities`; in `nexus-ai-pro/protocols/acp`: `AcpEditorBridge.mcpCapabilities`;
|
|
54
|
+
- in `nexus-ai-pro/video`, experimental with the family: `VideoSource`, `VideoGenerateRequest.video`,
|
|
55
|
+
`VideoProviderRequest.video`, the `VideoInputKind` values `extend` and `edit`,
|
|
56
|
+
`VideoModelCapabilities.extendDurations`, `requires`, and `continuesJobs`,
|
|
57
|
+
`VideoProviderCapabilities.inputVideoMimeTypes`, and `VideoConformanceOptions.video`; Gemini Omni
|
|
58
|
+
Flash in `nexus-ai-pro/video/google`, and video to video in `nexus-ai-pro/video/runway`.
|
|
59
|
+
|
|
60
|
+
Changed on purpose: `acpEditor()` reads MCP tool annotations by default. A policy that allowed the
|
|
61
|
+
editor's MCP tools as undeclared (`undeclared: 'allow'`) now decides annotated tools by their
|
|
62
|
+
capabilities; `mcpToolCapabilities: false` keeps the earlier decisions. `GoogleVideoProvider`'s
|
|
63
|
+
default models follow Google's shutdown of the Veo 3.1 previews: Gemini Omni Flash on the Gemini API,
|
|
64
|
+
and the generally available Veo 3.1 models on Vertex AI.
|
|
65
|
+
|
|
44
66
|
## 2.16 stage (2.16.0)
|
|
45
67
|
|
|
46
68
|
Everything 2.16 adds is stable from the start and additive, unless this section says otherwise.
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,58 @@ Notable changes to this project are recorded here. The format follows [Keep a Ch
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [2.17.0] - 2026-10-21
|
|
8
|
+
|
|
9
|
+
The editor's MCP servers over every transport, decided by what their servers say they do; and video
|
|
10
|
+
extended or edited on every provider, with Gemini Omni Flash on Google's Interactions API.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Video extended or edited.** A `VideoGenerateRequest` takes a `video`, a `VideoSource`: `extend`
|
|
15
|
+
continues it, and `edit` changes it as the prompt says. The video is an input (bytes, a URL, or a
|
|
16
|
+
stored asset), or `from` a finished job of the same provider, which continues from what it holds
|
|
17
|
+
with nothing uploaded. Models declare `extend` and `edit` among their `inputs`, with
|
|
18
|
+
`extendDurations`, `continuesJobs`, and `requires`, and providers their `inputVideoMimeTypes`;
|
|
19
|
+
anything a model cannot do is refused before it is sent, and the input travels with a durable
|
|
20
|
+
generation. The conformance suite checks extending and editing, and continuing a job.
|
|
21
|
+
- **Gemini Omni Flash on the Interactions API.** `GoogleVideoProvider` serves `gemini-omni-1.1-flash`
|
|
22
|
+
as a background interaction: 3 to 10 seconds at 360p to 4k, from a prompt, a first frame, or
|
|
23
|
+
reference images, extending and editing videos, continuing its own interactions by id, cancelled and
|
|
24
|
+
deleted with them. It is priced at its 720p token rate.
|
|
25
|
+
- **Veo extends on Vertex AI.** Veo 3.1 extends a video in Cloud Storage by seven seconds, or a job of
|
|
26
|
+
its own when `storageUri` keeps its videos there, and makes up to four videos a request.
|
|
27
|
+
- **Runway's video to video.** `gemini_omni_flash_1.1` extends and edits, `gemini_omni_flash` edits,
|
|
28
|
+
and `aleph2`, now among the models, only edits, from a video by URL, inline up to 5 MB, or a task's
|
|
29
|
+
own video.
|
|
30
|
+
|
|
31
|
+
- **MCP over SSE.** `createSseTransport()`, on `nexus-ai-pro/mcp`, speaks to a server over HTTP with
|
|
32
|
+
Server-Sent Events, MCP's transport before streamable HTTP: one `GET` on the event stream, a `POST`
|
|
33
|
+
per message to the endpoint the stream names, kept to the stream's origin. An ACP agent connects the
|
|
34
|
+
SSE servers an editor offers, beside stdio and HTTP.
|
|
35
|
+
- **MCP tools that declare what they do.** `toNexusTools({ capabilities: true })` gives each tool the
|
|
36
|
+
capabilities its server's annotations describe, from `mcpToolCapabilities()`: `mcp.read`,
|
|
37
|
+
`mcp.write`, or `mcp.destructive` under the server's name, and `network` for one that reaches
|
|
38
|
+
beyond it, with the specification's defaults for a hint left out. A permission policy then decides a
|
|
39
|
+
server's tools as it decides the agent's own: `{ custom: { 'mcp.read': 'allow' } }` runs a lookup
|
|
40
|
+
and refuses a delete, with no rule naming either. A function decides per tool instead.
|
|
41
|
+
- **The package's MCP server says what its tools do.** `McpServer` lists each tool with the
|
|
42
|
+
annotations its capabilities give it, from `mcpToolAnnotations()`, never claiming less than the
|
|
43
|
+
tool may do.
|
|
44
|
+
|
|
45
|
+
### Changed
|
|
46
|
+
|
|
47
|
+
- **Google's video defaults follow its shutdown of the Veo 3.1 previews on 2026-10-22.** On the Gemini
|
|
48
|
+
API, `GoogleVideoProvider` serves Gemini Omni Flash by default; on Vertex AI, the generally
|
|
49
|
+
available `veo-3.1-generate-001` and `veo-3.1-fast-generate-001`, and `veo-3.1-lite-generate-001`.
|
|
50
|
+
Name the preview models in `models` to keep them until Google stops serving them.
|
|
51
|
+
- `acpEditor()` reads its MCP servers' annotations by default (`mcpToolCapabilities`), so an
|
|
52
|
+
annotated tool is decided by its capabilities rather than as undeclared; `mcpToolCapabilities: false`
|
|
53
|
+
keeps the 2.16 behaviour. A tool its server says nothing about is undeclared, as before.
|
|
54
|
+
- The ACP agent tells the editor, in `initialize`, that it connects MCP servers over HTTP and SSE, so
|
|
55
|
+
an editor that follows the protocol offers them; before, it offered only stdio servers.
|
|
56
|
+
- The nightly interoperability check offers the ACP agent MCP servers built with
|
|
57
|
+
`@modelcontextprotocol/sdk` over stdio, HTTP, and SSE.
|
|
58
|
+
|
|
7
59
|
## [2.16.0] - 2026-10-20
|
|
8
60
|
|
|
9
61
|
A2A on all three of its bindings over one set of tasks, and webhooks told the answer as it is written.
|
|
@@ -3051,7 +3103,8 @@ shared policy vocabulary is in place for a future release that revisits this.
|
|
|
3051
3103
|
- URL fetching rejects unsafe private-network targets and limits response reads.
|
|
3052
3104
|
- CLI and audit findings no longer disclose detected secret values.
|
|
3053
3105
|
|
|
3054
|
-
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.
|
|
3106
|
+
[Unreleased]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.17.0...HEAD
|
|
3107
|
+
[2.17.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.16.0...v2.17.0
|
|
3055
3108
|
[2.16.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.15.1...v2.16.0
|
|
3056
3109
|
[2.15.1]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.15.0...v2.15.1
|
|
3057
3110
|
[2.15.0]: https://github.com/mkhitar-abrahamyan/nexus-ai/compare/v2.14.0...v2.15.0
|
package/README.md
CHANGED
|
@@ -324,13 +324,15 @@ Shipped so far:
|
|
|
324
324
|
[page](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/COMPATIBILITY.md) lists them;
|
|
325
325
|
- 2.16 A2A over every binding: one agent served over JSON-RPC, HTTP+JSON, and gRPC from one task
|
|
326
326
|
store, so a caller on any binding continues a task another started, and webhooks told the answer
|
|
327
|
-
as it is written
|
|
327
|
+
as it is written;
|
|
328
|
+
- 2.17 the editor's MCP servers over every transport, and video you can extend or edit: an ACP agent
|
|
329
|
+
connects stdio, HTTP, and SSE servers and decides each tool by what its server says it does, and a
|
|
330
|
+
video is extended from its own job or edited on Gemini Omni Flash, Veo, and Runway.
|
|
328
331
|
|
|
329
332
|
Upgrading from 1.x: `npx nexus migrate src --write` moves your imports, and
|
|
330
333
|
[MIGRATING.md](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/MIGRATING.md) covers the rest.
|
|
331
|
-
Next, 2.
|
|
332
|
-
|
|
333
|
-
each. Pinecone, images, the Gemini
|
|
334
|
+
Next, 2.18 records events and links on spans, as OpenTelemetry has them, and reads the length of
|
|
335
|
+
compressed audio and video, so an evaluation holds a clip to its duration. Pinecone, images, the Gemini
|
|
334
336
|
embedding batch tier, media understanding, video, Gemini Live, SIP, and both phone providers
|
|
335
337
|
graduate once the live workflow passes against each. The
|
|
336
338
|
[roadmap](https://github.com/mkhitar-abrahamyan/nexus-ai/blob/main/ROADMAP.md) lists what comes after, in the order
|
|
@@ -9,6 +9,9 @@ import type { ToolDefinition } from '../types/messages.js';
|
|
|
9
9
|
* | `network`, `network:<host or URL>` | Opens a network connection, to a host when given |
|
|
10
10
|
* | `shell`, `shell:<command line>` | Runs a shell command |
|
|
11
11
|
* | `code` | Runs code it was given, as an interpreter does |
|
|
12
|
+
* | `mcp.read:<server>/<tool>` | An MCP tool its server says only reads |
|
|
13
|
+
* | `mcp.write:<server>/<tool>` | An MCP tool its server says changes things but destroys nothing |
|
|
14
|
+
* | `mcp.destructive:<server>/<tool>` | An MCP tool its server says may destroy or overwrite |
|
|
12
15
|
* | `<name>`, `<name>:<detail>` | Anything the application names, such as `payments:refund` |
|
|
13
16
|
*
|
|
14
17
|
* A tool that declares `capabilities: []` does nothing to the world; one that declares none at all
|
package/dist/mcp/client.d.ts
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import type { ToolDefinition } from '../types/messages.js';
|
|
2
2
|
import { type McpTransport } from './protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* What a server says a tool does, as MCP's tool annotations: hints, which a client trusts only as far
|
|
5
|
+
* as it trusts the server. A hint left out takes the specification's default.
|
|
6
|
+
*/
|
|
7
|
+
export interface McpToolAnnotations {
|
|
8
|
+
/** A name for people. */
|
|
9
|
+
title?: string;
|
|
10
|
+
/** It changes nothing. Defaults to false. */
|
|
11
|
+
readOnlyHint?: boolean;
|
|
12
|
+
/** When it changes things, it may destroy or overwrite them, not only add. Defaults to true. */
|
|
13
|
+
destructiveHint?: boolean;
|
|
14
|
+
/** Calling it again with the same arguments changes nothing more. Defaults to false. */
|
|
15
|
+
idempotentHint?: boolean;
|
|
16
|
+
/** It reaches things beyond the server, such as the web. Defaults to true. */
|
|
17
|
+
openWorldHint?: boolean;
|
|
18
|
+
}
|
|
3
19
|
/** A tool an MCP server offers. */
|
|
4
20
|
export interface McpToolDescriptor {
|
|
5
21
|
/** The tool's name. */
|
|
@@ -8,6 +24,33 @@ export interface McpToolDescriptor {
|
|
|
8
24
|
description?: string;
|
|
9
25
|
/** JSON Schema for the arguments. */
|
|
10
26
|
inputSchema?: Record<string, unknown>;
|
|
27
|
+
/** What the server says the tool does. */
|
|
28
|
+
annotations?: McpToolAnnotations;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The capabilities an MCP tool declares, from what its server says of it in its annotations, so a
|
|
32
|
+
* permission policy decides it as it decides any tool:
|
|
33
|
+
* - `mcp.read:<server>/<tool>` for a tool that only reads;
|
|
34
|
+
* - `mcp.write:<server>/<tool>` for one that changes things but destroys nothing;
|
|
35
|
+
* - `mcp.destructive:<server>/<tool>` for one that may destroy or overwrite;
|
|
36
|
+
* - and `network`, beside it, for one that reaches beyond its server.
|
|
37
|
+
*
|
|
38
|
+
* A hint left out takes the specification's default: not read-only, destructive, and open. A tool
|
|
39
|
+
* with no annotations at all declares nothing, `undefined`, so a policy decides it as undeclared.
|
|
40
|
+
*/
|
|
41
|
+
export declare function mcpToolCapabilities(tool: McpToolDescriptor, server: string): string[] | undefined;
|
|
42
|
+
/** Options for `McpClient.toNexusTools()`. */
|
|
43
|
+
export interface McpToolsOptions {
|
|
44
|
+
/** Put before each tool's name with an underscore, so two servers' `search` tools differ. */
|
|
45
|
+
prefix?: string;
|
|
46
|
+
/**
|
|
47
|
+
* The capabilities each tool declares: `true` reads them from the server's annotations, with
|
|
48
|
+
* `mcpToolCapabilities()`, under the server's name (the prefix, or the name the server gives); a
|
|
49
|
+
* function decides them per tool. Without it, a tool declares none, as before 2.17.
|
|
50
|
+
*/
|
|
51
|
+
capabilities?: boolean | ((tool: McpToolDescriptor) => readonly string[] | undefined);
|
|
52
|
+
/** The server's name in each capability, when `capabilities` is `true`. Defaults to the prefix. */
|
|
53
|
+
server?: string;
|
|
11
54
|
}
|
|
12
55
|
/** A resource an MCP server offers. */
|
|
13
56
|
export interface McpResourceDescriptor {
|
|
@@ -109,9 +152,7 @@ export declare class McpClient {
|
|
|
109
152
|
* Names are prefixed when asked, because two servers can both offer `search` and a model has to be
|
|
110
153
|
* able to tell them apart.
|
|
111
154
|
*/
|
|
112
|
-
toNexusTools(options?:
|
|
113
|
-
prefix?: string;
|
|
114
|
-
}): Promise<ToolDefinition[]>;
|
|
155
|
+
toNexusTools(options?: McpToolsOptions): Promise<ToolDefinition[]>;
|
|
115
156
|
/** Closes the connection and rejects every request still waiting. */
|
|
116
157
|
close(): Promise<void>;
|
|
117
158
|
private request;
|
|
@@ -168,3 +209,21 @@ export interface HttpClientOptions {
|
|
|
168
209
|
* calling a tool needs.
|
|
169
210
|
*/
|
|
170
211
|
export declare function createHttpTransport(options: HttpClientOptions): McpTransport;
|
|
212
|
+
/** Connects to a remote MCP server over HTTP with Server-Sent Events, the transport before streamable HTTP. */
|
|
213
|
+
export interface SseClientOptions {
|
|
214
|
+
/** The server's event stream: a `GET` here opens it, and the server names where to post messages. */
|
|
215
|
+
url: string;
|
|
216
|
+
/** Headers sent with the stream's request and with every message, such as authorization. */
|
|
217
|
+
headers?: Record<string, string>;
|
|
218
|
+
/** Replaces the global `fetch`. */
|
|
219
|
+
fetch?: typeof globalThis.fetch;
|
|
220
|
+
/** How long to wait for the server to name where messages go, in milliseconds. Defaults to 10 seconds. */
|
|
221
|
+
timeoutMs?: number;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Speaks to an MCP server over HTTP with Server-Sent Events, as MCP's 2024-11-05 transport: one
|
|
225
|
+
* long `GET` the server answers on, and a `POST` per message to the endpoint the stream names
|
|
226
|
+
* first. The endpoint must be on the stream's own origin, so a server cannot send the messages, and
|
|
227
|
+
* their headers, anywhere else. When the stream ends, the transport closes.
|
|
228
|
+
*/
|
|
229
|
+
export declare function createSseTransport(options: SseClientOptions): McpTransport;
|
package/dist/mcp/client.js
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { isNotification, isRequest, isResponse, LineDecoder, MCP_PROTOCOL_VERSION, McpError, } from './protocol.js';
|
|
2
|
+
export function mcpToolCapabilities(tool, server) {
|
|
3
|
+
const hints = tool.annotations;
|
|
4
|
+
if (!hints || typeof hints !== 'object')
|
|
5
|
+
return undefined;
|
|
6
|
+
const effect = hints.readOnlyHint === true ? 'mcp.read' : hints.destructiveHint === false ? 'mcp.write' : 'mcp.destructive';
|
|
7
|
+
return [`${effect}:${server}/${tool.name}`, ...(hints.openWorldHint === false ? [] : ['network'])];
|
|
8
|
+
}
|
|
2
9
|
export class McpClient {
|
|
3
10
|
transport;
|
|
4
11
|
options;
|
|
@@ -79,22 +86,34 @@ export class McpClient {
|
|
|
79
86
|
}
|
|
80
87
|
async toNexusTools(options = {}) {
|
|
81
88
|
const tools = await this.listTools();
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
const server = options.server ?? options.prefix ?? this.serverInfo.name ?? 'mcp';
|
|
90
|
+
const declared = (descriptor) => {
|
|
91
|
+
const { capabilities } = options;
|
|
92
|
+
if (!capabilities)
|
|
93
|
+
return undefined;
|
|
94
|
+
const list = capabilities === true ? mcpToolCapabilities(descriptor, server) : capabilities(descriptor);
|
|
95
|
+
return list === undefined ? undefined : [...list];
|
|
96
|
+
};
|
|
97
|
+
return tools.map((descriptor) => {
|
|
98
|
+
const capabilities = declared(descriptor);
|
|
99
|
+
return {
|
|
100
|
+
name: options.prefix ? `${options.prefix}_${descriptor.name}` : descriptor.name,
|
|
101
|
+
description: descriptor.description ?? `MCP tool ${descriptor.name}`,
|
|
102
|
+
parameters: descriptor.inputSchema ?? { type: 'object', properties: {} },
|
|
103
|
+
...(capabilities ? { capabilities } : {}),
|
|
104
|
+
execute: async (args, context) => {
|
|
105
|
+
const progress = context?.progress;
|
|
106
|
+
const result = await this.callTool(descriptor.name, args, progress ? { progress } : {});
|
|
107
|
+
const text = result.content
|
|
108
|
+
.map((part) => part.text ?? (part.type === 'text' ? '' : JSON.stringify(part)))
|
|
109
|
+
.filter(Boolean)
|
|
110
|
+
.join('\n');
|
|
111
|
+
if (result.isError)
|
|
112
|
+
throw new McpError(text || `MCP tool ${descriptor.name} failed`);
|
|
113
|
+
return text;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
});
|
|
98
117
|
}
|
|
99
118
|
async close() {
|
|
100
119
|
this.failAll(new McpError('The MCP client was closed'));
|
|
@@ -297,6 +316,125 @@ export function createHttpTransport(options) {
|
|
|
297
316
|
},
|
|
298
317
|
};
|
|
299
318
|
}
|
|
319
|
+
export function createSseTransport(options) {
|
|
320
|
+
const fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
321
|
+
let onMessage = () => undefined;
|
|
322
|
+
const onClose = [];
|
|
323
|
+
let endpoint;
|
|
324
|
+
let opening;
|
|
325
|
+
let closed = false;
|
|
326
|
+
const stream = new AbortController();
|
|
327
|
+
const ended = () => {
|
|
328
|
+
if (closed)
|
|
329
|
+
return;
|
|
330
|
+
closed = true;
|
|
331
|
+
for (const handler of onClose)
|
|
332
|
+
handler();
|
|
333
|
+
};
|
|
334
|
+
const open = async () => {
|
|
335
|
+
const response = await fetchImplementation(options.url, {
|
|
336
|
+
headers: { accept: 'text/event-stream', ...options.headers },
|
|
337
|
+
signal: stream.signal,
|
|
338
|
+
});
|
|
339
|
+
if (!response.ok || !response.body)
|
|
340
|
+
throw new McpError(`MCP SSE transport failed with ${response.status}`);
|
|
341
|
+
const reader = response.body.getReader();
|
|
342
|
+
await new Promise((resolve, reject) => {
|
|
343
|
+
const timer = setTimeout(() => {
|
|
344
|
+
reject(new McpError(`The MCP server named no endpoint within ${options.timeoutMs ?? 10_000}ms`));
|
|
345
|
+
stream.abort();
|
|
346
|
+
}, options.timeoutMs ?? 10_000);
|
|
347
|
+
const event = (name, data) => {
|
|
348
|
+
if (name === 'endpoint') {
|
|
349
|
+
if (endpoint)
|
|
350
|
+
return;
|
|
351
|
+
const target = new URL(data.trim(), options.url);
|
|
352
|
+
if (target.origin !== new URL(options.url).origin) {
|
|
353
|
+
clearTimeout(timer);
|
|
354
|
+
reject(new McpError(`The MCP server named an endpoint on another origin, ${target.origin}`));
|
|
355
|
+
stream.abort();
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
endpoint = target.href;
|
|
359
|
+
clearTimeout(timer);
|
|
360
|
+
resolve();
|
|
361
|
+
}
|
|
362
|
+
else if (name === 'message') {
|
|
363
|
+
try {
|
|
364
|
+
onMessage(JSON.parse(data));
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
void readEvents(reader, event).then(() => {
|
|
371
|
+
clearTimeout(timer);
|
|
372
|
+
reject(new McpError('The MCP event stream ended'));
|
|
373
|
+
ended();
|
|
374
|
+
}, (error) => {
|
|
375
|
+
clearTimeout(timer);
|
|
376
|
+
reject(error instanceof McpError ? error : new McpError(`The MCP event stream failed: ${String(error)}`));
|
|
377
|
+
ended();
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
const started = () => {
|
|
382
|
+
opening ??= open();
|
|
383
|
+
return opening;
|
|
384
|
+
};
|
|
385
|
+
return {
|
|
386
|
+
start: started,
|
|
387
|
+
async send(message) {
|
|
388
|
+
await started();
|
|
389
|
+
if (closed || !endpoint)
|
|
390
|
+
throw new McpError('The MCP event stream is closed');
|
|
391
|
+
const response = await fetchImplementation(endpoint, {
|
|
392
|
+
method: 'POST',
|
|
393
|
+
headers: { 'content-type': 'application/json', ...options.headers },
|
|
394
|
+
body: JSON.stringify(message),
|
|
395
|
+
});
|
|
396
|
+
await response.body?.cancel().catch(() => undefined);
|
|
397
|
+
if (!response.ok)
|
|
398
|
+
throw new McpError(`MCP SSE transport failed with ${response.status}`);
|
|
399
|
+
},
|
|
400
|
+
onMessage(handler) {
|
|
401
|
+
onMessage = handler;
|
|
402
|
+
},
|
|
403
|
+
onClose(handler) {
|
|
404
|
+
onClose.push(handler);
|
|
405
|
+
},
|
|
406
|
+
close() {
|
|
407
|
+
stream.abort();
|
|
408
|
+
ended();
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
async function readEvents(reader, event) {
|
|
413
|
+
const decoder = new TextDecoder();
|
|
414
|
+
let buffer = '';
|
|
415
|
+
while (true) {
|
|
416
|
+
const { value, done } = await reader.read();
|
|
417
|
+
if (done)
|
|
418
|
+
return;
|
|
419
|
+
buffer += decoder.decode(value, { stream: true }).replace(/\r\n?/g, '\n');
|
|
420
|
+
let boundary = buffer.indexOf('\n\n');
|
|
421
|
+
while (boundary !== -1) {
|
|
422
|
+
const frame = buffer.slice(0, boundary);
|
|
423
|
+
buffer = buffer.slice(boundary + 2);
|
|
424
|
+
let name = 'message';
|
|
425
|
+
const data = [];
|
|
426
|
+
for (const line of frame.split('\n')) {
|
|
427
|
+
if (line.startsWith('event:'))
|
|
428
|
+
name = line.slice(6).trim();
|
|
429
|
+
else if (line.startsWith('data:'))
|
|
430
|
+
data.push(line.slice(5).replace(/^ /, ''));
|
|
431
|
+
}
|
|
432
|
+
if (data.length)
|
|
433
|
+
event(name, data.join('\n'));
|
|
434
|
+
boundary = buffer.indexOf('\n\n');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
300
438
|
function sseData(body) {
|
|
301
439
|
return body
|
|
302
440
|
.split('\n')
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { createHttpTransport, createStdioTransport, type HttpClientOptions, McpClient, type McpClientOptions, type McpContent, type McpResourceDescriptor, type McpToolDescriptor, type McpToolResult, type StdioClientOptions, } from './client.js';
|
|
1
|
+
export { createHttpTransport, createSseTransport, createStdioTransport, type HttpClientOptions, McpClient, type McpClientOptions, type McpContent, type McpResourceDescriptor, type McpToolAnnotations, type McpToolDescriptor, type McpToolResult, type McpToolsOptions, mcpToolCapabilities, type SseClientOptions, type StdioClientOptions, } from './client.js';
|
|
2
2
|
export { JSON_RPC_ERRORS, type JsonRpcMessage, type JsonRpcNotification, type JsonRpcRequest, type JsonRpcResponse, LineDecoder, MCP_PROTOCOL_VERSION, McpError, type McpTransport, } from './protocol.js';
|
|
3
|
-
export { createStdioServerTransport, McpServer, type McpServerOptions } from './server.js';
|
|
3
|
+
export { createStdioServerTransport, McpServer, type McpServerOptions, mcpToolAnnotations } from './server.js';
|
package/dist/mcp/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { createHttpTransport, createStdioTransport, McpClient, } from './client.js';
|
|
1
|
+
export { createHttpTransport, createSseTransport, createStdioTransport, McpClient, mcpToolCapabilities, } from './client.js';
|
|
2
2
|
export { JSON_RPC_ERRORS, LineDecoder, MCP_PROTOCOL_VERSION, McpError, } from './protocol.js';
|
|
3
|
-
export { createStdioServerTransport, McpServer } from './server.js';
|
|
3
|
+
export { createStdioServerTransport, McpServer, mcpToolAnnotations } from './server.js';
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ToolDefinition } from '../types/messages.js';
|
|
2
|
+
import type { McpToolAnnotations } from './client.js';
|
|
2
3
|
import { type JsonRpcMessage, type McpTransport } from './protocol.js';
|
|
3
4
|
/** Options for an MCP server. */
|
|
4
5
|
export interface McpServerOptions {
|
|
@@ -17,6 +18,17 @@ export interface McpServerOptions {
|
|
|
17
18
|
read(): Promise<unknown> | unknown;
|
|
18
19
|
}>;
|
|
19
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* The annotations a tool is served with, from the capabilities it declares, read so a client is never
|
|
23
|
+
* told less than the tool may do:
|
|
24
|
+
* - read-only only when every capability reads, `filesystem:read` or `mcp.read`, or there are none;
|
|
25
|
+
* - not destructive only when everything else it does is `mcp.write`;
|
|
26
|
+
* - closed to the world only when every capability is the filesystem's or an MCP tool's effect.
|
|
27
|
+
*
|
|
28
|
+
* A tool whose capabilities depend on its arguments, or that declares none, is served without
|
|
29
|
+
* annotations, which a client reads as the specification's defaults: it may do anything.
|
|
30
|
+
*/
|
|
31
|
+
export declare function mcpToolAnnotations(tool: Pick<ToolDefinition, 'capabilities'>): McpToolAnnotations | undefined;
|
|
20
32
|
/**
|
|
21
33
|
* Exposes tools over MCP, so other assistants can call them.
|
|
22
34
|
*
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
+
import { parseCapability } from '../agent/capabilities.js';
|
|
1
2
|
import { isNotification, isRequest, JSON_RPC_ERRORS, LineDecoder, MCP_PROTOCOL_VERSION, McpError, textContent, } from './protocol.js';
|
|
3
|
+
const CLOSED = new Set(['filesystem', 'mcp.read', 'mcp.write', 'mcp.destructive']);
|
|
4
|
+
export function mcpToolAnnotations(tool) {
|
|
5
|
+
const declared = tool.capabilities;
|
|
6
|
+
if (!declared || typeof declared === 'function')
|
|
7
|
+
return undefined;
|
|
8
|
+
const parsed = declared.map((capability) => parseCapability(capability));
|
|
9
|
+
const reads = (item) => (item.kind === 'filesystem' && item.access === 'read') || item.kind === 'mcp.read';
|
|
10
|
+
const readOnly = parsed.every(reads);
|
|
11
|
+
return {
|
|
12
|
+
readOnlyHint: readOnly,
|
|
13
|
+
...(readOnly ? {} : { destructiveHint: !parsed.every((item) => reads(item) || item.kind === 'mcp.write') }),
|
|
14
|
+
openWorldHint: !parsed.every((item) => CLOSED.has(item.kind)),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
2
17
|
export class McpServer {
|
|
3
18
|
options;
|
|
4
19
|
tools = new Map();
|
|
@@ -57,11 +72,15 @@ export class McpServer {
|
|
|
57
72
|
return {};
|
|
58
73
|
case 'tools/list':
|
|
59
74
|
return {
|
|
60
|
-
tools: [...this.tools.values()].map((item) =>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
75
|
+
tools: [...this.tools.values()].map((item) => {
|
|
76
|
+
const annotations = mcpToolAnnotations(item);
|
|
77
|
+
return {
|
|
78
|
+
name: item.name,
|
|
79
|
+
description: item.description,
|
|
80
|
+
inputSchema: item.parameters ?? { type: 'object', properties: {} },
|
|
81
|
+
...(annotations ? { annotations } : {}),
|
|
82
|
+
};
|
|
83
|
+
}),
|
|
65
84
|
};
|
|
66
85
|
case 'tools/call': {
|
|
67
86
|
const name = String(params.name ?? '');
|
|
@@ -10,9 +10,11 @@
|
|
|
10
10
|
*
|
|
11
11
|
* A tool is offered only when the editor said it can do that thing, in `initialize`. Each declares
|
|
12
12
|
* what it touches, as `filesystem:read:<path>`, `filesystem:write:<path>`, or `shell:<command>`, so a
|
|
13
|
-
* permission policy decides it like any other tool, and an approval asks the person in the editor.
|
|
13
|
+
* permission policy decides it like any other tool, and an approval asks the person in the editor. An
|
|
14
|
+
* MCP server's tools, over stdio, HTTP, or SSE, declare what their server says they do.
|
|
14
15
|
*/
|
|
15
16
|
import type { ToolSourceContext } from '../agent/create-agent.js';
|
|
17
|
+
import { type McpToolDescriptor } from '../mcp/client.js';
|
|
16
18
|
import type { ToolDefinition } from '../types/messages.js';
|
|
17
19
|
import type { AcpEditorBridge } from './acp.js';
|
|
18
20
|
/** Options for `acpEditor()`. */
|
|
@@ -25,8 +27,15 @@ export interface AcpEditorOptions {
|
|
|
25
27
|
mcp?: boolean;
|
|
26
28
|
/** How long a request to an MCP server may take, in milliseconds. Defaults to 30 seconds. */
|
|
27
29
|
mcpTimeoutMs?: number;
|
|
28
|
-
/** Replaces `fetch` for MCP servers reached over HTTP: a proxy, or a test. */
|
|
30
|
+
/** Replaces `fetch` for MCP servers reached over HTTP or SSE: a proxy, or a test. */
|
|
29
31
|
mcpFetch?: typeof globalThis.fetch;
|
|
32
|
+
/**
|
|
33
|
+
* What an MCP server's tools declare they do, for a permission policy. `true`, the default, reads
|
|
34
|
+
* it from the server's annotations, with `mcpToolCapabilities()`: `mcp.read`, `mcp.write`, or
|
|
35
|
+
* `mcp.destructive` under the server's name, and `network` for a tool that reaches beyond it. A
|
|
36
|
+
* function decides per tool, and `false` declares nothing, as before 2.17.
|
|
37
|
+
*/
|
|
38
|
+
mcpToolCapabilities?: boolean | ((tool: McpToolDescriptor, server: string) => readonly string[] | undefined);
|
|
30
39
|
/** The most terminal output a command returns, in bytes. Defaults to 64 KB. */
|
|
31
40
|
maxOutputBytes?: number;
|
|
32
41
|
/** Receives an MCP server that failed to start, whose tools are then not offered. */
|
|
@@ -34,6 +43,11 @@ export interface AcpEditorOptions {
|
|
|
34
43
|
}
|
|
35
44
|
/** The editor, as tools for an agent. */
|
|
36
45
|
export interface AcpEditor extends AcpEditorBridge {
|
|
46
|
+
/** The MCP transports it connects, which the agent tells the editor in `initialize`. */
|
|
47
|
+
readonly mcpCapabilities: {
|
|
48
|
+
http: boolean;
|
|
49
|
+
sse: boolean;
|
|
50
|
+
};
|
|
37
51
|
/** The tools of one session, for `CreateAgentOptions.toolSource`: the session is the run's thread. */
|
|
38
52
|
toolSource(context: ToolSourceContext): Promise<ToolDefinition[]>;
|
|
39
53
|
/** Closes every MCP server it connected. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createHttpTransport, createStdioTransport, McpClient } from '../mcp/client.js';
|
|
1
|
+
import { createHttpTransport, createSseTransport, createStdioTransport, McpClient, } from '../mcp/client.js';
|
|
2
2
|
export function acpEditor(options = {}) {
|
|
3
3
|
let connection;
|
|
4
4
|
const servers = new Map();
|
|
@@ -9,9 +9,18 @@ export function acpEditor(options = {}) {
|
|
|
9
9
|
connected = (async () => {
|
|
10
10
|
const clients = [];
|
|
11
11
|
const tools = [];
|
|
12
|
+
const declare = options.mcpToolCapabilities ?? true;
|
|
12
13
|
for (const offered of session.mcpServers) {
|
|
13
14
|
const name = String(offered.name ?? 'mcp');
|
|
15
|
+
const prefix = name.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
14
16
|
try {
|
|
17
|
+
const remote = offered.url
|
|
18
|
+
? {
|
|
19
|
+
url: offered.url,
|
|
20
|
+
headers: Object.fromEntries((offered.headers ?? []).map((item) => [item.name, item.value])),
|
|
21
|
+
...(options.mcpFetch ? { fetch: options.mcpFetch } : {}),
|
|
22
|
+
}
|
|
23
|
+
: undefined;
|
|
15
24
|
const transport = offered.command
|
|
16
25
|
? createStdioTransport({
|
|
17
26
|
command: offered.command,
|
|
@@ -19,18 +28,20 @@ export function acpEditor(options = {}) {
|
|
|
19
28
|
env: Object.fromEntries((offered.env ?? []).map((item) => [item.name, item.value])),
|
|
20
29
|
...(session.cwd ? { cwd: session.cwd } : {}),
|
|
21
30
|
})
|
|
22
|
-
:
|
|
23
|
-
?
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
})
|
|
28
|
-
: undefined;
|
|
31
|
+
: remote && offered.type === 'sse'
|
|
32
|
+
? createSseTransport({ ...remote, timeoutMs: options.mcpTimeoutMs ?? 30_000 })
|
|
33
|
+
: remote && (offered.type === 'http' || offered.type === undefined)
|
|
34
|
+
? createHttpTransport(remote)
|
|
35
|
+
: undefined;
|
|
29
36
|
if (!transport)
|
|
30
37
|
throw new Error(`MCP server "${name}" uses a transport this agent does not speak`);
|
|
31
38
|
const client = new McpClient(transport, { timeoutMs: options.mcpTimeoutMs ?? 30_000 });
|
|
32
|
-
tools.push(...(await client.toNexusTools({ prefix: name.replace(/[^A-Za-z0-9_-]/g, '_') })));
|
|
33
39
|
clients.push(client);
|
|
40
|
+
tools.push(...(await client.toNexusTools({
|
|
41
|
+
prefix,
|
|
42
|
+
server: prefix,
|
|
43
|
+
capabilities: typeof declare === 'function' ? (tool) => declare(tool, prefix) : declare,
|
|
44
|
+
})));
|
|
34
45
|
}
|
|
35
46
|
catch (error) {
|
|
36
47
|
options.onError?.(error, name);
|
|
@@ -130,6 +141,7 @@ export function acpEditor(options = {}) {
|
|
|
130
141
|
return list;
|
|
131
142
|
};
|
|
132
143
|
return {
|
|
144
|
+
mcpCapabilities: { http: options.mcp !== false, sse: options.mcp !== false },
|
|
133
145
|
attach(next) {
|
|
134
146
|
connection = next;
|
|
135
147
|
},
|
package/dist/protocols/acp.d.ts
CHANGED
|
@@ -104,6 +104,14 @@ export interface AcpConnection {
|
|
|
104
104
|
}
|
|
105
105
|
/** Something that uses the editor's connection, such as `acpEditor()`. `serveAcp()` attaches it when it starts. */
|
|
106
106
|
export interface AcpEditorBridge {
|
|
107
|
+
/**
|
|
108
|
+
* The MCP transports it connects beyond stdio, which the agent tells the editor in `initialize` so
|
|
109
|
+
* the editor offers servers over them too.
|
|
110
|
+
*/
|
|
111
|
+
readonly mcpCapabilities?: {
|
|
112
|
+
http?: boolean;
|
|
113
|
+
sse?: boolean;
|
|
114
|
+
};
|
|
107
115
|
/** Receives the connection to the editor. */
|
|
108
116
|
attach(connection: AcpConnection): void;
|
|
109
117
|
/** Called when the editor closes its end, or the server is closed: the place to stop what it started. */
|
package/dist/protocols/acp.js
CHANGED
|
@@ -249,6 +249,7 @@ export function serveAcp(graph, streams, options = {}) {
|
|
|
249
249
|
agentCapabilities: {
|
|
250
250
|
loadSession: true,
|
|
251
251
|
promptCapabilities: { image: true, audio: true, embeddedContext: true },
|
|
252
|
+
...(options.editor?.mcpCapabilities ? { mcpCapabilities: options.editor.mcpCapabilities } : {}),
|
|
252
253
|
},
|
|
253
254
|
authMethods: [],
|
|
254
255
|
...(options.agentInfo ? { agentInfo: options.agentInfo } : {}),
|
package/dist/version.d.ts
CHANGED
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const NEXUS_VERSION = '2.
|
|
1
|
+
export const NEXUS_VERSION = '2.17.0';
|