#
tokens: 10779/50000 12/12 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   └── workflows
│       ├── pr-feedback.yml
│       └── release.yml
├── .gitignore
├── .releaserc.json
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE
├── package.json
├── pnpm-lock.yaml
├── README.md
├── src
│   ├── index.ts
│   ├── server-manager.ts
│   └── types.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Node.js related
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log

# Build outputs
dist/
build/

# Logs
logs/
*.log

# IDE related
.idea/
.vscode/
*.swp
*.swo

# Environment variables
.env
.env.*
!.env.example

# OS related
.DS_Store
Thumbs.db 

.history
.cursor

```

--------------------------------------------------------------------------------
/.releaserc.json:
--------------------------------------------------------------------------------

```json
{
	"branches": [
		"main"
	],
	"plugins": [
		"@semantic-release/commit-analyzer",
		"@semantic-release/release-notes-generator",
		"@semantic-release/changelog",
		"@semantic-release/npm",
		"@semantic-release/github",
		[
			"@semantic-release/git",
			{
				"assets": [
					"package.json",
					"pnpm-lock.yaml",
					"CHANGELOG.md"
				],
				"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
			}
		]
	]
}
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
<p align="center">
  <img src="https://imgur.com/DgWxkmv.png" width="200" height="200">
</p>

[![Install MCP Server](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/install-mcp?name=mcp-hub&config=eyJjb21tYW5kIjoibnB4IC15IG1jcC1odWItbWNwIC0tY29uZmlnLXBhdGggfi8uY3Vyc29yL21jcC1odWIuanNvbiJ9)

[![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22mcp-hub%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22mcp-hub-mcp%40latest%22%2C%22--config-path%22%2C%22~%2Fmcp-hub.json%22%5D%7D)

# MCP-Hub-MCP Server

A hub server that connects to and manages other MCP (Model Context Protocol) servers.

## Overview

This project builds an MCP hub server that connects to and manages multiple MCP (Model Context Protocol) servers through a single interface.
It helps prevent excessive context usage and pollution from infrequently used MCPs (e.g., Atlassian MCP, Playwright MCP) by allowing you to connect them only when needed.
This reduces AI mistakes and improves performance by keeping the active tool set focused and manageable.

## Key Features

- Automatic connection to other MCP servers via configuration file
- List available tools on connected servers
- Call tools on connected servers and return results

## Configuration

Add this to your `mcp.json`:

#### Using npx

```json
{
  "mcpServers": {
    "other-tools": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-hub-mcp",
        "--config-path",
        "/Users/username/mcp.json"
      ]
    }
  }
}
```


## Installation and Running

### Requirements

- Node.js 18.0.0 or higher
- npm, yarn, or pnpm

### Installation

```bash
# Clone repository
git clone <repository-url>
cd mcp-hub-mcp

# Install dependencies
npm install
# or
yarn install
# or
pnpm install
```

### Build

```bash
npm run build
# or
yarn build
# or
pnpm build
```

### Run

```bash
npm start
# or
yarn start
# or
pnpm start
```

### Development Mode

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```

## Configuration File

The MCP-Hub-MCP server uses a Claude Desktop format configuration file to automatically connect to other MCP servers.
You can specify the configuration file in the following ways:

1. Environment variable: Set the `MCP_CONFIG_PATH` environment variable to the configuration file path
2. Command line argument: Use the `--config-path` option to specify the configuration file path
3. Default path: Use `mcp-config.json` file in the current directory

Configuration file format:

```json
{
  "mcpServers": {
    "serverName1": {
      "command": "command",
      "args": ["arg1", "arg2", ...],
      "env": { "ENV_VAR1": "value1", ... }
    },
    "serverName2": {
      "command": "anotherCommand",
      "args": ["arg1", "arg2", ...]
    }
  }
}
```

Example:

```json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/Users/username/Desktop",
        "/Users/username/Downloads"
      ]
    },
    "other-server": {
      "command": "node",
      "args": ["path/to/other-mcp-server.js"]
    }
  }
}
```

## Usage

The MCP-Hub-MCP server provides the following tools:

### 1. `list-all-tools`

Returns a list of tools from all connected servers.

```json
{
  "name": "list-all-tools",
  "arguments": {}
}
```

### 2. `call-tool`

Calls a tool on a specific server.

- `serverName`: Name of the MCP server to call the tool from
- `toolName`: Name of the tool to call
- `toolArgs`: Arguments to pass to the tool

```json
{
  "name": "call-tool",
  "arguments": {
    "serverName": "filesystem",
    "toolName": "readFile",
    "toolArgs": {
      "path": "/Users/username/Desktop/example.txt"
    }
  }
}
```

### 3. `find-tools`

Find tools matching a regex pattern across all connected servers (grep-like functionality).

- `pattern`: Regex pattern to search for in tool names and descriptions
- `searchIn`: Where to search: "name", "description", or "both" (default: "both")
- `caseSensitive`: Whether the search should be case-sensitive (default: false)

```json
{
  "name": "find-tools",
  "arguments": {
    "pattern": "file",
    "searchIn": "both",
    "caseSensitive": false
  }
}
```

Example patterns:
- `"file"` - Find all tools containing "file"
- `"^read"` - Find all tools starting with "read"
- `"(read|write).*file"` - Find tools for reading or writing files
- `"config$"` - Find tools ending with "config"

Example output:
```json
{
  "filesystem": [
    {
      "name": "readFile",
      "description": "Read the contents of a file",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Path to the file to read"
          }
        },
        "required": ["path"]
      }
    },
    {
      "name": "writeFile",
      "description": "Write content to a file",
      "inputSchema": {
        "type": "object",
        "properties": {
          "path": {
            "type": "string",
            "description": "Path to the file to write"
          },
          "content": {
            "type": "string",
            "description": "Content to write to the file"
          }
        },
        "required": ["path", "content"]
      }
    }
  ]
}
```

## Commit Message Convention

This project follows [Conventional Commits](https://www.conventionalcommits.org/) for automatic versioning and CHANGELOG generation.

Format: `<type>(<scope>): <description>`

Examples:

- `feat: add new hub connection feature`
- `fix: resolve issue with server timeout`
- `docs: update API documentation`
- `chore: update dependencies`

Types:

- `feat`: New feature (MINOR version bump)
- `fix`: Bug fix (PATCH version bump)
- `docs`: Documentation only changes
- `style`: Changes that do not affect the meaning of the code
- `refactor`: Code change that neither fixes a bug nor adds a feature
- `perf`: Code change that improves performance
- `test`: Adding missing tests or correcting existing tests
- `chore`: Changes to the build process or auxiliary tools

Breaking Changes:
Add `BREAKING CHANGE:` in the commit footer to trigger a MAJOR version bump.

## Other Links

- [MCP Reviews](https://mcpreview.com/mcp-servers/warpdev/mcp-hub-mcp)

## License

MIT

```

