@mantyx/sdk 0.2.0 → 0.4.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/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ### Added
9
+
10
+ - Add output schema
11
+ - Add support for a2a, mcp and local versions
12
+
13
+ ### Fixed
14
+
15
+ - Changelo
16
+
17
+ ## [0.2.0] — 2026-05-04
18
+
8
19
  ### Fixed
9
20
 
10
21
  - Use correct default base url
@@ -21,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
21
32
 
22
33
  ## [0.1.0] — 2026-05-02
23
34
 
24
- [unreleased]: https://github.com/mantyx-io/mantyx-sdk/compare/v0.1.1..HEAD
35
+ [unreleased]: https://github.com/mantyx-io/mantyx-sdk/compare/v0.2.0..HEAD
36
+ [0.2.0]: https://github.com/mantyx-io/mantyx-sdk/compare/v0.1.1..v0.2.0
25
37
  [0.1.1]: https://github.com/mantyx-io/mantyx-sdk/compare/v0.1.0..v0.1.1
26
38
  [0.1.0]: https://github.com/mantyx-io/mantyx-sdk/releases/tag/v0.1.0
package/README.md CHANGED
@@ -6,13 +6,20 @@ locally-executed tools, run them remotely, and stream events back into your
6
6
  process.
7
7
 
8
8
  - LLM loop runs on MANTYX (BYOK or platform-hosted models).
9
- - Server-side tools (`mantyx`, `mantyx_plugin`) execute inside MANTYX.
10
- - Local tools execute inside *your* process; the SDK shuttles inputs and
11
- outputs over an SSE stream + a tool-result POST.
9
+ - Server-resolved tools (`mantyx`, `mantyx_plugin`, `a2a`, `mcp`) execute
10
+ inside MANTYX including remote Agent2Agent peers and remote MCP servers.
11
+ - Client-resolved tools (`local`, `a2a_local`, `mcp_local`) execute inside
12
+ *your* process; the SDK shuttles inputs and outputs over an SSE stream +
13
+ a tool-result POST.
14
+ - Tunable provider thinking via `reasoningLevel` (string anchors or 0–100).
12
15
  - One-shot runs and multi-turn sessions, both with persisted observability.
13
16
  - Authenticated with a single workspace API key.
14
17
 
15
- For background, see the [agent-runs protocol spec](./docs/agent-runs-protocol.md).
18
+ For background, see the [agent-runs protocol spec](./docs/agent-runs-protocol.md)
19
+ and the messaging-layer reference in [`docs/wire-protocol.md`](./docs/wire-protocol.md)
20
+ — the latter pins down the exact `local_tool_call` event shape and the
21
+ resolved data structures (`a2a_local` Agent Card, `mcp_local` `Tool[]`)
22
+ that this SDK ships.
16
23
 
17
24
  ## Install
18
25
 
@@ -22,9 +29,11 @@ npm install @mantyx/sdk zod
22
29
  # or: yarn add @mantyx/sdk zod
