Skip to content

Commit 2930bf5

Browse files
fix: pass tool schema without conversion (#56)
1 parent 60458b9 commit 2930bf5

File tree

5 files changed

+102
-53
lines changed

5 files changed

+102
-53
lines changed

__tests__/tools.test.ts

+72-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { describe, test, expect, beforeEach, vi, MockedObject } from "vitest";
22
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
3-
import type { StructuredTool } from "@langchain/core/tools";
3+
import {
4+
StructuredTool,
5+
ToolInputParsingException,
6+
} from "@langchain/core/tools";
47
import type {
58
EmbeddedResource,
69
ImageContent,
@@ -59,6 +62,74 @@ describe("Simplified Tool Adapter Tests", () => {
5962
expect(tools[1].name).toBe("tool2");
6063
});
6164

65+
test("should validate tool input against input schema", async () => {
66+
// Set up mock response
67+
mockClient.listTools.mockReturnValueOnce(
68+
Promise.resolve({
69+
tools: [
70+
{
71+
name: "weather",
72+
description: "Get the weather for a given city",
73+
inputSchema: {
74+
type: "object",
75+
properties: {
76+
city: { type: "string" },
77+
},
78+
required: ["city"],
79+
},
80+
},
81+
],
82+
})
83+
);
84+
85+
mockClient.callTool.mockImplementation((params) => {
86+
// should not be called if input is invalid
87+
const args = params.arguments as { city: string };
88+
expect(args.city).toBeDefined();
89+
expect(typeof args.city).toBe("string");
90+
91+
return Promise.resolve({
92+
content: [
93+
{
94+
type: "text",
95+
text: `It is currently 70 degrees and cloudy in ${args.city}.`,
96+
},
97+
],
98+
});
99+
});
100+
101+
// Load tools
102+
const tools = await loadMcpTools(
103+
"mockServer(should validate tool input against input schema)",
104+
mockClient as Client
105+
);
106+
107+
// Verify results
108+
expect(tools.length).toBe(1);
109+
expect(tools[0].name).toBe("weather");
110+
111+
const weatherTool = tools[0];
112+
113+
// should not invoke the tool when input is invalid
114+
await expect(
115+
weatherTool.invoke({ location: "New York" })
116+
).rejects.toThrow(ToolInputParsingException);
117+
118+
expect(mockClient.callTool).not.toHaveBeenCalled();
119+
120+
// should invoke the tool when input is valid
121+
await expect(weatherTool.invoke({ city: "New York" })).resolves.toEqual(
122+
"It is currently 70 degrees and cloudy in New York."
123+
);
124+
125+
expect(mockClient.callTool).toHaveBeenCalledWith({
126+
arguments: {
127+
city: "New York",
128+
},
129+
name: "weather",
130+
});
131+
});
132+
62133
test("should handle empty tool list", async () => {
63134
// Set up mock response
64135
mockClient.listTools.mockReturnValueOnce(

examples/langgraph_example.ts

+8-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
* - Built-in persistence capabilities
1111
*
1212
* In this example, we:
13-
* 1. Set up an MCP client to connect to the MCP Math server reference example
13+
* 1. Set up an MCP client to connect to the MCP everything server reference example
1414
* 2. Create a LangGraph workflow with two nodes: one for the LLM and one for tools
1515
* 3. Define the edges and conditional routing between the nodes
1616
* 4. Execute the workflow with example queries
@@ -42,20 +42,20 @@ dotenv.config();
4242

4343
/**
4444
* Example demonstrating how to use MCP tools with LangGraph agent flows
45-
* This example connects to a math server and uses its tools
45+
* This example connects to a everything server and uses its tools
4646
*/
4747
async function runExample() {
4848
let client: MultiServerMCPClient | null = null;
4949

5050
try {
5151
console.log("Initializing MCP client...");
5252

53-
// Create a client with configurations for the math server only
53+
// Create a client with configurations for the everything server only
5454
client = new MultiServerMCPClient({
55-
math: {
55+
everything: {
5656
transport: "stdio",
5757
command: "npx",
58-
args: ["-y", "@modelcontextprotocol/server-math"],
58+
args: ["-y", "@modelcontextprotocol/server-everything"],
5959
},
6060
});
6161

@@ -137,7 +137,9 @@ async function runExample() {
137137
const app = workflow.compile();
138138

139139
// Define queries for testing
140-
const queries = ["What is 5 + 3?", "What is 7 * 9?"];
140+
const queries = [
141+
"If Sally has 420324 apples and mark steals 7824 of them, how many does she have left?",
142+
];
141143

142144
// Test the LangGraph agent with the queries
143145
console.log("\n=== RUNNING LANGGRAPH AGENT ===");

package.json

+5-4
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,16 @@
5252
"zod": "^3.24.2"
5353
},
5454
"peerDependencies": {
55-
"@langchain/core": "^0.3.40"
55+
"@langchain/core": "^0.3.44"
5656
},
5757
"optionalDependencies": {
5858
"extended-eventsource": "^1.x"
5959
},
6060
"devDependencies": {
6161
"@eslint/js": "^9.21.0",
62-
"@langchain/core": "^0.3.43",
62+
"@langchain/core": "^0.3.44",
6363
"@langchain/langgraph": "^0.2.62",
64-
"@langchain/openai": "^0.5.2",
64+
"@langchain/openai": "^0.5.5",
6565
"@langchain/scripts": "^0.1.3",
6666
"@tsconfig/recommended": "^1.0.8",
6767
"@types/debug": "^4.1.12",
@@ -91,7 +91,8 @@
9191
"vitest": "^3.1.1"
9292
},
9393
"resolutions": {
94-
"typescript": "4.9.5"
94+
"typescript": "4.9.5",
95+
"uuid": "^11.0.0"
9596
},
9697
"engines": {
9798
"node": ">=18"

src/tools.ts

+1-17
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,8 @@ import {
1818
MessageContentImageUrl,
1919
MessageContentText,
2020
} from "@langchain/core/messages";
21-
import type { JSONSchema } from "@dmitryrechkin/json-schema-to-zod";
22-
import type { ZodSchema } from "zod";
2321
import debug from "debug";
2422

25-
let JSONSchemaToZod: { convert: (schema: JSONSchema) => ZodSchema } | undefined;
26-
27-
async function convertSchema(schema: JSONSchema): Promise<ZodSchema> {
28-
if (!JSONSchemaToZod) {
29-
({ JSONSchemaToZod } = await import("@dmitryrechkin/json-schema-to-zod"));
30-
}
31-
return JSONSchemaToZod.convert(schema);
32-
}
33-
3423
// Replace direct initialization with lazy initialization
3524
let debugLog: debug.Debugger;
3625
function getDebugLog() {
@@ -279,12 +268,7 @@ export async function loadMcpTools(
279268
const dst = new DynamicStructuredTool({
280269
name: `${toolNamePrefix}${tool.name}`,
281270
description: tool.description || "",
282-
schema: await convertSchema(
283-
(tool.inputSchema ?? {
284-
type: "object",
285-
properties: {},
286-
}) as JSONSchema
287-
),
271+
schema: tool.inputSchema,
288272
responseFormat: "content_and_artifact",
289273
func: _callTool.bind(
290274
null,

yarn.lock

+16-25
Original file line numberDiff line numberDiff line change
@@ -438,9 +438,9 @@ __metadata:
438438
languageName: node
439439
linkType: hard
440440

441-
"@langchain/core@npm:^0.3.43":
442-
version: 0.3.43
443-
resolution: "@langchain/core@npm:0.3.43"
441+
"@langchain/core@npm:^0.3.44":
442+
version: 0.3.44
443+
resolution: "@langchain/core@npm:0.3.44"
444444
dependencies:
445445
"@cfworker/json-schema": ^4.0.2
446446
ansi-styles: ^5.0.0
@@ -454,7 +454,7 @@ __metadata:
454454
uuid: ^10.0.0
455455
zod: ^3.22.4
456456
zod-to-json-schema: ^3.22.3
457-
checksum: 5e5e9cd1ce39891177bc1d0d240785b60edeb0f9d53bb78a2ce394adf6fd15b972b63fb7a9d13c1f7b1341c79631baaa77225f6df36f509d0a606fb350f1d60f
457+
checksum: d517305ec4fa0b0924b0224d3876eff621bbf97c673819910c53920d9c614db2c2293dc4b14ff408f517183b2aff9c7ea5a37eb2c4b8926b0c7605ca72b9f3cb
458458
languageName: node
459459
linkType: hard
460460

@@ -513,9 +513,9 @@ __metadata:
513513
dependencies:
514514
"@dmitryrechkin/json-schema-to-zod": ^1.0.1
515515
"@eslint/js": ^9.21.0
516-
"@langchain/core": ^0.3.43
516+
"@langchain/core": ^0.3.44
517517
"@langchain/langgraph": ^0.2.62
518-
"@langchain/openai": ^0.5.2
518+
"@langchain/openai": ^0.5.5
519519
"@langchain/scripts": ^0.1.3
520520
"@modelcontextprotocol/sdk": ^1.7.0
521521
"@tsconfig/recommended": ^1.0.8
@@ -548,24 +548,24 @@ __metadata:
548548
vitest: ^3.1.1
549549
zod: ^3.24.2
550550
peerDependencies:
551-
"@langchain/core": ^0.3.40
551+
"@langchain/core": ^0.3.44
552552
dependenciesMeta:
553553
extended-eventsource:
554554
optional: true
555555
languageName: unknown
556556
linkType: soft
557557

558-
"@langchain/openai@npm:^0.5.2":
559-
version: 0.5.2
560-
resolution: "@langchain/openai@npm:0.5.2"
558+
"@langchain/openai@npm:^0.5.5":
559+
version: 0.5.5
560+
resolution: "@langchain/openai@npm:0.5.5"
561561
dependencies:
562562
js-tiktoken: ^1.0.12
563563
openai: ^4.87.3
564564
zod: ^3.22.4
565565
zod-to-json-schema: ^3.22.3
566566
peerDependencies:
567567
"@langchain/core": ">=0.3.39 <0.4.0"
568-
checksum: 34c941c0d086b2e575e781506765322aac3bc54af2f0086aa51ca759a46d2c8b2bc20b7e3f618f71e6d73d97815b65bc057bf982a9f6719d4a46059cc3244d3a
568+
checksum: 45268375ee66f847a4b5ce64dbca86ad8ad07f0c6c6c9e23c50851f7abd501f841806aac7fb08c2213e6f2ef9f7ca3373243ff59e695626be22752114bfee142
569569
languageName: node
570570
linkType: hard
571571

@@ -7730,21 +7730,12 @@ __metadata:
77307730
languageName: node
77317731
linkType: hard
77327732

7733-
"uuid@npm:^10.0.0":
7734-
version: 10.0.0
7735-
resolution: "uuid@npm:10.0.0"
7736-
bin:
7737-
uuid: dist/bin/uuid
7738-
checksum: 4b81611ade2885d2313ddd8dc865d93d8dccc13ddf901745edca8f86d99bc46d7a330d678e7532e7ebf93ce616679fb19b2e3568873ac0c14c999032acb25869
7739-
languageName: node
7740-
linkType: hard
7741-
7742-
"uuid@npm:^9.0.0":
7743-
version: 9.0.1
7744-
resolution: "uuid@npm:9.0.1"
7733+
"uuid@npm:^11.0.0":
7734+
version: 11.1.0
7735+
resolution: "uuid@npm:11.1.0"
77457736
bin:
7746-
uuid: dist/bin/uuid
7747-
checksum: 39931f6da74e307f51c0fb463dc2462807531dc80760a9bff1e35af4316131b4fc3203d16da60ae33f07fdca5b56f3f1dd662da0c99fea9aaeab2004780cc5f4
7737+
uuid: dist/esm/bin/uuid
7738+
checksum: 840f19758543c4631e58a29439e51b5b669d5f34b4dd2700b6a1d15c5708c7a6e0c3e2c8c4a2eae761a3a7caa7e9884d00c86c02622ba91137bd3deade6b4b4a
77487739
languageName: node
77497740
linkType: hard
77507741

0 commit comments

Comments
 (0)