--------------------------------------------------------------------------------
/CLAUDE.md:
--------------------------------------------------------------------------------

```markdown
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is an MCP (Model Context Protocol) Hub server that acts as a proxy/aggregator for multiple MCP servers. It bypasses tool limitations in AI assistants (especially Cursor's 40-tool limit) and provides a unified interface to multiple MCP servers.

## Development Commands

### Build and Run
```bash
# Install dependencies
pnpm install

# Build TypeScript to JavaScript
pnpm build

# Run the server (after building)
pnpm start

# Build and run in one command
pnpm dev
```

### Development Workflow
```bash
# For testing changes locally with npx
npm link  # Creates a global link to the local package
npx mcp-hub-mcp  # Test the linked version

# To unlink after testing
npm unlink -g mcp-hub-mcp
```

### Release Process
- Uses semantic-release with Conventional Commits
- Commits should follow: `type(scope): description`
- Types: feat, fix, docs, style, refactor, perf, test, chore
- Breaking changes: add `!` after type or `BREAKING CHANGE:` in body
- Releases are automated via GitHub Actions on the main branch

## Architecture

### Core Components

1. **src/index.ts** - Entry point that creates the MCP server with three tools:
   - `list-all-tools`: Lists all available tools from connected servers
   - `call-tool`: Executes a tool on a specific server
   - `find-tools`: Grep-like search for tools matching regex patterns

2. **src/server-manager.ts** - `McpServerManager` class that:
   - Loads server configurations from JSON file
   - Manages connections to multiple MCP servers
   - Proxies tool calls to appropriate servers
   - Handles server lifecycle and error recovery
   - Implements `findTools` method for regex-based tool search

3. **src/types.ts** - Type definitions and Zod schemas for:
   - Server configuration validation
   - MCP SDK type exports
   - Configuration file structure
   - Parameter schemas for all tools

### Configuration

The server configuration is loaded from (in order of precedence):
1. Environment variable: `MCP_CONFIG_PATH`
2. Command-line argument: `--config-path`
3. Default location: `./mcp-config.json` or `{cwd}/mcp-config.json`

Configuration format (Claude Desktop compatible):
```json
{
  "mcpServers": {
    "server-name": {
      "command": "command-to-run",
      "args": ["arg1", "arg2"],
      "env": { "KEY": "value" }
    }
  }
}
```

### Tool Naming Convention

Tools from connected servers are prefixed with the server name:
- Original tool: `list-files`
- Through hub: `server-name__list-files`

## Important Considerations

1. **Error Handling**: The hub gracefully handles server connection failures and continues operating with available servers.

2. **Environment Variables**: Server configurations can include environment variables that are passed to child processes.

3. **Logging**: Uses console.error for error messages since stdout is reserved for MCP protocol communication.

4. **No Tests**: Currently no test framework is set up. When implementing tests, consider:
   - Unit tests for server-manager.ts logic
   - Integration tests for MCP protocol handling
   - Mock MCP server responses for testing

5. **TypeScript Configuration**: 
   - Target: ES2020
   - Module: NodeNext with NodeNext resolution
   - Strict mode enabled
   - Outputs to `dist/` directory
   - Declaration files generated

6. **Dependencies**: 
   - Keep dependencies minimal (currently only @modelcontextprotocol/sdk and zod)
   - All types are exported from types.ts for consistency

7. **Binary Execution**: Configured as npm binary `mcp-hub-mcp` with shebang in index.ts
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist",
    "declaration": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
{
	"name": "mcp-hub-mcp",
	"version": "1.3.0",
	"description": "MCP Hub server that connects to and manages other MCP servers",
	"main": "dist/index.js",
	"type": "module",
	"files": [
		"dist",
		"README.md",
		"LICENSE"
	],
	"bin": {
		"mcp-hub-mcp": "dist/index.js"
	},
	"scripts": {
		"build": "tsc",
		"start": "node dist/index.js",
		"dev": "tsc && node dist/index.js",
		"test": "echo \"Error: no test specified\" && exit 1"
	},
	"keywords": [
		"mcp",
		"model-context-protocol"
	],
	"author": "",
	"license": "MIT",
	"dependencies": {
		"@modelcontextprotocol/sdk": "^1.13.0",
		"zod": "^3.25.67"
	},
	"devDependencies": {
		"@semantic-release/changelog": "^6.0.3",
		"@semantic-release/git": "^10.0.1",
		"@types/node": "^22.15.32",
		"semantic-release": "^24.2.5",
		"typescript": "^5.8.3"
	}
}

```

