toolnexus 0.23.0 → 0.25.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/README.md +139 -1
- package/dist/acp.d.ts +37 -6
- package/dist/acp.js +239 -52
- package/dist/agents/loop.js +4 -1
- package/dist/agents/runtime.js +4 -1
- package/dist/client.d.ts +55 -9
- package/dist/client.js +154 -41
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/mcp.d.ts +32 -7
- package/dist/mcp.js +125 -36
- package/dist/mcp2026/auth.d.ts +137 -0
- package/dist/mcp2026/auth.js +446 -0
- package/dist/mcp2026/client.d.ts +48 -0
- package/dist/mcp2026/client.js +160 -0
- package/dist/mcp2026/protocol.d.ts +54 -0
- package/dist/mcp2026/protocol.js +113 -0
- package/dist/mcp2026/skills.d.ts +5 -0
- package/dist/mcp2026/skills.js +25 -0
- package/dist/mcp2026/source.d.ts +86 -0
- package/dist/mcp2026/source.js +372 -0
- package/dist/mcp2026/stdio.d.ts +24 -0
- package/dist/mcp2026/stdio.js +99 -0
- package/dist/mcp2026/toolsearch.d.ts +5 -0
- package/dist/mcp2026/toolsearch.js +58 -0
- package/dist/mcphandler.d.ts +169 -0
- package/dist/mcphandler.js +1145 -0
- package/dist/mcpheader.d.ts +5 -0
- package/dist/mcpheader.js +18 -0
- package/dist/mcpserve.d.ts +24 -15
- package/dist/mcpserve.js +105 -2
- package/dist/mcpserverauth.d.ts +48 -0
- package/dist/mcpserverauth.js +203 -0
- package/dist/media.d.ts +126 -0
- package/dist/media.js +540 -0
- package/dist/skill.d.ts +21 -0
- package/dist/skill.js +44 -0
- package/dist/toolconcurrency.d.ts +16 -0
- package/dist/toolconcurrency.js +59 -0
- package/dist/toolkit.d.ts +23 -1
- package/dist/toolkit.js +26 -4
- package/dist/types.d.ts +26 -1
- package/package.json +5 -3
- package/src/acp.ts +258 -55
- package/src/agents/loop.ts +4 -1
- package/src/agents/runtime.ts +4 -1
- package/src/client.ts +196 -48
- package/src/index.ts +4 -0
- package/src/mcp.ts +162 -43
- package/src/mcp2026/auth.ts +473 -0
- package/src/mcp2026/client.ts +161 -0
- package/src/mcp2026/protocol.ts +135 -0
- package/src/mcp2026/skills.ts +22 -0
- package/src/mcp2026/source.ts +407 -0
- package/src/mcp2026/stdio.ts +102 -0
- package/src/mcp2026/toolsearch.ts +56 -0
- package/src/mcphandler.ts +1252 -0
- package/src/mcpheader.ts +19 -0
- package/src/mcpserve.ts +115 -2
- package/src/mcpserverauth.ts +216 -0
- package/src/media.ts +604 -0
- package/src/skill.ts +50 -0
- package/src/toolconcurrency.ts +67 -0
- package/src/toolkit.ts +35 -5
- package/src/types.ts +27 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool scheduling (SPEC §8 "Tool scheduling"): the calls of one model response run in emitted
|
|
3
|
+
* order as consecutive runs — safe calls overlap under a cap, each unsafe call runs alone.
|
|
4
|
+
*/
|
|
5
|
+
import type { Tool } from "./types.js"
|
|
6
|
+
|
|
7
|
+
/** The cap when neither the option nor TOOLNEXUS_MAX_TOOL_CONCURRENCY sets one. */
|
|
8
|
+
export const DEFAULT_MAX_TOOL_CONCURRENCY = 10
|
|
9
|
+
|
|
10
|
+
/** option > env TOOLNEXUS_MAX_TOOL_CONCURRENCY > 10. Non-positive or non-numeric values fall back. */
|
|
11
|
+
export function resolveMaxToolConcurrency(option?: number): number {
|
|
12
|
+
if (typeof option === "number" && Number.isInteger(option) && option > 0) return option
|
|
13
|
+
const raw = typeof process !== "undefined" ? process.env.TOOLNEXUS_MAX_TOOL_CONCURRENCY : undefined
|
|
14
|
+
if (raw !== undefined && /^\s*[+]?\d+\s*$/.test(raw)) {
|
|
15
|
+
const n = Number.parseInt(raw, 10)
|
|
16
|
+
if (n > 0) return n
|
|
17
|
+
}
|
|
18
|
+
return DEFAULT_MAX_TOOL_CONCURRENCY
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Whether a call to `tool` with `args` may run alongside other safe calls. The tool's own
|
|
22
|
+
* predicate wins; otherwise only a tool whose hints say readOnly is safe. */
|
|
23
|
+
export function isConcurrencySafe(tool: Tool, args: Record<string, unknown>): boolean {
|
|
24
|
+
if (tool.concurrencySafe) return tool.concurrencySafe(args)
|
|
25
|
+
return tool.hints?.readOnly === true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Runs `fn` over `items` in emitted order: safe items overlap (at most `cap`), an unsafe item
|
|
29
|
+
* waits for everything before it and runs alone. Results keep item order. The first failure
|
|
30
|
+
* stops new items from starting and is rethrown once the running ones settle. */
|
|
31
|
+
export async function scheduleCalls<T, R>(
|
|
32
|
+
items: T[],
|
|
33
|
+
safe: (item: T) => boolean,
|
|
34
|
+
cap: number,
|
|
35
|
+
fn: (item: T) => Promise<R>,
|
|
36
|
+
): Promise<R[]> {
|
|
37
|
+
const out = new Array<R>(items.length)
|
|
38
|
+
const running = new Set<Promise<void>>()
|
|
39
|
+
let failed: { e: unknown } | undefined
|
|
40
|
+
for (let i = 0; i < items.length && !failed; i++) {
|
|
41
|
+
const item = items[i]
|
|
42
|
+
if (!safe(item)) {
|
|
43
|
+
await Promise.all(running)
|
|
44
|
+
if (failed) break
|
|
45
|
+
try {
|
|
46
|
+
out[i] = await fn(item)
|
|
47
|
+
} catch (e) {
|
|
48
|
+
failed = { e }
|
|
49
|
+
}
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
while (running.size >= cap) await Promise.race(running)
|
|
53
|
+
if (failed) break
|
|
54
|
+
const p: Promise<void> = fn(item).then(
|
|
55
|
+
(r) => {
|
|
56
|
+
out[i] = r
|
|
57
|
+
},
|
|
58
|
+
(e) => {
|
|
59
|
+
if (!failed) failed = { e }
|
|
60
|
+
},
|
|
61
|
+
).finally(() => running.delete(p))
|
|
62
|
+
running.add(p)
|
|
63
|
+
}
|
|
64
|
+
await Promise.all(running)
|
|
65
|
+
if (failed) throw failed.e
|
|
66
|
+
return out
|
|
67
|
+
}
|
package/src/toolkit.ts
CHANGED
|
@@ -8,9 +8,11 @@ import { loadSkills, type SkillDef, type SkillSource } from "./skill.js"
|
|
|
8
8
|
import { selectBuiltins, type BuiltinsConfig } from "./builtin.js"
|
|
9
9
|
import { agentTools, parseAgentsConfig, type Agent } from "./a2a.js"
|
|
10
10
|
import { startA2AServer, type A2AConfig, type OnTask, type ServeHandle } from "./serve.js"
|
|
11
|
-
import { type MCPServeConfig, type OnCall } from "./mcpserve.js"
|
|
11
|
+
import { serveLocalMcp, type LocalMcpServer, type MCPServeConfig, type OnCall } from "./mcpserve.js"
|
|
12
|
+
import { createMcpHandler, type McpHandler, type McpHandlerConfig } from "./mcphandler.js"
|
|
12
13
|
import { toAnthropic, toGemini, toOpenAI } from "./adapters.js"
|
|
13
14
|
import type { Client } from "./client.js"
|
|
15
|
+
import type { Media } from "./media.js"
|
|
14
16
|
|
|
15
17
|
export interface ToolkitOptions {
|
|
16
18
|
mcpConfig?: string | object
|
|
@@ -31,6 +33,8 @@ export interface ToolkitOptions {
|
|
|
31
33
|
/** Sibling-file sample cap: 0 ⇒ default 10, n>0 ⇒ cap, -1 ⇒ omit <skill_files> (§3, S5). */
|
|
32
34
|
skillSampleLimit?: number
|
|
33
35
|
extraTools?: Tool[]
|
|
36
|
+
/** Media generation (§7G): registers `media.tools()` (source "media"). */
|
|
37
|
+
media?: Media
|
|
34
38
|
/** Built-in tools (§4A). On by default. false | { disabled:true } | { enabled:false } ⇒ off. */
|
|
35
39
|
builtins?: BuiltinsConfig
|
|
36
40
|
/** Remote A2A agents — each advertised skill becomes a tool (source:"a2a"). */
|
|
@@ -45,6 +49,10 @@ export interface ToolkitOptions {
|
|
|
45
49
|
/** §2 Gap 3. Bounds the MCP load: each connect/list is capped by the server timeout and the
|
|
46
50
|
* whole load aborts promptly if this signal fires. Omit ⇒ unbounded as before. */
|
|
47
51
|
signal?: AbortSignal
|
|
52
|
+
/** §2A.1a where MCP OAuth tokens live (default: in memory, per load). */
|
|
53
|
+
tokenStore?: import("./mcp2026/auth.js").TokenStore
|
|
54
|
+
/** §2A.1a how `auto` MCP OAuth opens the authorization URL (default: the OS opener). */
|
|
55
|
+
openUrl?: (url: string) => unknown | Promise<unknown>
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
export class Toolkit {
|
|
@@ -80,12 +88,13 @@ export class Toolkit {
|
|
|
80
88
|
}
|
|
81
89
|
|
|
82
90
|
static async create(opts: ToolkitOptions = {}): Promise<Toolkit> {
|
|
83
|
-
const mcp = opts.mcpConfig !== undefined ? await loadMcp(opts.mcpConfig, { waitFor: opts.waitFor, signal: opts.signal }) : undefined
|
|
91
|
+
const mcp = opts.mcpConfig !== undefined ? await loadMcp(opts.mcpConfig, { waitFor: opts.waitFor, signal: opts.signal, tokenStore: opts.tokenStore, openUrl: opts.openUrl, disableTools: opts.disableTools }) : undefined
|
|
84
92
|
// Skills come from directories, in-memory data, and/or a lazy provider (§3,
|
|
85
93
|
// S1). The provider is resolved here (create() is async) and merged with the
|
|
86
94
|
// data list; a provider failure is isolated so other sources still load.
|
|
87
95
|
let skill: SkillSource | undefined
|
|
88
|
-
|
|
96
|
+
const mcpSkills = mcp?.skills ?? []
|
|
97
|
+
if (opts.skillsDir !== undefined || opts.skills !== undefined || opts.skillProvider !== undefined || mcpSkills.length > 0) {
|
|
89
98
|
let providerDefs: SkillDef[] = []
|
|
90
99
|
if (opts.skillProvider) {
|
|
91
100
|
try {
|
|
@@ -95,7 +104,8 @@ export class Toolkit {
|
|
|
95
104
|
providerDefs = []
|
|
96
105
|
}
|
|
97
106
|
}
|
|
98
|
-
|
|
107
|
+
// §2.7 remote skills (MCP skills extension) join the same inventory, after local ones.
|
|
108
|
+
const dataDefs = [...(opts.skills ?? []), ...providerDefs, ...mcpSkills]
|
|
99
109
|
// disableSkills is sugar over a skillsFilter drop-list: fold each name in as false
|
|
100
110
|
// WITHOUT overriding an explicit skillsFilter entry (explicit entries win).
|
|
101
111
|
const filter =
|
|
@@ -137,7 +147,7 @@ export class Toolkit {
|
|
|
137
147
|
}),
|
|
138
148
|
),
|
|
139
149
|
)
|
|
140
|
-
const agentToolsFlat = agentToolLists.flat()
|
|
150
|
+
const agentToolsFlat = [...agentToolLists.flat(), ...(opts.media ? opts.media.tools() : [])]
|
|
141
151
|
|
|
142
152
|
// A2A inbound profile: a top-level `a2a` block on a parsed config object
|
|
143
153
|
// (mirrors builtins/agents). serve() prefers its inline `a2a` over this.
|
|
@@ -218,6 +228,26 @@ export class Toolkit {
|
|
|
218
228
|
})
|
|
219
229
|
}
|
|
220
230
|
|
|
231
|
+
/**
|
|
232
|
+
* §7C.1 (ADR 0039). An embeddable streamable-HTTP MCP server as a Node request listener
|
|
233
|
+
* `(req, res) => void` (+ `notifyToolsChanged()`), to mount on the host's own server/router
|
|
234
|
+
* behind its own TLS, CORS and OAuth. Every policy is a hook in `cfg`; `serve()` is unchanged.
|
|
235
|
+
* Without `cfg.listTools` it exposes this toolkit's tools.
|
|
236
|
+
*/
|
|
237
|
+
mcpHandler(cfg: McpHandlerConfig = {}): McpHandler {
|
|
238
|
+
return createMcpHandler(() => this.tools(), cfg)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* SPEC §8C. Serves this toolkit for a local agent to call: the §7C MCP mapping (tool names
|
|
243
|
+
* verbatim, `skill` stays `skill`, optional `config` filter) on `127.0.0.1:<ephemeral>/mcp`
|
|
244
|
+
* behind a fresh 64-hex bearer token. The caller builds whatever `mcpServers` entry its agent
|
|
245
|
+
* accepts from `{url, token}`; `close()` stops it (idempotent).
|
|
246
|
+
*/
|
|
247
|
+
asMcpServer(config?: MCPServeConfig): Promise<LocalMcpServer> {
|
|
248
|
+
return serveLocalMcp(this.tools(), config)
|
|
249
|
+
}
|
|
250
|
+
|
|
221
251
|
async execute(name: string, args: Record<string, unknown>, ctx?: ToolContext): Promise<ToolResult> {
|
|
222
252
|
const tool = this.byName.get(name)
|
|
223
253
|
if (!tool) {
|
package/src/types.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type JSONSchema = {
|
|
|
12
12
|
|
|
13
13
|
import type { ContentPart } from "./content.js"
|
|
14
14
|
|
|
15
|
-
export type ToolSource = "mcp" | "skill" | "builtin" | "native" | "http" | "a2a" | "custom"
|
|
15
|
+
export type ToolSource = "mcp" | "skill" | "builtin" | "native" | "http" | "a2a" | "media" | "custom"
|
|
16
16
|
|
|
17
17
|
export interface ToolResult {
|
|
18
18
|
output: string
|
|
@@ -25,6 +25,22 @@ export interface ToolResult {
|
|
|
25
25
|
*/
|
|
26
26
|
parts?: ContentPart[]
|
|
27
27
|
metadata?: Record<string, unknown>
|
|
28
|
+
/**
|
|
29
|
+
* Machine-readable result (MCP `structuredContent`, SPEC §1). Absent ⇒ byte-identical wire.
|
|
30
|
+
* When set and `output` is empty, MCP serve emits the JSON text of `structured` as the text block.
|
|
31
|
+
*/
|
|
32
|
+
structured?: unknown
|
|
33
|
+
/** Protocol metadata for the wire result (MCP `_meta`, SPEC §1). Absent ⇒ omitted. */
|
|
34
|
+
meta?: Record<string, unknown>
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** MCP tool annotations (SPEC §1, §7C.1), sent verbatim on an MCP `tools/list` entry. */
|
|
38
|
+
export interface ToolAnnotations {
|
|
39
|
+
title?: string
|
|
40
|
+
readOnlyHint?: boolean
|
|
41
|
+
destructiveHint?: boolean
|
|
42
|
+
idempotentHint?: boolean
|
|
43
|
+
openWorldHint?: boolean
|
|
28
44
|
}
|
|
29
45
|
|
|
30
46
|
export interface ToolContext {
|
|
@@ -35,6 +51,9 @@ export interface ToolContext {
|
|
|
35
51
|
/** The model's id for this tool call, set by the client loop (§4C). A message's calls run
|
|
36
52
|
* concurrently, so this is how a tool tells its sibling calls apart. Absent outside the loop. */
|
|
37
53
|
callId?: string
|
|
54
|
+
/** Set by `toolkit.mcpHandler` (§7C.1): the inbound MCP request this call serves. Read it with
|
|
55
|
+
* `identityFrom(ctx)` / `requestFrom(ctx)` / `mcpSessionIdFrom(ctx)` / `mcpRequestMetaFrom(ctx)`. */
|
|
56
|
+
mcp?: import("./mcphandler.js").McpContext
|
|
38
57
|
}
|
|
39
58
|
|
|
40
59
|
/**
|
|
@@ -130,6 +149,13 @@ export interface Tool {
|
|
|
130
149
|
/** What the tool may do (§4C). Builtins always carry hints; MCP tools carry them when the
|
|
131
150
|
* server sends annotations. Advisory: a server can mislabel its tools. */
|
|
132
151
|
hints?: ToolHints
|
|
152
|
+
/** Decides per call whether this tool may run alongside other safe calls of the same model
|
|
153
|
+
* response (SPEC §8 "Tool scheduling"). Absent ⇒ safe only when `hints.readOnly`. */
|
|
154
|
+
concurrencySafe?: (args: Record<string, unknown>) => boolean
|
|
155
|
+
/** JSON Schema of `ToolResult.structured` (SPEC §1); absent ⇒ omitted everywhere. */
|
|
156
|
+
outputSchema?: Record<string, unknown>
|
|
157
|
+
/** MCP annotations (§7C.1), verbatim on the MCP wire; absent ⇒ derived from `hints`, else omitted. */
|
|
158
|
+
annotations?: ToolAnnotations
|
|
133
159
|
}
|
|
134
160
|
|
|
135
161
|
export type McpStatus = "connected" | "disabled" | "failed"
|