@slashfi/agents-sdk 0.35.0 → 0.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/events.js.map +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/registry-consumer.js +2 -0
- package/dist/cjs/registry-consumer.js.map +1 -1
- package/dist/cjs/registry.js +67 -10
- package/dist/cjs/registry.js.map +1 -1
- package/dist/cjs/server.js +73 -71
- package/dist/cjs/server.js.map +1 -1
- package/dist/cjs/types.js.map +1 -1
- package/dist/events.d.ts +85 -9
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/registry-consumer.d.ts.map +1 -1
- package/dist/registry-consumer.js +2 -0
- package/dist/registry-consumer.js.map +1 -1
- package/dist/registry.d.ts +16 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +67 -10
- package/dist/registry.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +73 -71
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/events.ts +83 -9
- package/src/hooks.test.ts +439 -0
- package/src/index.ts +3 -1
- package/src/registry-consumer.ts +8 -1
- package/src/registry.ts +94 -12
- package/src/server.ts +97 -90
- package/src/types.ts +6 -0
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AA2zBH,2EAA2E;AAC3E,MAAM,UAAU,wBAAwB,CACtC,CAAoB;IAEpB,OAAO,CAAC,CAAC,OAAO,KAAK,KAAK,CAAC;AAC7B,CAAC;AA8BD,MAAM,UAAU,4BAA4B,CAC1C,CAA0B;IAE1B,OAAO,CAAC,CAAC,OAAO,KAAK,IAAI,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,CAA0B;IAE1B,OAAO,CAAC,CAAC,OAAO,KAAK,KAAK,IAAI,WAAW,IAAI,CAAC,IAAI,CAAC,CAAC,SAAS,KAAK,IAAI,CAAC;AACzE,CAAC"}
|
package/package.json
CHANGED
package/src/events.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* Filtering happens in the callback, not the API.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import type { CallAgentRequest, CallAgentResponse } from "./types.js";
|
|
13
|
+
import type { AgentDefinition, CallAgentRequest, CallAgentResponse } from "./types.js";
|
|
14
14
|
// =============================================================================
|
|
15
15
|
// Event Types
|
|
16
16
|
// =============================================================================
|
|
@@ -24,7 +24,8 @@ export type SystemEventType =
|
|
|
24
24
|
| "tool/error"
|
|
25
25
|
| "step"
|
|
26
26
|
| "invoke"
|
|
27
|
-
| "call"
|
|
27
|
+
| "tools/call/call_agent"
|
|
28
|
+
| "tools/call/list_agents";
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* Augmentable map for custom event types. Consumers extend this
|
|
@@ -124,12 +125,28 @@ export interface InvokeEvent extends BaseEvent {
|
|
|
124
125
|
}
|
|
125
126
|
|
|
126
127
|
/**
|
|
127
|
-
* Event emitted when
|
|
128
|
-
*
|
|
129
|
-
*
|
|
128
|
+
* Event emitted when the `call_agent` MCP tool is invoked.
|
|
129
|
+
* Replaces the legacy `call` event with a namespaced type.
|
|
130
|
+
*
|
|
131
|
+
* Call `next()` to run the default call handler (optionally with a modified request).
|
|
132
|
+
* Call `resolve(response)` to short-circuit with a custom response.
|
|
133
|
+
* If neither is called, the default handler runs.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* registry.on('tools/call/call_agent', async (event) => {
|
|
138
|
+
* // Proxy to a remote registry
|
|
139
|
+
* if (isRemoteAgent(event.request.path)) {
|
|
140
|
+
* const result = await proxyToRemote(event.request);
|
|
141
|
+
* event.resolve(result);
|
|
142
|
+
* return;
|
|
143
|
+
* }
|
|
144
|
+
* // Fall through to default handler
|
|
145
|
+
* });
|
|
146
|
+
* ```
|
|
130
147
|
*/
|
|
131
|
-
export interface
|
|
132
|
-
type: "call";
|
|
148
|
+
export interface CallAgentToolCallEvent extends BaseEvent {
|
|
149
|
+
type: "tools/call/call_agent";
|
|
133
150
|
/** The incoming call_agent request */
|
|
134
151
|
request: CallAgentRequest;
|
|
135
152
|
/** Run the default call handler and return its result.
|
|
@@ -139,6 +156,61 @@ export interface CallEvent extends BaseEvent {
|
|
|
139
156
|
resolve(response: CallAgentResponse): void;
|
|
140
157
|
}
|
|
141
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Result shape for list_agents responses.
|
|
161
|
+
*/
|
|
162
|
+
export interface ListAgentsResult {
|
|
163
|
+
success: true;
|
|
164
|
+
total: number;
|
|
165
|
+
nextCursor?: string;
|
|
166
|
+
agents: Array<{
|
|
167
|
+
path: string;
|
|
168
|
+
name?: string;
|
|
169
|
+
description?: string;
|
|
170
|
+
supportedActions?: string[];
|
|
171
|
+
integration?: unknown;
|
|
172
|
+
security?: { type: string };
|
|
173
|
+
resources?: Array<{ uri: string; name?: string; mimeType?: string }>;
|
|
174
|
+
tools: string[];
|
|
175
|
+
}>;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Event emitted when the `list_agents` MCP tool is invoked.
|
|
180
|
+
*
|
|
181
|
+
* Gives hosts a chance to inject additional agents (e.g., from remote registries
|
|
182
|
+
* or consumer config) before BM25 search and pagination run.
|
|
183
|
+
*
|
|
184
|
+
* Call `next(additionalAgents?)` to continue default behavior with optional
|
|
185
|
+
* extra agents merged into the base set.
|
|
186
|
+
* Call `resolve(result)` to short-circuit with a fully formed response.
|
|
187
|
+
* If neither is called, the default handler runs with the base agents.
|
|
188
|
+
*
|
|
189
|
+
* @example
|
|
190
|
+
* ```ts
|
|
191
|
+
* registry.on('tools/call/list_agents', async (event) => {
|
|
192
|
+
* const remoteAgents = await fetchRemoteAgents();
|
|
193
|
+
* await event.next(remoteAgents);
|
|
194
|
+
* });
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
export interface ListAgentsToolCallEvent extends BaseEvent {
|
|
198
|
+
type: "tools/call/list_agents";
|
|
199
|
+
/** Agents from the local registry (before search/pagination) */
|
|
200
|
+
baseAgents: AgentDefinition[];
|
|
201
|
+
/** Search query, if provided */
|
|
202
|
+
query?: string;
|
|
203
|
+
/** Requested page size */
|
|
204
|
+
limit?: number;
|
|
205
|
+
/** Pagination cursor */
|
|
206
|
+
cursor?: string;
|
|
207
|
+
/** Continue with default BM25/pagination behavior.
|
|
208
|
+
* Pass additional agents to merge into the base set. */
|
|
209
|
+
next(additionalAgents?: AgentDefinition[]): Promise<ListAgentsResult>;
|
|
210
|
+
/** Short-circuit with a complete response (same shape as list_agents output) */
|
|
211
|
+
resolve(result: ListAgentsResult): void;
|
|
212
|
+
}
|
|
213
|
+
|
|
142
214
|
/**
|
|
143
215
|
* Union of all built-in event types.
|
|
144
216
|
*/
|
|
@@ -148,7 +220,8 @@ export type AgentEvent =
|
|
|
148
220
|
| ToolErrorEvent
|
|
149
221
|
| StepEvent
|
|
150
222
|
| InvokeEvent
|
|
151
|
-
|
|
|
223
|
+
| CallAgentToolCallEvent
|
|
224
|
+
| ListAgentsToolCallEvent;
|
|
152
225
|
|
|
153
226
|
/**
|
|
154
227
|
* Map from system event type string to event interface.
|
|
@@ -159,7 +232,8 @@ export interface SystemEventMap {
|
|
|
159
232
|
"tool/error": ToolErrorEvent;
|
|
160
233
|
step: StepEvent;
|
|
161
234
|
invoke: InvokeEvent;
|
|
162
|
-
call:
|
|
235
|
+
"tools/call/call_agent": CallAgentToolCallEvent;
|
|
236
|
+
"tools/call/list_agents": ListAgentsToolCallEvent;
|
|
163
237
|
}
|
|
164
238
|
|
|
165
239
|
/**
|
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for tools/call/call_agent and tools/call/list_agents hooks.
|
|
3
|
+
*
|
|
4
|
+
* These go through the full production path:
|
|
5
|
+
* HTTP POST → JSON-RPC → MCP tools/call → handleToolCall → registry hooks
|
|
6
|
+
*
|
|
7
|
+
* Mirrors how atlas-os uses these hooks to intercept and route requests.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
11
|
+
import {
|
|
12
|
+
createAgentRegistry,
|
|
13
|
+
createAgentServer,
|
|
14
|
+
defineAgent,
|
|
15
|
+
defineTool,
|
|
16
|
+
} from "./index";
|
|
17
|
+
import type {
|
|
18
|
+
AgentDefinition,
|
|
19
|
+
AgentServer,
|
|
20
|
+
CallAgentToolCallEvent,
|
|
21
|
+
ListAgentsToolCallEvent,
|
|
22
|
+
} from "./index";
|
|
23
|
+
|
|
24
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
25
|
+
// Test agents
|
|
26
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
27
|
+
|
|
28
|
+
const echoAgent = defineAgent({
|
|
29
|
+
path: "@echo",
|
|
30
|
+
entrypoint: "Echo agent",
|
|
31
|
+
config: { name: "Echo", description: "Echoes input back" },
|
|
32
|
+
visibility: "public" as const,
|
|
33
|
+
tools: [
|
|
34
|
+
defineTool({
|
|
35
|
+
name: "echo",
|
|
36
|
+
description: "Echo the input",
|
|
37
|
+
inputSchema: {
|
|
38
|
+
type: "object",
|
|
39
|
+
properties: { message: { type: "string" } },
|
|
40
|
+
required: ["message"],
|
|
41
|
+
},
|
|
42
|
+
execute: async (input: { message: string }) => ({
|
|
43
|
+
echoed: input.message,
|
|
44
|
+
}),
|
|
45
|
+
}),
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const mathAgent = defineAgent({
|
|
50
|
+
path: "@math",
|
|
51
|
+
entrypoint: "Math agent",
|
|
52
|
+
config: { name: "Math", description: "Does math" },
|
|
53
|
+
visibility: "public" as const,
|
|
54
|
+
tools: [
|
|
55
|
+
defineTool({
|
|
56
|
+
name: "add",
|
|
57
|
+
description: "Add two numbers",
|
|
58
|
+
inputSchema: {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
a: { type: "number" },
|
|
62
|
+
b: { type: "number" },
|
|
63
|
+
},
|
|
64
|
+
required: ["a", "b"],
|
|
65
|
+
},
|
|
66
|
+
execute: async (input: { a: number; b: number }) => ({
|
|
67
|
+
result: input.a + input.b,
|
|
68
|
+
}),
|
|
69
|
+
}),
|
|
70
|
+
],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
74
|
+
// Helper: MCP JSON-RPC call through HTTP
|
|
75
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
76
|
+
|
|
77
|
+
let nextId = 1;
|
|
78
|
+
async function mcpCall(
|
|
79
|
+
baseUrl: string,
|
|
80
|
+
toolName: string,
|
|
81
|
+
args: Record<string, unknown>,
|
|
82
|
+
) {
|
|
83
|
+
const res = await fetch(baseUrl, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: { "Content-Type": "application/json" },
|
|
86
|
+
body: JSON.stringify({
|
|
87
|
+
jsonrpc: "2.0",
|
|
88
|
+
id: nextId++,
|
|
89
|
+
method: "tools/call",
|
|
90
|
+
params: { name: toolName, arguments: args },
|
|
91
|
+
}),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const json = (await res.json()) as {
|
|
95
|
+
result?: { content?: Array<{ text: string }> };
|
|
96
|
+
};
|
|
97
|
+
const text = json.result?.content?.[0]?.text;
|
|
98
|
+
return text ? JSON.parse(text) : json;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
102
|
+
// tools/call/call_agent tests
|
|
103
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
104
|
+
|
|
105
|
+
describe("tools/call/call_agent hook", () => {
|
|
106
|
+
const PORT = 19870;
|
|
107
|
+
|
|
108
|
+
test("resolve() short-circuits the default handler", async () => {
|
|
109
|
+
// This mirrors atlas-os: intercept call_agent, route externally, resolve()
|
|
110
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
111
|
+
registry.register(echoAgent);
|
|
112
|
+
|
|
113
|
+
registry.on("tools/call/call_agent", async (event) => {
|
|
114
|
+
event.resolve({
|
|
115
|
+
success: true,
|
|
116
|
+
result: { intercepted: true, originalPath: event.request.path },
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const server = createAgentServer(registry, { port: PORT });
|
|
121
|
+
await server.start();
|
|
122
|
+
|
|
123
|
+
const result = await mcpCall(`http://localhost:${PORT}`, "call_agent", {
|
|
124
|
+
action: "execute_tool",
|
|
125
|
+
path: "@echo",
|
|
126
|
+
tool: "echo",
|
|
127
|
+
params: { message: "hello" },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
expect(result.success).toBe(true);
|
|
131
|
+
expect(result.result.intercepted).toBe(true);
|
|
132
|
+
expect(result.result.originalPath).toBe("@echo");
|
|
133
|
+
await server.stop();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("next() runs default handler and hook observes", async () => {
|
|
137
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
138
|
+
registry.register(echoAgent);
|
|
139
|
+
|
|
140
|
+
let hookSawRequest = false;
|
|
141
|
+
registry.on("tools/call/call_agent", async (event) => {
|
|
142
|
+
hookSawRequest = true;
|
|
143
|
+
await event.next();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const server = createAgentServer(registry, { port: PORT + 1 });
|
|
147
|
+
await server.start();
|
|
148
|
+
|
|
149
|
+
const result = await mcpCall(`http://localhost:${PORT + 1}`, "call_agent", {
|
|
150
|
+
action: "execute_tool",
|
|
151
|
+
path: "@echo",
|
|
152
|
+
tool: "echo",
|
|
153
|
+
params: { message: "passthrough" },
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(hookSawRequest).toBe(true);
|
|
157
|
+
expect(result.success).toBe(true);
|
|
158
|
+
expect(result.result.echoed).toBe("passthrough");
|
|
159
|
+
await server.stop();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("next() with modified request reroutes the call", async () => {
|
|
163
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
164
|
+
registry.register(echoAgent);
|
|
165
|
+
registry.register(mathAgent);
|
|
166
|
+
|
|
167
|
+
registry.on("tools/call/call_agent", async (event) => {
|
|
168
|
+
if (event.request.action === "execute_tool" && event.request.path === "@echo") {
|
|
169
|
+
await event.next({
|
|
170
|
+
...event.request,
|
|
171
|
+
path: "@math",
|
|
172
|
+
tool: "add",
|
|
173
|
+
params: { a: 10, b: 20 },
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const server = createAgentServer(registry, { port: PORT + 2 });
|
|
179
|
+
await server.start();
|
|
180
|
+
|
|
181
|
+
const result = await mcpCall(`http://localhost:${PORT + 2}`, "call_agent", {
|
|
182
|
+
action: "execute_tool",
|
|
183
|
+
path: "@echo",
|
|
184
|
+
tool: "echo",
|
|
185
|
+
params: { message: "this gets rerouted" },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
expect(result.success).toBe(true);
|
|
189
|
+
expect(result.result.result).toBe(30);
|
|
190
|
+
await server.stop();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
test("no listener = default behavior unchanged", async () => {
|
|
194
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
195
|
+
registry.register(echoAgent);
|
|
196
|
+
|
|
197
|
+
const server = createAgentServer(registry, { port: PORT + 3 });
|
|
198
|
+
await server.start();
|
|
199
|
+
|
|
200
|
+
const result = await mcpCall(`http://localhost:${PORT + 3}`, "call_agent", {
|
|
201
|
+
action: "execute_tool",
|
|
202
|
+
path: "@echo",
|
|
203
|
+
tool: "echo",
|
|
204
|
+
params: { message: "no hook" },
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(result.success).toBe(true);
|
|
208
|
+
expect(result.result.echoed).toBe("no hook");
|
|
209
|
+
await server.stop();
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("resolve() with error response", async () => {
|
|
213
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
214
|
+
registry.register(echoAgent);
|
|
215
|
+
|
|
216
|
+
registry.on("tools/call/call_agent", async (event) => {
|
|
217
|
+
event.resolve({
|
|
218
|
+
success: false,
|
|
219
|
+
error: "Agent not available in remote registry",
|
|
220
|
+
code: "AGENT_NOT_FOUND",
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const server = createAgentServer(registry, { port: PORT + 4 });
|
|
225
|
+
await server.start();
|
|
226
|
+
|
|
227
|
+
const result = await mcpCall(`http://localhost:${PORT + 4}`, "call_agent", {
|
|
228
|
+
action: "execute_tool",
|
|
229
|
+
path: "@nonexistent",
|
|
230
|
+
tool: "foo",
|
|
231
|
+
params: {},
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
expect(result.success).toBe(false);
|
|
235
|
+
expect(result.error).toBe("Agent not available in remote registry");
|
|
236
|
+
await server.stop();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
241
|
+
// tools/call/list_agents tests
|
|
242
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
243
|
+
|
|
244
|
+
describe("tools/call/list_agents hook", () => {
|
|
245
|
+
const PORT = 19880;
|
|
246
|
+
|
|
247
|
+
test("next() with additional agents merges into listing", async () => {
|
|
248
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
249
|
+
registry.register(echoAgent);
|
|
250
|
+
|
|
251
|
+
const remoteAgent = defineAgent({
|
|
252
|
+
path: "@remote-db",
|
|
253
|
+
entrypoint: "Remote database agent",
|
|
254
|
+
config: { name: "RemoteDB", description: "Query remote databases" },
|
|
255
|
+
visibility: "public" as const,
|
|
256
|
+
tools: [
|
|
257
|
+
defineTool({
|
|
258
|
+
name: "query",
|
|
259
|
+
description: "Run a SQL query",
|
|
260
|
+
inputSchema: { type: "object", properties: { sql: { type: "string" } } },
|
|
261
|
+
execute: async () => ({ rows: [] }),
|
|
262
|
+
}),
|
|
263
|
+
],
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
registry.on("tools/call/list_agents", async (event) => {
|
|
267
|
+
await event.next([remoteAgent]);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const server = createAgentServer(registry, { port: PORT });
|
|
271
|
+
await server.start();
|
|
272
|
+
|
|
273
|
+
const result = await mcpCall(`http://localhost:${PORT}`, "list_agents", {});
|
|
274
|
+
|
|
275
|
+
expect(result.success).toBe(true);
|
|
276
|
+
const paths = result.agents.map((a: { path: string }) => a.path);
|
|
277
|
+
expect(paths).toContain("@echo");
|
|
278
|
+
expect(paths).toContain("@remote-db");
|
|
279
|
+
expect(result.total).toBe(2);
|
|
280
|
+
await server.stop();
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test("injected agents appear in BM25 search results", async () => {
|
|
284
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
285
|
+
registry.register(echoAgent);
|
|
286
|
+
|
|
287
|
+
const remoteAgent = defineAgent({
|
|
288
|
+
path: "@snowflake",
|
|
289
|
+
entrypoint: "Snowflake analytics",
|
|
290
|
+
config: { name: "Snowflake", description: "Analytics data warehouse queries" },
|
|
291
|
+
visibility: "public" as const,
|
|
292
|
+
tools: [
|
|
293
|
+
defineTool({
|
|
294
|
+
name: "run_query",
|
|
295
|
+
description: "Run an analytics query on Snowflake",
|
|
296
|
+
inputSchema: { type: "object", properties: { sql: { type: "string" } } },
|
|
297
|
+
execute: async () => ({ rows: [] }),
|
|
298
|
+
}),
|
|
299
|
+
],
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
registry.on("tools/call/list_agents", async (event) => {
|
|
303
|
+
await event.next([remoteAgent]);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const server = createAgentServer(registry, { port: PORT + 1 });
|
|
307
|
+
await server.start();
|
|
308
|
+
|
|
309
|
+
const result = await mcpCall(`http://localhost:${PORT + 1}`, "list_agents", {
|
|
310
|
+
query: "analytics warehouse",
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
expect(result.success).toBe(true);
|
|
314
|
+
const paths = result.agents.map((a: { path: string }) => a.path);
|
|
315
|
+
expect(paths).toContain("@snowflake");
|
|
316
|
+
await server.stop();
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("resolve() fully replaces the response", async () => {
|
|
320
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
321
|
+
registry.register(echoAgent);
|
|
322
|
+
registry.register(mathAgent);
|
|
323
|
+
|
|
324
|
+
registry.on("tools/call/list_agents", async (event) => {
|
|
325
|
+
event.resolve({
|
|
326
|
+
success: true,
|
|
327
|
+
total: 1,
|
|
328
|
+
agents: [
|
|
329
|
+
{
|
|
330
|
+
path: "@custom-only",
|
|
331
|
+
name: "Custom",
|
|
332
|
+
description: "Only this one",
|
|
333
|
+
tools: ["do_stuff"],
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
});
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const server = createAgentServer(registry, { port: PORT + 2 });
|
|
340
|
+
await server.start();
|
|
341
|
+
|
|
342
|
+
const result = await mcpCall(`http://localhost:${PORT + 2}`, "list_agents", {});
|
|
343
|
+
|
|
344
|
+
expect(result.success).toBe(true);
|
|
345
|
+
expect(result.agents.length).toBe(1);
|
|
346
|
+
expect(result.agents[0].path).toBe("@custom-only");
|
|
347
|
+
const paths = result.agents.map((a: { path: string }) => a.path);
|
|
348
|
+
expect(paths).not.toContain("@echo");
|
|
349
|
+
expect(paths).not.toContain("@math");
|
|
350
|
+
await server.stop();
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("no listener = default behavior unchanged", async () => {
|
|
354
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
355
|
+
registry.register(echoAgent);
|
|
356
|
+
registry.register(mathAgent);
|
|
357
|
+
|
|
358
|
+
const server = createAgentServer(registry, { port: PORT + 3 });
|
|
359
|
+
await server.start();
|
|
360
|
+
|
|
361
|
+
const result = await mcpCall(`http://localhost:${PORT + 3}`, "list_agents", {});
|
|
362
|
+
|
|
363
|
+
expect(result.success).toBe(true);
|
|
364
|
+
expect(result.total).toBe(2);
|
|
365
|
+
const paths = result.agents.map((a: { path: string }) => a.path);
|
|
366
|
+
expect(paths).toContain("@echo");
|
|
367
|
+
expect(paths).toContain("@math");
|
|
368
|
+
await server.stop();
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("deduplicates by path — injected agent overrides local", async () => {
|
|
372
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
373
|
+
registry.register(echoAgent);
|
|
374
|
+
|
|
375
|
+
const overrideEcho = defineAgent({
|
|
376
|
+
path: "@echo",
|
|
377
|
+
entrypoint: "Overridden echo",
|
|
378
|
+
config: { name: "Echo Override", description: "This replaced the original" },
|
|
379
|
+
visibility: "public" as const,
|
|
380
|
+
tools: [
|
|
381
|
+
defineTool({
|
|
382
|
+
name: "echo",
|
|
383
|
+
description: "Overridden echo tool",
|
|
384
|
+
inputSchema: { type: "object", properties: { message: { type: "string" } } },
|
|
385
|
+
execute: async () => ({ overridden: true }),
|
|
386
|
+
}),
|
|
387
|
+
],
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
registry.on("tools/call/list_agents", async (event) => {
|
|
391
|
+
await event.next([overrideEcho]);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
const server = createAgentServer(registry, { port: PORT + 4 });
|
|
395
|
+
await server.start();
|
|
396
|
+
|
|
397
|
+
const result = await mcpCall(`http://localhost:${PORT + 4}`, "list_agents", {});
|
|
398
|
+
|
|
399
|
+
expect(result.success).toBe(true);
|
|
400
|
+
expect(result.total).toBe(1);
|
|
401
|
+
expect(result.agents[0].description).toBe("This replaced the original");
|
|
402
|
+
await server.stop();
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("event carries query/limit/cursor params", async () => {
|
|
406
|
+
const registry = createAgentRegistry({ defaultVisibility: "public" });
|
|
407
|
+
registry.register(echoAgent);
|
|
408
|
+
|
|
409
|
+
let capturedEvent: {
|
|
410
|
+
query?: string;
|
|
411
|
+
limit?: number;
|
|
412
|
+
cursor?: string;
|
|
413
|
+
baseAgentCount: number;
|
|
414
|
+
} | null = null;
|
|
415
|
+
|
|
416
|
+
registry.on("tools/call/list_agents", async (event) => {
|
|
417
|
+
capturedEvent = {
|
|
418
|
+
query: event.query,
|
|
419
|
+
limit: event.limit,
|
|
420
|
+
cursor: event.cursor,
|
|
421
|
+
baseAgentCount: event.baseAgents.length,
|
|
422
|
+
};
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const server = createAgentServer(registry, { port: PORT + 5 });
|
|
426
|
+
await server.start();
|
|
427
|
+
|
|
428
|
+
await mcpCall(`http://localhost:${PORT + 5}`, "list_agents", {
|
|
429
|
+
query: "echo",
|
|
430
|
+
limit: 5,
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
expect(capturedEvent).not.toBeNull();
|
|
434
|
+
expect(capturedEvent!.query).toBe("echo");
|
|
435
|
+
expect(capturedEvent!.limit).toBe(5);
|
|
436
|
+
expect(capturedEvent!.baseAgentCount).toBe(1);
|
|
437
|
+
await server.stop();
|
|
438
|
+
});
|
|
439
|
+
});
|
package/src/index.ts
CHANGED
package/src/registry-consumer.ts
CHANGED
|
@@ -902,13 +902,20 @@ export async function createRegistryConsumer(
|
|
|
902
902
|
action: "describe_tools",
|
|
903
903
|
path: agentPath,
|
|
904
904
|
tools: undefined,
|
|
905
|
-
})) as {
|
|
905
|
+
})) as {
|
|
906
|
+
tools?: unknown[];
|
|
907
|
+
description?: string;
|
|
908
|
+
security?: SecuritySchemeSummary;
|
|
909
|
+
resources?: Array<{ uri: string; name?: string; mimeType?: string }>;
|
|
910
|
+
} | null;
|
|
906
911
|
if (!data) return null;
|
|
907
912
|
return {
|
|
908
913
|
path: agentPath,
|
|
909
914
|
publisher: registry.publisher,
|
|
910
915
|
tools: data.tools,
|
|
911
916
|
description: data.description,
|
|
917
|
+
security: data.security,
|
|
918
|
+
resources: data.resources,
|
|
912
919
|
} as AgentListing;
|
|
913
920
|
}),
|
|
914
921
|
);
|