--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------

```yaml
name: Release
on:
  push:
    branches: [main]

permissions:
  contents: write
  issues: write
  pull-requests: write

jobs:
  release:
    name: Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "lts/*"
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.12.2
          run_install: false
      - name: Get pnpm store directory
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
      - name: Setup pnpm cache
        uses: actions/cache@v3
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-
      - name: Install dependencies
        run: pnpm install
      - name: Build project
        run: pnpm build
      - name: Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
        run: npx semantic-release

```

--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------

```markdown
# [1.3.0](https://github.com/warpdev/mcp-hub-mcp/compare/v1.2.0...v1.3.0) (2025-07-05)


### Features

* enhance MCP server functionality with new tools and transport options ([ae319dd](https://github.com/warpdev/mcp-hub-mcp/commit/ae319dd22311f3d2d6beaf07f53b7d5621bd7543))

# [1.2.0](https://github.com/warpdev/mcp-hub-mcp/compare/v1.1.1...v1.2.0) (2025-06-21)


### Features

* add find-tools command for grep-like tool search ([8ffc2e7](https://github.com/warpdev/mcp-hub-mcp/commit/8ffc2e7e0012d8a8df7c0e399341c27ec6771c5b))
* enhance tool descriptions to promote find-tools usage ([353169d](https://github.com/warpdev/mcp-hub-mcp/commit/353169d92c55a67973d2d0704d3c447d03b4fab2))

## [1.1.1](https://github.com/warpdev/mcp-hub-mcp/compare/v1.1.0...v1.1.1) (2025-06-02)


### Bug Fixes

* allow Claude Desktop to use this MCP ([6406cda](https://github.com/warpdev/mcp-hub-mcp/commit/6406cdaaeaf554cc1fb5c2194a8024280d603a9c))

# [1.1.0](https://github.com/warpdev/mcp-hub-mcp/compare/v1.0.3...v1.1.0) (2025-04-16)


### Features

* add description for better ai recognition ([0fd23a2](https://github.com/warpdev/mcp-hub-mcp/commit/0fd23a2d53337cf8fa36604c26bbccf7bcadcce1))

## [1.0.3](https://github.com/warpdev/mcp-hub-mcp/compare/v1.0.2...v1.0.3) (2025-04-11)


### Bug Fixes

* fix run with npx cli ([c44218c](https://github.com/warpdev/mcp-hub-mcp/commit/c44218c5e56f25c399a267075238404b806ee451))

## [1.0.2](https://github.com/warpdev/mcp-hub-mcp/compare/v1.0.1...v1.0.2) (2025-04-11)


### Bug Fixes

* fix missing bin ([7dd5a1f](https://github.com/warpdev/mcp-hub-mcp/commit/7dd5a1fc5e8e701c0135f4f31dddeec168a663bb))

## [1.0.1](https://github.com/warpdev/mcp-hub-mcp/compare/v1.0.0...v1.0.1) (2025-04-11)


### Bug Fixes

* fix package publish files ([627a30b](https://github.com/warpdev/mcp-hub-mcp/commit/627a30b74183e1dadc45aa5cec02ec3de374f165))

# 1.0.0 (2025-04-11)


### Bug Fixes

* update GitHub Actions workflows to use pnpm instead of npm ([fd9f19e](https://github.com/warpdev/mcp-hub-mcp/commit/fd9f19e70f73a0cdba43dfd9132da850a4a3a760))


### Features

* add semantic-release for automated versioning ([ee3ebfd](https://github.com/warpdev/mcp-hub-mcp/commit/ee3ebfd84f34bef7b53200c74c8bb9fd75d69e21))

```