23
30
  ```
24
31
 
25
- Requires Node.js 18.17+ (for `fetch` and `ReadableStream`). `zod` is the only
26
- runtime dependency the SDK adds; the rest is the standard library and your
27
- own modules.
32
+ Requires Node.js 18.17+ (for `fetch` and `ReadableStream`). The SDK depends
33
+ on `zod` (parameter schemas) and `@modelcontextprotocol/sdk` (the official
34
+ MCP TypeScript SDK that powers `defineLocalMcp`'s stdio + Streamable HTTP
35
+ transports). The MCP SDK is loaded lazily — apps that never use
36
+ `defineLocalMcp` don't pay its startup cost.
28
37
 
29
38
  ## Quickstart
30
39
 
@@ -103,6 +112,235 @@ Notes:
103
112
  The same `agentId` field works on `client.createSession({ ... })` for
104
113
  multi-turn conversations against a persisted agent.
105
114
 
115
+ ## Agent2Agent delegation
116
+
117
+ Hand a turn off to another agent — either a remote peer MANTYX dials directly
118
+ (`mantyxA2A`) or a peer that only the SDK can reach (`defineLocalA2A`). The
119
+ model addresses both with the same `{ message: string }` argument shape, so
120
+ an agent prompt that uses one works unchanged with the other.
121
+
122
+ `defineLocalA2A` is fully URL-driven: pass the Agent Card URL and the SDK
123
+ takes care of the rest — fetching the card on the first run, shipping it
124
+ inline as part of the spec, and POSTing JSON-RPC `message/send` to the
125
+ card's `url` whenever MANTYX emits a `local_tool_call`. You don't write any
126
+ A2A code yourself.
127
+
128
+ ```ts
129
+ import { MantyxClient, defineLocalA2A, mantyxA2A } from "@mantyx/sdk";
130
+
131
+ const client = new MantyxClient({ apiKey: "...", workspaceSlug: "acme" });
132
+
133
+ await client.runAgent({
134
+ systemPrompt: "You are a helpful router. Delegate billing to billing_agent.",
135
+ prompt: "Why was I charged twice last month?",
136
+ tools: [
137
+ // Public peer MANTYX dials directly.
138
+ mantyxA2A({
139
+ name: "billing_agent",
140
+ description: "Delegate billing questions to the Acme billing agent.",
141
+ agentCardUrl: "https://billing.acme.com/.well-known/agent-card.json",
142
+ headers: { Authorization: `Bearer ${process.env.BILLING_TOKEN}` },
143
+ }),
144
+ // Intranet peer the SDK reaches on MANTYX's behalf — URL only.
145
+ defineLocalA2A({
146
+ name: "intranet_hr",
147
+ agentCardUrl: "https://hr.intranet.acme/.well-known/agent-card.json",
148
+ headers: { Authorization: `Bearer ${process.env.HR_TOKEN}` },
149
+ }),
150
+ ],
151
+ });
152
+ ```
153
+
154
+ The same `headers` are sent on both the card fetch *and* every subsequent
155
+ `message/send` POST, which is typically what intranet peers want. The SDK
156
+ caches the resolved card on the tool ref for the duration of the run /
157
+ session — re-construct the ref to force a refetch.
158
+
159
+ > **Headers and secrets.** The `headers` you pass to `mantyxA2A` are forwarded
160
+ > as-is. For long-lived credentials, register the peer as a workspace
161
+ > `ExternalAgent` instead — those headers support `{{secret:NAME}}`
162
+ > placeholders. Use `mantyxA2A` for short-lived, per-run tokens minted by
163
+ > your application.
164
+
165
+ ### Exposing an agent over A2A
166
+
167
+ The inverse direction also works: wrap a MANTYX agent (ephemeral spec or a
168
+ persisted `agentId`) and serve it as an Agent2Agent peer using the official
169
+ [`@a2a-js/sdk`](https://www.npmjs.com/package/@a2a-js/sdk) library. Other
170
+ agents can then discover it at `/.well-known/agent-card.json` and call
171
+ `message/send` over JSON-RPC — including MANTYX agents elsewhere in your
172
+ estate consuming this one via `mantyxA2A` or `defineLocalA2A`.
173
+
174
+ ```ts
175
+ import { MantyxClient } from "@mantyx/sdk";
176
+ import { serveAgentOverA2A } from "@mantyx/sdk/a2a-server";
177
+
178
+ const client = new MantyxClient({ apiKey: "...", workspaceSlug: "acme" });
179
+
180
+ const handle = await serveAgentOverA2A({
181
+ client,
182
+ agent: { agentId: "agent_cm6abc123" }, // or { systemPrompt, modelId, tools }
183
+ port: 4000,
184
+ agentCard: {
185
+ name: "Acme Support",
186
+ description: "Customer support questions.",
187
+ protocolVersion: "0.3.0",
188
+ version: "1.0.0",
189
+ url: "http://localhost:4000",
190
+ skills: [{ id: "support", name: "Support", tags: ["support"] }],
191
+ capabilities: { streaming: true, pushNotifications: false },
192
+ defaultInputModes: ["text"],
193
+ defaultOutputModes: ["text"],
194
+ },
195
+ });
196
+
197
+ console.log(`A2A peer up on ${handle.url}`);
198
+ // later: await handle.close();
199
+ ```
200
+
201
+ `@a2a-js/sdk` and `express` are declared as **optional peer dependencies**,
202
+ so apps that don't expose an A2A server pay zero bundle cost. Install them
203
+ on demand:
204
+
205
+ ```bash
206
+ npm install @a2a-js/sdk express
207
+ ```
208
+
209
+ Each unique A2A `contextId` opens a long-lived MANTYX session by default, so
210
+ multi-turn `message/send` calls share conversational history. Pass
211
+ `conversation: "stateless"` to reduce every A2A request to a one-shot
212
+ `runAgent` call.
213
+
214
+ For lower-level integration (mounting the executor in your own Express /
215
+ Fastify / Connect app), `@mantyx/sdk/a2a-server` also exports a
216
+ `MantyxAgentExecutor` class implementing `@a2a-js/sdk/server`'s
217
+ `AgentExecutor` interface.
218
+
219
+ ## MCP connectors
220
+
221
+ Expose every tool published by an MCP server to the agent loop in one go,
222
+ without listing them individually.
223
+
224
+ ```ts
225
+ import { MantyxClient, mantyxMcp, defineLocalMcp } from "@mantyx/sdk";
226
+
227
+ const client = new MantyxClient({ apiKey: "...", workspaceSlug: "acme" });
228
+
229
+ await client.runAgent({
230
+ systemPrompt: "You are a developer assistant with GitHub + filesystem access.",
231
+ prompt: "Summarize the latest 5 issues on octocat/hello-world.",
232
+ tools: [
233
+ // Remote MCP server (Streamable HTTP) — MANTYX lists the catalog at run
234
+ // start and proxies every call. Tools surface as `github_<tool>`.
235
+ mantyxMcp({
236
+ name: "github",
237
+ url: "https://mcp.github.com/v1",
238
+ headers: { Authorization: `Bearer ${process.env.GH_PAT}` },
239
+ toolFilter: ["search_issues", "get_repo"],
240
+ }),
241
+ // Local MCP server — fully managed by the SDK. Pass either a
242
+ // Streamable HTTP `url` *or* an stdio `command`; the SDK opens the
243
+ // transport, runs `Initialize` + `tools/list`, ships the resolved
244
+ // catalog inline, and forwards every invocation to `tools/call`. The
245
+ // model sees `<server>_<tool>` (`fs_read_file`, `fs_list_dir`, …) —
246
+ // same shape as `mantyxMcp` above.
247
+
248
+ // (a) Streamable HTTP MCP server.
249
+ defineLocalMcp({
250
+ name: "fs",
251
+ url: "http://localhost:8080/mcp",
252
+ headers: { Authorization: `Bearer ${process.env.FS_TOKEN}` },
253
+ }),
254
+
255
+ // (b) stdio MCP server — the SDK spawns the process for you.
256
+ // defineLocalMcp({
257
+ // name: "fs",
258
+ // command: "mcp-server-filesystem",
259
+ // args: ["/workspace"],
260
+ // env: { LOG_LEVEL: "info" },
261
+ // }),
262
+ ],
263
+ });
264
+ ```
265
+
266
+ The MCP transport is opened lazily on the first `runAgent` / first
267
+ `session.send`, kept warm for subsequent calls within the same run /
268
+ session, and closed when the run completes or `session.end()` is called.
269
+ If the MCP server can't be reached, the SDK throws before submitting the
270
+ spec — you get the failure synchronously rather than mid-conversation.
271
+
272
+ If a remote (`kind: "mcp"`) MCP server is unreachable when the run starts,
273
+ MANTYX still exposes a single `<server>_unavailable` stub so the model can
274
+ tell the user why the connector is missing.
275
+
276
+ ## Reasoning effort (`reasoningLevel`)
277
+
278
+ Crank up provider thinking on reasoning models without writing
279
+ provider-specific code:
280
+
281
+ ```ts
282
+ await client.runAgent({
283
+ systemPrompt: "...",
284
+ prompt: "Plan a multi-week migration.",
285
+ reasoningLevel: "high", // or 80, etc.
286
+ });
287
+ ```
288
+
289
+ | Form | Values | Notes |
290
+ | ------------ | -------------------------------------------- | ----- |
291
+ | String | `"off"`, `"low"`, `"medium"`, `"high"` | Snaps to the same anchors the web composer uses (Fast=30, Moderate=50, Smart=80; off=0). |
292
+ | Number | integer `0`–`100` | `0` explicitly disables provider thinking on reasoning models. |
293
+
294
+ The server maps this onto each LLM's native dial — `reasoning.effort` for
295
+ OpenAI, `thinkingConfig` for Gemini, extended-thinking budget for Anthropic.
296
+ Non-reasoning models silently ignore it. On sessions, `reasoningLevel`
297
+ inherits from the session and can be overridden per `session.send`.
298
+
299
+ ## Structured output (`outputSchema`)
300
+
301
+ Constrain the assistant's **final reply** to a JSON document matching a
302
+ JSON Schema. The wire still ships the reply as `text: string`, but that
303
+ string is guaranteed-parseable JSON. Pair with `parseRunOutput` for a
304
+ typed value with a clean error path:
305
+
306
+ ```ts
307
+ import { z } from "zod";
308
+ import { MantyxClient, parseRunOutput } from "@mantyx/sdk";
309
+
310
+ const Weather = z.object({ city: z.string(), temperature_c: z.number() });
311
+ const WeatherJsonSchema = {
312
+ type: "object",
313
+ properties: {
314
+ city: { type: "string" },
315
+ temperature_c: { type: "number" },
316
+ },
317
+ required: ["city", "temperature_c"],
318
+ additionalProperties: false,
319
+ } as const;
320
+
321
+ const result = await client.runAgent({
322
+ systemPrompt: "Return the weather as JSON.",
323
+ prompt: "What's the weather in San Francisco right now?",
324
+ outputSchema: { name: "weather_report", schema: WeatherJsonSchema },
325
+ });
326
+
327
+ const report = parseRunOutput(result, (v) => Weather.parse(v));
328
+ // ^? { city: string; temperature_c: number }
329
+ ```
330
+
331
+ The SDK validates `name` (regex `/^[a-zA-Z0-9_-]{1,64}$/`), schema shape
332
+ (non-array JSON object), and total size (≤ 32 KB) locally so you get a
333
+ typed `MantyxError` up front instead of a server round-trip rejection.
334
+ On parse failure (rare; bad model output), `parseRunOutput` throws
335
+ `MantyxParseError` with the original `text` preserved.
336
+
337
+ `outputSchema` is independent of `reasoningLevel` — combine them for
338
+ deep-reasoning JSON outputs. On sessions it inherits from
339
+ `createSession({ outputSchema })` and can be overridden per
340
+ `session.send(prompt, { outputSchema })`. See
341
+ [`docs/wire-protocol.md` §7](./docs/wire-protocol.md) for the full
342
+ per-provider mapping.
343
+
106
344
  ## Picking a model
107
345
 
108
346
  ```ts
