@mcpmesh/sdk 2.6.0 → 2.8.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/dist/__tests__/inbound-job-dispatch.spec.js +21 -8
- package/dist/__tests__/inbound-job-dispatch.spec.js.map +1 -1
- package/dist/__tests__/llm-agent-content-recovery.test.d.ts +17 -0
- package/dist/__tests__/llm-agent-content-recovery.test.d.ts.map +1 -0
- package/dist/__tests__/llm-agent-content-recovery.test.js +203 -0
- package/dist/__tests__/llm-agent-content-recovery.test.js.map +1 -0
- package/dist/__tests__/llm-provider-multistep.test.js +30 -0
- package/dist/__tests__/llm-provider-multistep.test.js.map +1 -1
- package/dist/__tests__/llm-provider-sampling-gating.test.d.ts +20 -0
- package/dist/__tests__/llm-provider-sampling-gating.test.d.ts.map +1 -0
- package/dist/__tests__/llm-provider-sampling-gating.test.js +192 -0
- package/dist/__tests__/llm-provider-sampling-gating.test.js.map +1 -0
- package/dist/__tests__/llm-token-telemetry.test.d.ts +17 -0
- package/dist/__tests__/llm-token-telemetry.test.d.ts.map +1 -0
- package/dist/__tests__/llm-token-telemetry.test.js +199 -0
- package/dist/__tests__/llm-token-telemetry.test.js.map +1 -0
- package/dist/__tests__/proxy-empty-content.test.d.ts +16 -0
- package/dist/__tests__/proxy-empty-content.test.d.ts.map +1 -0
- package/dist/__tests__/proxy-empty-content.test.js +124 -0
- package/dist/__tests__/proxy-empty-content.test.js.map +1 -0
- package/dist/__tests__/proxy-resource-link.test.js +4 -2
- package/dist/__tests__/proxy-resource-link.test.js.map +1 -1
- package/dist/__tests__/proxy-tool-error.test.js +3 -2
- package/dist/__tests__/proxy-tool-error.test.js.map +1 -1
- package/dist/__tests__/resolver-meshjob.spec.js +2 -2
- package/dist/__tests__/resolver-meshjob.spec.js.map +1 -1
- package/dist/__tests__/route-required.spec.d.ts +9 -0
- package/dist/__tests__/route-required.spec.d.ts.map +1 -0
- package/dist/__tests__/route-required.spec.js +154 -0
- package/dist/__tests__/route-required.spec.js.map +1 -0
- package/dist/__tests__/unwired-slot-warn.spec.d.ts +2 -0
- package/dist/__tests__/unwired-slot-warn.spec.d.ts.map +1 -0
- package/dist/__tests__/unwired-slot-warn.spec.js +167 -0
- package/dist/__tests__/unwired-slot-warn.spec.js.map +1 -0
- package/dist/agent.d.ts +19 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +61 -19
- package/dist/agent.js.map +1 -1
- package/dist/api-runtime.d.ts.map +1 -1
- package/dist/api-runtime.js +3 -0
- package/dist/api-runtime.js.map +1 -1
- package/dist/claim-dispatcher.d.ts.map +1 -1
- package/dist/claim-dispatcher.js +12 -2
- package/dist/claim-dispatcher.js.map +1 -1
- package/dist/express.d.ts.map +1 -1
- package/dist/express.js +3 -0
- package/dist/express.js.map +1 -1
- package/dist/inbound-job-dispatch.d.ts +9 -6
- package/dist/inbound-job-dispatch.d.ts.map +1 -1
- package/dist/inbound-job-dispatch.js +35 -10
- package/dist/inbound-job-dispatch.js.map +1 -1
- package/dist/job-context.d.ts +9 -0
- package/dist/job-context.d.ts.map +1 -1
- package/dist/job-context.js.map +1 -1
- package/dist/jobs.d.ts +8 -4
- package/dist/jobs.d.ts.map +1 -1
- package/dist/jobs.js +8 -4
- package/dist/jobs.js.map +1 -1
- package/dist/llm-agent.d.ts +3 -0
- package/dist/llm-agent.d.ts.map +1 -1
- package/dist/llm-agent.js +72 -7
- package/dist/llm-agent.js.map +1 -1
- package/dist/llm-provider.d.ts +31 -0
- package/dist/llm-provider.d.ts.map +1 -1
- package/dist/llm-provider.js +98 -15
- package/dist/llm-provider.js.map +1 -1
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +34 -2
- package/dist/llm.js.map +1 -1
- package/dist/proxy.d.ts +13 -2
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +83 -11
- package/dist/proxy.js.map +1 -1
- package/dist/route.d.ts +3 -0
- package/dist/route.d.ts.map +1 -1
- package/dist/route.js +42 -0
- package/dist/route.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #1249: per-dependency `required` flag + route perimeter 503.
|
|
3
|
+
*
|
|
4
|
+
* Uses the REAL `normalizeDependency` (proxy.js) and the REAL route
|
|
5
|
+
* middleware — no proxy mock — so the declaration → normalized-dep → wire
|
|
6
|
+
* mapping and the perimeter check are genuinely exercised.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi, beforeEach, afterAll } from "vitest";
|
|
9
|
+
import { normalizeDependency } from "../proxy.js";
|
|
10
|
+
import { route, RouteRegistry } from "../route.js";
|
|
11
|
+
import { resetSettleStateForTests } from "../settle.js";
|
|
12
|
+
// Disable the settling-window grace (#1193) so the perimeter is judged
|
|
13
|
+
// immediately (MCP_MESH_SETTLE_TIMEOUT=0 tuning case). The perimeter runs
|
|
14
|
+
// AFTER the settle wait, so with the grace on a still-settling dep would get
|
|
15
|
+
// its full window before being called unavailable — covered elsewhere.
|
|
16
|
+
const savedSettleTimeout = process.env.MCP_MESH_SETTLE_TIMEOUT;
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
process.env.MCP_MESH_SETTLE_TIMEOUT = "0";
|
|
19
|
+
resetSettleStateForTests();
|
|
20
|
+
RouteRegistry.reset();
|
|
21
|
+
});
|
|
22
|
+
afterAll(() => {
|
|
23
|
+
if (savedSettleTimeout === undefined) {
|
|
24
|
+
delete process.env.MCP_MESH_SETTLE_TIMEOUT;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
process.env.MCP_MESH_SETTLE_TIMEOUT = savedSettleTimeout;
|
|
28
|
+
}
|
|
29
|
+
resetSettleStateForTests();
|
|
30
|
+
});
|
|
31
|
+
function mockRes() {
|
|
32
|
+
return {
|
|
33
|
+
status: vi.fn().mockReturnThis(),
|
|
34
|
+
json: vi.fn().mockReturnThis(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
describe("normalizeDependency required flag (#1249)", () => {
|
|
38
|
+
it("carries required:true through to the normalized dep (napi wire seam)", () => {
|
|
39
|
+
const norm = normalizeDependency({ capability: "weather-api", required: true });
|
|
40
|
+
expect(norm.capability).toBe("weather-api");
|
|
41
|
+
expect(norm.required).toBe(true);
|
|
42
|
+
// The three JsDependencySpec builders map `dep.required ? true : undefined`,
|
|
43
|
+
// so a true here becomes required:true on the napi DependencySpec.
|
|
44
|
+
expect(norm.required ? true : undefined).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
it("omits required when declared false (byte-identical soft-fail default)", () => {
|
|
47
|
+
const norm = normalizeDependency({ capability: "weather-api", required: false });
|
|
48
|
+
expect(norm.required).toBeUndefined();
|
|
49
|
+
expect(norm.required ? true : undefined).toBeUndefined();
|
|
50
|
+
});
|
|
51
|
+
it("omits required when absent (default optional)", () => {
|
|
52
|
+
const norm = normalizeDependency({ capability: "weather-api" });
|
|
53
|
+
expect(norm.required).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
it("string-form deps are always optional", () => {
|
|
56
|
+
const norm = normalizeDependency("weather-api");
|
|
57
|
+
expect(norm.required).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe("RouteRegistry stores required in route metadata (#1249)", () => {
|
|
61
|
+
it("persists required:true on the declared route dep", () => {
|
|
62
|
+
const registry = RouteRegistry.getInstance();
|
|
63
|
+
const routeId = registry.registerRoute("GET", "/x", [
|
|
64
|
+
{ capability: "weather-api", required: true },
|
|
65
|
+
{ capability: "logger" },
|
|
66
|
+
]);
|
|
67
|
+
const meta = registry.getRoute(routeId);
|
|
68
|
+
expect(meta?.dependencies[0].required).toBe(true);
|
|
69
|
+
expect(meta?.dependencies[1].required).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("route perimeter 503 (#1249)", () => {
|
|
73
|
+
it("returns 503 before user code when a required dep is unavailable", async () => {
|
|
74
|
+
const handler = vi.fn();
|
|
75
|
+
const middleware = route([{ capability: "calculator", required: true }], handler);
|
|
76
|
+
const req = { method: "POST", path: "/compute", headers: {} };
|
|
77
|
+
const res = mockRes();
|
|
78
|
+
const next = vi.fn();
|
|
79
|
+
await middleware(req, res, next);
|
|
80
|
+
expect(res.status).toHaveBeenCalledWith(503);
|
|
81
|
+
expect(res.json).toHaveBeenCalledWith({
|
|
82
|
+
error: "dependency_unavailable",
|
|
83
|
+
capability: "calculator",
|
|
84
|
+
});
|
|
85
|
+
// User code must NOT run.
|
|
86
|
+
expect(handler).not.toHaveBeenCalled();
|
|
87
|
+
expect(next).not.toHaveBeenCalled();
|
|
88
|
+
});
|
|
89
|
+
it("runs the handler (no 503) when the required dep IS available", async () => {
|
|
90
|
+
const registry = RouteRegistry.getInstance();
|
|
91
|
+
const handler = vi.fn();
|
|
92
|
+
const middleware = route([{ capability: "calculator", required: true }], handler);
|
|
93
|
+
const mockCalc = (async () => "result");
|
|
94
|
+
registry.setDependency(middleware._meshRouteId, 0, mockCalc);
|
|
95
|
+
const req = { method: "POST", path: "/compute", headers: {} };
|
|
96
|
+
const res = mockRes();
|
|
97
|
+
const next = vi.fn();
|
|
98
|
+
await middleware(req, res, next);
|
|
99
|
+
expect(res.status).not.toHaveBeenCalled();
|
|
100
|
+
expect(handler).toHaveBeenCalledWith(req, res, expect.objectContaining({ calculator: mockCalc }));
|
|
101
|
+
});
|
|
102
|
+
it("does NOT 503 for an unavailable OPTIONAL dep (soft-fail preserved)", async () => {
|
|
103
|
+
const handler = vi.fn();
|
|
104
|
+
const middleware = route([{ capability: "calculator" }], handler);
|
|
105
|
+
const req = { method: "POST", path: "/compute", headers: {} };
|
|
106
|
+
const res = mockRes();
|
|
107
|
+
const next = vi.fn();
|
|
108
|
+
await middleware(req, res, next);
|
|
109
|
+
expect(res.status).not.toHaveBeenCalled();
|
|
110
|
+
// Handler runs with a null proxy — the existing degraded behavior.
|
|
111
|
+
expect(handler).toHaveBeenCalledWith(req, res, expect.objectContaining({ calculator: null }));
|
|
112
|
+
});
|
|
113
|
+
it("does NOT 503 when a duplicate capability is live in the winning slot", async () => {
|
|
114
|
+
// A capability declared twice collapses to one capability-keyed `deps`
|
|
115
|
+
// slot (last resolution wins). The perimeter must judge the same slot the
|
|
116
|
+
// handler sees — not a dead sibling index — so a live proxy means run.
|
|
117
|
+
const registry = RouteRegistry.getInstance();
|
|
118
|
+
const handler = vi.fn();
|
|
119
|
+
const middleware = route([
|
|
120
|
+
{ capability: "calculator", required: true }, // required, its index unresolved
|
|
121
|
+
{ capability: "calculator" }, // optional, this index is live → wins deps[cap]
|
|
122
|
+
], handler);
|
|
123
|
+
const mockCalc = (async () => "result");
|
|
124
|
+
registry.setDependency(middleware._meshRouteId, 1, mockCalc);
|
|
125
|
+
const req = { method: "POST", path: "/compute", headers: {} };
|
|
126
|
+
const res = mockRes();
|
|
127
|
+
const next = vi.fn();
|
|
128
|
+
await middleware(req, res, next);
|
|
129
|
+
expect(res.status).not.toHaveBeenCalled();
|
|
130
|
+
expect(handler).toHaveBeenCalledWith(req, res, expect.objectContaining({ calculator: mockCalc }));
|
|
131
|
+
});
|
|
132
|
+
it("503s on the first unavailable required dep among a mix", async () => {
|
|
133
|
+
const registry = RouteRegistry.getInstance();
|
|
134
|
+
const handler = vi.fn();
|
|
135
|
+
const middleware = route([
|
|
136
|
+
{ capability: "logger" }, // optional, unresolved
|
|
137
|
+
{ capability: "calculator", required: true }, // required, resolved
|
|
138
|
+
{ capability: "weather-api", required: true }, // required, unresolved
|
|
139
|
+
], handler);
|
|
140
|
+
const mockCalc = (async () => "result");
|
|
141
|
+
registry.setDependency(middleware._meshRouteId, 1, mockCalc);
|
|
142
|
+
const req = { method: "POST", path: "/compute", headers: {} };
|
|
143
|
+
const res = mockRes();
|
|
144
|
+
const next = vi.fn();
|
|
145
|
+
await middleware(req, res, next);
|
|
146
|
+
expect(res.status).toHaveBeenCalledWith(503);
|
|
147
|
+
expect(res.json).toHaveBeenCalledWith({
|
|
148
|
+
error: "dependency_unavailable",
|
|
149
|
+
capability: "weather-api",
|
|
150
|
+
});
|
|
151
|
+
expect(handler).not.toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
//# sourceMappingURL=route-required.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-required.spec.js","sourceRoot":"","sources":["../../src/__tests__/route-required.spec.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwired-slot-warn.spec.d.ts","sourceRoot":"","sources":["../../src/__tests__/unwired-slot-warn.spec.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unwired typed-dependency-slot diagnostics (issue #1231).
|
|
3
|
+
*
|
|
4
|
+
* A declared typed dependency slot can remain null after the settle window
|
|
5
|
+
* closes — no proxy was ever injected — leaving the parameter silently null.
|
|
6
|
+
* The deps array is rebuilt PER CALL, so the diagnostic must warn ONCE per
|
|
7
|
+
* tool+slot (a per-call warning would spam) and only AFTER the settle latch
|
|
8
|
+
* has flipped (during settling the proxy may still land). The same warn-once
|
|
9
|
+
* dedupe spans BOTH the inbound HTTP path and the claim-dispatch path.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { MeshAgent, __resetUnwiredSlotWarnedForTests } from "../agent.js";
|
|
14
|
+
import { RouteRegistry } from "../route.js";
|
|
15
|
+
import { resetSettleStateForTests } from "../settle.js";
|
|
16
|
+
function makeFastMCPStub() {
|
|
17
|
+
return {
|
|
18
|
+
addTool: vi.fn(),
|
|
19
|
+
start: vi.fn(),
|
|
20
|
+
getApp: vi.fn(),
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
let autoStartSpy = null;
|
|
25
|
+
let warnSpy = null;
|
|
26
|
+
const savedEnv = {};
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
autoStartSpy = vi
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
.spyOn(MeshAgent.prototype, "_autoStart")
|
|
31
|
+
.mockImplementation(async () => {
|
|
32
|
+
/* no-op */
|
|
33
|
+
});
|
|
34
|
+
warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {
|
|
35
|
+
/* swallow */
|
|
36
|
+
});
|
|
37
|
+
savedEnv.MCP_MESH_SETTLE_TIMEOUT = process.env.MCP_MESH_SETTLE_TIMEOUT;
|
|
38
|
+
savedEnv.MCP_MESH_TOOL_ISOLATION = process.env.MCP_MESH_TOOL_ISOLATION;
|
|
39
|
+
process.env.MCP_MESH_TOOL_ISOLATION = "false";
|
|
40
|
+
// Disable the settle grace so the agent is settled immediately — a null
|
|
41
|
+
// slot here is genuinely unwired, exactly the case the warning targets.
|
|
42
|
+
process.env.MCP_MESH_SETTLE_TIMEOUT = "0";
|
|
43
|
+
resetSettleStateForTests();
|
|
44
|
+
RouteRegistry.reset();
|
|
45
|
+
__resetUnwiredSlotWarnedForTests();
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
if (autoStartSpy) {
|
|
49
|
+
autoStartSpy.mockRestore();
|
|
50
|
+
autoStartSpy = null;
|
|
51
|
+
}
|
|
52
|
+
if (warnSpy) {
|
|
53
|
+
warnSpy.mockRestore();
|
|
54
|
+
warnSpy = null;
|
|
55
|
+
}
|
|
56
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
delete process.env[key];
|
|
59
|
+
else
|
|
60
|
+
process.env[key] = value;
|
|
61
|
+
}
|
|
62
|
+
resetSettleStateForTests();
|
|
63
|
+
RouteRegistry.reset();
|
|
64
|
+
__resetUnwiredSlotWarnedForTests();
|
|
65
|
+
});
|
|
66
|
+
function unwiredWarnings() {
|
|
67
|
+
return (warnSpy?.mock.calls ?? [])
|
|
68
|
+
.map((call) => String(call[0]))
|
|
69
|
+
.filter((msg) => msg.includes("no proxy was injected"));
|
|
70
|
+
}
|
|
71
|
+
describe("unwired typed-slot warning (#1231)", () => {
|
|
72
|
+
it("warns ONCE per tool+slot across multiple calls, not per-call", async () => {
|
|
73
|
+
const fastmcp = makeFastMCPStub();
|
|
74
|
+
const agent = new MeshAgent(fastmcp, {
|
|
75
|
+
name: "unwired-agent",
|
|
76
|
+
httpPort: 0,
|
|
77
|
+
});
|
|
78
|
+
agent.addTool({
|
|
79
|
+
name: "consumer_tool",
|
|
80
|
+
parameters: z.object({}),
|
|
81
|
+
dependencies: [{ capability: "missing_cap" }],
|
|
82
|
+
// Defensive user idiom — the dep slot is never resolved, so it stays
|
|
83
|
+
// null on every call.
|
|
84
|
+
execute: async (_args, dep) => dep ? "wired" : "unwired",
|
|
85
|
+
});
|
|
86
|
+
const execute = fastmcp.addTool.mock.calls[0][0].execute;
|
|
87
|
+
for (let i = 0; i < 3; i++) {
|
|
88
|
+
expect(await execute({})).toBe("unwired");
|
|
89
|
+
}
|
|
90
|
+
const warnings = unwiredWarnings();
|
|
91
|
+
expect(warnings).toHaveLength(1);
|
|
92
|
+
expect(warnings[0]).toContain("missing_cap");
|
|
93
|
+
expect(warnings[0]).toContain("consumer_tool");
|
|
94
|
+
expect(warnings[0]).toContain("slot 0");
|
|
95
|
+
});
|
|
96
|
+
it("does not warn when the slot actually wires", async () => {
|
|
97
|
+
const fastmcp = makeFastMCPStub();
|
|
98
|
+
const agent = new MeshAgent(fastmcp, {
|
|
99
|
+
name: "wired-agent",
|
|
100
|
+
httpPort: 0,
|
|
101
|
+
});
|
|
102
|
+
agent.addTool({
|
|
103
|
+
name: "wired_tool",
|
|
104
|
+
parameters: z.object({}),
|
|
105
|
+
dependencies: [{ capability: "present_cap" }],
|
|
106
|
+
execute: async (_args, dep) => dep ? "wired" : "unwired",
|
|
107
|
+
});
|
|
108
|
+
const execute = fastmcp.addTool.mock.calls[0][0].execute;
|
|
109
|
+
// Resolve the dependency so a real proxy lands in the slot.
|
|
110
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
111
|
+
agent.handleDependencyAvailable("present_cap", "http://localhost:19999", "remote_fn", "provider-agent", "wired_tool", 0);
|
|
112
|
+
expect(await execute({})).toBe("wired");
|
|
113
|
+
expect(unwiredWarnings()).toHaveLength(0);
|
|
114
|
+
});
|
|
115
|
+
it("warns ONCE for an unwired slot on the claim-dispatch path", async () => {
|
|
116
|
+
const fastmcp = makeFastMCPStub();
|
|
117
|
+
const agent = new MeshAgent(fastmcp, {
|
|
118
|
+
name: "claim-agent",
|
|
119
|
+
httpPort: 0,
|
|
120
|
+
});
|
|
121
|
+
agent.addTool({
|
|
122
|
+
name: "task_tool",
|
|
123
|
+
task: true,
|
|
124
|
+
parameters: z.object({}),
|
|
125
|
+
dependencies: [{ capability: "missing_cap" }],
|
|
126
|
+
execute: async (_args, dep) => dep ? "wired" : "unwired",
|
|
127
|
+
});
|
|
128
|
+
// The claim-dispatch path is exercised via the registered ClaimHandler
|
|
129
|
+
// (one per task: true tool), not via FastMCP's execute().
|
|
130
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
131
|
+
const { handler } = agent._taskHandlers.get("task_tool");
|
|
132
|
+
// meshJobParamIndex is unset, so the dummy controller is never spliced
|
|
133
|
+
// into callArgs — the user execute() only sees (payload, dep).
|
|
134
|
+
for (let i = 0; i < 3; i++) {
|
|
135
|
+
expect(await handler({}, {})).toBe("unwired");
|
|
136
|
+
}
|
|
137
|
+
const warnings = unwiredWarnings();
|
|
138
|
+
expect(warnings).toHaveLength(1);
|
|
139
|
+
expect(warnings[0]).toContain("missing_cap");
|
|
140
|
+
expect(warnings[0]).toContain("task_tool");
|
|
141
|
+
expect(warnings[0]).toContain("slot 0");
|
|
142
|
+
});
|
|
143
|
+
it("dedupes the unwired warning ACROSS the inbound and claim paths", async () => {
|
|
144
|
+
const fastmcp = makeFastMCPStub();
|
|
145
|
+
const agent = new MeshAgent(fastmcp, {
|
|
146
|
+
name: "both-paths-agent",
|
|
147
|
+
httpPort: 0,
|
|
148
|
+
});
|
|
149
|
+
agent.addTool({
|
|
150
|
+
name: "task_tool",
|
|
151
|
+
task: true,
|
|
152
|
+
parameters: z.object({}),
|
|
153
|
+
dependencies: [{ capability: "missing_cap" }],
|
|
154
|
+
execute: async (_args, dep) => dep ? "wired" : "unwired",
|
|
155
|
+
});
|
|
156
|
+
const execute = fastmcp.addTool.mock.calls[0][0].execute;
|
|
157
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
158
|
+
const { handler } = agent._taskHandlers.get("task_tool");
|
|
159
|
+
// Exercise the same tool+slot via BOTH paths — the shared
|
|
160
|
+
// _unwiredSlotWarned Set must dedupe to a single warning total.
|
|
161
|
+
expect(await execute({})).toBe("unwired");
|
|
162
|
+
expect(await handler({}, {})).toBe("unwired");
|
|
163
|
+
expect(await execute({})).toBe("unwired");
|
|
164
|
+
expect(unwiredWarnings()).toHaveLength(1);
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=unwired-slot-warn.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unwired-slot-warn.spec.js","sourceRoot":"","sources":["../../src/__tests__/unwired-slot-warn.spec.ts"],"names":[],"mappings":"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"}
|
package/dist/agent.d.ts
CHANGED
|
@@ -38,6 +38,8 @@ export declare const IN_WORKER_SYMBOL: unique symbol;
|
|
|
38
38
|
* module. Not part of the public API.
|
|
39
39
|
*/
|
|
40
40
|
export declare function __getWorkerToolMap(): Map<string, (...args: unknown[]) => unknown>;
|
|
41
|
+
/** Test support: drop the warn-once dedupe so a fresh suite warns again. */
|
|
42
|
+
export declare function __resetUnwiredSlotWarnedForTests(): void;
|
|
41
43
|
/**
|
|
42
44
|
* MeshAgent wraps a FastMCP server with MCP Mesh capabilities.
|
|
43
45
|
*
|
|
@@ -184,6 +186,23 @@ export declare class MeshAgent {
|
|
|
184
186
|
* tool-A's bearer onto tool-B's outbound traffic.
|
|
185
187
|
*/
|
|
186
188
|
private _bearerCacheKey;
|
|
189
|
+
/**
|
|
190
|
+
* Build the positional dependency-slot array for one tool call, shared by
|
|
191
|
+
* BOTH the inbound HTTP wrapper (wrappedExecute) and the claim-dispatch
|
|
192
|
+
* path (the ClaimHandler for task:true tools). Centralising this keeps the
|
|
193
|
+
* two slot-assembly sites identical:
|
|
194
|
+
* - the MeshJob slot (meshJobDepIndex) → a freshly-bound MeshJobSubmitter
|
|
195
|
+
* - every other slot → resolvedDeps.get(`${toolName}:dep_${index}`) ?? null
|
|
196
|
+
*
|
|
197
|
+
* #1231: a typed dep slot can resolve at the registry yet never receive an
|
|
198
|
+
* injected proxy, leaving the parameter null. Once the settle latch has
|
|
199
|
+
* flipped (during settling the proxy may still land), warn ONCE per
|
|
200
|
+
* tool+slot — the array is rebuilt per call, so a per-call warning would
|
|
201
|
+
* spam. The warn-once dedupe keys on `${toolName}:dep_${index}` via the
|
|
202
|
+
* module-level `_unwiredSlotWarned` Set, so a tool exercised via BOTH the
|
|
203
|
+
* inbound and claim paths warns at most once total.
|
|
204
|
+
*/
|
|
205
|
+
private _buildDepSlots;
|
|
187
206
|
private _getOrBuildA2AClient;
|
|
188
207
|
/**
|
|
189
208
|
* Convert Zod schema to JSON Schema.
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAG7B,OAAO,EAGL,KAAK,aAAa,EAInB,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EACV,WAAW,EACX,mBAAmB,EACnB,WAAW,EAEX,WAAW,EAEX,iBAAiB,EAClB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
package/dist/agent.js
CHANGED
|
@@ -16,7 +16,7 @@ import { enrichSchemaWithMediaTypes } from "./media-param.js";
|
|
|
16
16
|
import { createProxy, normalizeDependency, runWithTraceContext, runWithPropagatedHeaders, PROXY_DISPATCH_META } from "./proxy.js";
|
|
17
17
|
import { readJobHeaders, runWithJobContext, makeJobController, spliceJobController, } from "./inbound-job-dispatch.js";
|
|
18
18
|
import { MeshJobSubmitter } from "./mesh-job-submitter.js";
|
|
19
|
-
import { getSettleState } from "./settle.js";
|
|
19
|
+
import { getSettleState, } from "./settle.js";
|
|
20
20
|
import { ClaimDispatcher, stopDispatchers, } from "./claim-dispatcher.js";
|
|
21
21
|
import { registerJobHelperTools } from "./jobs-helper-tools.js";
|
|
22
22
|
import { registerCancelRoute } from "./jobs-cancel-route.js";
|
|
@@ -84,6 +84,16 @@ let autoStartScheduled = false;
|
|
|
84
84
|
// pre-consumption danger zone, while sequential constructions across
|
|
85
85
|
// async boundaries (e.g. one agent per test in a harness) stay allowed.
|
|
86
86
|
let constructionGuardName = null;
|
|
87
|
+
// #1231: warn-once dedupe for typed dependency slots that resolved at the
|
|
88
|
+
// registry ("N/N deps resolved") but did not actually wire — the proxy was
|
|
89
|
+
// never injected and the slot stays null. The deps array is rebuilt PER
|
|
90
|
+
// CALL, so a per-call warn would spam; this set keys on `${toolName}:${dep}`
|
|
91
|
+
// so each unwired slot warns exactly once per process.
|
|
92
|
+
const _unwiredSlotWarned = new Set();
|
|
93
|
+
/** Test support: drop the warn-once dedupe so a fresh suite warns again. */
|
|
94
|
+
export function __resetUnwiredSlotWarnedForTests() {
|
|
95
|
+
_unwiredSlotWarned.clear();
|
|
96
|
+
}
|
|
87
97
|
// Schedule auto-start after module loading completes
|
|
88
98
|
function scheduleAutoStart() {
|
|
89
99
|
if (autoStartScheduled)
|
|
@@ -495,15 +505,7 @@ export class MeshAgent {
|
|
|
495
505
|
// MeshJobSubmitter targeting that dep's capability. We bind the
|
|
496
506
|
// submitter to the live registryUrl/agentId so it can submit
|
|
497
507
|
// jobs without needing access to the agent instance.
|
|
498
|
-
const depsArray =
|
|
499
|
-
if (depIndex === meshJobDepIndex) {
|
|
500
|
-
// Build the submitter lazily per call so we always pick
|
|
501
|
-
// up the current registryUrl (test harnesses sometimes
|
|
502
|
-
// mutate it between calls).
|
|
503
|
-
return new MeshJobSubmitter(dep.capability, this.agentId, this.config.registryUrl);
|
|
504
|
-
}
|
|
505
|
-
return this.resolvedDeps.get(`${toolName}:dep_${depIndex}`) ?? null;
|
|
506
|
-
});
|
|
508
|
+
const depsArray = this._buildDepSlots(toolName, normalizedDeps, meshJobDepIndex, settleState);
|
|
507
509
|
const injectedCount = depsArray.filter((d) => d !== null).length;
|
|
508
510
|
// Extract trace context from arguments (injected by upstream proxy)
|
|
509
511
|
// This is the fallback mechanism since fastmcp doesn't expose HTTP headers
|
|
@@ -619,11 +621,11 @@ export class MeshAgent {
|
|
|
619
621
|
result = await runWithTraceContext(traceContext, async () => {
|
|
620
622
|
return await runWithPropagatedHeaders(propagatedHeaders, async () => {
|
|
621
623
|
if (isTaskTool) {
|
|
622
|
-
const [jobId, deadlineSecs] = readJobHeaders(propagatedHeaders);
|
|
624
|
+
const [jobId, deadlineSecs, claimEpoch] = readJobHeaders(propagatedHeaders);
|
|
623
625
|
let controller = null;
|
|
624
626
|
if (jobId && this.config.registryUrl && this.agentId) {
|
|
625
627
|
try {
|
|
626
|
-
controller = makeJobController(jobId, this.agentId, this.config.registryUrl);
|
|
628
|
+
controller = makeJobController(jobId, this.agentId, this.config.registryUrl, claimEpoch);
|
|
627
629
|
}
|
|
628
630
|
catch (err) {
|
|
629
631
|
// Don't silently fall back to a regular tool call —
|
|
@@ -653,7 +655,7 @@ export class MeshAgent {
|
|
|
653
655
|
if (a2aClient !== null) {
|
|
654
656
|
callArgs.push(a2aClient);
|
|
655
657
|
}
|
|
656
|
-
return await runWithJobContext(jobId, deadlineSecs, controller, () => Promise.resolve(execute(...callArgs)), retryOn);
|
|
658
|
+
return await runWithJobContext(jobId, deadlineSecs, controller, () => Promise.resolve(execute(...callArgs)), retryOn, claimEpoch);
|
|
657
659
|
}
|
|
658
660
|
if (a2aClient !== null) {
|
|
659
661
|
return await execute(cleanArgs, ...depsArray, a2aClient);
|
|
@@ -753,12 +755,7 @@ export class MeshAgent {
|
|
|
753
755
|
await settleState.awaitPending(pendingSettle);
|
|
754
756
|
}
|
|
755
757
|
}
|
|
756
|
-
const liveDeps =
|
|
757
|
-
if (depIndex === meshJobDepIndex) {
|
|
758
|
-
return new MeshJobSubmitter(dep.capability, this.agentId, this.config.registryUrl);
|
|
759
|
-
}
|
|
760
|
-
return this.resolvedDeps.get(`${toolName}:dep_${depIndex}`) ?? null;
|
|
761
|
-
});
|
|
758
|
+
const liveDeps = this._buildDepSlots(toolName, normalizedDeps, meshJobDepIndex, settleState);
|
|
762
759
|
const callArgs = spliceJobController(payload, liveDeps, controller, meshJobParamIndex);
|
|
763
760
|
// Issue #917: A2A consumer tools dispatched via the claim
|
|
764
761
|
// path get the same trailing A2AClient argument as the
|
|
@@ -905,6 +902,48 @@ export class MeshAgent {
|
|
|
905
902
|
}
|
|
906
903
|
return id;
|
|
907
904
|
}
|
|
905
|
+
/**
|
|
906
|
+
* Build the positional dependency-slot array for one tool call, shared by
|
|
907
|
+
* BOTH the inbound HTTP wrapper (wrappedExecute) and the claim-dispatch
|
|
908
|
+
* path (the ClaimHandler for task:true tools). Centralising this keeps the
|
|
909
|
+
* two slot-assembly sites identical:
|
|
910
|
+
* - the MeshJob slot (meshJobDepIndex) → a freshly-bound MeshJobSubmitter
|
|
911
|
+
* - every other slot → resolvedDeps.get(`${toolName}:dep_${index}`) ?? null
|
|
912
|
+
*
|
|
913
|
+
* #1231: a typed dep slot can resolve at the registry yet never receive an
|
|
914
|
+
* injected proxy, leaving the parameter null. Once the settle latch has
|
|
915
|
+
* flipped (during settling the proxy may still land), warn ONCE per
|
|
916
|
+
* tool+slot — the array is rebuilt per call, so a per-call warning would
|
|
917
|
+
* spam. The warn-once dedupe keys on `${toolName}:dep_${index}` via the
|
|
918
|
+
* module-level `_unwiredSlotWarned` Set, so a tool exercised via BOTH the
|
|
919
|
+
* inbound and claim paths warns at most once total.
|
|
920
|
+
*/
|
|
921
|
+
_buildDepSlots(toolName, normalizedDeps, meshJobDepIndex, settleState) {
|
|
922
|
+
return normalizedDeps.map((dep, depIndex) => {
|
|
923
|
+
if (depIndex === meshJobDepIndex) {
|
|
924
|
+
// Build the submitter lazily per call so we always pick up the
|
|
925
|
+
// current registryUrl (test harnesses sometimes mutate it between
|
|
926
|
+
// calls).
|
|
927
|
+
return new MeshJobSubmitter(dep.capability, this.agentId, this.config.registryUrl);
|
|
928
|
+
}
|
|
929
|
+
const slotKey = `${toolName}:dep_${depIndex}`;
|
|
930
|
+
const proxy = this.resolvedDeps.get(slotKey) ?? null;
|
|
931
|
+
// #1231: a declared typed dep slot that is still null AFTER settling
|
|
932
|
+
// never received an injected proxy. Warn ONCE per tool+slot. Note this
|
|
933
|
+
// is a post-settle null — there is no per-slot "resolved" signal here,
|
|
934
|
+
// so the message must not claim registry resolution.
|
|
935
|
+
if (proxy === null && settleState.isSettled()) {
|
|
936
|
+
if (!_unwiredSlotWarned.has(slotKey)) {
|
|
937
|
+
_unwiredSlotWarned.add(slotKey);
|
|
938
|
+
console.warn(`[mesh-tool] dependency '${dep.capability}' on '${toolName}' ` +
|
|
939
|
+
`is still null after settling — no proxy was injected into ` +
|
|
940
|
+
`positional slot ${depIndex}. Fix: ensure the provider for ` +
|
|
941
|
+
`'${dep.capability}' is registered and reachable.`);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return proxy;
|
|
945
|
+
});
|
|
946
|
+
}
|
|
908
947
|
_getOrBuildA2AClient(config) {
|
|
909
948
|
const key = [
|
|
910
949
|
config.url,
|
|
@@ -1300,6 +1339,9 @@ export class MeshAgent {
|
|
|
1300
1339
|
expectedSchemaCanonical: expectedCanonical,
|
|
1301
1340
|
expectedSchemaHash: expectedHash,
|
|
1302
1341
|
matchMode: dep.matchMode,
|
|
1342
|
+
// Issue #1249: only carry `required` when opted in (absent →
|
|
1343
|
+
// false) so existing heartbeats stay byte-identical.
|
|
1344
|
+
required: dep.required ? true : undefined,
|
|
1303
1345
|
};
|
|
1304
1346
|
}),
|
|
1305
1347
|
inputSchema: meta.inputSchema,
|