--------------------------------------------------------------------------------
/.github/workflows/pr-feedback.yml:
--------------------------------------------------------------------------------

```yaml
name: PR Feedback
on:
  pull_request:
    types:
      - opened
      - synchronize
      - reopened
      - labeled
      - unlabeled

permissions:
  contents: read
  pull-requests: write

jobs:
  preview:
    name: Preview Release
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v3
        with:
          fetch-depth: 0
      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "lts/*"
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
        with:
          version: 9.12.2
          run_install: false
      - name: Get pnpm store directory
        shell: bash
        run: |
          echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
      - name: Setup pnpm cache
        uses: actions/cache@v3
        with:
          path: ${{ env.STORE_PATH }}
          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
          restore-keys: |
            ${{ runner.os }}-pnpm-store-
      - name: Install dependencies
        run: pnpm install
      - name: Preview Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: npx semantic-release --dry-run
      - name: Find Comment
        uses: peter-evans/find-comment@v2
        id: fc
        with:
          issue-number: ${{ github.event.pull_request.number }}
          comment-author: "github-actions[bot]"
          body-includes: "### Semantic Release Preview"
      - name: Generate Release Notes
        id: release_notes
        run: |
          echo "RELEASE_NOTES<<EOF" >> $GITHUB_ENV
          npx semantic-release --dry-run | grep -A 100 "Release note for" | sed 's/`//g' >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV
      - name: Create or Update Comment
        uses: peter-evans/create-or-update-comment@v2
        with:
          comment-id: ${{ steps.fc.outputs.comment-id }}
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            ### Semantic Release Preview

            When this PR is merged to main, the following release will be created:

            ${{ env.RELEASE_NOTES }}

            The version is determined by [Conventional Commits](https://www.conventionalcommits.org/):
            - `fix:` = PATCH release (1.0.0 → 1.0.1)
            - `feat:` = MINOR release (1.0.0 → 1.1.0)
            - `BREAKING CHANGE:` = MAJOR release (1.0.0 → 2.0.0)
          edit-mode: replace

```

--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------

```typescript
import { z } from "zod";

export const ConnectMcpParamsSchema = z.object({
  // For stdio servers
  command: z
    .string()
    .describe("Command to run the MCP server")
    .optional(),
  args: z
    .array(z.string())
    .optional()
    .describe("Command arguments"),
  
  // For HTTP servers
  type: z
    .enum(["stdio", "http"])
    .optional()
    .describe("Server transport type"),
  url: z
    .string()
    .describe("URL for HTTP-based MCP server")
    .optional(),
  headers: z
    .record(z.string())
    .optional()
    .describe("HTTP headers for authentication"),
  
  // Environment variables (applies to both)
  env: z
    .record(z.string())
    .optional()
    .describe("Environment variables"),
});

export type ConnectMcpParams = z.infer<
  typeof ConnectMcpParamsSchema
>;

export const ListToolsParamsSchema = z.object({
  serverName: z
    .string()
    .describe("Name of the MCP server to list tools from"),
});

export type ListToolsParams = z.infer<
  typeof ListToolsParamsSchema
>;

export const CallToolParamsSchema = z.object({
  serverName: z
    .string()
    .describe("Name of the MCP server to call tool from"),
  toolName: z.string().describe("Name of the tool to call"),
  toolArgs: z
    .record(z.unknown())
    .describe("Arguments to pass to the tool"),
});

export type CallToolParams = z.infer<
  typeof CallToolParamsSchema
>;

export const FindToolsParamsSchema = z.object({
  pattern: z
    .string()
    .describe("Regex pattern to search for in tool names and descriptions"),
  searchIn: z
    .enum(["name", "description", "both"])
    .optional()
    .default("both")
    .describe("Where to search: in tool names, descriptions, or both"),
  caseSensitive: z
    .boolean()
    .optional()
    .default(false)
    .describe("Whether the search should be case-sensitive"),
});

export type FindToolsParams = z.infer<
  typeof FindToolsParamsSchema
>;

export const GetToolParamsSchema = z.object({
  serverName: z
    .string()
    .describe("Name of the MCP server containing the tool"),
  toolName: z
    .string()
    .describe("Exact name of the tool to retrieve"),
});

export type GetToolParams = z.infer<
  typeof GetToolParamsSchema
>;

export const ListToolsInServerParamsSchema = z.object({
  serverName: z
    .string()
    .describe("Name of the MCP server to list tools from"),
});

export type ListToolsInServerParams = z.infer<
  typeof ListToolsInServerParamsSchema
>;

export const FindToolsInServerParamsSchema = z.object({
  serverName: z
    .string()
    .describe("Name of the MCP server to search tools in"),
  pattern: z
    .string()
    .describe("Regex pattern to search for in tool names and descriptions"),
  searchIn: z
    .enum(["name", "description", "both"])
    .default("both")
    .describe("Where to search: in tool names, descriptions, or both"),
  caseSensitive: z
    .boolean()
    .default(false)
    .describe("Whether the search should be case-sensitive"),
});

export type FindToolsInServerParams = z.infer<
  typeof FindToolsInServerParamsSchema
>;

// MCP configuration file interface (claude_desktop_config.json format)
export interface McpServerConfig {
  // For stdio servers
  command?: string;
  args?: string[];
  env?: Record<string, string>;
  
  // For HTTP servers  
  type?: "stdio" | "http";
  url?: string;
  headers?: Record<string, string>;
}

export interface McpConfig {
  mcpServers: Record<string, McpServerConfig>;
}

```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
#!/usr/bin/env node

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { McpServerManager } from "./server-manager.js";
import { 
  CallToolParamsSchema, 
  FindToolsParamsSchema, 
  GetToolParamsSchema,
  ListToolsInServerParamsSchema,
  FindToolsInServerParamsSchema
} from "./types.js";

// Create MCP server manager instance (auto load enabled)
const serverManager = new McpServerManager({
  autoLoad: true,
});

// Create MCP server
const server = new McpServer({
  name: "MCP-Hub-Server",
  version: "1.0.0",
  description:
    "Your central hub for ALL available tools. Use this server to discover and execute any tool you need. All system tools are accessible through here - search, find, and call them via this server.",
});

// Tool to return tools list from all servers
server.tool(
  "list-all-tools",
  "List ALL available tools from all connected servers. NOTE: For better performance, use find-tools with keywords first. Only use this when you need to see everything or if find-tools didn't find what you need",
  {}, // Use empty object when there are no parameters
  async (args, extra) => {
    try {
      const servers = serverManager.getConnectedServers();

      if (servers.length === 0) {
        return {
          content: [
            {
              type: "text",
              text: "No connected servers.",
            },
          ],
        };
      }

      const allTools: Record<string, any> = {};

      // Get tools list from each server
      for (const serverName of servers) {
        try {
          const toolsResponse = await serverManager.listTools(serverName);
          
          // Filter to only include name and description
          if (toolsResponse.tools && Array.isArray(toolsResponse.tools)) {
            allTools[serverName] = {
              tools: toolsResponse.tools.map((tool: any) => ({
                name: tool.name,
                description: tool.description,
              }))
            };
          } else {
            allTools[serverName] = toolsResponse;
          }
        } catch (error) {
          allTools[serverName] = {
            error: `Failed to get tools list: ${(error as Error).message}`,
          };
        }
      }

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(allTools, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Failed to get tools list from all servers: ${
              (error as Error).message
            }`,
          },
        ],
        isError: true,
      };
    }
  },
);