@@ -248,11 +486,15 @@ interface MantyxClientOptions {
248
486
 
249
487
  ### Tools
250
488
 
251
- | Helper | Use case |
252
- | -------------------------- | ------------------------------------------------------------ |
253
- | `defineLocalTool(opts)` | Define a local tool with a Zod parameter schema and handler. |
254
- | `mantyxTool(id)` | Reference an existing MANTYX tool by id. |
255
- | `mantyxPluginTool(name)` | Reference an installed platform plugin tool by name. |
489
+ | Helper | Use case |
490
+ | -------------------------- | ----------------------------------------------------------------------- |
491
+ | `defineLocalTool(opts)` | Define a local tool with a Zod parameter schema and handler. |
492
+ | `defineLocalA2A(opts)` | Local Agent2Agent peer — pass an `agentCardUrl`; the SDK fetches the card and speaks `message/send` for you. |
493
+ | `defineLocalMcp(opts)` | Local MCP server — pass either a Streamable HTTP `url` or an stdio `command`; the SDK runs `Initialize` + `tools/list` + `tools/call` for you. |
494
+ | `mantyxTool(id)` | Reference an existing MANTYX tool by id. |
495
+ | `mantyxPluginTool(name)` | Reference an installed platform plugin tool by name. |
496
+ | `mantyxA2A(opts)` | Remote Agent2Agent peer reachable from MANTYX (server-resolved). |
497
+ | `mantyxMcp(opts)` | Remote MCP server (Streamable HTTP) MANTYX dials and proxies for you. |
256
498
 
257
499
  ### Errors
258
500
 
@@ -272,6 +514,8 @@ Self-contained example projects live under [`examples/`](./examples/):
272
514
  - `examples/mixed-tools` — combines local, MANTYX, and plugin tools.
273
515
  - `examples/streaming` — token streaming to stdout.
274
516
  - `examples/list-models` — model catalog + pick-and-run.
517
+ - `examples/a2a-tools` — remote (`mantyxA2A`) + local (`defineLocalA2A`) Agent2Agent peers.
518
+ - `examples/mcp-tools` — remote (`mantyxMcp`) + local (`defineLocalMcp`) MCP servers.
275
519
 
276
520
  Each example is its own project (`package.json`, `tsconfig.json`, `README.md`)
277
521
  so you can copy any one of them out of the repo and run it standalone.