# Directory Structure
```
├── .gitignore
├── jest.config.js
├── package.json
├── README.md
├── src
│ ├── __tests__
│ │ └── index.test.ts
│ ├── index.ts
│ └── types.ts
├── tsconfig.json
└── vitest.config.ts
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | node_modules/
2 | dist/
3 | coverage/
4 | *.log
5 | .DS_Store
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # mcp-langchain-ts-client
2 |
3 | A LangChain.js client for Model Context Protocol.
4 |
5 | This is a port of [rectalogic/langchain-mcp](https://github.com/rectalogic/langchain-mcp) to the JS/TS LangChain and MCP APIs.
6 |
7 | ## Installation
8 |
9 | ```bash
10 | npm install mcp-langchain-ts-client
11 | ```
12 |
13 | ## Usage
14 |
15 | ```typescript
16 | const serverParams = {
17 | command: "npx",
18 | args: [
19 | "-y",
20 | "@modelcontextprotocol/server-everything"
21 | ]
22 | }
23 |
24 | // Initialize the toolkit
25 | const toolkit = new MCPToolkit(serverParams);
26 | await toolkit.initialize();
27 |
28 | // Extract LangChain.js compatible tools
29 | const tools = toolkit.tools;
30 |
31 | // Use the tools
32 | import { createReactAgent } from "@langchain/langgraph/prebuilt";
33 | import { ChatAnthropic } from "@langchain/anthropic";
34 |
35 | const llm = new ChatAnthropic({ model: 'claude-3-5-sonnet-20241022' });
36 | const agent = createReactAgent({ llm, tools });
37 | ```
```
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
```typescript
1 |
```
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
```javascript
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | testMatch: ['**/__tests__/**/*.test.ts'],
5 | };
```
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { defineConfig } from 'vitest/config'
2 |
3 | export default defineConfig({
4 | test: {
5 | include: ['src/**/*.test.ts'],
6 | coverage: {
7 | reporter: ['text', 'json', 'html'],
8 | },
9 | },
10 | })
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "ESNext",
5 | "moduleResolution": "bundler",
6 | "outDir": "./dist",
7 | "declaration": true,
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "rootDir": "./src"
13 | },
14 | "include": ["src"],
15 | "exclude": ["node_modules", "**/__tests__/*"]
16 | }
```
--------------------------------------------------------------------------------
/src/__tests__/index.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { describe, it, expect } from 'vitest';
2 | import { MCPToolkit } from '../index';
3 |
4 | const serverParams = {
5 | command: "npx",
6 | args: [
7 | "-y",
8 | "@modelcontextprotocol/server-everything"
9 | ]
10 | }
11 |
12 | const toolkit = new MCPToolkit(serverParams);
13 | await toolkit.initialize();
14 |
15 | const toolsByName = toolkit.tools.reduce((acc, tool) => {
16 | acc[tool.name] = tool;
17 | return acc;
18 | });
19 |
20 | describe('MCPToolkit', () => {
21 | describe('initialize', () => {
22 | it('should initialize the toolkit', () => {
23 | expect(toolkit.tools.length).toBeGreaterThan(0);
24 | });
25 | });
26 | describe('tools', () => {
27 | it('should echo'), async () => {
28 | const tool = toolsByName['echo'];
29 | const result = await tool.invoke({ message: 'hello' });
30 | expect(result).toBe('hello');
31 | }
32 | });
33 | });
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-langchain-ts-client",
3 | "version": "0.0.2.1",
4 | "description": "TypeScript client for MCP Langchain",
5 | "type": "module",
6 | "main": "./dist/index.js",
7 | "module": "./dist/index.js",
8 | "types": "./dist/index.d.ts",
9 | "exports": {
10 | ".": {
11 | "import": "./dist/index.js",
12 | "types": "./dist/index.d.ts"
13 | }
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/isaacwasserman/mcp-langchain-ts-client.git"
18 | },
19 | "bugs": {
20 | "url": "https://github.com/isaacwasserman/mcp-langchain-ts-client/issues"
21 | },
22 | "homepage": "https://github.com/isaacwasserman/mcp-langchain-ts-client#readme",
23 | "files": [
24 | "dist"
25 | ],
26 | "scripts": {
27 | "build": "tsc",
28 | "test": "vitest run",
29 | "test:watch": "vitest",
30 | "test:coverage": "vitest run --coverage",
31 | "prepare": "npm run build"
32 | },
33 | "keywords": [
34 | "typescript",
35 | "langchain",
36 | "mcp"
37 | ],
38 | "author": "Isaac Wasserman",
39 | "license": "MIT",
40 | "devDependencies": {
41 | "@vitest/coverage-v8": "^1.2.0",
42 | "typescript": "^5.0.0",
43 | "vite": "^5.0.0",
44 | "vitest": "^1.2.0"
45 | },
46 | "peerDependencies": {
47 | "typescript": ">=4.0.0"
48 | },
49 | "dependencies": {
50 | "@modelcontextprotocol/sdk": "^1.0.3"
51 | }
52 | }
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { CallToolRequest, CallToolResultSchema, ListToolsResult, ListToolsResultSchema } from "@modelcontextprotocol/sdk/types.js";
2 | import { StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js";
3 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
4 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
5 | import { BaseToolkit, StructuredToolInterface, StructuredToolParams, tool, Tool } from "@langchain/core/tools";
6 | import { z } from "zod";
7 |
8 | export { StdioServerParameters } from "@modelcontextprotocol/sdk/client/stdio.js";
9 |
10 | export class MCPToolkit extends BaseToolkit {
11 | tools: Tool[] = [];
12 | _tools: ListToolsResult | null = null;
13 | model_config: any;
14 | transport: StdioClientTransport | null = null;
15 | client: Client | null = null;
16 | constructor(serverParams: StdioServerParameters) {
17 | super();
18 | this.transport = new StdioClientTransport(serverParams);
19 | }
20 | async initialize() {
21 | if (this._tools === null) {
22 | this.client = new Client({
23 | name: "langchain-js-client",
24 | version: "1.0.0",
25 | }, {
26 | capabilities: {}
27 | });
28 | if (this.transport === null) {
29 | throw new Error("Transport is not initialized");
30 | }
31 | await this.client.connect(this.transport);
32 | this._tools = await this.client.request(
33 | { method: "tools/list" },
34 | ListToolsResultSchema
35 | );
36 |
37 | this.tools = await this.get_tools();
38 | }
39 | }
40 |
41 | async get_tools(): Promise<Tool[]> {
42 | if (this._tools === null || this.client === null) {
43 | throw new Error("Must initialize the toolkit first");
44 | }
45 | const toolsPromises = this._tools.tools.map(async (tool: any) => {
46 | if (this.client === null) {
47 | throw new Error("Client is not initialized");
48 | }
49 | return await MCPTool({
50 | client: this.client,
51 | name: tool.name,
52 | description: tool.description || "",
53 | argsSchema: createSchemaModel(tool.inputSchema)
54 | });
55 | });
56 | return Promise.all(toolsPromises);
57 | }
58 |
59 | }
60 |
61 | export async function MCPTool({ client, name, description, argsSchema }: { client: Client, name: string, description: string, argsSchema: any }): Promise<Tool> {
62 | return tool(
63 | async (input): Promise<string> => {
64 | const req: CallToolRequest = { method: "tools/call", params: { name: name, arguments: input } };
65 | const res = await client.request(req, CallToolResultSchema);
66 | const content = res.content;
67 | const contentString = JSON.stringify(content);
68 | return contentString;
69 | },
70 | {
71 | name: name,
72 | description: description,
73 | schema: argsSchema,
74 | }
75 | );
76 | }
77 |
78 | function createSchemaModel(inputSchema: { type: "object"; properties?: import("zod").objectOutputType<{}, import("zod").ZodTypeAny, "passthrough"> | undefined; } & { [k: string]: unknown; }): any {
79 | if (inputSchema.type !== "object" || !inputSchema.properties) {
80 | throw new Error("Invalid schema type or missing properties");
81 | }
82 |
83 | const schemaProperties = Object.entries(inputSchema.properties).reduce((acc, [key, value]) => {
84 | acc[key] = z.any(); // You can customize this to map specific types
85 | return acc;
86 | }, {} as Record<string, import("zod").ZodTypeAny>);
87 |
88 | return z.object(schemaProperties);
89 | }
```