// Tool to call a specific tool from a specific server
server.tool(
  "call-tool",
  "Call a specific tool from a specific server. TIP: Use find-tools first to discover the tool and get the correct serverName and toolName",
  {
    serverName: CallToolParamsSchema.shape.serverName,
    toolName: CallToolParamsSchema.shape.toolName,
    toolArgs: CallToolParamsSchema.shape.toolArgs,
  },
  async (args, extra) => {
    try {
      const { serverName, toolName, toolArgs } = args;
      const result = await serverManager.callTool(
        serverName,
        toolName,
        toolArgs,
      );

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(result, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Tool call failed: ${(error as Error).message}`,
          },
        ],
        isError: true,
      };
    }
  },
);

// Tool to find tools matching a pattern across all servers
server.tool(
  "find-tools",
  `Use this tool to find best tools by searching with keywords or regex patterns.
  If you don't have a specific tool for a task, this is the best way to discover what tools are available.
  `,
  {
    pattern: FindToolsParamsSchema.shape.pattern,
    searchIn: FindToolsParamsSchema.shape.searchIn,
    caseSensitive: FindToolsParamsSchema.shape.caseSensitive,
  },
  async (args, extra) => {
    try {
      const { pattern, searchIn, caseSensitive } = args;
      const results = await serverManager.findTools(pattern, {
        searchIn,
        caseSensitive,
      });

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(results, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Failed to find tools: ${(error as Error).message}`,
          },
        ],
        isError: true,
      };
    }
  },
);

