mirror of
https://github.com/langchain-ai/langchainjs-mcp-adapters.git
synced 2026-07-01 12:27:48 -04:00
chore: prepare for npm publishing, add .npmignore, CHANGELOG and CONTRIBUTING files
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: '[BUG] '
|
||||
labels: bug
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Bug Description
|
||||
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## Reproduction Steps
|
||||
|
||||
Steps to reproduce the behavior:
|
||||
|
||||
1. Create a client with '...'
|
||||
2. Connect to server using '...'
|
||||
3. Call method '...'
|
||||
4. See error
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
## Actual Behavior
|
||||
|
||||
What actually happened, including any error messages, stack traces, or unexpected output.
|
||||
|
||||
## Environment
|
||||
|
||||
- OS: [e.g. macOS, Windows, Linux]
|
||||
- Node.js version: [e.g. 18.15.0]
|
||||
- Package version: [e.g. 0.1.0]
|
||||
- MCP SDK version: [e.g. 1.6.1]
|
||||
|
||||
## Additional Context
|
||||
|
||||
Add any other context about the problem here, such as:
|
||||
|
||||
- Server implementation details
|
||||
- Transport type (stdio, SSE)
|
||||
- Any relevant logs
|
||||
|
||||
## Possible Solution
|
||||
|
||||
If you have suggestions on how to fix the issue, please describe them here.
|
||||
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: '[FEATURE] '
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
---
|
||||
|
||||
## Feature Description
|
||||
|
||||
A clear and concise description of the feature you'd like to see implemented.
|
||||
|
||||
## Use Case
|
||||
|
||||
Describe the use case or problem this feature would solve. Ex. I'm always frustrated when [...]
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
A description of any alternative solutions or features you've considered.
|
||||
|
||||
## Additional Context
|
||||
|
||||
Add any other context, code examples, or references about the feature request here.
|
||||
@@ -0,0 +1,27 @@
|
||||
## Description
|
||||
|
||||
Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context.
|
||||
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Documentation update
|
||||
- [ ] Refactoring (no functional changes)
|
||||
|
||||
## How Has This Been Tested?
|
||||
|
||||
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce.
|
||||
|
||||
## Checklist:
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have commented my code, particularly in hard-to-understand areas
|
||||
- [ ] I have made corresponding changes to the documentation
|
||||
- [ ] My changes generate no new warnings
|
||||
- [ ] I have added tests that prove my fix is effective or that my feature works
|
||||
- [ ] New and existing unit tests pass locally with my changes
|
||||
@@ -0,0 +1,57 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
coverage:
|
||||
name: Coverage
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Generate coverage report
|
||||
run: npm test -- --coverage
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
fail_ci_if_error: false
|
||||
@@ -0,0 +1,68 @@
|
||||
name: Publish to npm
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Version to publish (patch, minor, major, or specific version)'
|
||||
required: true
|
||||
default: 'patch'
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Configure Git
|
||||
run: |
|
||||
git config --local user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
|
||||
- name: Version bump (automatic)
|
||||
if: github.event_name == 'release'
|
||||
run: |
|
||||
VERSION=$(echo "${{ github.ref }}" | sed -e 's/refs\/tags\/v//')
|
||||
npm version $VERSION --no-git-tag-version
|
||||
|
||||
- name: Version bump (manual)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: npm version ${{ github.event.inputs.version }} --no-git-tag-version
|
||||
|
||||
- name: Publish to npm
|
||||
run: npm publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Push version changes
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
NEW_VERSION=$(node -p "require('./package.json').version")
|
||||
git add package.json package-lock.json
|
||||
git commit -m "chore: bump version to v${NEW_VERSION} [skip ci]"
|
||||
git tag -a "v${NEW_VERSION}" -m "Release v${NEW_VERSION}"
|
||||
git push origin main
|
||||
git push origin "v${NEW_VERSION}"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -0,0 +1,51 @@
|
||||
name: PR Validation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
name: Validate
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
|
||||
- name: Type check
|
||||
run: npm run build --noEmit
|
||||
|
||||
- name: Run tests
|
||||
run: npm test
|
||||
|
||||
format-check:
|
||||
name: Format Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Check formatting
|
||||
run: npx prettier --check "src/**/*.ts" "examples/**/*.ts"
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
.pnp/
|
||||
.pnp.js
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
*.tsbuildinfo
|
||||
.env
|
||||
|
||||
# Coverage directory
|
||||
coverage/
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
.DS_Store
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
.env
|
||||
.venv
|
||||
.python-version
|
||||
|
||||
# Misc
|
||||
.cache/
|
||||
.temp/
|
||||
.tmp/
|
||||
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npx lint-staged
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
# Source files
|
||||
src/
|
||||
|
||||
# Examples
|
||||
examples/
|
||||
|
||||
# Tests
|
||||
__tests__/
|
||||
*.test.ts
|
||||
jest.config.js
|
||||
|
||||
# Development configs
|
||||
.eslintrc.js
|
||||
.eslintignore
|
||||
.prettierrc
|
||||
.prettierrc.json
|
||||
.prettierignore
|
||||
tsconfig.json
|
||||
.github/
|
||||
.husky/
|
||||
|
||||
# Git files
|
||||
.git/
|
||||
.gitignore
|
||||
|
||||
# Editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# Logs
|
||||
logs/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Coverage directory
|
||||
coverage/
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- GitHub Actions workflows for PR validation, CI, and npm publishing
|
||||
- Husky for Git hooks
|
||||
- lint-staged for running linters on staged files
|
||||
- Issue and PR templates
|
||||
- CHANGELOG.md and CONTRIBUTING.md
|
||||
|
||||
## [0.1.0] - 2023-03-03
|
||||
|
||||
### Added
|
||||
|
||||
- Initial release
|
||||
- Support for stdio and SSE transports
|
||||
- MultiServerMCPClient for connecting to multiple MCP servers
|
||||
- Configuration file support
|
||||
- Examples for various use cases
|
||||
- Integration with LangChain.js agents
|
||||
@@ -0,0 +1,88 @@
|
||||
# Contributing to LangChain.js MCP Adapters
|
||||
|
||||
Thank you for considering contributing to LangChain.js MCP Adapters! This document provides guidelines and instructions for contributing to this project.
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
By participating in this project, you agree to abide by our code of conduct. Please be respectful and considerate of others.
|
||||
|
||||
## How Can I Contribute?
|
||||
|
||||
### Reporting Bugs
|
||||
|
||||
Before creating bug reports, please check the existing issues to see if the problem has already been reported. When you are creating a bug report, please include as many details as possible:
|
||||
|
||||
- Use a clear and descriptive title
|
||||
- Describe the exact steps to reproduce the problem
|
||||
- Provide specific examples to demonstrate the steps
|
||||
- Describe the behavior you observed and what you expected to see
|
||||
- Include screenshots if applicable
|
||||
- Include details about your environment (OS, Node.js version, package version)
|
||||
|
||||
### Suggesting Enhancements
|
||||
|
||||
Enhancement suggestions are welcome! When suggesting an enhancement:
|
||||
|
||||
- Use a clear and descriptive title
|
||||
- Provide a detailed description of the suggested enhancement
|
||||
- Explain why this enhancement would be useful to most users
|
||||
- List some examples of how this enhancement would be used
|
||||
|
||||
### Pull Requests
|
||||
|
||||
- Fill in the required template
|
||||
- Follow the TypeScript coding style
|
||||
- Include tests for new features or bug fixes
|
||||
- Update documentation as needed
|
||||
- End all files with a newline
|
||||
- Make sure your code passes all tests and linting
|
||||
|
||||
## Development Workflow
|
||||
|
||||
1. Fork the repository
|
||||
2. Clone your fork: `git clone https://github.com/your-username/langchainjs-mcp-adapters.git`
|
||||
3. Create a new branch: `git checkout -b feature/your-feature-name`
|
||||
4. Make your changes
|
||||
5. Run tests: `npm test`
|
||||
6. Run linting: `npm run lint`
|
||||
7. Commit your changes: `git commit -m "Add some feature"`
|
||||
8. Push to the branch: `git push origin feature/your-feature-name`
|
||||
9. Submit a pull request
|
||||
|
||||
## Setting Up Development Environment
|
||||
|
||||
1. Install dependencies: `npm install`
|
||||
2. Build the project: `npm run build`
|
||||
3. Run tests: `npm test`
|
||||
|
||||
## Testing
|
||||
|
||||
- Write tests for all new features and bug fixes
|
||||
- Run tests before submitting a pull request: `npm test`
|
||||
- Ensure code coverage remains high
|
||||
|
||||
## Coding Style
|
||||
|
||||
- Follow the ESLint and Prettier configurations
|
||||
- Use meaningful variable and function names
|
||||
- Write clear comments for complex logic
|
||||
- Document public APIs using JSDoc comments
|
||||
|
||||
## Commit Messages
|
||||
|
||||
- Use the present tense ("Add feature" not "Added feature")
|
||||
- Use the imperative mood ("Move cursor to..." not "Moves cursor to...")
|
||||
- Limit the first line to 72 characters or less
|
||||
- Reference issues and pull requests after the first line
|
||||
|
||||
## Versioning
|
||||
|
||||
This project follows [Semantic Versioning](https://semver.org/). When contributing, consider the impact of your changes:
|
||||
|
||||
- PATCH version for backwards-compatible bug fixes
|
||||
- MINOR version for backwards-compatible new features
|
||||
- MAJOR version for incompatible API changes
|
||||
|
||||
## License
|
||||
|
||||
By contributing to this project, you agree that your contributions will be licensed under the project's MIT license.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Ravi Kiran Vemula
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,328 @@
|
||||
# LangChain.js MCP Adapters
|
||||
|
||||
This package provides adapters for using [Model Context Protocol (MCP)](https://github.com/model-context-protocol/model-context-protocol) tools with LangChain.js.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install langchainjs-mcp-adapters
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Connecting to an MCP Server
|
||||
|
||||
You can connect to an MCP server using either stdio or SSE transport:
|
||||
|
||||
```typescript
|
||||
import { MultiServerMCPClient } from 'langchainjs-mcp-adapters';
|
||||
|
||||
// Create a client
|
||||
const client = new MultiServerMCPClient();
|
||||
|
||||
// Connect to a server using stdio
|
||||
await client.connectToServerViaStdio(
|
||||
'math-server', // A name to identify this server
|
||||
'python', // Command to run
|
||||
['./math_server.py'] // Arguments for the command
|
||||
);
|
||||
|
||||
// Connect to a server using SSE
|
||||
await client.connectToServerViaSSE(
|
||||
'weather-server', // A name to identify this server
|
||||
'http://localhost:8000/sse' // URL of the SSE server
|
||||
);
|
||||
|
||||
// Get all tools from all connected servers
|
||||
const tools = client.getTools();
|
||||
|
||||
// Use the tools
|
||||
const result = await tools[0].invoke({ param1: 'value1', param2: 'value2' });
|
||||
|
||||
// Close the client when done
|
||||
await client.close();
|
||||
```
|
||||
|
||||
### Initializing Multiple Connections
|
||||
|
||||
You can also initialize multiple connections at once:
|
||||
|
||||
```typescript
|
||||
import { MultiServerMCPClient } from 'langchainjs-mcp-adapters';
|
||||
|
||||
const client = new MultiServerMCPClient({
|
||||
'math-server': {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./math_server.py'],
|
||||
},
|
||||
'weather-server': {
|
||||
transport: 'sse',
|
||||
url: 'http://localhost:8000/sse',
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize all connections
|
||||
await client.initialize();
|
||||
|
||||
// Get all tools
|
||||
const tools = client.getTools();
|
||||
|
||||
// Close all connections when done
|
||||
await client.close();
|
||||
```
|
||||
|
||||
### Using Configuration File
|
||||
|
||||
You can define your MCP server configurations in a JSON file (`mcp.json`) and load them:
|
||||
|
||||
```typescript
|
||||
import { MultiServerMCPClient } from 'langchainjs-mcp-adapters';
|
||||
|
||||
// Create a client from the config file
|
||||
const client = MultiServerMCPClient.fromConfigFile();
|
||||
// Or specify a custom path: MultiServerMCPClient.fromConfigFile("./config/mcp.json");
|
||||
|
||||
// Initialize all connections
|
||||
await client.initialize();
|
||||
|
||||
// Get all tools
|
||||
const tools = client.getTools();
|
||||
|
||||
// Close all connections when done
|
||||
await client.close();
|
||||
```
|
||||
|
||||
Example `mcp.json` file:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"math": {
|
||||
"transport": "stdio",
|
||||
"command": "python",
|
||||
"args": ["./examples/math_server.py"]
|
||||
},
|
||||
"weather": {
|
||||
"transport": "sse",
|
||||
"url": "http://localhost:8000/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can also omit the `transport` field for stdio servers, as it's the default transport:
|
||||
|
||||
```json
|
||||
{
|
||||
"servers": {
|
||||
"math": {
|
||||
"command": "python",
|
||||
"args": ["./examples/math_server.py"]
|
||||
},
|
||||
"weather": {
|
||||
"transport": "sse",
|
||||
"url": "http://localhost:8000/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The client will attempt to connect to all servers defined in the configuration file. If a server is not available, it will log an error and continue with the available servers. If no servers are available, it will throw an error.
|
||||
|
||||
```typescript
|
||||
// Error handling when initializing connections
|
||||
try {
|
||||
const client = MultiServerMCPClient.fromConfigFile();
|
||||
await client.initialize();
|
||||
// Use the client...
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to any servers:', error.message);
|
||||
}
|
||||
```
|
||||
|
||||
### Using with LangChain Agents
|
||||
|
||||
You can use MCP tools with LangChain agents:
|
||||
|
||||
```typescript
|
||||
import { MultiServerMCPClient } from 'langchainjs-mcp-adapters';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { createOpenAIFunctionsAgent, AgentExecutor } from 'langchain/agents';
|
||||
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
||||
|
||||
// Create a client and connect to servers
|
||||
const client = new MultiServerMCPClient();
|
||||
await client.connectToServerViaStdio('math-server', 'python', ['./math_server.py']);
|
||||
|
||||
// Get tools
|
||||
const tools = client.getTools();
|
||||
|
||||
// Create an agent
|
||||
const model = new ChatOpenAI({ temperature: 0 });
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
['system', 'You are a helpful assistant that can use tools to solve problems.'],
|
||||
['human', '{input}'],
|
||||
]);
|
||||
|
||||
const agent = createOpenAIFunctionsAgent({
|
||||
llm: model,
|
||||
tools,
|
||||
prompt,
|
||||
});
|
||||
|
||||
const agentExecutor = new AgentExecutor({
|
||||
agent,
|
||||
tools,
|
||||
});
|
||||
|
||||
// Run the agent
|
||||
const result = await agentExecutor.invoke({
|
||||
input: 'What is 5 + 3?',
|
||||
});
|
||||
|
||||
console.log(result.output);
|
||||
|
||||
// Close the client when done
|
||||
await client.close();
|
||||
```
|
||||
|
||||
## Example MCP Servers
|
||||
|
||||
### Math Server (stdio transport)
|
||||
|
||||
Here's an example of a simple MCP server in Python using stdio transport:
|
||||
|
||||
```python
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Create a server
|
||||
mcp = FastMCP(name="Math")
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two integers and return the result."""
|
||||
return a + b
|
||||
|
||||
@mcp.tool()
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two integers and return the result."""
|
||||
return a * b
|
||||
|
||||
# Run the server with stdio transport
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="stdio")
|
||||
```
|
||||
|
||||
### Weather Server (SSE transport)
|
||||
|
||||
Here's an example of an MCP server using SSE transport:
|
||||
|
||||
```python
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Create a server
|
||||
mcp = FastMCP(name="Weather")
|
||||
|
||||
@mcp.tool()
|
||||
def get_temperature(city: str) -> str:
|
||||
"""Get the current temperature for a city."""
|
||||
# Mock implementation
|
||||
temperatures = {
|
||||
"new york": "72°F",
|
||||
"london": "65°F",
|
||||
"tokyo": "25 degrees Celsius",
|
||||
}
|
||||
|
||||
city_lower = city.lower()
|
||||
if city_lower in temperatures:
|
||||
return f"The current temperature in {city} is {temperatures[city_lower]}."
|
||||
else:
|
||||
return "Temperature data not available for this city"
|
||||
|
||||
# Run the server with SSE transport
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="sse")
|
||||
```
|
||||
|
||||
## Running the Examples
|
||||
|
||||
The package includes several example files that demonstrate how to use MCP adapters:
|
||||
|
||||
1. `math_example.ts` - Basic example using a math server with stdio transport
|
||||
2. `sse_example.ts` - Example using a weather server with SSE transport
|
||||
3. `multi_sse_example.ts` - Example connecting to multiple servers with different transport types
|
||||
4. `config_example.ts` - Example using server configurations from an `mcp.json` file
|
||||
|
||||
To run the examples:
|
||||
|
||||
```bash
|
||||
# Start the weather server with SSE transport
|
||||
python examples/weather_server.py
|
||||
|
||||
# In another terminal, run the SSE example
|
||||
node --loader ts-node/esm examples/sse_example.ts
|
||||
|
||||
# Or run the multi-server example
|
||||
node --loader ts-node/esm examples/multi_sse_example.ts
|
||||
|
||||
# Or run the config-based example (requires mcp.json in the project root)
|
||||
node --loader ts-node/esm examples/config_example.ts
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### GitHub Actions Workflows
|
||||
|
||||
This project uses GitHub Actions for continuous integration and deployment:
|
||||
|
||||
#### PR Validation
|
||||
|
||||
The PR validation workflow runs automatically on all pull requests to the `main` branch. It performs:
|
||||
|
||||
- Code linting with ESLint
|
||||
- Type checking with TypeScript
|
||||
- Unit tests with Jest
|
||||
- Format checking with Prettier
|
||||
|
||||
#### Continuous Integration
|
||||
|
||||
The CI workflow runs on the `main` branch after merges and:
|
||||
|
||||
- Runs linting and tests
|
||||
- Builds the package
|
||||
- Generates and uploads test coverage reports
|
||||
|
||||
#### Publishing to npm
|
||||
|
||||
The package can be published to npm in two ways:
|
||||
|
||||
1. **Automatic publishing on GitHub Release**:
|
||||
|
||||
- Create a new release in GitHub
|
||||
- The workflow will automatically publish the package with the release version
|
||||
|
||||
2. **Manual publishing**:
|
||||
- Go to the "Actions" tab in GitHub
|
||||
- Select the "Publish to npm" workflow
|
||||
- Click "Run workflow"
|
||||
- Choose the version bump type (patch, minor, major) or specify a version
|
||||
|
||||
### Setting up npm publishing
|
||||
|
||||
To enable npm publishing, you need to:
|
||||
|
||||
1. Create an npm access token with publish permissions
|
||||
2. Add the token as a GitHub repository secret named `NPM_TOKEN`
|
||||
|
||||
### Contributing
|
||||
|
||||
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to this project.
|
||||
|
||||
### Changelog
|
||||
|
||||
For a detailed list of changes between versions, see the [CHANGELOG.md](CHANGELOG.md) file.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,258 @@
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
||||
import { StructuredTool } from '@langchain/core/tools';
|
||||
import { MultiServerMCPClient } from '../src/client';
|
||||
import * as toolsModule from '../src/tools';
|
||||
|
||||
// Mock the Client class
|
||||
jest.mock('@modelcontextprotocol/sdk/client/index.js', () => {
|
||||
return {
|
||||
Client: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
connect: jest.fn().mockResolvedValue(undefined),
|
||||
listTools: jest.fn().mockResolvedValue({ tools: [] }),
|
||||
callTool: jest.fn().mockResolvedValue({ content: [{ type: 'text', text: 'result' }] }),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock the transports
|
||||
jest.mock('@modelcontextprotocol/sdk/client/stdio.js', () => {
|
||||
return {
|
||||
StdioClientTransport: jest.fn().mockImplementation(() => ({
|
||||
close: jest.fn().mockResolvedValue(undefined),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('@modelcontextprotocol/sdk/client/sse.js', () => {
|
||||
return {
|
||||
SSEClientTransport: jest.fn().mockImplementation(() => ({
|
||||
close: jest.fn().mockResolvedValue(undefined),
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// Mock the tools module
|
||||
jest.mock('../src/tools', () => {
|
||||
return {
|
||||
loadMcpTools: jest.fn().mockResolvedValue([
|
||||
{
|
||||
name: 'test-tool',
|
||||
description: 'A test tool',
|
||||
invoke: jest.fn().mockResolvedValue('test result'),
|
||||
},
|
||||
]),
|
||||
};
|
||||
});
|
||||
|
||||
describe('MultiServerMCPClient', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('constructor with stdio config', () => {
|
||||
it('should set up a server with stdio transport', async () => {
|
||||
const client = new MultiServerMCPClient({
|
||||
'test-server': {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['script.py'],
|
||||
env: { ENV_VAR: 'value' },
|
||||
},
|
||||
});
|
||||
|
||||
await client.initializeConnections();
|
||||
|
||||
// Verify StdioClientTransport was created with the correct parameters
|
||||
expect(StdioClientTransport).toHaveBeenCalledWith({
|
||||
command: 'python',
|
||||
args: ['script.py'],
|
||||
env: { ENV_VAR: 'value' },
|
||||
});
|
||||
|
||||
// Verify Client was created and connected
|
||||
expect(Client).toHaveBeenCalled();
|
||||
const mockClientInstance = (Client as jest.Mock).mock.results[0].value;
|
||||
expect(mockClientInstance.connect).toHaveBeenCalled();
|
||||
|
||||
// Verify tools were loaded
|
||||
expect(toolsModule.loadMcpTools).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor with SSE config', () => {
|
||||
it('should set up a server with SSE transport', async () => {
|
||||
const client = new MultiServerMCPClient({
|
||||
'test-server': {
|
||||
transport: 'sse',
|
||||
url: 'https://example.com/sse',
|
||||
},
|
||||
});
|
||||
|
||||
await client.initializeConnections();
|
||||
|
||||
// Verify SSEClientTransport was created with the correct URL
|
||||
expect(SSEClientTransport).toHaveBeenCalledWith(new URL('https://example.com/sse'));
|
||||
|
||||
// Verify Client was created and connected
|
||||
expect(Client).toHaveBeenCalled();
|
||||
const mockClientInstance = (Client as jest.Mock).mock.results[0].value;
|
||||
expect(mockClientInstance.connect).toHaveBeenCalled();
|
||||
|
||||
// Verify tools were loaded
|
||||
expect(toolsModule.loadMcpTools).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('constructor validation', () => {
|
||||
it('should skip servers with unsupported transport', async () => {
|
||||
const client = new MultiServerMCPClient({
|
||||
'test-server': {
|
||||
transport: 'unsupported' as any,
|
||||
},
|
||||
});
|
||||
|
||||
// Should not throw, just log a warning and return empty Map
|
||||
const result = await client.initializeConnections();
|
||||
expect(result.size).toBe(0);
|
||||
});
|
||||
|
||||
it('should skip servers with missing required parameters', async () => {
|
||||
const clientWithMissingCommand = new MultiServerMCPClient({
|
||||
'test-server': {
|
||||
transport: 'stdio',
|
||||
} as any,
|
||||
});
|
||||
|
||||
// Should not throw, just log a warning and return empty Map
|
||||
const result1 = await clientWithMissingCommand.initializeConnections();
|
||||
expect(result1.size).toBe(0);
|
||||
|
||||
const clientWithMissingArgs = new MultiServerMCPClient({
|
||||
'test-server': {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
} as any,
|
||||
});
|
||||
|
||||
// Should not throw, just log a warning and return empty Map
|
||||
const result2 = await clientWithMissingArgs.initializeConnections();
|
||||
expect(result2.size).toBe(0);
|
||||
|
||||
const clientWithMissingUrl = new MultiServerMCPClient({
|
||||
'test-server': {
|
||||
transport: 'sse',
|
||||
} as any,
|
||||
});
|
||||
|
||||
// Should not throw, just log a warning and return empty Map
|
||||
const result3 = await clientWithMissingUrl.initializeConnections();
|
||||
expect(result3.size).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getTools', () => {
|
||||
it('should return all tools from all servers', async () => {
|
||||
const client = new MultiServerMCPClient({
|
||||
server1: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['script1.py'],
|
||||
},
|
||||
server2: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['script2.py'],
|
||||
},
|
||||
});
|
||||
|
||||
await client.initializeConnections();
|
||||
|
||||
const serverTools = client.getTools();
|
||||
expect(serverTools.size).toBe(2); // Two servers
|
||||
|
||||
const server1Tools = serverTools.get('server1');
|
||||
const server2Tools = serverTools.get('server2');
|
||||
|
||||
expect(server1Tools).toBeDefined();
|
||||
expect(server2Tools).toBeDefined();
|
||||
expect(server1Tools![0].name).toBe('test-tool');
|
||||
expect(server2Tools![0].name).toBe('test-tool');
|
||||
});
|
||||
});
|
||||
|
||||
describe('initializeConnections', () => {
|
||||
it('should initialize connections from constructor', async () => {
|
||||
const client = new MultiServerMCPClient({
|
||||
server1: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['script1.py'],
|
||||
},
|
||||
server2: {
|
||||
transport: 'sse',
|
||||
url: 'https://example.com/sse',
|
||||
},
|
||||
});
|
||||
|
||||
await client.initializeConnections();
|
||||
|
||||
// Verify both connections were established
|
||||
expect(StdioClientTransport).toHaveBeenCalledWith({
|
||||
command: 'python',
|
||||
args: ['script1.py'],
|
||||
env: undefined,
|
||||
});
|
||||
|
||||
expect(SSEClientTransport).toHaveBeenCalledWith(new URL('https://example.com/sse'));
|
||||
|
||||
// Verify Client was created and connected twice
|
||||
expect(Client).toHaveBeenCalledTimes(2);
|
||||
|
||||
// Verify tools were loaded twice
|
||||
expect(toolsModule.loadMcpTools).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should do nothing if no connections are provided', async () => {
|
||||
const client = new MultiServerMCPClient();
|
||||
await client.initializeConnections();
|
||||
|
||||
// Verify no connections were established
|
||||
expect(StdioClientTransport).not.toHaveBeenCalled();
|
||||
expect(SSEClientTransport).not.toHaveBeenCalled();
|
||||
expect(Client).not.toHaveBeenCalled();
|
||||
expect(toolsModule.loadMcpTools).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('close', () => {
|
||||
it('should close all connections', async () => {
|
||||
const client = new MultiServerMCPClient({
|
||||
server1: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['script1.py'],
|
||||
},
|
||||
server2: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['script2.py'],
|
||||
},
|
||||
});
|
||||
|
||||
await client.initializeConnections();
|
||||
await client.close();
|
||||
|
||||
// Verify all transports were closed
|
||||
const mockTransportInstances = [
|
||||
(StdioClientTransport as jest.Mock).mock.results[0].value,
|
||||
(StdioClientTransport as jest.Mock).mock.results[1].value,
|
||||
];
|
||||
expect(mockTransportInstances[0].close).toHaveBeenCalled();
|
||||
expect(mockTransportInstances[1].close).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,194 @@
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StructuredTool } from '@langchain/core/tools';
|
||||
import { convertMcpToolToLangchainTool, loadMcpTools } from '../src/tools';
|
||||
|
||||
// Mock Client
|
||||
const mockClient: jest.Mocked<Client> = {
|
||||
listTools: jest.fn(),
|
||||
callTool: jest.fn(),
|
||||
close: jest.fn(),
|
||||
} as unknown as jest.Mocked<Client>;
|
||||
|
||||
describe('tools', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('convertMcpToolToLangchainTool', () => {
|
||||
it('should convert an MCP tool to a LangChain tool', async () => {
|
||||
// Mock tool call result
|
||||
const mockToolResult = {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: '42',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
mockClient.callTool.mockResolvedValue(mockToolResult);
|
||||
|
||||
// Create a LangChain tool from an MCP tool
|
||||
const tool = convertMcpToolToLangchainTool(mockClient, 'calculator', 'A calculator tool', {
|
||||
type: 'object',
|
||||
properties: {
|
||||
a: { type: 'number' },
|
||||
b: { type: 'number' },
|
||||
},
|
||||
required: ['a', 'b'],
|
||||
});
|
||||
|
||||
// Verify the tool properties
|
||||
expect(tool).toBeInstanceOf(StructuredTool);
|
||||
expect(tool.name).toBe('calculator');
|
||||
expect(tool.description).toBe('A calculator tool');
|
||||
|
||||
// Invoke the tool
|
||||
const result = await tool.invoke({ a: 2, b: 3 });
|
||||
|
||||
// Verify the tool was called with the correct parameters
|
||||
expect(mockClient.callTool).toHaveBeenCalledWith({
|
||||
name: 'calculator',
|
||||
arguments: { a: 2, b: 3 },
|
||||
});
|
||||
|
||||
// Verify the result
|
||||
expect(result).toBe('42');
|
||||
});
|
||||
|
||||
it('should handle errors from the MCP tool', async () => {
|
||||
// Mock tool call result with an error
|
||||
const mockToolResult = {
|
||||
isError: true,
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Invalid input',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
mockClient.callTool.mockResolvedValue(mockToolResult);
|
||||
|
||||
// Create a LangChain tool from an MCP tool
|
||||
const tool = convertMcpToolToLangchainTool(mockClient, 'calculator', 'A calculator tool', {
|
||||
type: 'object',
|
||||
properties: {
|
||||
a: { type: 'number' },
|
||||
b: { type: 'number' },
|
||||
},
|
||||
required: ['a', 'b'],
|
||||
});
|
||||
|
||||
// Invoke the tool and expect it to throw
|
||||
await expect(tool.invoke({ a: -1, b: 3 })).rejects.toThrow('Invalid input');
|
||||
});
|
||||
|
||||
it('should handle non-text content from the MCP tool', async () => {
|
||||
// Mock tool call result with non-text content
|
||||
const mockContent = {
|
||||
type: 'image',
|
||||
data: 'base64-encoded-data',
|
||||
mimeType: 'image/png',
|
||||
};
|
||||
|
||||
const mockToolResult = {
|
||||
content: [mockContent],
|
||||
};
|
||||
|
||||
mockClient.callTool.mockResolvedValue(mockToolResult);
|
||||
|
||||
// Create a LangChain tool from an MCP tool
|
||||
const tool = convertMcpToolToLangchainTool(
|
||||
mockClient,
|
||||
'image-generator',
|
||||
'An image generator tool',
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
prompt: { type: 'string' },
|
||||
},
|
||||
required: ['prompt'],
|
||||
}
|
||||
);
|
||||
|
||||
// Invoke the tool
|
||||
const result = await tool.invoke({ prompt: 'A cat' });
|
||||
|
||||
// Verify the result is the content object
|
||||
expect(result).toBe('[object Object]'); // String(_convertCallToolResult) converts objects to string
|
||||
});
|
||||
});
|
||||
|
||||
describe('loadMcpTools', () => {
|
||||
it('should load all tools from an MCP client', async () => {
|
||||
// Mock listTools response
|
||||
mockClient.listTools.mockResolvedValue({
|
||||
tools: [
|
||||
{
|
||||
name: 'add',
|
||||
description: 'Add two numbers',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
a: { type: 'number' },
|
||||
b: { type: 'number' },
|
||||
},
|
||||
required: ['a', 'b'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtract',
|
||||
description: 'Subtract two numbers',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
a: { type: 'number' },
|
||||
b: { type: 'number' },
|
||||
},
|
||||
required: ['a', 'b'],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Load tools
|
||||
const tools = await loadMcpTools(mockClient);
|
||||
|
||||
// Verify listTools was called
|
||||
expect(mockClient.listTools).toHaveBeenCalled();
|
||||
|
||||
// Verify the tools were loaded correctly
|
||||
expect(tools).toHaveLength(2);
|
||||
expect(tools[0].name).toBe('add');
|
||||
expect(tools[1].name).toBe('subtract');
|
||||
});
|
||||
|
||||
it('should handle empty tool descriptions', async () => {
|
||||
// Mock listTools response with a tool missing a description
|
||||
mockClient.listTools.mockResolvedValue({
|
||||
tools: [
|
||||
{
|
||||
name: 'add',
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
a: { type: 'number' },
|
||||
b: { type: 'number' },
|
||||
},
|
||||
required: ['a', 'b'],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Load tools
|
||||
const tools = await loadMcpTools(mockClient);
|
||||
|
||||
// Verify the tool was loaded with an empty description
|
||||
expect(tools).toHaveLength(1);
|
||||
expect(tools[0].name).toBe('add');
|
||||
expect(tools[0].description).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
import eslint from "@eslint/js";
|
||||
import tseslint from "typescript-eslint";
|
||||
import prettierConfig from "eslint-config-prettier";
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
prettierConfig,
|
||||
{
|
||||
languageOptions: {
|
||||
ecmaVersion: 2022,
|
||||
sourceType: "module",
|
||||
},
|
||||
files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
|
||||
rules: {
|
||||
// TypeScript specific rules
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
argsIgnorePattern: "^_",
|
||||
varsIgnorePattern: "^_",
|
||||
},
|
||||
],
|
||||
|
||||
// General rules
|
||||
"no-console": ["warn", { allow: ["warn", "error", "info"] }],
|
||||
"prefer-const": "warn",
|
||||
"no-var": "error",
|
||||
eqeqeq: ["error", "always", { null: "ignore" }],
|
||||
"no-duplicate-imports": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-shadow": "off", // TypeScript has better handling with no-shadow
|
||||
"@typescript-eslint/no-shadow": "warn",
|
||||
|
||||
// Import rules
|
||||
"import/no-unresolved": "off", // TypeScript handles this
|
||||
"import/prefer-default-export": "off",
|
||||
"import/extensions": "off",
|
||||
|
||||
// Formatting (Prettier will handle most of this)
|
||||
"max-len": [
|
||||
"warn",
|
||||
{
|
||||
code: 100,
|
||||
ignoreUrls: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignoreRegExpLiterals: true,
|
||||
ignoreComments: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
ignores: ["node_modules/", "dist/", "coverage/", "**/*.js", "**/*.cjs"],
|
||||
},
|
||||
{
|
||||
files: ["**/*.test.ts", "**/*.spec.ts", "**/__tests__/**/*.ts"],
|
||||
rules: {
|
||||
// Relaxed rules for test files
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"no-console": "off",
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,187 @@
|
||||
# MCP Adapter Examples
|
||||
|
||||
This directory contains examples demonstrating how to use the LangChain.js MCP (Machine Communication Protocol) adapter with various configurations and use cases.
|
||||
|
||||
## Basic Examples
|
||||
|
||||
### Math Server Example
|
||||
|
||||
`math_example.ts` - A simple example demonstrating how to use the MCP adapter with a math server via stdio transport.
|
||||
|
||||
```bash
|
||||
node --loader ts-node/esm examples/math_example.ts
|
||||
```
|
||||
|
||||
### SSE Example
|
||||
|
||||
`sse_example.ts` - Demonstrates how to use the MCP adapter with a server using SSE (Server-Sent Events) transport.
|
||||
|
||||
```bash
|
||||
# First start the weather server
|
||||
python examples/weather_server.py
|
||||
|
||||
# Then run the example
|
||||
node --loader ts-node/esm examples/sse_example.ts
|
||||
```
|
||||
|
||||
### Logging Example
|
||||
|
||||
`logging_example.ts` - Demonstrates how to use the built-in Winston logger with the MCP adapter for detailed logging of client operations.
|
||||
|
||||
```bash
|
||||
# First start both servers
|
||||
python examples/math_server.py &
|
||||
python examples/weather_server.py &
|
||||
|
||||
# Then run the example
|
||||
node --loader ts-node/esm examples/logging_example.ts
|
||||
```
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Transport Configuration Example
|
||||
|
||||
`transport_config_example.ts` - Shows how to create an MCP client with a custom configuration that specifies different transport types for different servers.
|
||||
|
||||
```bash
|
||||
# First start the weather server
|
||||
python examples/weather_server.py
|
||||
|
||||
# Then run the example
|
||||
node --loader ts-node/esm examples/transport_config_example.ts
|
||||
```
|
||||
|
||||
### JSON Configuration Example
|
||||
|
||||
`json_config_example.ts` - Demonstrates how to load server configurations from JSON files.
|
||||
|
||||
```bash
|
||||
# First start the weather server
|
||||
python examples/weather_server.py
|
||||
|
||||
# Then run the example
|
||||
node --loader ts-node/esm examples/json_config_example.ts
|
||||
```
|
||||
|
||||
## Multi-Server Examples
|
||||
|
||||
### Multi-Transport Example
|
||||
|
||||
`multi_transport_example.ts` - Shows how to connect to multiple servers using different transport methods (stdio and SSE) and how to use tools from different servers.
|
||||
|
||||
```bash
|
||||
# First start the weather server
|
||||
python examples/weather_server.py
|
||||
|
||||
# Then run the example
|
||||
node --loader ts-node/esm examples/multi_transport_example.ts
|
||||
```
|
||||
|
||||
## LLM Integration Examples
|
||||
|
||||
### Agent Example
|
||||
|
||||
`agent_example.ts` - Demonstrates how to use MCP tools with a LangChain agent using OpenAI.
|
||||
|
||||
```bash
|
||||
# Set your OpenAI API key in a .env file
|
||||
echo "OPENAI_API_KEY=your-api-key" > .env
|
||||
|
||||
# Start the servers
|
||||
python examples/math_server.py &
|
||||
python examples/weather_server.py &
|
||||
|
||||
# Run the example
|
||||
node --loader ts-node/esm examples/agent_example.ts
|
||||
```
|
||||
|
||||
### Gemini Example
|
||||
|
||||
`gemini_example.ts` - Shows how to use MCP tools with Google's Gemini model.
|
||||
|
||||
```bash
|
||||
# Set your Google API key in a .env file
|
||||
echo "GOOGLE_API_KEY=your-api-key" > .env
|
||||
|
||||
# Start the servers
|
||||
python examples/math_server.py &
|
||||
python examples/weather_server.py &
|
||||
|
||||
# Run the example
|
||||
node --loader ts-node/esm examples/gemini_example.ts
|
||||
```
|
||||
|
||||
### Gemini Agent Example
|
||||
|
||||
`gemini_agent_example.ts` - Demonstrates how to use MCP tools with a LangChain agent using Google's Gemini model.
|
||||
|
||||
```bash
|
||||
# Set your Google API key in a .env file
|
||||
echo "GOOGLE_API_KEY=your-api-key" > .env
|
||||
|
||||
# Start the servers
|
||||
python examples/math_server.py &
|
||||
python examples/weather_server.py &
|
||||
|
||||
# Run the example
|
||||
node --loader ts-node/esm examples/gemini_agent_example.ts
|
||||
```
|
||||
|
||||
## Server Examples
|
||||
|
||||
### Math Server
|
||||
|
||||
`math_server.py` - A simple Python server that provides math operations (add, multiply) via the MCP protocol.
|
||||
|
||||
### Weather Server
|
||||
|
||||
`weather_server.py` - A Python server that provides weather information (temperature, forecast) via the MCP protocol with SSE transport.
|
||||
|
||||
```bash
|
||||
# Run the weather server
|
||||
python examples/weather_server.py
|
||||
|
||||
# By default, it runs on port 8000
|
||||
# You can specify a different port using command line arguments:
|
||||
python examples/weather_server.py --sse-port 8001
|
||||
```
|
||||
|
||||
## Configuration Files
|
||||
|
||||
### Simple MCP Configuration
|
||||
|
||||
`simple_mcp.json` - A simple configuration file for the MCP client that specifies a math server and a weather server.
|
||||
|
||||
### Complex MCP Configuration
|
||||
|
||||
`complex_mcp.json` - A more complex configuration file that includes environment variables and additional server configurations.
|
||||
|
||||
## Logging
|
||||
|
||||
The MCP adapter includes a built-in logging system using Winston. The logger provides the following features:
|
||||
|
||||
- Different log levels (error, warn, info, http, debug)
|
||||
- Colorized console output
|
||||
- File logging for errors and all logs
|
||||
- Environment-aware log levels (more verbose in development, less in production)
|
||||
|
||||
To use the logger in your own code:
|
||||
|
||||
```typescript
|
||||
import { MultiServerMCPClient } from "../src/client.js";
|
||||
import logger from "../src/logger.js";
|
||||
|
||||
// Use the logger
|
||||
logger.info("Starting MCP client");
|
||||
logger.debug("Detailed debug information");
|
||||
logger.warn("Warning message");
|
||||
logger.error("Error message");
|
||||
|
||||
// The client uses the logger internally
|
||||
const client = new MultiServerMCPClient({...});
|
||||
```
|
||||
|
||||
Log files are stored in the `logs` directory:
|
||||
|
||||
- `logs/error.log`: Contains only error-level logs
|
||||
- `logs/all.log`: Contains all logs
|
||||
@@ -0,0 +1,103 @@
|
||||
import { MultiServerMCPClient } from '../src/client.js';
|
||||
import { ChatOpenAI } from '@langchain/openai';
|
||||
import { createOpenAIFunctionsAgent, AgentExecutor } from 'langchain/agents';
|
||||
import { ChatPromptTemplate } from '@langchain/core/prompts';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* This example demonstrates how to use MCP tools with a LangChain agent.
|
||||
*
|
||||
* It connects to both a math server and a weather server, retrieves the available tools,
|
||||
* and creates an agent that can use these tools to solve problems.
|
||||
*
|
||||
* Note: You need to set the OPENAI_API_KEY environment variable to run this example.
|
||||
*/
|
||||
async function main() {
|
||||
if (!process.env.OPENAI_API_KEY) {
|
||||
console.error('Please set the OPENAI_API_KEY environment variable in the .env file');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create a client with configurations for both servers
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/math_server.py'],
|
||||
},
|
||||
weather: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/weather_server.py'],
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// Initialize connections to both servers
|
||||
console.log('Initializing connections to servers...');
|
||||
await client.initializeConnections();
|
||||
console.log('Connected to servers');
|
||||
|
||||
// Get all tools from all servers
|
||||
const serverTools = client.getTools();
|
||||
|
||||
// Flatten all tools for use with the agent
|
||||
const allTools = Array.from(serverTools.values()).flat();
|
||||
console.log(`Available tools: ${allTools.map(tool => tool.name).join(', ')}`);
|
||||
|
||||
// Create an agent
|
||||
console.log('\nCreating agent...');
|
||||
const model = new ChatOpenAI({
|
||||
temperature: 0,
|
||||
modelName: 'gpt-4o', // or any other model that supports function calling
|
||||
});
|
||||
|
||||
const prompt = ChatPromptTemplate.fromMessages([
|
||||
[
|
||||
'system',
|
||||
"You are a helpful assistant that can perform calculations and get weather information. Use the tools available to you to answer the user's questions.",
|
||||
],
|
||||
['human', '{input}'],
|
||||
]);
|
||||
|
||||
const agent = await createOpenAIFunctionsAgent({
|
||||
llm: model,
|
||||
tools: allTools,
|
||||
prompt,
|
||||
});
|
||||
|
||||
const agentExecutor = new AgentExecutor({
|
||||
agent,
|
||||
tools: allTools,
|
||||
});
|
||||
|
||||
// Run the agent with different queries
|
||||
const queries = [
|
||||
'What is 5 + 3?',
|
||||
'What is 7 * 9?',
|
||||
"What's the current temperature in Tokyo?",
|
||||
"What's the 3-day forecast for London?",
|
||||
"If it's 72°F in New York, what is that in Celsius?",
|
||||
];
|
||||
|
||||
for (const query of queries) {
|
||||
console.log(`\n--- Query: "${query}" ---`);
|
||||
const result = await agentExecutor.invoke({
|
||||
input: query,
|
||||
});
|
||||
console.log(`Answer: ${result.output}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
// Close the client
|
||||
console.log('\nClosing client...');
|
||||
await client.close();
|
||||
console.log('Client closed');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"servers": {
|
||||
"math": {
|
||||
"command": "python",
|
||||
"args": ["./examples/math_server.py"],
|
||||
"env": {
|
||||
"DEBUG": "true",
|
||||
"PYTHONPATH": "./examples"
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
"transport": "sse",
|
||||
"url": "http://localhost:8000/sse"
|
||||
},
|
||||
"custom-server": {
|
||||
"transport": "stdio",
|
||||
"command": "node",
|
||||
"args": ["./examples/custom_server.js"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { MultiServerMCPClient } from '../src/client.js';
|
||||
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
|
||||
import { initializeAgentExecutorWithOptions } from 'langchain/agents';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* This example demonstrates how to use MCP tools with a LangChain agent using Google's Gemini model.
|
||||
*
|
||||
* It connects to both a math server and a weather server, retrieves the available tools,
|
||||
* and creates an agent that can use these tools to solve problems.
|
||||
*
|
||||
* Note: You need to set the GOOGLE_API_KEY environment variable in the .env file to run this example.
|
||||
*/
|
||||
async function main() {
|
||||
if (!process.env.GOOGLE_API_KEY) {
|
||||
console.error('Please set the GOOGLE_API_KEY environment variable in the .env file');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create a client with configurations for both servers
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/math_server.py'],
|
||||
},
|
||||
weather: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/weather_server.py'],
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// Initialize connections to both servers
|
||||
console.log('Initializing connections to servers...');
|
||||
await client.initializeConnections();
|
||||
console.log('Connected to servers');
|
||||
|
||||
// Get all tools from all servers
|
||||
const serverTools = client.getTools();
|
||||
|
||||
// Flatten all tools for use with the agent
|
||||
const allTools = Array.from(serverTools.values()).flat();
|
||||
console.log(`Available tools: ${allTools.map(tool => tool.name).join(', ')}`);
|
||||
|
||||
// Print tool descriptions
|
||||
console.log('Tool descriptions:');
|
||||
for (const [serverName, tools] of serverTools.entries()) {
|
||||
console.log(`\nServer: ${serverName}`);
|
||||
for (const tool of tools) {
|
||||
console.log(`- ${tool.name}: ${tool.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create an agent
|
||||
console.log('\nCreating agent...');
|
||||
const model = new ChatGoogleGenerativeAI({
|
||||
apiKey: process.env.GOOGLE_API_KEY,
|
||||
modelName: 'gemini-2.0-flash',
|
||||
temperature: 0,
|
||||
});
|
||||
|
||||
// Initialize the agent executor with a simple agent type
|
||||
const executor = await initializeAgentExecutorWithOptions(allTools, model, {
|
||||
agentType: 'chat-zero-shot-react-description',
|
||||
verbose: true,
|
||||
});
|
||||
|
||||
// Run the agent with different queries
|
||||
const queries = [
|
||||
'What is 5 + 3?',
|
||||
'What is 7 * 9?',
|
||||
"What's the current temperature in Tokyo?",
|
||||
"What's the 3-day forecast for London?",
|
||||
"If it's 72°F in New York, what is that in Celsius?",
|
||||
];
|
||||
|
||||
for (const query of queries) {
|
||||
console.log(`\n--- Query: "${query}" ---`);
|
||||
try {
|
||||
const result = await executor.invoke({
|
||||
input: query,
|
||||
});
|
||||
console.log(`Answer: ${result.output}`);
|
||||
} catch (error) {
|
||||
console.error(`Error processing query "${query}":`, error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
// Close the client
|
||||
console.log('\nClosing client...');
|
||||
await client.close();
|
||||
console.log('Client closed');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,155 @@
|
||||
import { MultiServerMCPClient } from '../src/client.js';
|
||||
import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables from .env file
|
||||
dotenv.config();
|
||||
|
||||
/**
|
||||
* This example demonstrates how to use MCP tools with Google's Gemini model.
|
||||
*
|
||||
* It connects to both a math server and a weather server, retrieves the available tools,
|
||||
* and uses them directly with the model.
|
||||
*
|
||||
* Note: You need to set the GOOGLE_API_KEY environment variable in the .env file to run this example.
|
||||
*/
|
||||
async function main() {
|
||||
if (!process.env.GOOGLE_API_KEY) {
|
||||
console.error('Please set the GOOGLE_API_KEY environment variable in the .env file');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Create a client with configurations for both servers
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/math_server.py'],
|
||||
},
|
||||
weather: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/weather_server.py'],
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// Initialize connections to both servers
|
||||
console.log('Initializing connections to servers...');
|
||||
await client.initializeConnections();
|
||||
console.log('Connected to servers');
|
||||
|
||||
// Get all tools from all servers
|
||||
const serverTools = client.getTools();
|
||||
|
||||
// Flatten all tools for display purposes
|
||||
const allTools = Array.from(serverTools.values()).flat();
|
||||
console.log(`Available tools: ${allTools.map(tool => tool.name).join(', ')}`);
|
||||
|
||||
// Print tool descriptions
|
||||
console.log('Tool descriptions:');
|
||||
for (const [serverName, tools] of serverTools.entries()) {
|
||||
console.log(`\nServer: ${serverName}`);
|
||||
for (const tool of tools) {
|
||||
console.log(`- ${tool.name}: ${tool.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create the model
|
||||
console.log('\nCreating model...');
|
||||
const model = new ChatGoogleGenerativeAI({
|
||||
apiKey: process.env.GOOGLE_API_KEY,
|
||||
modelName: 'gemini-2.0-flash',
|
||||
temperature: 0,
|
||||
});
|
||||
|
||||
// Define some example operations
|
||||
console.log('\n--- Math Operations ---');
|
||||
|
||||
// Get math tools
|
||||
const mathTools = serverTools.get('math') || [];
|
||||
|
||||
// Add two numbers
|
||||
const addTool = mathTools.find(t => t.name === 'add');
|
||||
if (addTool) {
|
||||
const addResult = await addTool.invoke({
|
||||
a: 5,
|
||||
b: 3,
|
||||
});
|
||||
console.log(`5 + 3 = ${addResult}`);
|
||||
} else {
|
||||
console.log('Add tool not available');
|
||||
}
|
||||
|
||||
// Multiply two numbers
|
||||
const multiplyTool = mathTools.find(t => t.name === 'multiply');
|
||||
if (multiplyTool) {
|
||||
const multiplyResult = await multiplyTool.invoke({
|
||||
a: 4,
|
||||
b: 7,
|
||||
});
|
||||
console.log(`4 * 7 = ${multiplyResult}`);
|
||||
} else {
|
||||
console.log('Multiply tool not available');
|
||||
}
|
||||
|
||||
// Get weather information
|
||||
console.log('\n--- Weather Information ---');
|
||||
|
||||
// Get weather tools
|
||||
const weatherTools = serverTools.get('weather') || [];
|
||||
|
||||
// Get temperature for a city
|
||||
const temperatureTool = weatherTools.find(t => t.name === 'get_temperature');
|
||||
let temperatureResult;
|
||||
if (temperatureTool) {
|
||||
temperatureResult = await temperatureTool.invoke({
|
||||
city: 'New York',
|
||||
});
|
||||
console.log(`Temperature in New York: ${temperatureResult}`);
|
||||
} else {
|
||||
console.log('Temperature tool not available');
|
||||
}
|
||||
|
||||
// Get forecast for a city
|
||||
const forecastTool = weatherTools.find(t => t.name === 'get_forecast');
|
||||
let forecastResult;
|
||||
if (forecastTool) {
|
||||
forecastResult = await forecastTool.invoke({
|
||||
city: 'London',
|
||||
days: 3,
|
||||
});
|
||||
console.log(`Forecast for London: ${forecastResult}`);
|
||||
} else {
|
||||
console.log('Forecast tool not available');
|
||||
}
|
||||
|
||||
// Use Gemini to interpret the results
|
||||
console.log('\n--- Gemini Interpretations ---');
|
||||
|
||||
// Ask Gemini to convert Fahrenheit to Celsius
|
||||
if (temperatureResult) {
|
||||
const tempConversionPrompt = `If the temperature in New York is ${temperatureResult}, what is that in Celsius?`;
|
||||
console.log(`Query: "${tempConversionPrompt}"`);
|
||||
const tempConversionResponse = await model.invoke(tempConversionPrompt);
|
||||
console.log(`Gemini's answer: ${tempConversionResponse.content}`);
|
||||
}
|
||||
|
||||
// Ask Gemini to summarize the forecast
|
||||
if (forecastResult) {
|
||||
const forecastSummaryPrompt = `Summarize this forecast: ${forecastResult}`;
|
||||
console.log(`\nQuery: "${forecastSummaryPrompt}"`);
|
||||
const forecastSummaryResponse = await model.invoke(forecastSummaryPrompt);
|
||||
console.log(`Gemini's answer: ${forecastSummaryResponse.content}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
} finally {
|
||||
// Close the client
|
||||
console.log('\nClosing client...');
|
||||
await client.close();
|
||||
console.log('Client closed');
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,147 @@
|
||||
import { MultiServerMCPClient } from '../src/client.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* This example demonstrates how to use the MCP adapter with server configurations
|
||||
* loaded from JSON configuration files.
|
||||
*
|
||||
* It shows:
|
||||
* 1. How to load configurations from a specific JSON file
|
||||
* 2. How to load configurations from the default mcp.json file
|
||||
* 3. How to handle connection errors gracefully
|
||||
* 4. How to use tools from different servers
|
||||
*
|
||||
* Before running this example:
|
||||
* 1. Make sure you have the simple_mcp.json file in the examples directory
|
||||
* 2. Start the weather server with SSE transport:
|
||||
* python examples/weather_server.py
|
||||
*
|
||||
* 3. Then run this example with:
|
||||
* node --loader ts-node/esm examples/json_config_example.ts
|
||||
*/
|
||||
async function main() {
|
||||
// Determine which config file to use
|
||||
const simpleMcpPath = './examples/simple_mcp.json';
|
||||
const defaultMcpPath = './mcp.json';
|
||||
|
||||
let configPath;
|
||||
if (fs.existsSync(simpleMcpPath)) {
|
||||
configPath = simpleMcpPath;
|
||||
console.log(`Loading MCP server configurations from ${path.basename(configPath)}...`);
|
||||
} else if (fs.existsSync(defaultMcpPath)) {
|
||||
configPath = defaultMcpPath;
|
||||
console.log(`Loading MCP server configurations from ${path.basename(configPath)}...`);
|
||||
} else {
|
||||
console.error(`Neither ${simpleMcpPath} nor ${defaultMcpPath} exists.`);
|
||||
console.log('Creating a client with default configuration instead...');
|
||||
configPath = null;
|
||||
}
|
||||
|
||||
try {
|
||||
// Create a client from the config file or with default configuration
|
||||
const client = configPath
|
||||
? MultiServerMCPClient.fromConfigFile(configPath)
|
||||
: new MultiServerMCPClient({
|
||||
math: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/math_server.py'],
|
||||
},
|
||||
weather: {
|
||||
transport: 'sse',
|
||||
url: 'http://localhost:8000/sse',
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize all connections
|
||||
console.log('Initializing connections to all servers...');
|
||||
|
||||
try {
|
||||
await client.initializeConnections();
|
||||
console.log('Connected to all servers');
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error connecting to some servers:',
|
||||
error instanceof Error ? error.message : String(error)
|
||||
);
|
||||
console.log('Continuing with available servers...');
|
||||
}
|
||||
|
||||
// Get all tools from all servers
|
||||
const serverTools = client.getTools();
|
||||
|
||||
if (serverTools.size === 0) {
|
||||
console.log('No tools available. Make sure at least one server is running.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Flatten all tools for display purposes
|
||||
const allTools = Array.from(serverTools.values()).flat();
|
||||
console.log(`Available tools: ${allTools.map(tool => tool.name).join(', ')}`);
|
||||
|
||||
console.log(`Tool descriptions:`);
|
||||
for (const [serverName, tools] of serverTools.entries()) {
|
||||
console.log(`\nServer: ${serverName}`);
|
||||
for (const tool of tools) {
|
||||
console.log(`- ${tool.name}: ${tool.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the add tool from math server if available
|
||||
const mathTools = serverTools.get('math') || [];
|
||||
const addTool = mathTools.find(tool => tool.name === 'add');
|
||||
if (addTool) {
|
||||
console.log('\nUsing add tool from math server...');
|
||||
const addResult = await addTool.invoke({ a: 10, b: 5 });
|
||||
console.log(`10 + 5 = ${addResult}`);
|
||||
} else {
|
||||
console.log('\nAdd tool not available. Make sure the math server is running.');
|
||||
}
|
||||
|
||||
// Use the multiply tool from math server if available
|
||||
const multiplyTool = mathTools.find(tool => tool.name === 'multiply');
|
||||
if (multiplyTool) {
|
||||
console.log('\nUsing multiply tool from math server...');
|
||||
const multiplyResult = await multiplyTool.invoke({ a: 7, b: 8 });
|
||||
console.log(`7 * 8 = ${multiplyResult}`);
|
||||
} else {
|
||||
console.log('\nMultiply tool not available. Make sure the math server is running.');
|
||||
}
|
||||
|
||||
// Get temperature from weather server if available
|
||||
const weatherTools = serverTools.get('weather') || [];
|
||||
const getTempTool = weatherTools.find(tool => tool.name === 'get_temperature');
|
||||
if (getTempTool) {
|
||||
console.log('\nGetting temperature from weather server...');
|
||||
const tempResult = await getTempTool.invoke({ city: 'Tokyo' });
|
||||
console.log(tempResult);
|
||||
} else {
|
||||
console.log('\nTemperature tool not available. Make sure the weather server is running.');
|
||||
}
|
||||
|
||||
// Get forecast from weather server if available
|
||||
const getForecastTool = weatherTools.find(tool => tool.name === 'get_forecast');
|
||||
if (getForecastTool) {
|
||||
console.log('\nGetting forecast from weather server...');
|
||||
const forecastResult = await getForecastTool.invoke({
|
||||
city: 'London',
|
||||
days: 3,
|
||||
});
|
||||
console.log(forecastResult);
|
||||
} else {
|
||||
console.log('\nForecast tool not available. Make sure the weather server is running.');
|
||||
}
|
||||
|
||||
// Close the client when done
|
||||
console.log('\nClosing client...');
|
||||
await client.close();
|
||||
console.log('Client closed');
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error =>
|
||||
console.error('Unhandled error:', error instanceof Error ? error.message : String(error))
|
||||
);
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* This example demonstrates how to use the built-in Winston logger with the MCP adapter.
|
||||
*
|
||||
* To run this example:
|
||||
* 1. Start the math server: python examples/math_server.py
|
||||
* 2. Start the weather server: python examples/weather_server.py
|
||||
* 3. Run this example: node --loader ts-node/esm examples/logging_example.ts
|
||||
*/
|
||||
|
||||
import { MultiServerMCPClient, logger } from '../src/index.js';
|
||||
|
||||
// Set log level to debug for more verbose output
|
||||
// This is the default in development mode
|
||||
// In production mode, the default is 'info'
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
async function main() {
|
||||
logger.info('Starting logging example');
|
||||
|
||||
// Create a client with connections to both the math and weather servers
|
||||
logger.info('Creating MCP client with connections to math and weather servers');
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
command: 'python',
|
||||
args: ['examples/math_server.py'],
|
||||
},
|
||||
weather: {
|
||||
transport: 'sse',
|
||||
url: 'http://localhost:8000/sse',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// Initialize connections to all servers
|
||||
logger.info('Initializing connections to servers');
|
||||
const serverTools = await client.initializeConnections();
|
||||
|
||||
// Log the available tools from each server
|
||||
// Use Array.from to convert the Map entries to an array
|
||||
Array.from(serverTools.keys()).forEach(serverName => {
|
||||
const tools = serverTools.get(serverName) || [];
|
||||
logger.info(`Server "${serverName}" has ${tools.length} tools available:`);
|
||||
tools.forEach(tool => {
|
||||
logger.info(` - ${tool.name}: ${tool.description}`);
|
||||
});
|
||||
});
|
||||
|
||||
// Get the math tools
|
||||
const mathTools = serverTools.get('math') || [];
|
||||
if (mathTools.length > 0) {
|
||||
// Find the add tool
|
||||
const addTool = mathTools.find(tool => tool.name === 'add');
|
||||
if (addTool) {
|
||||
logger.debug('Calling add tool with numbers 5 and 7');
|
||||
const result = await addTool.invoke({ a: 5, b: 7 });
|
||||
logger.info(`Result of 5 + 7 = ${result}`);
|
||||
} else {
|
||||
logger.warn('Add tool not found in math server');
|
||||
}
|
||||
} else {
|
||||
logger.error('No tools found for math server');
|
||||
}
|
||||
|
||||
// Get the weather tools
|
||||
const weatherTools = serverTools.get('weather') || [];
|
||||
if (weatherTools.length > 0) {
|
||||
// Find the temperature tool
|
||||
const tempTool = weatherTools.find(tool => tool.name === 'get_temperature');
|
||||
if (tempTool) {
|
||||
logger.debug('Calling temperature tool for San Francisco');
|
||||
const result = await tempTool.invoke({ city: 'San Francisco' });
|
||||
logger.info(`Temperature in San Francisco: ${result}`);
|
||||
} else {
|
||||
logger.warn('Temperature tool not found in weather server');
|
||||
}
|
||||
} else {
|
||||
logger.error('No tools found for weather server');
|
||||
}
|
||||
|
||||
// Demonstrate different log levels
|
||||
logger.error('This is an error message - will appear in error.log and all.log');
|
||||
logger.warn('This is a warning message');
|
||||
logger.info('This is an info message');
|
||||
logger.http('This is an HTTP message');
|
||||
logger.debug('This is a debug message - only visible in development mode');
|
||||
|
||||
// Close all connections
|
||||
logger.info('Closing all connections');
|
||||
await client.close();
|
||||
|
||||
logger.info('Logging example completed successfully');
|
||||
} catch (error) {
|
||||
logger.error(`Error in logging example: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
logger.error(`Unhandled error in main: ${error}`);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* This example demonstrates how to use the MCP adapter with a math server.
|
||||
*
|
||||
* To run this example:
|
||||
* 1. Run this example: node --loader ts-node/esm examples/math_example.ts
|
||||
*
|
||||
* The math server will be started automatically via stdio transport.
|
||||
*/
|
||||
|
||||
import { MultiServerMCPClient } from '../src/client.js';
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Create a client
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
// No transport specified - will default to stdio
|
||||
command: 'python',
|
||||
args: ['./examples/math_server.py'],
|
||||
},
|
||||
});
|
||||
|
||||
// Connect to the math server via stdio
|
||||
console.log('Connecting to math server via stdio...');
|
||||
const serverTools = await client.initializeConnections();
|
||||
console.log('Connected to math server');
|
||||
|
||||
// Get the math tools
|
||||
const mathTools = serverTools.get('math') || [];
|
||||
console.log(`Available tools: ${mathTools.map(tool => tool.name).join(', ')}`);
|
||||
console.log(`Tool descriptions:`);
|
||||
for (const tool of mathTools) {
|
||||
console.log(`- ${tool.name}: ${tool.description}`);
|
||||
}
|
||||
|
||||
// Add two numbers
|
||||
console.log('\nAdding 5 + 3...');
|
||||
const addTool = mathTools.find(tool => tool.name === 'add');
|
||||
if (!addTool) {
|
||||
throw new Error('add tool not found');
|
||||
}
|
||||
const addResult = await addTool.invoke({ a: 5, b: 3 });
|
||||
console.log(`Result: ${addResult}`);
|
||||
|
||||
// Multiply two numbers
|
||||
console.log('\nMultiplying 4 * 7...');
|
||||
const multiplyTool = mathTools.find(tool => tool.name === 'multiply');
|
||||
if (!multiplyTool) {
|
||||
throw new Error('multiply tool not found');
|
||||
}
|
||||
const multiplyResult = await multiplyTool.invoke({ a: 4, b: 7 });
|
||||
console.log(`Result: ${multiplyResult}`);
|
||||
|
||||
// Close the client
|
||||
await client.close();
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Create a server
|
||||
mcp = FastMCP(name="Math")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two integers and return the result."""
|
||||
return a + b
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def multiply(a: int, b: int) -> int:
|
||||
"""Multiply two integers and return the result."""
|
||||
return a * b
|
||||
|
||||
|
||||
# Run the server
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="stdio")
|
||||
@@ -0,0 +1,245 @@
|
||||
import { MultiServerMCPClient } from '../src/client.js';
|
||||
|
||||
/**
|
||||
* This example demonstrates how to use the MCP adapter with multiple servers
|
||||
* using different transport methods.
|
||||
*
|
||||
* It shows:
|
||||
* 1. How to connect to servers using different transport methods (stdio and SSE)
|
||||
* 2. Two different approaches to configure multiple servers:
|
||||
* - Using a configuration object at initialization
|
||||
* - Using individual connect methods after initialization
|
||||
* 3. How to use tools from different servers
|
||||
* 4. How to perform complex operations using tools from multiple servers
|
||||
*
|
||||
* Before running this example:
|
||||
* 1. Start the weather server with SSE transport:
|
||||
* python examples/weather_server.py
|
||||
*
|
||||
* 2. Then run this example with:
|
||||
* node --loader ts-node/esm examples/multi_transport_example.ts
|
||||
*/
|
||||
async function main() {
|
||||
console.log('=== Method 1: Using Configuration Object ===');
|
||||
await runWithConfigObject();
|
||||
|
||||
console.log('\n\n=== Method 2: Using Individual Connect Methods ===');
|
||||
await runWithConnectMethods();
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstrates using a configuration object to connect to multiple servers
|
||||
*/
|
||||
async function runWithConfigObject() {
|
||||
// Create a client with multiple server connections using a configuration object
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/math_server.py'],
|
||||
},
|
||||
weather: {
|
||||
transport: 'sse',
|
||||
url: 'http://localhost:8000/sse',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// Initialize all connections
|
||||
console.log('Initializing connections to all servers...');
|
||||
|
||||
try {
|
||||
await client.initializeConnections();
|
||||
console.log('Connected to all servers');
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error connecting to some servers:',
|
||||
error instanceof Error ? error.message : String(error)
|
||||
);
|
||||
console.log('Continuing with available servers...');
|
||||
}
|
||||
|
||||
await demonstrateTools(client);
|
||||
} finally {
|
||||
// Close the client
|
||||
console.log('\nClosing client...');
|
||||
await client.close();
|
||||
console.log('Client closed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstrates using individual connect methods to connect to multiple servers
|
||||
*/
|
||||
async function runWithConnectMethods() {
|
||||
// Create a client with math server configuration
|
||||
const mathClient = new MultiServerMCPClient({
|
||||
math: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/math_server.py'],
|
||||
},
|
||||
});
|
||||
|
||||
// Create a client with weather server configuration
|
||||
const weatherClient = new MultiServerMCPClient({
|
||||
weather: {
|
||||
transport: 'sse',
|
||||
url: 'http://localhost:8000/sse',
|
||||
},
|
||||
});
|
||||
|
||||
// Create a combined client for demonstration
|
||||
const combinedClient = new MultiServerMCPClient({
|
||||
math: {
|
||||
transport: 'stdio',
|
||||
command: 'python',
|
||||
args: ['./examples/math_server.py'],
|
||||
},
|
||||
weather: {
|
||||
transport: 'sse',
|
||||
url: 'http://localhost:8000/sse',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
// Initialize math server connection
|
||||
console.log('Connecting to math server...');
|
||||
try {
|
||||
await mathClient.initializeConnections();
|
||||
console.log('Connected to math server');
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error connecting to math server:',
|
||||
error instanceof Error ? error.message : String(error)
|
||||
);
|
||||
}
|
||||
|
||||
// Initialize weather server connection
|
||||
console.log('\nConnecting to weather server...');
|
||||
try {
|
||||
await weatherClient.initializeConnections();
|
||||
console.log('Connected to weather server');
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error connecting to weather server:',
|
||||
error instanceof Error ? error.message : String(error)
|
||||
);
|
||||
}
|
||||
|
||||
// For demonstration purposes, we'll use the combined client
|
||||
// In a real application, you might want to merge the tools from both clients
|
||||
console.log('\nInitializing combined client for demonstration...');
|
||||
await combinedClient.initializeConnections();
|
||||
|
||||
await demonstrateTools(combinedClient);
|
||||
} finally {
|
||||
// Close all clients
|
||||
console.log('\nClosing clients...');
|
||||
await mathClient.close();
|
||||
await weatherClient.close();
|
||||
await combinedClient.close();
|
||||
console.log('Clients closed');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Demonstrates using tools from multiple servers
|
||||
*/
|
||||
async function demonstrateTools(client: MultiServerMCPClient) {
|
||||
// Get all tools from all servers
|
||||
const serverTools = client.getTools();
|
||||
|
||||
if (serverTools.size === 0) {
|
||||
console.log('No tools available. Make sure at least one server is running.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Flatten all tools for display purposes
|
||||
const allTools = Array.from(serverTools.values()).flat();
|
||||
console.log(`\nAvailable tools: ${allTools.map(tool => tool.name).join(', ')}`);
|
||||
|
||||
console.log(`Tool descriptions:`);
|
||||
for (const [serverName, tools] of serverTools.entries()) {
|
||||
console.log(`\nServer: ${serverName}`);
|
||||
for (const tool of tools) {
|
||||
console.log(`- ${tool.name}: ${tool.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the math tools if available
|
||||
console.log('\n--- Math Operations ---');
|
||||
const mathTools = serverTools.get('math') || [];
|
||||
const addTool = mathTools.find(tool => tool.name === 'add');
|
||||
if (addTool) {
|
||||
const addResult = await addTool.invoke({ a: 5, b: 3 });
|
||||
console.log(`5 + 3 = ${addResult}`);
|
||||
} else {
|
||||
console.log('Add tool not available');
|
||||
}
|
||||
|
||||
const multiplyTool = mathTools.find(tool => tool.name === 'multiply');
|
||||
if (multiplyTool) {
|
||||
const multiplyResult = await multiplyTool.invoke({ a: 4, b: 7 });
|
||||
console.log(`4 * 7 = ${multiplyResult}`);
|
||||
} else {
|
||||
console.log('Multiply tool not available');
|
||||
}
|
||||
|
||||
// Use the weather tools if available
|
||||
console.log('\n--- Weather Information ---');
|
||||
const weatherTools = serverTools.get('weather') || [];
|
||||
const temperatureTool = weatherTools.find(tool => tool.name === 'get_temperature');
|
||||
if (temperatureTool) {
|
||||
const temperatureResult = await temperatureTool.invoke({
|
||||
city: 'New York',
|
||||
});
|
||||
console.log(`Temperature in New York: ${temperatureResult}`);
|
||||
} else {
|
||||
console.log('Temperature tool not available');
|
||||
}
|
||||
|
||||
const forecastTool = weatherTools.find(tool => tool.name === 'get_forecast');
|
||||
if (forecastTool) {
|
||||
const forecastResult = await forecastTool.invoke({
|
||||
city: 'London',
|
||||
days: 3,
|
||||
});
|
||||
console.log(`Forecast for London: ${forecastResult}`);
|
||||
} else {
|
||||
console.log('Forecast tool not available');
|
||||
}
|
||||
|
||||
// Perform complex operations if all required tools are available
|
||||
if (addTool && multiplyTool && temperatureTool) {
|
||||
console.log('\n--- Complex Operations ---');
|
||||
|
||||
// Example 1: Simple math operation
|
||||
const sum = await addTool.invoke({ a: 5, b: 3 });
|
||||
const product = await multiplyTool.invoke({ a: sum, b: 2 });
|
||||
console.log(`(5 + 3) * 2 = ${product}`);
|
||||
|
||||
// Example 2: Convert temperature from Fahrenheit to Celsius
|
||||
const temperatureResult = await temperatureTool.invoke({
|
||||
city: 'New York',
|
||||
});
|
||||
const tempStr = temperatureResult.toString();
|
||||
const tempMatch = tempStr.match(/(\d+)°F/);
|
||||
|
||||
if (tempMatch && tempMatch[1]) {
|
||||
const tempF = parseInt(tempMatch[1]);
|
||||
const tempMinusThirtyTwo = await addTool.invoke({ a: tempF, b: -32 });
|
||||
const tempCelsius = await multiplyTool.invoke({
|
||||
a: tempMinusThirtyTwo,
|
||||
b: 5 / 9,
|
||||
});
|
||||
console.log(`Temperature in New York converted to Celsius: ${Math.round(tempCelsius)}°C`);
|
||||
} else {
|
||||
console.log('Could not extract temperature value for conversion');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error =>
|
||||
console.error('Unhandled error:', error instanceof Error ? error.message : String(error))
|
||||
);
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"servers": {
|
||||
"math": {
|
||||
"command": "python",
|
||||
"args": ["./examples/math_server.py"]
|
||||
},
|
||||
"weather-sse": {
|
||||
"transport": "sse",
|
||||
"url": "http://localhost:8000/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { MultiServerMCPClient } from '../src/client.js';
|
||||
|
||||
/**
|
||||
* This example demonstrates how to use the MCP adapter with an SSE server.
|
||||
*
|
||||
* Before running this example, you need to:
|
||||
*
|
||||
* 1. Start the weather server with SSE transport:
|
||||
* python examples/weather_server.py
|
||||
*
|
||||
* 2. Then run this example with:
|
||||
* node --loader ts-node/esm examples/sse_example.ts
|
||||
*/
|
||||
async function main() {
|
||||
try {
|
||||
// Create a client with a connection to the weather server
|
||||
const client = new MultiServerMCPClient({
|
||||
weather: {
|
||||
transport: 'sse',
|
||||
url: 'http://localhost:8000/sse',
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize the connection
|
||||
console.log('Connecting to weather server via SSE...');
|
||||
const serverTools = await client.initializeConnections();
|
||||
console.log('Connected to weather server');
|
||||
|
||||
// Get the weather tools
|
||||
const weatherTools = serverTools.get('weather') || [];
|
||||
console.log(`Available tools: ${weatherTools.map(tool => tool.name).join(', ')}`);
|
||||
console.log(`Tool descriptions:`);
|
||||
for (const tool of weatherTools) {
|
||||
console.log(`- ${tool.name}: ${tool.description}`);
|
||||
}
|
||||
|
||||
// Get temperature for a city
|
||||
console.log('\nGetting temperature for New York...');
|
||||
const getTempTool = weatherTools.find(tool => tool.name === 'get_temperature');
|
||||
if (!getTempTool) {
|
||||
throw new Error('get_temperature tool not found');
|
||||
}
|
||||
const tempResult = await getTempTool.invoke({ city: 'New York' });
|
||||
console.log(tempResult);
|
||||
|
||||
// Get forecast for a city
|
||||
console.log('\nGetting forecast for London...');
|
||||
const getForecastTool = weatherTools.find(tool => tool.name === 'get_forecast');
|
||||
if (!getForecastTool) {
|
||||
throw new Error('get_forecast tool not found');
|
||||
}
|
||||
const forecastResult = await getForecastTool.invoke({
|
||||
city: 'London',
|
||||
days: 3,
|
||||
});
|
||||
console.log(forecastResult);
|
||||
|
||||
// Close the client
|
||||
console.log('\nClosing client...');
|
||||
await client.close();
|
||||
console.log('Client closed');
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,129 @@
|
||||
import { MultiServerMCPClient } from '../src/client.js';
|
||||
|
||||
/**
|
||||
* This example demonstrates how to use the MCP adapter with a custom configuration
|
||||
* that specifies different transport types for different servers.
|
||||
*
|
||||
* It shows:
|
||||
* 1. How to create a client with a custom configuration
|
||||
* 2. How to use the default stdio transport implicitly
|
||||
* 3. How to specify the SSE transport explicitly
|
||||
* 4. How to handle connection errors gracefully
|
||||
* 5. How to use tools from different servers
|
||||
*
|
||||
* Before running this example:
|
||||
* 1. Start the weather server with SSE transport:
|
||||
* python examples/weather_server.py
|
||||
*
|
||||
* 2. Then run this example with:
|
||||
* node --loader ts-node/esm examples/transport_config_example.ts
|
||||
*/
|
||||
async function main() {
|
||||
console.log('Creating MCP client with custom transport configuration...');
|
||||
|
||||
try {
|
||||
// Create a client with a custom configuration
|
||||
const client = new MultiServerMCPClient({
|
||||
math: {
|
||||
// No transport specified - will default to stdio
|
||||
command: 'python',
|
||||
args: ['./examples/math_server.py'],
|
||||
},
|
||||
weather: {
|
||||
// Explicitly specify SSE transport
|
||||
transport: 'sse',
|
||||
url: 'http://localhost:8000/sse',
|
||||
},
|
||||
});
|
||||
|
||||
// Initialize all connections
|
||||
console.log('Initializing connections to all servers...');
|
||||
|
||||
try {
|
||||
await client.initializeConnections();
|
||||
console.log('Connected to all servers');
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Error connecting to some servers:',
|
||||
error instanceof Error ? error.message : String(error)
|
||||
);
|
||||
console.log('Continuing with available servers...');
|
||||
}
|
||||
|
||||
// Get all tools from all servers
|
||||
const serverTools = client.getTools();
|
||||
|
||||
if (serverTools.size === 0) {
|
||||
console.log('No tools available. Make sure at least one server is running.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Flatten all tools for display purposes
|
||||
const allTools = Array.from(serverTools.values()).flat();
|
||||
console.log(`Available tools: ${allTools.map(tool => tool.name).join(', ')}`);
|
||||
|
||||
console.log(`Tool descriptions:`);
|
||||
for (const [serverName, tools] of serverTools.entries()) {
|
||||
console.log(`\nServer: ${serverName}`);
|
||||
for (const tool of tools) {
|
||||
console.log(`- ${tool.name}: ${tool.description}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Use the add tool from math server if available
|
||||
const mathTools = serverTools.get('math') || [];
|
||||
const addTool = mathTools.find(tool => tool.name === 'add');
|
||||
if (addTool) {
|
||||
console.log('\nUsing add tool from math server...');
|
||||
const addResult = await addTool.invoke({ a: 10, b: 5 });
|
||||
console.log(`10 + 5 = ${addResult}`);
|
||||
} else {
|
||||
console.log('\nAdd tool not available. Make sure the math server is running.');
|
||||
}
|
||||
|
||||
// Use the multiply tool from math server if available
|
||||
const multiplyTool = mathTools.find(tool => tool.name === 'multiply');
|
||||
if (multiplyTool) {
|
||||
console.log('\nUsing multiply tool from math server...');
|
||||
const multiplyResult = await multiplyTool.invoke({ a: 7, b: 8 });
|
||||
console.log(`7 * 8 = ${multiplyResult}`);
|
||||
} else {
|
||||
console.log('\nMultiply tool not available. Make sure the math server is running.');
|
||||
}
|
||||
|
||||
// Get temperature from weather server if available
|
||||
const weatherTools = serverTools.get('weather') || [];
|
||||
const getTempTool = weatherTools.find(tool => tool.name === 'get_temperature');
|
||||
if (getTempTool) {
|
||||
console.log('\nGetting temperature from weather server...');
|
||||
const tempResult = await getTempTool.invoke({ city: 'Tokyo' });
|
||||
console.log(tempResult);
|
||||
} else {
|
||||
console.log('\nTemperature tool not available. Make sure the weather server is running.');
|
||||
}
|
||||
|
||||
// Get forecast from weather server if available
|
||||
const getForecastTool = weatherTools.find(tool => tool.name === 'get_forecast');
|
||||
if (getForecastTool) {
|
||||
console.log('\nGetting forecast from weather server...');
|
||||
const forecastResult = await getForecastTool.invoke({
|
||||
city: 'London',
|
||||
days: 3,
|
||||
});
|
||||
console.log(forecastResult);
|
||||
} else {
|
||||
console.log('\nForecast tool not available. Make sure the weather server is running.');
|
||||
}
|
||||
|
||||
// Close the client when done
|
||||
console.log('\nClosing client...');
|
||||
await client.close();
|
||||
console.log('Client closed');
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error =>
|
||||
console.error('Unhandled error:', error instanceof Error ? error.message : String(error))
|
||||
);
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
import sys
|
||||
|
||||
# Create a server
|
||||
mcp = FastMCP(name="Weather")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_temperature(city: str) -> str:
|
||||
"""Get the current temperature for a city.
|
||||
|
||||
This is a mock implementation that returns fake data.
|
||||
In a real application, this would call a weather API.
|
||||
"""
|
||||
# Mock implementation - in a real app, this would call a weather API
|
||||
temperatures = {
|
||||
"new york": "72°F",
|
||||
"london": "65°F",
|
||||
"tokyo": "25 degrees Celsius",
|
||||
"paris": "70°F",
|
||||
"sydney": "80°F",
|
||||
}
|
||||
|
||||
city_lower = city.lower()
|
||||
if city_lower in temperatures:
|
||||
return f"The current temperature in {city} is {temperatures[city_lower]}."
|
||||
else:
|
||||
return f"Temperature data not available for {city}."
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_forecast(city: str, days: int = 3) -> str:
|
||||
"""Get the weather forecast for a city.
|
||||
|
||||
Args:
|
||||
city: The name of the city to get the forecast for.
|
||||
days: The number of days to forecast (default: 3).
|
||||
|
||||
Returns:
|
||||
A string containing the weather forecast.
|
||||
"""
|
||||
# Mock implementation - in a real app, this would call a weather API
|
||||
forecasts = {
|
||||
"new york": "Sunny with a chance of rain",
|
||||
"london": "Cloudy with occasional showers",
|
||||
"tokyo": "Clear skies",
|
||||
"paris": "Partly cloudy",
|
||||
"sydney": "Warm and sunny",
|
||||
}
|
||||
|
||||
city_lower = city.lower()
|
||||
if city_lower in forecasts:
|
||||
return f"The {days}-day forecast for {city} is: {forecasts[city_lower]}."
|
||||
else:
|
||||
return f"Forecast data not available for {city}."
|
||||
|
||||
|
||||
# Run the server
|
||||
if __name__ == "__main__":
|
||||
# Set the port using command line arguments
|
||||
# The FastMCP server will read these arguments
|
||||
if len(sys.argv) == 1: # No arguments provided
|
||||
# Add command line arguments for the port
|
||||
sys.argv.extend(["--sse-port", "8000"])
|
||||
port = 8000
|
||||
else:
|
||||
# Check if --sse-port is already in the arguments
|
||||
if "--sse-port" in sys.argv:
|
||||
port_index = sys.argv.index("--sse-port") + 1
|
||||
if port_index < len(sys.argv):
|
||||
port = int(sys.argv[port_index])
|
||||
else:
|
||||
port = 8000
|
||||
else:
|
||||
# Add the port argument
|
||||
sys.argv.extend(["--sse-port", "8000"])
|
||||
port = 8000
|
||||
|
||||
# Run with SSE transport
|
||||
print(f"Starting weather server with SSE transport on port {port}...")
|
||||
mcp.run(transport="sse")
|
||||
@@ -0,0 +1,22 @@
|
||||
export default {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testMatch: ['**/__tests__/**/*.test.ts'],
|
||||
collectCoverage: true,
|
||||
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.test.ts'],
|
||||
coverageDirectory: 'coverage',
|
||||
coverageReporters: ['text', 'lcov'],
|
||||
moduleFileExtensions: ['ts', 'js', 'json'],
|
||||
transform: {
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
useESM: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
moduleNameMapper: {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"servers": {
|
||||
"math": {
|
||||
"transport": "stdio",
|
||||
"command": "python",
|
||||
"args": ["./examples/math_server.py"]
|
||||
},
|
||||
"weather": {
|
||||
"transport": "sse",
|
||||
"url": "http://localhost:8000/sse"
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+7610
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "langchainjs-mcp-adapters",
|
||||
"version": "0.1.0",
|
||||
"description": "LangChain.js adapters for Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "jest",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"examples/**/*.ts\"",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"keywords": [
|
||||
"langchain",
|
||||
"mcp",
|
||||
"model-context-protocol",
|
||||
"ai",
|
||||
"tools"
|
||||
],
|
||||
"author": "Ravi Kiran Vemula",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@langchain/core": "^0.3.40",
|
||||
"@langchain/google-genai": "^0.1.10",
|
||||
"@langchain/openai": "^0.4.4",
|
||||
"@modelcontextprotocol/sdk": "^1.6.1",
|
||||
"dotenv": "^16.4.7",
|
||||
"langchain": "^0.3.19",
|
||||
"winston": "^3.17.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.21.0",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20.11.30",
|
||||
"@typescript-eslint/eslint-plugin": "^7.3.1",
|
||||
"@typescript-eslint/parser": "^7.3.1",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^10.0.2",
|
||||
"husky": "^9.0.11",
|
||||
"jest": "^29.7.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"prettier": "^3.2.5",
|
||||
"ts-jest": "^29.1.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.4.2",
|
||||
"typescript-eslint": "^8.26.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"README.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"directories": {
|
||||
"example": "examples"
|
||||
}
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
||||
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
||||
import { StructuredTool } from '@langchain/core/tools';
|
||||
import { loadMcpTools } from './tools.js';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import logger from './logger.js';
|
||||
|
||||
type StdioConnection = {
|
||||
transport: 'stdio';
|
||||
command: string;
|
||||
args: string[];
|
||||
env?: Record<string, string>;
|
||||
encoding?: string;
|
||||
encodingErrorHandler?: 'strict' | 'ignore' | 'replace';
|
||||
};
|
||||
|
||||
type SSEConnection = {
|
||||
transport: 'sse';
|
||||
url: string;
|
||||
};
|
||||
|
||||
type Connection = StdioConnection | SSEConnection;
|
||||
|
||||
type MCPConfig = {
|
||||
servers: Record<string, Connection>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client for connecting to multiple MCP servers and loading LangChain-compatible tools.
|
||||
*/
|
||||
export class MultiServerMCPClient {
|
||||
private clients: Map<string, Client> = new Map();
|
||||
private serverNameToTools: Map<string, StructuredTool[]> = new Map();
|
||||
private connections?: Record<string, Connection>;
|
||||
private cleanupFunctions: Array<() => Promise<void>> = [];
|
||||
|
||||
/**
|
||||
* Create a new MultiServerMCPClient.
|
||||
*
|
||||
* @param connections - Optional connections to initialize
|
||||
*/
|
||||
constructor(connections?: Record<string, any>) {
|
||||
if (connections) {
|
||||
// Process connections to ensure they have the correct format
|
||||
const processedConnections: Record<string, Connection> = {};
|
||||
|
||||
for (const [serverName, config] of Object.entries(connections)) {
|
||||
if (typeof config !== 'object' || config === null) {
|
||||
logger.warn(`Invalid configuration for server "${serverName}". Skipping.`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// If transport is explicitly set
|
||||
if (config.transport) {
|
||||
if (config.transport === 'stdio') {
|
||||
if (!config.command || !Array.isArray(config.args)) {
|
||||
logger.warn(
|
||||
`Server "${serverName}" is missing required properties for stdio transport. Skipping.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const stdioConfig: StdioConnection = {
|
||||
transport: 'stdio',
|
||||
command: config.command,
|
||||
args: config.args,
|
||||
};
|
||||
|
||||
// Add optional properties if they exist
|
||||
if (config.env && typeof config.env === 'object') {
|
||||
stdioConfig.env = config.env;
|
||||
}
|
||||
|
||||
if (typeof config.encoding === 'string') {
|
||||
stdioConfig.encoding = config.encoding;
|
||||
}
|
||||
|
||||
if (['strict', 'ignore', 'replace'].includes(config.encodingErrorHandler)) {
|
||||
stdioConfig.encodingErrorHandler = config.encodingErrorHandler as
|
||||
| 'strict'
|
||||
| 'ignore'
|
||||
| 'replace';
|
||||
}
|
||||
|
||||
processedConnections[serverName] = stdioConfig;
|
||||
} else if (config.transport === 'sse') {
|
||||
if (!config.url) {
|
||||
logger.warn(
|
||||
`Server "${serverName}" is missing required URL for SSE transport. Skipping.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
processedConnections[serverName] = {
|
||||
transport: 'sse',
|
||||
url: config.url,
|
||||
};
|
||||
} else {
|
||||
logger.warn(
|
||||
`Server "${serverName}" has unsupported transport type: ${config.transport}. Skipping.`
|
||||
);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// If transport is not explicitly set, try to infer it
|
||||
if (config.command && Array.isArray(config.args)) {
|
||||
// Looks like stdio
|
||||
const stdioConfig: StdioConnection = {
|
||||
transport: 'stdio',
|
||||
command: config.command,
|
||||
args: config.args,
|
||||
};
|
||||
|
||||
// Add optional properties if they exist
|
||||
if (config.env && typeof config.env === 'object') {
|
||||
stdioConfig.env = config.env;
|
||||
}
|
||||
|
||||
if (typeof config.encoding === 'string') {
|
||||
stdioConfig.encoding = config.encoding;
|
||||
}
|
||||
|
||||
if (['strict', 'ignore', 'replace'].includes(config.encodingErrorHandler)) {
|
||||
stdioConfig.encodingErrorHandler = config.encodingErrorHandler as
|
||||
| 'strict'
|
||||
| 'ignore'
|
||||
| 'replace';
|
||||
}
|
||||
|
||||
processedConnections[serverName] = stdioConfig;
|
||||
} else if (config.url) {
|
||||
// Looks like SSE
|
||||
processedConnections[serverName] = {
|
||||
transport: 'sse',
|
||||
url: config.url,
|
||||
};
|
||||
} else {
|
||||
logger.warn(`Server "${serverName}" has invalid configuration. Skipping.`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.connections = processedConnections;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a configuration from a JSON file.
|
||||
*
|
||||
* @param configPath - Path to the configuration file
|
||||
* @returns A new MultiServerMCPClient
|
||||
*/
|
||||
static fromConfigFile(configPath: string): MultiServerMCPClient {
|
||||
try {
|
||||
const configData = fs.readFileSync(configPath, 'utf8');
|
||||
const config = JSON.parse(configData) as MCPConfig;
|
||||
logger.info(`Loaded MCP configuration from ${configPath}`);
|
||||
return new MultiServerMCPClient(config.servers);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to load MCP configuration from ${configPath}: ${error}`);
|
||||
throw new Error(`Failed to load MCP configuration: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize connections to all servers.
|
||||
*
|
||||
* @returns A map of server names to arrays of tools
|
||||
*/
|
||||
async initializeConnections(): Promise<Map<string, StructuredTool[]>> {
|
||||
if (!this.connections) {
|
||||
logger.warn('No connections to initialize');
|
||||
return new Map();
|
||||
}
|
||||
|
||||
for (const [serverName, connection] of Object.entries(this.connections)) {
|
||||
try {
|
||||
logger.info(`Initializing connection to server "${serverName}"...`);
|
||||
|
||||
let client: Client;
|
||||
let cleanup: () => Promise<void>;
|
||||
|
||||
if (connection.transport === 'stdio') {
|
||||
const { command, args, env } = connection;
|
||||
|
||||
logger.debug(
|
||||
`Creating stdio transport for server "${serverName}" with command: ${command} ${args.join(' ')}`
|
||||
);
|
||||
|
||||
const transport = new StdioClientTransport({
|
||||
command,
|
||||
args,
|
||||
env,
|
||||
});
|
||||
|
||||
client = new Client({
|
||||
name: 'langchain-mcp-adapter',
|
||||
version: '0.1.0',
|
||||
});
|
||||
await client.connect(transport);
|
||||
|
||||
cleanup = async () => {
|
||||
logger.debug(`Closing stdio transport for server "${serverName}"`);
|
||||
await transport.close();
|
||||
};
|
||||
} else if (connection.transport === 'sse') {
|
||||
const { url } = connection;
|
||||
|
||||
logger.debug(`Creating SSE transport for server "${serverName}" with URL: ${url}`);
|
||||
|
||||
const transport = new SSEClientTransport(new URL(url));
|
||||
|
||||
client = new Client({
|
||||
name: 'langchain-mcp-adapter',
|
||||
version: '0.1.0',
|
||||
});
|
||||
await client.connect(transport);
|
||||
|
||||
cleanup = async () => {
|
||||
logger.debug(`Closing SSE transport for server "${serverName}"`);
|
||||
await transport.close();
|
||||
};
|
||||
} else {
|
||||
// This should never happen due to the validation in the constructor
|
||||
logger.error(`Unsupported transport type for server "${serverName}"`);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.clients.set(serverName, client);
|
||||
this.cleanupFunctions.push(cleanup);
|
||||
|
||||
// Load tools for this server
|
||||
try {
|
||||
logger.debug(`Loading tools for server "${serverName}"...`);
|
||||
const tools = await loadMcpTools(client);
|
||||
this.serverNameToTools.set(serverName, tools);
|
||||
logger.info(`Successfully loaded ${tools.length} tools from server "${serverName}"`);
|
||||
} catch (error) {
|
||||
logger.error(`Failed to load tools from server "${serverName}": ${error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Failed to connect to server "${serverName}": ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
return this.serverNameToTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tools from all servers.
|
||||
*
|
||||
* @returns A map of server names to arrays of tools
|
||||
*/
|
||||
getTools(): Map<string, StructuredTool[]> {
|
||||
return this.serverNameToTools;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a client for a specific server.
|
||||
*
|
||||
* @param serverName - The name of the server
|
||||
* @returns The client for the server, or undefined if the server is not connected
|
||||
*/
|
||||
getClient(serverName: string): Client | undefined {
|
||||
return this.clients.get(serverName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Close all connections.
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
logger.info('Closing all MCP connections...');
|
||||
|
||||
for (const cleanup of this.cleanupFunctions) {
|
||||
try {
|
||||
await cleanup();
|
||||
} catch (error) {
|
||||
logger.error(`Error during cleanup: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.cleanupFunctions = [];
|
||||
this.clients.clear();
|
||||
this.serverNameToTools.clear();
|
||||
|
||||
logger.info('All MCP connections closed');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export { MultiServerMCPClient } from './client.js';
|
||||
export { convertMcpToolToLangchainTool, loadMcpTools } from './tools.js';
|
||||
export { default as logger } from './logger.js';
|
||||
@@ -0,0 +1,84 @@
|
||||
import * as winston from 'winston';
|
||||
|
||||
/**
|
||||
* Logging levels:
|
||||
* error: 0 - Severe errors that cause the application to crash or malfunction
|
||||
* warn: 1 - Warnings that don't stop the application but should be addressed
|
||||
* info: 2 - General information about application operation
|
||||
* http: 3 - HTTP request/response information
|
||||
* debug: 4 - Detailed debugging information
|
||||
*/
|
||||
const levels = {
|
||||
error: 0,
|
||||
warn: 1,
|
||||
info: 2,
|
||||
http: 3,
|
||||
debug: 4,
|
||||
};
|
||||
|
||||
/**
|
||||
* Determine the appropriate log level based on the environment.
|
||||
* In development, we want to see all logs.
|
||||
* In production, we only want to see warnings and errors.
|
||||
*/
|
||||
const level = () => {
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const isDevelopment = env === 'development';
|
||||
return isDevelopment ? 'debug' : 'warn';
|
||||
};
|
||||
|
||||
/**
|
||||
* Define colors for each log level to improve readability in the console.
|
||||
*/
|
||||
const colors = {
|
||||
error: 'red',
|
||||
warn: 'yellow',
|
||||
info: 'green',
|
||||
http: 'magenta',
|
||||
debug: 'white',
|
||||
};
|
||||
|
||||
// Add colors to Winston
|
||||
winston.addColors(colors);
|
||||
|
||||
/**
|
||||
* Define the format for log messages.
|
||||
* We include a timestamp, colorize the output, and format the message.
|
||||
*/
|
||||
const format = winston.format.combine(
|
||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
|
||||
winston.format.colorize({ all: true }),
|
||||
winston.format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
|
||||
);
|
||||
|
||||
/**
|
||||
* Define the transports for log messages.
|
||||
* We log to the console and to files.
|
||||
*/
|
||||
const transports = [
|
||||
// Console transport
|
||||
new winston.transports.Console(),
|
||||
|
||||
// File transport for errors
|
||||
new winston.transports.File({
|
||||
filename: 'logs/error.log',
|
||||
level: 'error',
|
||||
}),
|
||||
|
||||
// File transport for all logs
|
||||
new winston.transports.File({
|
||||
filename: 'logs/all.log',
|
||||
}),
|
||||
];
|
||||
|
||||
/**
|
||||
* Create the logger instance with our configuration.
|
||||
*/
|
||||
const logger = winston.createLogger({
|
||||
level: level(),
|
||||
levels,
|
||||
format,
|
||||
transports,
|
||||
});
|
||||
|
||||
export default logger;
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
||||
import { StructuredTool } from '@langchain/core/tools';
|
||||
import { z } from 'zod';
|
||||
import logger from './logger.js';
|
||||
|
||||
// Define the types that were previously imported from the SDK
|
||||
interface ContentItem {
|
||||
type: string;
|
||||
text?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
// Custom error class for tool exceptions
|
||||
class ToolException extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'ToolException';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the result from calling an MCP tool.
|
||||
*
|
||||
* @param result - The result from the MCP tool call
|
||||
* @returns The processed result
|
||||
*/
|
||||
function _convertCallToolResult(result: any): any {
|
||||
// Check for error in the response
|
||||
if (result.isError) {
|
||||
// Find the first text content for error message
|
||||
if (Array.isArray(result.content)) {
|
||||
const textContent = result.content.find((item: ContentItem) => item.type === 'text');
|
||||
if (textContent && textContent.text) {
|
||||
throw new ToolException(textContent.text);
|
||||
}
|
||||
}
|
||||
throw new ToolException('Tool execution failed');
|
||||
}
|
||||
|
||||
// Handle content array from the new SDK format
|
||||
if (Array.isArray(result.content)) {
|
||||
// Find the first text content
|
||||
const textContent = result.content.find((item: ContentItem) => item.type === 'text');
|
||||
if (textContent) {
|
||||
return textContent.text;
|
||||
}
|
||||
// If there's only one content item, return it
|
||||
if (result.content.length === 1) {
|
||||
return result.content[0];
|
||||
}
|
||||
// Return the whole content array if no text found
|
||||
return result.content;
|
||||
}
|
||||
|
||||
// Handle old format or other formats
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an MCP tool to a LangChain tool.
|
||||
*
|
||||
* @param client - The MCP client
|
||||
* @param toolName - The name of the tool
|
||||
* @param toolDescription - The description of the tool
|
||||
* @param toolSchema - The schema of the tool
|
||||
* @returns A LangChain tool
|
||||
*/
|
||||
export function convertMcpToolToLangchainTool(
|
||||
client: Client,
|
||||
toolName: string,
|
||||
toolDescription: string,
|
||||
toolSchema: any
|
||||
): StructuredTool {
|
||||
// Convert the JSON schema to a Zod schema
|
||||
let zodSchema: z.ZodObject<any>;
|
||||
|
||||
try {
|
||||
// Create a Zod schema based on the tool's schema
|
||||
if (toolSchema && toolSchema.type === 'object' && toolSchema.properties) {
|
||||
const schemaShape: Record<string, z.ZodType> = {};
|
||||
|
||||
// Convert each property to a Zod type
|
||||
Object.entries(toolSchema.properties).forEach(([key, value]: [string, any]) => {
|
||||
if (value.type === 'string') {
|
||||
schemaShape[key] = z.string();
|
||||
} else if (value.type === 'number') {
|
||||
schemaShape[key] = z.number();
|
||||
} else if (value.type === 'boolean') {
|
||||
schemaShape[key] = z.boolean();
|
||||
} else if (value.type === 'array') {
|
||||
schemaShape[key] = z.array(z.any());
|
||||
} else {
|
||||
schemaShape[key] = z.any();
|
||||
}
|
||||
});
|
||||
|
||||
zodSchema = z.object(schemaShape);
|
||||
} else {
|
||||
zodSchema = z.object({});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`Error creating Zod schema for tool ${toolName}:`, error);
|
||||
zodSchema = z.object({});
|
||||
}
|
||||
|
||||
// Create a class that extends StructuredTool
|
||||
class MCPTool extends StructuredTool {
|
||||
name = toolName;
|
||||
description = toolDescription;
|
||||
schema = zodSchema;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
protected async _call(input: Record<string, any>): Promise<string> {
|
||||
try {
|
||||
logger.debug(`Calling MCP tool ${toolName} with input:`, input);
|
||||
// Use the new SDK format for calling tools
|
||||
const result = await client.callTool({
|
||||
name: toolName,
|
||||
arguments: input,
|
||||
});
|
||||
const processedResult = _convertCallToolResult(result);
|
||||
logger.debug(`MCP tool ${toolName} returned:`, processedResult);
|
||||
return String(processedResult);
|
||||
} catch (error) {
|
||||
logger.error(`Error calling tool ${toolName}:`, error);
|
||||
throw new ToolException(`Error calling tool ${toolName}: ${error}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new MCPTool();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all tools from an MCP client.
|
||||
*
|
||||
* @param client - The MCP client
|
||||
* @returns A list of LangChain tools
|
||||
*/
|
||||
export async function loadMcpTools(client: Client): Promise<StructuredTool[]> {
|
||||
const tools: StructuredTool[] = [];
|
||||
logger.debug('Listing available MCP tools...');
|
||||
const toolsResponse = await client.listTools();
|
||||
const toolsInfo = toolsResponse.tools;
|
||||
|
||||
logger.info(`Found ${toolsInfo.length} MCP tools`);
|
||||
|
||||
for (const toolInfo of toolsInfo) {
|
||||
logger.debug(`Converting MCP tool "${toolInfo.name}" to LangChain tool`);
|
||||
const tool = convertMcpToolToLangchainTool(
|
||||
client,
|
||||
toolInfo.name,
|
||||
toolInfo.description || '',
|
||||
toolInfo.inputSchema
|
||||
);
|
||||
tools.push(tool);
|
||||
}
|
||||
|
||||
return tools;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"downlevelIteration": true
|
||||
},
|
||||
"include": ["src/**/*", "examples/**/*"],
|
||||
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user