-
Notifications
You must be signed in to change notification settings - Fork 44
feat: add structured tool #387
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ import { RequestOptions } from '@modelcontextprotocol/sdk/shared/protocol.js'; | |
| import { fetch } from 'urllib'; | ||
| import { mergeHeaders } from './HeaderUtil'; | ||
| import type { Logger } from '@eggjs/tegg'; | ||
| import { loadMcpTools } from '@langchain/mcp-adapters'; | ||
| export interface BaseHttpClientOptions extends ClientOptions { | ||
| logger: Logger; | ||
| fetch?: typeof fetch; | ||
|
|
@@ -30,12 +31,14 @@ export class HttpMCPClient extends Client { | |
| #transport: SSEClientTransport | StreamableHTTPClientTransport; | ||
| #fetch: typeof fetch; | ||
| url: string; | ||
| clientInfo: Implementation; | ||
| constructor(clientInfo: Implementation, options: HttpClientOptions) { | ||
| super(clientInfo, options); | ||
| this.options = options; | ||
| this.#fetch = options.fetch ?? fetch; | ||
| this.logger = options.logger; | ||
| this.url = options.url; | ||
| this.clientInfo = clientInfo; | ||
| } | ||
| async #buildSSESTransport() { | ||
| const self = this; | ||
|
|
@@ -113,4 +116,11 @@ export class HttpMCPClient extends Client { | |
| } | ||
| await this.connect(this.#transport, this.options.requestOptions); | ||
| } | ||
| async getLangChainTool() { | ||
| return await loadMcpTools(this.clientInfo.name, this as any, { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The use of |
||
| throwOnLoadError: true, | ||
| prefixToolNameWithServerName: false, | ||
| additionalToolNamePrefix: '', | ||
| }); | ||
| } | ||
|
Comment on lines
+119
to
+125
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# First, find where loadMcpTools is defined and its signature
rg -n 'export.*loadMcpTools|function loadMcpTools|const loadMcpTools' --type=ts --type=tsx -A 15Repository: eggjs/tegg Length of output: 81 🏁 Script executed: #!/bin/bash
# Find all usages of getLangChainTool
rg -n 'getLangChainTool' --type=ts --type=tsx -B 2 -A 2Repository: eggjs/tegg Length of output: 81 🏁 Script executed: #!/bin/bash
# Check the HttpMCPClient class definition and see what interface it implements
rg -n 'class HttpMCPClient|interface.*HttpMCPClient' --type=ts --type=tsx -A 20Repository: eggjs/tegg Length of output: 81 🏁 Script executed: #!/bin/bash
# Look for the interface that loadMcpTools expects
rg -n 'MCPClient|MCPClientI' --type=ts --type=tsx | head -30Repository: eggjs/tegg Length of output: 81 🏁 Script executed: #!/bin/bash
# Find where loadMcpTools is defined and its signature
rg -n 'export.*loadMcpTools|function loadMcpTools|const loadMcpTools' --type=ts -A 15Repository: eggjs/tegg Length of output: 36 🏁 Script executed: #!/bin/bash
# Find all usages of getLangChainTool
rg -n 'getLangChainTool' --type=ts -B 2 -A 2Repository: eggjs/tegg Length of output: 1063 🏁 Script executed: #!/bin/bash
# Check the HttpMCPClient class definition
rg -n 'class HttpMCPClient' --type=ts -A 30Repository: eggjs/tegg Length of output: 3642 🏁 Script executed: #!/bin/bash
# Search for MCPClient interface definitions
rg -n 'interface.*MCPClient|type.*MCPClient' --type=ts | head -20Repository: eggjs/tegg Length of output: 141 🏁 Script executed: #!/bin/bash
# Find the loadMcpTools function definition
fd -t f -e ts . | xargs grep -l 'loadMcpTools' | head -10Repository: eggjs/tegg Length of output: 192 🏁 Script executed: #!/bin/bash
# Get the full file path and context around loadMcpTools definition
rg -n 'loadMcpTools' --type=ts -B 5 -A 20 | head -100Repository: eggjs/tegg Length of output: 9429 🏁 Script executed: #!/bin/bash
# Check what Client base class HttpMCPClient extends
rg -n "from.*Client|import.*Client" core/mcp-client/src/HttpMCPClient.tsRepository: eggjs/tegg Length of output: 385 🏁 Script executed: #!/bin/bash
# Check the return value of loadMcpTools in the usage
cat core/mcp-client/src/HttpMCPClient.ts | head -130 | tail -20Repository: eggjs/tegg Length of output: 624 🏁 Script executed: #!/bin/bash
# Check package.json to see the version of @langchain/mcp-adapters
find . -name "package.json" -type f | xargs grep -l "mcp-adapters" 2>/dev/nullRepository: eggjs/tegg Length of output: 116 🏁 Script executed: #!/bin/bash
# Look at the actual usage in test files to understand the return type better
cat plugin/mcp-client/test/fixtures/apps/mcpclient/app/modules/bar/controller/AppController.ts | head -80 | tail -20Repository: eggjs/tegg Length of output: 510 🏁 Script executed: #!/bin/bash
# Check if there are any type definitions or imports related to MCPClient interface
rg -n "MCPClient|interface.*Client" --type=ts core/mcp-client/src/ -A 5 | head -50Repository: eggjs/tegg Length of output: 4012 🏁 Script executed: #!/bin/bash
# Verify the complete file at HttpMCPClient.ts to ensure we see the full context
wc -l core/mcp-client/src/HttpMCPClient.tsRepository: eggjs/tegg Length of output: 94 🏁 Script executed: #!/bin/bash
# Check if there are any other callers of getLangChainTool besides the one in AppController
rg -n 'getLangChainTool' --type=tsRepository: eggjs/tegg Length of output: 276 Rename method and remove type bypass to improve naming consistency and type safety. Three issues need to be addressed:
The method has one caller in 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,30 @@ | ||
| import { EggProtoImplClass, LifecycleHook } from '@eggjs/tegg'; | ||
| import { AccessLevel, EggProtoImplClass, LifecycleHook, LifecyclePostInject, MCPInfoUtil, SingletonProto } from '@eggjs/tegg'; | ||
| import { | ||
| ClassProtoDescriptor, | ||
| EggPrototypeCreatorFactory, | ||
| EggPrototypeFactory, | ||
| EggPrototypeWithClazz, | ||
| LoadUnit, | ||
| LoadUnitLifecycleContext, | ||
| ProtoDescriptorHelper, | ||
| } from '@eggjs/tegg-metadata'; | ||
| import { CompiledStateGraphProto } from './CompiledStateGraphProto'; | ||
| import { GraphInfoUtil, IGraphMetadata } from '@eggjs/tegg-langchain-decorator'; | ||
| import { GraphInfoUtil, GraphToolInfoUtil, IGraphMetadata, IGraphTool, IGraphToolMetadata } from '@eggjs/tegg-langchain-decorator'; | ||
| import assert from 'node:assert'; | ||
| import { EggContainerFactory } from '@eggjs/tegg-runtime'; | ||
| import { DynamicStructuredTool } from 'langchain'; | ||
| import * as z from 'zod/v4'; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The import from |
||
|
|
||
| export class GraphLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext, LoadUnit> { | ||
| private readonly eggPrototypeFactory: EggPrototypeFactory; | ||
| clazzMap: Map<EggProtoImplClass, IGraphMetadata>; | ||
| graphCompiledNameMap: Map<string, CompiledStateGraphProto> = new Map(); | ||
| tools: Map<EggProtoImplClass, IGraphToolMetadata>; | ||
|
|
||
| constructor(eggPrototypeFactory: EggPrototypeFactory) { | ||
| this.eggPrototypeFactory = eggPrototypeFactory; | ||
| this.clazzMap = GraphInfoUtil.getAllGraphMetadata(); | ||
| this.tools = GraphToolInfoUtil.getAllGraphToolMetadata(); | ||
| } | ||
|
|
||
| async preCreate(ctx: LoadUnitLifecycleContext, loadUnit: LoadUnit): Promise<void> { | ||
|
|
@@ -30,7 +39,41 @@ export class GraphLoadUnitHook implements LifecycleHook<LoadUnitLifecycleContext | |
| this.eggPrototypeFactory.registerPrototype(proto, loadUnit); | ||
| this.graphCompiledNameMap.set(protoName, proto); | ||
| } | ||
| const toolMeta = this.tools.get(clazz as EggProtoImplClass); | ||
| if (toolMeta) { | ||
| const StructuredTool = this.createStructuredTool(clazz, toolMeta); | ||
| const protoDescriptor = ProtoDescriptorHelper.createByInstanceClazz(StructuredTool, { | ||
| moduleName: loadUnit.name, | ||
| unitPath: loadUnit.unitPath, | ||
| }) as ClassProtoDescriptor; | ||
| const proto = await EggPrototypeCreatorFactory.createProtoByDescriptor(protoDescriptor, loadUnit); | ||
| this.eggPrototypeFactory.registerPrototype(proto, loadUnit); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| createStructuredTool(clazz: EggProtoImplClass, toolMeta: IGraphToolMetadata) { | ||
| class StructuredTool { | ||
| @LifecyclePostInject() | ||
| async init() { | ||
| const toolsObj = await EggContainerFactory.getOrCreateEggObjectFromClazz(clazz); | ||
| const toolMetadata = GraphToolInfoUtil.getGraphToolMetadata((toolsObj.proto as unknown as EggPrototypeWithClazz).clazz!); | ||
| const ToolDetail = MCPInfoUtil.getMCPToolArgsIndex((toolsObj.proto as unknown as EggPrototypeWithClazz).clazz!, 'execute'); | ||
|
Comment on lines
+60
to
+61
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The expression const toolClazz = (toolsObj.proto as EggPrototypeWithClazz).clazz;
if (!toolClazz) {
throw new Error(`Could not find class for prototype '${toolsObj.proto.name}'`);
}
const toolMetadata = GraphToolInfoUtil.getGraphToolMetadata(toolClazz);
const ToolDetail = MCPInfoUtil.getMCPToolArgsIndex(toolClazz, 'execute'); |
||
| if (toolMetadata && ToolDetail) { | ||
| const tool = new DynamicStructuredTool({ | ||
| description: toolMetadata.description, | ||
| name: toolMetadata.toolName, | ||
| func: (toolsObj.obj as unknown as IGraphTool<any>).execute.bind(toolsObj.obj), | ||
| schema: z.object(ToolDetail.argsSchema) as any, | ||
| }); | ||
| Object.setPrototypeOf(this, tool); | ||
| } else { | ||
| throw new Error(`graph tool ${toolMeta.name ?? clazz.name} not found`); | ||
| } | ||
| } | ||
| } | ||
| SingletonProto({ name: `structured${toolMeta.name ?? clazz.name}`, accessLevel: AccessLevel.PUBLIC })(StructuredTool); | ||
| return StructuredTool; | ||
| } | ||
|
Comment on lines
+55
to
77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent property access: Based on the 🔎 Proposed fix } else {
- throw new Error(`graph tool ${toolMeta.name ?? clazz.name} not found`);
+ throw new Error(`graph tool ${toolMeta.toolName ?? clazz.name} not found`);
}
}
}
- SingletonProto({ name: `structured${toolMeta.name ?? clazz.name}`, accessLevel: AccessLevel.PUBLIC })(StructuredTool);
+ SingletonProto({ name: `structured${toolMeta.toolName ?? clazz.name}`, accessLevel: AccessLevel.PUBLIC })(StructuredTool);
return StructuredTool;🤖 Prompt for AI Agents |
||
|
|
||
| async postCreate(_ctx: LoadUnitLifecycleContext, obj: LoadUnit): Promise<void> { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🌐 Web query:
@langchain/mcp-adapters npm package version 1.0.0💡 Result:
I checked the package on official npm and CDN sources. The npm package @langchain/mcp-adapters is published (current visible npm release listed as 0.6.0 on npmjs.com) and the LangChain docs recommend installing @langchain/mcp-adapters. I could not find an official npm release page showing a 1.0.0 tag; jsDelivr shows higher CDN versions (e.g., 1.1.1) which may reflect mirrored/packaged builds rather than the npm registry listing. Sources: [npmjs package page][1], [LangChain docs][2], [jsDelivr version listing][3].
If you want, I can fetch the npm registry metadata directly to list all published versions.
Sources:
[1] https://www.npmjs.com/package/%40langchain/mcp-adapters
[2] https://docs.langchain.com/oss/javascript/langchain/mcp
[3] https://www.jsdelivr.com/package/npm/%40langchain/mcp-adapters
Fix version constraint:
@langchain/mcp-adaptersversion 1.0.0 does not exist on npm.The latest published version on npm registry is 0.6.0. The current constraint
^1.0.0will fail during installation. Update to a published version (e.g.,^0.6.0or later if available).🤖 Prompt for AI Agents