// Tool to get detailed information about a specific tool
server.tool(
  "get-tool",
  "Get complete schema for a specific tool from a specific server, including inputSchema. TIP: Use find-tools first to discover the tool and get the correct serverName and toolName",
  {
    serverName: GetToolParamsSchema.shape.serverName,
    toolName: GetToolParamsSchema.shape.toolName,
  },
  async (args, extra) => {
    try {
      const { serverName, toolName } = args;
      const tool = await serverManager.getTool(serverName, toolName);

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify(tool, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error getting tool: ${(error as Error).message}`,
          },
        ],
      };
    }
  }
);

// Tool to list all tools from a specific server
server.tool(
  "list-all-tools-in-server", 
  "List ALL tools from a specific MCP server (returns name and description only)",
  {
    serverName: ListToolsInServerParamsSchema.shape.serverName,
  },
  async (args, extra) => {
    try {
      const { serverName } = args;
      const result = await serverManager.listToolsInServer(serverName);

      return {
        content: [
          {
            type: "text", 
            text: JSON.stringify(result, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error listing tools from server '${args.serverName}': ${(error as Error).message}`,
          },
        ],
      };
    }
  }
);

// Tool to find tools in a specific server
server.tool(
  "find-tools-in-server",
  "Find tools matching a pattern in a specific MCP server (returns name and description only)",
  {
    serverName: FindToolsInServerParamsSchema.shape.serverName,
    pattern: FindToolsInServerParamsSchema.shape.pattern,
    searchIn: FindToolsInServerParamsSchema.shape.searchIn,
    caseSensitive: FindToolsInServerParamsSchema.shape.caseSensitive,
  },
  async (args, extra) => {
    try {
      const { serverName, pattern, searchIn, caseSensitive } = args;
      const results = await serverManager.findToolsInServer(
        serverName,
        pattern,
        searchIn,
        caseSensitive
      );

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify({ tools: results }, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error finding tools in server '${args.serverName}': ${(error as Error).message}`,
          },
        ],
      };
    }
  }
);

// Tool to list all connected servers
server.tool(
  "list-servers",
  "List all connected MCP servers",
  {}, // No parameters needed
  async (args, extra) => {
    try {
      const servers = serverManager.listServers();

      return {
        content: [
          {
            type: "text",
            text: JSON.stringify({ servers }, null, 2),
          },
        ],
      };
    } catch (error) {
      return {
        content: [
          {
            type: "text",
            text: `Error listing servers: ${(error as Error).message}`,
          },
        ],
      };
    }
  }
);

// Start server
async function startServer() {
  try {
    // Communication through standard input/output
    const transport = new StdioServerTransport();
    await server.connect(transport);

    // Disconnect all connections on process termination
    process.on("SIGINT", async () => {
      console.log("Shutting down server...");
      await serverManager.disconnectAll();
      process.exit(0);
    });
  } catch (error) {
    console.error("Failed to start server:", error);
    process.exit(1);
  }
}

startServer();

```

--------------------------------------------------------------------------------
/src/server-manager.ts:
--------------------------------------------------------------------------------

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import {
  ConnectMcpParams,
  McpConfig,
  McpServerConfig,
} from "./types.js";
import fs from "fs";
import path from "path";

/**
 * Find configuration file path
 * Check in order: environment variable > command line argument > default path
 */
function findConfigPath(): string | undefined {
  // Check environment variable
  if (process.env.MCP_CONFIG_PATH) {
    return process.env.MCP_CONFIG_PATH;
  }

  // Check command line arguments
  const configArgIndex = process.argv.findIndex(
    (arg) => arg === "--config-path"
  );
  if (
    configArgIndex !== -1 &&
    configArgIndex < process.argv.length - 1
  ) {
    return process.argv[configArgIndex + 1];
  }

  // Check default paths
  const defaultPaths = [
    "./mcp-config.json",
    path.join(process.cwd(), "mcp-config.json"),
  ];

  for (const defaultPath of defaultPaths) {
    if (fs.existsSync(defaultPath)) {
      return defaultPath;
    }
  }

  return undefined;
}

/**
 * Load configuration file
 */
function loadConfigFile(configPath: string): McpConfig {
  try {
    const configContent = fs.readFileSync(
      configPath,
      "utf-8"
    );
    return JSON.parse(configContent) as McpConfig;
  } catch (error) {
    console.error(
      `Failed to load configuration file: ${
        (error as Error).message
      }`
    );
    throw new Error(
      `Failed to load configuration file '${configPath}': ${
        (error as Error).message
      }`
    );
  }
}

export class McpServerManager {
  private clients: Map<string, Client> = new Map();
  private configPath?: string;

  /**
   * MCP Server Manager constructor
   */
  constructor(options?: {
    configPath?: string;
    autoLoad?: boolean;
  }) {
    this.configPath =
      options?.configPath || findConfigPath();

    if (options?.autoLoad && this.configPath) {
      try {
        this.loadFromConfig(this.configPath);
      } catch (error) {
        console.error(
          `Failed to load servers from configuration file: ${
            (error as Error).message
          }`
        );
      }
    }
  }

  /**
   * Load server configuration from configuration file
   */
  async loadFromConfig(configPath?: string): Promise<void> {
    const path = configPath || this.configPath;
    if (!path) {
      throw new Error(
        "Configuration file path not specified."
      );
    }

    const config = loadConfigFile(path);

    if (
      !config.mcpServers ||
      Object.keys(config.mcpServers).length === 0
    ) {
      console.warn(
        "No server information in configuration file."
      );
      return;
    }

    // Connect to all servers
    const serverEntries = Object.entries(config.mcpServers);
    for (const [
      serverName,
      serverConfig,
    ] of serverEntries) {
      if (this.clients.has(serverName)) {
        continue;
      }

      try {
        await this.connectToServer(
          serverName,
          serverConfig
        );
      } catch (error) {
        console.error(
          `Failed to connect to server '${serverName}' from configuration file: ${
            (error as Error).message
          }`
        );
      }
    }
  }

  /**
   * Connect to MCP server.
   */
  async connectToServer(
    serverName: string,
    params: ConnectMcpParams | McpServerConfig
  ): Promise<void> {
    if (this.clients.has(serverName)) {
      throw new Error(
        `Already connected to server '${serverName}'.`
      );
    }

    // Determine transport type
    const transportType = params.type || (params.command ? "stdio" : "http");
    
    let transport: StdioClientTransport | StreamableHTTPClientTransport;

    if (transportType === "http") {
      // HTTP transport
      if (!params.url) {
        throw new Error(
          `HTTP server '${serverName}' requires a URL.`
        );
      }
      
      const url = new URL(params.url);
      
      // Create transport with headers in requestInit
      const transportOptions: any = {};
      if (params.headers) {
        transportOptions.requestInit = {
          headers: params.headers
        };
      }
      
      transport = new StreamableHTTPClientTransport(url, transportOptions);
    } else {
      // Stdio transport
      if (!params.command) {
        throw new Error(
          `Stdio server '${serverName}' requires a command.`
        );
      }

      // Set environment variables
      const env: Record<string, string | undefined> = {
        ...process.env,
      };
      if ("env" in params && params.env) {
        Object.assign(env, params.env);
      }

      transport = new StdioClientTransport({
        command: params.command,
        args: params.args || [],
        env: env as Record<string, string>,
      });
    }

    const client = new Client({
      name: `mcp-client-${serverName}`,
      version: "1.0.0",
    });

    try {
      await client.connect(transport);
      this.clients.set(serverName, client);
    } catch (error) {
      console.error(
        `Failed to connect to server '${serverName}':`,
        error
      );
      throw new Error(
        `Failed to connect to server '${serverName}': ${
          (error as Error).message
        }`
      );
    }
  }

  /**
   * Return the list of tools from connected server.
   */
  async listTools(serverName: string): Promise<any> {
    const client = this.getClient(serverName);
    return await client.listTools();
  }

  /**
   * Get a specific tool with complete schema from a connected server.
   */
  async getTool(serverName: string, toolName: string): Promise<any> {
    const client = this.getClient(serverName);
    const toolsResponse = await client.listTools();
    
    if (!toolsResponse.tools || !Array.isArray(toolsResponse.tools)) {
      throw new Error(`No tools found on server '${serverName}'`);
    }

    const tool = toolsResponse.tools.find((t: any) => t.name === toolName);
    
    if (!tool) {
      throw new Error(`Tool '${toolName}' not found on server '${serverName}'`);
    }

    return tool;
  }

  /**
   * List tools from a specific server (name and description only).
   */
  async listToolsInServer(serverName: string): Promise<any> {
    const client = this.getClient(serverName);
    const toolsResponse = await client.listTools();
    
    if (!toolsResponse.tools || !Array.isArray(toolsResponse.tools)) {
      return { tools: [] };
    }

    // Filter to only include name and description
    return {
      tools: toolsResponse.tools.map((tool: any) => ({
        name: tool.name,
        description: tool.description,
      }))
    };
  }

  /**
   * Find tools matching a pattern in a specific server (name and description only).
   */
  async findToolsInServer(
    serverName: string,
    pattern: string,
    searchIn: "name" | "description" | "both" = "both",
    caseSensitive: boolean = false
  ): Promise<any[]> {
    const client = this.getClient(serverName);
    const toolsResponse = await client.listTools();

    if (!toolsResponse.tools || !Array.isArray(toolsResponse.tools)) {
      return [];
    }

    const flags = caseSensitive ? "g" : "gi";
    const regex = new RegExp(pattern, flags);

    const matchedTools = toolsResponse.tools.filter((tool: any) => {
      const nameMatch = searchIn !== "description" && tool.name && regex.test(tool.name);
      const descriptionMatch = searchIn !== "name" && tool.description && regex.test(tool.description);
      return nameMatch || descriptionMatch;
    });

    // Filter to only include name and description
    return matchedTools.map((tool: any) => ({
      name: tool.name,
      description: tool.description,
    }));
  }

  /**
   * List all connected server names.
   */
  listServers(): string[] {
    return this.getConnectedServers();
  }

  /**
   * Call a tool on server.
   */
  async callTool(
    serverName: string,
    toolName: string,
    args: Record<string, unknown>
  ): Promise<any> {
    const client = this.getClient(serverName);
    return await client.callTool({
      name: toolName,
      arguments: args,
    });
  }

  /**
   * Return all connected server names.
   */
  getConnectedServers(): string[] {
    return Array.from(this.clients.keys());
  }

  /**
   * Find tools matching a pattern across all connected servers.
   */
  async findTools(
    pattern: string,
    options: {
      searchIn?: "name" | "description" | "both";
      caseSensitive?: boolean;
    } = {}
  ): Promise<Record<string, any[]>> {
    const { searchIn = "both", caseSensitive = false } = options;
    const servers = this.getConnectedServers();
    
    if (servers.length === 0) {
      return {};
    }

    // Create regex pattern
    let regex: RegExp;
    try {
      regex = new RegExp(pattern, caseSensitive ? "" : "i");
    } catch (error) {
      throw new Error(`Invalid regex pattern: ${(error as Error).message}`);
    }

    const results: Record<string, any[]> = {};

    // Search tools in each server
    for (const serverName of servers) {
      try {
        const toolsResponse = await this.listTools(serverName);
        
        if (toolsResponse.tools && Array.isArray(toolsResponse.tools)) {
          const matchedTools = toolsResponse.tools.filter((tool: any) => {
            const nameMatch = searchIn !== "description" && tool.name && regex.test(tool.name);
            const descriptionMatch = searchIn !== "name" && tool.description && regex.test(tool.description);
            return nameMatch || descriptionMatch;
          }).map((tool: any) => ({
            name: tool.name,
            description: tool.description,
          }));

          if (matchedTools.length > 0) {
            results[serverName] = matchedTools;
          }
        }
      } catch (error) {
        // Include error information in results
        results[serverName] = [{
          error: `Failed to search tools: ${(error as Error).message}`
        }];
      }
    }

    return results;
  }

  /**
   * Disconnect from server.
   */
  async disconnectServer(
    serverName: string
  ): Promise<void> {
    const client = this.clients.get(serverName);
    if (!client) {
      throw new Error(
        `Not connected to server '${serverName}'.`
      );
    }
    try {
      await client.close();
      this.clients.delete(serverName);
    } catch (error) {
      console.error(
        `Failed to disconnect from server '${serverName}':`,
        error
      );
      throw new Error(
        `Failed to disconnect from server '${serverName}': ${
          (error as Error).message
        }`
      );
    }
  }

  /**
   * Disconnect from all servers.
   */
  async disconnectAll(): Promise<void> {
    const serverNames = this.getConnectedServers();
    for (const serverName of serverNames) {
      await this.disconnectServer(serverName);
    }
  }

  private getClient(serverName: string): Client {
    const client = this.clients.get(serverName);
    if (!client) {
      throw new Error(
        `Not connected to server '${serverName}'.`
      );
    }
    return client;
  }
}

```