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

```
├── .gitattributes
├── .gitignore
├── docker-compose.yml
├── Dockerfile
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── smithery.yaml
├── src
│   ├── client.ts
│   ├── fetch-capabilities.ts
│   ├── fetch-metamcp.ts
│   ├── fetch-tools.ts
│   ├── index.ts
│   ├── mcp-proxy.ts
│   ├── report-tools.ts
│   ├── sessions.ts
│   ├── sse.ts
│   ├── streamable-http.ts
│   ├── tool-logs.ts
│   └── utils.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------

```
# Auto detect text files and perform LF normalization
* text=auto

```

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

```
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

dist/**/*

```

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

```markdown
# MetaMCP MCP Server

> **🚨 DEPRECATED PACKAGE WARNING 🚨**
> 
> This local proxy package is deprecated in MetaMCP's 2.0 all-in-one architecture. 
> 
> **Please checkout https://github.com/metatool-ai/metamcp for more details.**

MetaMCP MCP Server is a proxy server that joins multiple MCP⁠ servers into one. It fetches tool/prompt/resource configurations from MetaMCP App⁠ and routes tool/prompt/resource requests to the correct underlying server.

[![smithery badge](https://smithery.ai/badge/@metatool-ai/mcp-server-metamcp)](https://smithery.ai/server/@metatool-ai/mcp-server-metamcp)

<a href="https://glama.ai/mcp/servers/0po36lc7i6">
  <img width="380" height="200" src="https://glama.ai/mcp/servers/0po36lc7i6/badge" alt="MetaServer MCP server" />
</a>

MetaMCP App repo: https://github.com/metatool-ai/metatool-app

## Installation

### Installing via Smithery

Sometimes Smithery works (confirmed in Windsurf locally) but sometimes it is unstable because MetaMCP is special that it runs other MCPs on top of it. Please consider using manual installation if it doesn't work instead.

To install MetaMCP MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@metatool-ai/mcp-server-metamcp):

```bash
npx -y @smithery/cli install @metatool-ai/mcp-server-metamcp --client claude
```

### Manual Installation

```bash
export METAMCP_API_KEY=<env>
npx -y @metamcp/mcp-server-metamcp@latest
```

```json
{
  "mcpServers": {
    "MetaMCP": {
      "command": "npx",
      "args": ["-y", "@metamcp/mcp-server-metamcp@latest"],
      "env": {
        "METAMCP_API_KEY": "<your api key>"
      }
    }
  }
}
```

## Usage

### Using as a stdio server (default)

```bash
mcp-server-metamcp --metamcp-api-key <your-api-key>
```

### Using as an SSE server

```bash
mcp-server-metamcp --metamcp-api-key <your-api-key> --transport sse --port 12006
```

With the SSE transport option, the server will start an Express.js web server that listens for SSE connections on the `/sse` endpoint and accepts messages on the `/messages` endpoint.

### Using as a Streamable HTTP server

```bash
mcp-server-metamcp --metamcp-api-key <your-api-key> --transport streamable-http --port 12006
```

With the Streamable HTTP transport option, the server will start an Express.js web server that handles HTTP requests. You can optionally use `--stateless` mode for stateless operation.

### Using with Docker

When running the server inside a Docker container and connecting to services on the host machine, use the `--use-docker-host` option to automatically transform localhost URLs:

```bash
mcp-server-metamcp --metamcp-api-key <your-api-key> --transport sse --port 12006 --use-docker-host
```

This will transform any localhost or 127.0.0.1 URLs to `host.docker.internal`, allowing the container to properly connect to services running on the host.

### Configuring stderr handling

For STDIO transport, you can control how stderr is handled from child MCP processes:

```bash
# Use inherit to see stderr output from child processes
mcp-server-metamcp --metamcp-api-key <your-api-key> --stderr inherit

# Use pipe to capture stderr (default is ignore)
mcp-server-metamcp --metamcp-api-key <your-api-key> --stderr pipe

# Or set via environment variable
METAMCP_STDERR=inherit mcp-server-metamcp --metamcp-api-key <your-api-key>
```

Available stderr options:
- `ignore` (default): Ignore stderr output from child processes
- `inherit`: Pass through stderr from child processes to the parent
- `pipe`: Capture stderr in a pipe for processing
- `overlapped`: Use overlapped I/O (Windows-specific)

### Command Line Options

```
Options:
  --metamcp-api-key <key>       API key for MetaMCP (can also be set via METAMCP_API_KEY env var)
  --metamcp-api-base-url <url>  Base URL for MetaMCP API (can also be set via METAMCP_API_BASE_URL env var)
  --report                      Fetch all MCPs, initialize clients, and report tools to MetaMCP API
  --transport <type>            Transport type to use (stdio, sse, or streamable-http) (default: "stdio")
  --port <port>                 Port to use for SSE or Streamable HTTP transport, defaults to 12006 (default: "12006")
  --require-api-auth            Require API key in SSE or Streamable HTTP URL path
  --stateless                   Use stateless mode for Streamable HTTP transport
  --use-docker-host             Transform localhost URLs to use host.docker.internal (can also be set via USE_DOCKER_HOST env var)
  --stderr <type>               Stderr handling for STDIO transport (overlapped, pipe, ignore, inherit) (default: "ignore")
  -h, --help                    display help for command
```

## Environment Variables

- `METAMCP_API_KEY`: API key for MetaMCP
- `METAMCP_API_BASE_URL`: Base URL for MetaMCP API
- `USE_DOCKER_HOST`: When set to "true", transforms localhost URLs to host.docker.internal for Docker compatibility
- `METAMCP_STDERR`: Stderr handling for STDIO transport (overlapped, pipe, ignore, inherit). Defaults to "ignore"

## Development

```bash
# Install dependencies
npm install

# Build the application
npm run build

# Watch for changes
npm run watch
```

## Highlights

- Compatible with ANY MCP Client
- Multi-Workspaces layer enables you to switch to another set of MCP configs within one-click.
- GUI dynamic updates of MCP configs.
- Namespace isolation for joined MCPs.

## Architecture Overview

```mermaid
sequenceDiagram
    participant MCPClient as MCP Client (e.g. Claude Desktop)
    participant MetaMCP-mcp-server as MetaMCP MCP Server
    participant MetaMCPApp as MetaMCP App
    participant MCPServers as Installed MCP Servers in Metatool App

    MCPClient ->> MetaMCP-mcp-server: Request list tools
    MetaMCP-mcp-server ->> MetaMCPApp: Get tools configuration & status
    MetaMCPApp ->> MetaMCP-mcp-server: Return tools configuration & status

    loop For each listed MCP Server
        MetaMCP-mcp-server ->> MCPServers: Request list_tools
        MCPServers ->> MetaMCP-mcp-server: Return list of tools
    end

    MetaMCP-mcp-server ->> MetaMCP-mcp-server: Aggregate tool lists
    MetaMCP-mcp-server ->> MCPClient: Return aggregated list of tools

    MCPClient ->> MetaMCP-mcp-server: Call tool
    MetaMCP-mcp-server ->> MCPServers: call_tool to target MCP Server
    MCPServers ->> MetaMCP-mcp-server: Return tool response
    MetaMCP-mcp-server ->> MCPClient: Return tool response
```

## Credits

- Inspirations and some code (refactored in this project) from https://github.com/adamwattis/mcp-proxy-server/
```

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

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------

```yaml
services:
  mcp-server:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    env_file:
      - .env.production.local
    entrypoint: ["/bin/bash"]
    command: ["-c", "uvx --version && echo 'uvx is working!' && tail -f /dev/null"]
    healthcheck:
      test: ["CMD", "ps", "aux", "|", "grep", "tail"]
      interval: 30s
      timeout: 10s
      retries: 3
    environment:
      - NODE_ENV=production
    restart: unless-stopped
    # Add any additional environment variables or command arguments here
    # command: --metamcp-api-key your-api-key --metamcp-api-base-url your-base-url

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
# Use the official uv Debian image as base
FROM ghcr.io/astral-sh/uv:debian

# Install Node.js and npm
RUN apt-get update && apt-get install -y \
    curl \
    gnupg \
    && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
    && apt-get install -y nodejs \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Verify Node.js and npm installation
RUN node --version && npm --version

# Verify uv is installed correctly
RUN uv --version

# Verify npx is available
RUN npx --version || npm install -g npx

# Set the working directory
WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci

# Copy the rest of the application
COPY . .

# Build the application
RUN npm run build

# Set environment variables
ENV NODE_ENV=production

# Expose the application port
EXPOSE 3000

# Run the application
ENTRYPOINT ["node", "dist/index.js"] 
```

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml

startCommand:
  type: stdio
  configSchema:
    # JSON Schema defining the configuration options for the MCP.
    type: object
    required:
      - metamcpApiKey
    properties:
      metamcpApiKey:
        type: string
        description: The API key from metamcp.com/api-keys. Required.
      metamcpApiBaseUrl:
        type: string
        description: Optional override for the MetaMCP App URL (default is https://api.metamcp.com).
  commandFunction:
    # A function that produces the CLI command to start the MCP on stdio.
    # Note: Command line arguments can also be used directly:
    # --metamcp-api-key <your-api-key> --metamcp-api-base-url <base-url>
    |-
    (config) => ({ command: 'node', args: ['dist/index.js'], env: { METAMCP_API_KEY: config.metamcpApiKey, ...(config.metamcpApiBaseUrl && { METAMCP_API_BASE_URL: config.metamcpApiBaseUrl }) } })

```

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

```json
{
  "name": "@metamcp/mcp-server-metamcp",
  "version": "0.6.5",
  "description": "MCP Server MetaMCP manages all your other MCPs in one MCP.",
  "scripts": {
    "build": "tsc && shx chmod +x dist/*.js",
    "watch": "tsc --watch",
    "inspector": "dotenv -e .env.local npx @modelcontextprotocol/inspector dist/index.js -e METAMCP_API_KEY=${METAMCP_API_KEY} -e METAMCP_API_BASE_URL=${METAMCP_API_BASE_URL}",
    "inspector:prod": "dotenv -e .env.production.local npx @modelcontextprotocol/inspector dist/index.js -e METAMCP_API_KEY=${METAMCP_API_KEY}",
    "report": "dotenv -e .env.local -- node dist/index.js --report"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/metatool-ai/mcp-server-metamcp.git"
  },
  "author": "James Zhang",
  "license": "Apache-2.0",
  "bugs": {
    "url": "https://github.com/metatool-ai/mcp-server-metamcp/issues"
  },
  "homepage": "https://github.com/metatool-ai/mcp-server-metamcp#readme",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.11.4",
    "axios": "^1.7.9",
    "commander": "^13.1.0",
    "express": "^4.21.2",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/express": "^5.0.1",
    "@types/node": "^22.13.4",
    "dotenv-cli": "^8.0.0",
    "shx": "^0.3.4",
    "typescript": "^5.8.2"
  },
  "type": "module",
  "bin": {
    "mcp-server-metamcp": "dist/index.js"
  },
  "files": [
    "dist"
  ]
}

```

--------------------------------------------------------------------------------
/src/sessions.ts:
--------------------------------------------------------------------------------

```typescript
import { getMcpServers, ServerParameters } from "./fetch-metamcp.js";
import {
  ConnectedClient,
  createMetaMcpClient,
  connectMetaMcpClient,
} from "./client.js";
import { getSessionKey } from "./utils.js";

const _sessions: Record<string, ConnectedClient> = {};

export const getSession = async (
  sessionKey: string,
  uuid: string,
  params: ServerParameters
): Promise<ConnectedClient | undefined> => {
  if (sessionKey in _sessions) {
    return _sessions[sessionKey];
  } else {
    // Close existing session for this UUID if it exists with a different hash
    const old_session_keys = Object.keys(_sessions).filter((k) =>
      k.startsWith(`${uuid}_`)
    );

    await Promise.allSettled(
      old_session_keys.map(async (old_session_key) => {
        await _sessions[old_session_key].cleanup();
        delete _sessions[old_session_key];
      })
    );

    const { client, transport } = createMetaMcpClient(params);
    if (!client || !transport) {
      return;
    }

    const newClient = await connectMetaMcpClient(client, transport);
    if (!newClient) {
      return;
    }

    _sessions[sessionKey] = newClient;

    return newClient;
  }
};

export const initSessions = async (): Promise<void> => {
  const serverParams = await getMcpServers(true);

  await Promise.allSettled(
    Object.entries(serverParams).map(async ([uuid, params]) => {
      const sessionKey = getSessionKey(uuid, params);
      try {
        await getSession(sessionKey, uuid, params);
      } catch (error) {}
    })
  );
};

export const cleanupAllSessions = async (): Promise<void> => {
  await Promise.allSettled(
    Object.entries(_sessions).map(async ([sessionKey, session]) => {
      await session.cleanup();
      delete _sessions[sessionKey];
    })
  );
};

```

--------------------------------------------------------------------------------
/src/fetch-tools.ts:
--------------------------------------------------------------------------------

```typescript
import axios from "axios";
import { getMetaMcpApiBaseUrl, getMetaMcpApiKey } from "./utils.js";

enum ToolStatus {
  ACTIVE = "ACTIVE",
  INACTIVE = "INACTIVE",
}

// Define interface for tool parameters with only required fields
export interface ToolParameters {
  mcp_server_uuid: string;
  name: string;
  status: ToolStatus;
}

let _toolsCache: Record<string, ToolParameters> | null = null;
let _toolsCacheTimestamp: number = 0;
const CACHE_TTL_MS = 1000; // 1 second cache TTL

export async function getInactiveTools(
  forceRefresh: boolean = false
): Promise<Record<string, ToolParameters>> {
  const currentTime = Date.now();
  const cacheAge = currentTime - _toolsCacheTimestamp;

  // Use cache if it exists, is not null, and either:
  // 1. forceRefresh is false, or
  // 2. forceRefresh is true but cache is less than 1 second old
  if (_toolsCache !== null && (!forceRefresh || cacheAge < CACHE_TTL_MS)) {
    return _toolsCache;
  }

  try {
    const apiKey = getMetaMcpApiKey();
    const apiBaseUrl = getMetaMcpApiBaseUrl();

    if (!apiKey) {
      console.error(
        "METAMCP_API_KEY is not set. Please set it via environment variable or command line argument."
      );
      return _toolsCache || {};
    }

    const headers = { Authorization: `Bearer ${apiKey}` };
    const response = await axios.get(
      `${apiBaseUrl}/api/tools?status=${ToolStatus.INACTIVE}`,
      {
        headers,
      }
    );
    const data = response.data;

    const toolDict: Record<string, ToolParameters> = {};
    // Access the 'results' array in the response
    if (data && data.results) {
      for (const tool of data.results) {
        const params: ToolParameters = {
          mcp_server_uuid: tool.mcp_server_uuid,
          name: tool.name,
          status: tool.status,
        };

        const uniqueId = `${tool.mcp_server_uuid}:${tool.name}`;
        toolDict[uniqueId] = params;
      }
    }

    _toolsCache = toolDict;
    _toolsCacheTimestamp = currentTime;
    return toolDict;
  } catch (error) {
    // Return empty object if API doesn't exist or has errors
    if (_toolsCache !== null) {
      return _toolsCache;
    }
    return {};
  }
}

```

--------------------------------------------------------------------------------
/src/fetch-capabilities.ts:
--------------------------------------------------------------------------------

```typescript
import axios from "axios";
import { getMetaMcpApiBaseUrl, getMetaMcpApiKey } from "./utils.js";

export enum ProfileCapability {
  TOOLS_MANAGEMENT = "TOOLS_MANAGEMENT",
  TOOL_LOGS = "TOOL_LOGS",
}

let _capabilitiesCache: ProfileCapability[] | null = null;
let _capabilitiesCacheTimestamp: number = 0;
const CACHE_TTL_MS = 1000; // 1 second cache TTL

export async function getProfileCapabilities(
  forceRefresh: boolean = false
): Promise<ProfileCapability[]> {
  const currentTime = Date.now();
  const cacheAge = currentTime - _capabilitiesCacheTimestamp;

  // Use cache if it exists, is not null, and either:
  // 1. forceRefresh is false, or
  // 2. forceRefresh is true but cache is less than 1 second old
  if (
    _capabilitiesCache !== null &&
    (!forceRefresh || cacheAge < CACHE_TTL_MS)
  ) {
    return _capabilitiesCache;
  }

  try {
    const apiKey = getMetaMcpApiKey();
    const apiBaseUrl = getMetaMcpApiBaseUrl();

    if (!apiKey) {
      console.error(
        "METAMCP_API_KEY is not set. Please set it via environment variable or command line argument."
      );
      return _capabilitiesCache || [];
    }

    const headers = { Authorization: `Bearer ${apiKey}` };
    const response = await axios.get(`${apiBaseUrl}/api/profile-capabilities`, {
      headers,
    });
    const data = response.data;

    // Access the 'profileCapabilities' array in the response
    if (data && data.profileCapabilities) {
      const capabilities = data.profileCapabilities
        .map((capability: string) => {
          // Map string to enum value if it exists, otherwise return undefined
          return ProfileCapability[
            capability as keyof typeof ProfileCapability
          ];
        })
        .filter(
          (
            capability: ProfileCapability | undefined
          ): capability is ProfileCapability => capability !== undefined
        );

      _capabilitiesCache = capabilities;
      _capabilitiesCacheTimestamp = currentTime;
      return capabilities;
    }

    return _capabilitiesCache || [];
  } catch (error) {
    // Return empty array if API doesn't exist or has errors
    if (_capabilitiesCache !== null) {
      return _capabilitiesCache;
    }
    return [];
  }
}

```

--------------------------------------------------------------------------------
/src/utils.ts:
--------------------------------------------------------------------------------

```typescript
import { ServerParameters } from "./fetch-metamcp.js";
import crypto from "crypto";

/**
 * Environment variables to inherit by default, if an environment is not explicitly given.
 */
export const DEFAULT_INHERITED_ENV_VARS =
  process.platform === "win32"
    ? [
        "APPDATA",
        "HOMEDRIVE",
        "HOMEPATH",
        "LOCALAPPDATA",
        "PATH",
        "PROCESSOR_ARCHITECTURE",
        "SYSTEMDRIVE",
        "SYSTEMROOT",
        "TEMP",
        "USERNAME",
        "USERPROFILE",
      ]
    : /* list inspired by the default env inheritance of sudo */
      ["HOME", "LOGNAME", "PATH", "SHELL", "TERM", "USER"];

/**
 * Returns a default environment object including only environment variables deemed safe to inherit.
 */
export function getDefaultEnvironment(): Record<string, string> {
  const env: Record<string, string> = {};

  for (const key of DEFAULT_INHERITED_ENV_VARS) {
    const value = process.env[key];
    if (value === undefined) {
      continue;
    }

    if (value.startsWith("()")) {
      // Skip functions, which are a security risk.
      continue;
    }

    env[key] = value;
  }

  return env;
}

/**
 * Get the MetaMCP API base URL from environment variables
 */
export function getMetaMcpApiBaseUrl(): string {
  return process.env.METAMCP_API_BASE_URL || "https://api.metamcp.com";
}

/**
 * Get the MetaMCP API key from environment variables
 */
export function getMetaMcpApiKey(): string | undefined {
  return process.env.METAMCP_API_KEY;
}

export function sanitizeName(name: string): string {
  return name.replace(/[^a-zA-Z0-9_-]/g, "");
}

export function computeParamsHash(
  params: ServerParameters,
  uuid: string
): string {
  let paramsDict: any;

  // Default to "STDIO" if type is undefined
  if (!params.type || params.type === "STDIO") {
    paramsDict = {
      uuid,
      type: "STDIO", // Explicitly set type to "STDIO" for consistent hashing
      command: params.command,
      args: params.args,
      env: params.env
        ? Object.fromEntries(
            Object.entries(params.env).sort((a, b) => a[0].localeCompare(b[0]))
          )
        : null,
    };
  } else if (params.type === "SSE" || params.type === "STREAMABLE_HTTP") {
    paramsDict = {
      uuid,
      type: params.type,
      url: params.url,
    };
  } else {
    throw new Error(`Unsupported server type: ${params.type}`);
  }

  const paramsJson = JSON.stringify(paramsDict);
  return crypto.createHash("sha256").update(paramsJson).digest("hex");
}

export function getSessionKey(uuid: string, params: ServerParameters): string {
  return `${uuid}_${computeParamsHash(params, uuid)}`;
}

```

--------------------------------------------------------------------------------
/src/sse.ts:
--------------------------------------------------------------------------------

```typescript
import express from "express";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";

export interface SSEServerOptions {
  port: number;
  requireApiAuth?: boolean;
}

// Starts an SSE server and returns a cleanup function
export async function startSSEServer(
  server: Server,
  options: SSEServerOptions
): Promise<() => Promise<void>> {
  const app = express();
  const port = options.port || 12006;
  const requireApiAuth = options.requireApiAuth || false;
  const apiKey = process.env.METAMCP_API_KEY;

  // to support multiple simultaneous connections we have a lookup object from
  // sessionId to transport
  const transports: { [sessionId: string]: SSEServerTransport } = {};

  // Define the SSE endpoint based on authentication requirement
  const sseEndpoint = requireApiAuth ? `/:apiKey/sse` : `/sse`;

  app.get(sseEndpoint, async (req: express.Request, res: express.Response) => {
    // If API auth is required, validate the API key
    if (requireApiAuth) {
      const requestApiKey = req.params.apiKey;
      if (!apiKey || requestApiKey !== apiKey) {
        res.status(401).send("Unauthorized: Invalid API key");
        return;
      }
    }

    // Set the messages path based on authentication requirement
    const messagesPath = requireApiAuth ? `/${apiKey}/messages` : `/messages`;
    const transport = new SSEServerTransport(messagesPath, res);
    transports[transport.sessionId] = transport;
    res.on("close", () => {
      delete transports[transport.sessionId];
    });
    await server.connect(transport);
  });

  // Define the messages endpoint
  const messagesEndpoint = requireApiAuth ? `/:apiKey/messages` : `/messages`;

  app.post(
    messagesEndpoint,
    async (req: express.Request, res: express.Response) => {
      // If API auth is required, validate the API key
      if (requireApiAuth) {
        const requestApiKey = req.params.apiKey;
        if (!apiKey || requestApiKey !== apiKey) {
          res.status(401).send("Unauthorized: Invalid API key");
          return;
        }
      }

      const sessionId = req.query.sessionId as string;
      const transport = transports[sessionId];
      if (transport) {
        await transport.handlePostMessage(req, res);
      } else {
        res.status(400).send("No transport found for sessionId");
      }
    }
  );

  const serverInstance = app.listen(port, () => {
    const baseUrl = `http://localhost:${port}`;
    const sseUrl = requireApiAuth
      ? `${baseUrl}/${apiKey}/sse`
      : `${baseUrl}/sse`;
    console.log(`SSE server listening on port ${port}`);
    console.log(`SSE endpoint: ${sseUrl}`);
  });

  // Return cleanup function
  return async () => {
    // Close all active transports
    await Promise.all(
      Object.values(transports).map((transport) => transport.close())
    );
    serverInstance.close();
  };
}

```

--------------------------------------------------------------------------------
/src/fetch-metamcp.ts:
--------------------------------------------------------------------------------

```typescript
import axios from "axios";
import {
  getDefaultEnvironment,
  getMetaMcpApiBaseUrl,
  getMetaMcpApiKey,
} from "./utils.js";

// Define IOType for stderr handling
export type IOType = "overlapped" | "pipe" | "ignore" | "inherit";

// Define a new interface for server parameters that can be STDIO, SSE or STREAMABLE_HTTP
export interface ServerParameters {
  uuid: string;
  name: string;
  description: string;
  type?: "STDIO" | "SSE" | "STREAMABLE_HTTP"; // Optional field, defaults to "STDIO" when undefined
  command?: string | null;
  args?: string[] | null;
  env?: Record<string, string> | null;
  stderr?: IOType; // Optional field for stderr handling, defaults to "ignore"
  url?: string | null;
  created_at: string;
  profile_uuid: string;
  status: string;
  oauth_tokens?: {
    access_token: string;
    token_type: string;
    expires_in?: number | undefined;
    scope?: string | undefined;
    refresh_token?: string | undefined;
  } | null;
}

let _mcpServersCache: Record<string, ServerParameters> | null = null;
let _mcpServersCacheTimestamp: number = 0;
const CACHE_TTL_MS = 1000; // 1 second cache TTL

export async function getMcpServers(
  forceRefresh: boolean = false
): Promise<Record<string, ServerParameters>> {
  const currentTime = Date.now();
  const cacheAge = currentTime - _mcpServersCacheTimestamp;

  // Use cache if it exists, is not null, and either:
  // 1. forceRefresh is false, or
  // 2. forceRefresh is true but cache is less than 1 second old
  if (_mcpServersCache !== null && (!forceRefresh || cacheAge < CACHE_TTL_MS)) {
    return _mcpServersCache;
  }

  try {
    const apiKey = getMetaMcpApiKey();
    const apiBaseUrl = getMetaMcpApiBaseUrl();

    if (!apiKey) {
      console.error(
        "METAMCP_API_KEY is not set. Please set it via environment variable or command line argument."
      );
      return _mcpServersCache || {};
    }

    const headers = { Authorization: `Bearer ${apiKey}` };
    const response = await axios.get(`${apiBaseUrl}/api/mcp-servers`, {
      headers,
    });
    const data = response.data;

    const serverDict: Record<string, ServerParameters> = {};
    for (const serverParams of data) {
      const params: ServerParameters = {
        ...serverParams,
        type: serverParams.type || "STDIO",
      };

      // Process based on server type
      if (params.type === "STDIO") {
        if ("args" in params && !params.args) {
          params.args = undefined;
        }

        params.env = {
          ...getDefaultEnvironment(),
          ...(params.env || {}),
        };
      } else if (params.type === "SSE" || params.type === "STREAMABLE_HTTP") {
        // For SSE or STREAMABLE_HTTP servers, ensure url is present
        if (!params.url) {
          console.warn(
            `${params.type} server ${params.uuid} is missing url field, skipping`
          );
          continue;
        }
      }

      const uuid = params.uuid;
      if (uuid) {
        serverDict[uuid] = params;
      }
    }

    _mcpServersCache = serverDict;
    _mcpServersCacheTimestamp = currentTime;
    return serverDict;
  } catch (error) {
    if (_mcpServersCache !== null) {
      return _mcpServersCache;
    }
    return {};
  }
}

```

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

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

import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { createServer } from "./mcp-proxy.js";
import { Command } from "commander";
import { reportAllTools } from "./report-tools.js";
import { cleanupAllSessions } from "./sessions.js";
import { startSSEServer } from "./sse.js";
import { startStreamableHTTPServer } from "./streamable-http.js";
import { IOType } from "./fetch-metamcp.js";

const program = new Command();

program
  .name("mcp-server-metamcp")
  .description("MetaMCP MCP Server - The One MCP to manage all your MCPs")
  .option(
    "--metamcp-api-key <key>",
    "API key for MetaMCP (can also be set via METAMCP_API_KEY env var)"
  )
  .option(
    "--metamcp-api-base-url <url>",
    "Base URL for MetaMCP API (can also be set via METAMCP_API_BASE_URL env var)"
  )
  .option(
    "--report",
    "Fetch all MCPs, initialize clients, and report tools to MetaMCP API"
  )
  .option("--transport <type>", "Transport type to use (stdio, sse, or streamable-http)", "stdio")
  .option("--port <port>", "Port to use for SSE or Streamable HTTP transport, defaults to 12006", "12006")
  .option("--require-api-auth", "Require API key in SSE or Streamable HTTP URL path")
  .option("--stateless", "Use stateless mode for Streamable HTTP transport")
  .option(
    "--use-docker-host",
    "Transform localhost URLs to use host.docker.internal (can also be set via USE_DOCKER_HOST env var)"
  )
  .option(
    "--stderr <type>",
    "Stderr handling for STDIO transport (overlapped, pipe, ignore, inherit)",
    "ignore"
  )
  .parse(process.argv);

const options = program.opts();

// Validate stderr option
const validStderrTypes: IOType[] = ["overlapped", "pipe", "ignore", "inherit"];
if (!validStderrTypes.includes(options.stderr as IOType)) {
  console.error(`Invalid stderr type: ${options.stderr}. Must be one of: ${validStderrTypes.join(", ")}`);
  process.exit(1);
}

// Set environment variables from command line arguments
if (options.metamcpApiKey) {
  process.env.METAMCP_API_KEY = options.metamcpApiKey;
}
if (options.metamcpApiBaseUrl) {
  process.env.METAMCP_API_BASE_URL = options.metamcpApiBaseUrl;
}
if (options.useDockerHost) {
  process.env.USE_DOCKER_HOST = "true";
}
if (options.stderr) {
  process.env.METAMCP_STDERR = options.stderr;
}

async function main() {
  // If --report flag is set, run the reporting function instead of starting the server
  if (options.report) {
    await reportAllTools();
    await cleanupAllSessions();
    return;
  }

  const { server, cleanup } = await createServer();

  if (options.transport.toLowerCase() === "sse") {
    // Start SSE server
    const port = parseInt(options.port) || 12006;
    const sseCleanup = await startSSEServer(server, {
      port,
      requireApiAuth: options.requireApiAuth,
    });

    // Cleanup on exit
    const handleExit = async () => {
      await cleanup();
      await sseCleanup();
      await server.close();
      process.exit(0);
    };

    process.on("SIGINT", handleExit);
    process.on("SIGTERM", handleExit);
  } else if (options.transport.toLowerCase() === "streamable-http") {
    // Start Streamable HTTP server
    const port = parseInt(options.port) || 12006;
    const streamableHttpCleanup = await startStreamableHTTPServer(server, {
      port,
      requireApiAuth: options.requireApiAuth,
      stateless: options.stateless,
    });

    // Cleanup on exit
    const handleExit = async () => {
      await cleanup();
      await streamableHttpCleanup();
      await server.close();
      process.exit(0);
    };

    process.on("SIGINT", handleExit);
    process.on("SIGTERM", handleExit);
  } else {
    // Default: Start stdio server
    const transport = new StdioServerTransport();
    await server.connect(transport);

    const handleExit = async () => {
      await cleanup();
      await transport.close();
      await server.close();
      process.exit(0);
    };

    // Cleanup on exit
    process.on("SIGINT", handleExit);
    process.on("SIGTERM", handleExit);

    process.stdin.resume();
    process.stdin.on("end", handleExit);
    process.stdin.on("close", handleExit);
  }
}

main().catch((error) => {
  console.error("Server error:", error);
});

```

--------------------------------------------------------------------------------
/src/client.ts:
--------------------------------------------------------------------------------

```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import {
  StdioClientTransport,
  StdioServerParameters,
} from "@modelcontextprotocol/sdk/client/stdio.js";
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
import { ServerParameters, IOType } from "./fetch-metamcp.js";

const sleep = (time: number) =>
  new Promise<void>((resolve) => setTimeout(() => resolve(), time));
export interface ConnectedClient {
  client: Client;
  cleanup: () => Promise<void>;
}

/**
 * Transforms localhost URLs to use host.docker.internal when running inside Docker
 */
const transformDockerUrl = (url: string): string => {
  if (process.env.USE_DOCKER_HOST === "true") {
    return url.replace(/localhost|127\.0\.0\.1/g, "host.docker.internal");
  }
  return url;
};

export const createMetaMcpClient = (
  serverParams: ServerParameters
): { client: Client | undefined; transport: Transport | undefined } => {
  let transport: Transport | undefined;

  // Create the appropriate transport based on server type
  // Default to "STDIO" if type is undefined
  if (!serverParams.type || serverParams.type === "STDIO") {
    // Get stderr value from serverParams, environment variable, or default to "ignore"
    const stderrValue: IOType = 
      serverParams.stderr || 
      (process.env.METAMCP_STDERR as IOType) || 
      "ignore";

    const stdioParams: StdioServerParameters = {
      command: serverParams.command || "",
      args: serverParams.args || undefined,
      env: serverParams.env || undefined,
      stderr: stderrValue,
    };
    transport = new StdioClientTransport(stdioParams);

    // Handle stderr stream when set to "pipe"
    if (stderrValue === "pipe" && (transport as any).stderr) {
      const stderrStream = (transport as any).stderr;
      
      stderrStream.on('data', (chunk: Buffer) => {
        console.error(`[${serverParams.name}] ${chunk.toString().trim()}`);
      });

      stderrStream.on('error', (error: Error) => {
        console.error(`[${serverParams.name}] stderr error:`, error);
      });
    }
  } else if (serverParams.type === "SSE" && serverParams.url) {
    // Transform the URL if USE_DOCKER_HOST is set to "true"
    const transformedUrl = transformDockerUrl(serverParams.url);

    if (!serverParams.oauth_tokens) {
      transport = new SSEClientTransport(new URL(transformedUrl));
    } else {
      const headers: HeadersInit = {};
      headers[
        "Authorization"
      ] = `Bearer ${serverParams.oauth_tokens.access_token}`;
      transport = new SSEClientTransport(new URL(transformedUrl), {
        requestInit: {
          headers,
        },
        eventSourceInit: {
          fetch: (url, init) => fetch(url, { ...init, headers }),
        },
      });
    }
  } else if (serverParams.type === "STREAMABLE_HTTP" && serverParams.url) {
    // Transform the URL if USE_DOCKER_HOST is set to "true"
    const transformedUrl = transformDockerUrl(serverParams.url);

    if (!serverParams.oauth_tokens) {
      transport = new StreamableHTTPClientTransport(new URL(transformedUrl));
    } else {
      const headers: HeadersInit = {};
      headers[
        "Authorization"
      ] = `Bearer ${serverParams.oauth_tokens.access_token}`;
      transport = new StreamableHTTPClientTransport(new URL(transformedUrl), {
        requestInit: {
          headers,
        },
      });
    }
  } else {
    console.error(`Unsupported server type: ${serverParams.type}`);
    return { client: undefined, transport: undefined };
  }

  const client = new Client(
    {
      name: "MetaMCP",
      version: "0.6.5",
    },
    {
      capabilities: {
        prompts: {},
        resources: { subscribe: true },
        tools: {},
      },
    }
  );
  return { client, transport };
};

export const connectMetaMcpClient = async (
  client: Client,
  transport: Transport
): Promise<ConnectedClient | undefined> => {
  const waitFor = 2500;
  const retries = 3;
  let count = 0;
  let retry = true;

  while (retry) {
    try {
      await client.connect(transport);

      return {
        client,
        cleanup: async () => {
          await transport.close();
          await client.close();
        },
      };
    } catch (error) {
      count++;
      retry = count < retries;
      if (retry) {
        try {
          await client.close();
        } catch {}
        await sleep(waitFor);
      }
    }
  }
};

```

--------------------------------------------------------------------------------
/src/streamable-http.ts:
--------------------------------------------------------------------------------

```typescript
import express from "express";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { randomUUID } from "crypto";

export interface StreamableHTTPServerOptions {
  port: number;
  requireApiAuth?: boolean;
  stateless?: boolean;
}

// Starts a Streamable HTTP server and returns a cleanup function
export async function startStreamableHTTPServer(
  server: Server,
  options: StreamableHTTPServerOptions
): Promise<() => Promise<void>> {
  const app = express();
  app.use(express.json());
  
  const port = options.port || 12006;
  const requireApiAuth = options.requireApiAuth || false;
  const stateless = options.stateless || false;
  const apiKey = process.env.METAMCP_API_KEY;
  
  // Map to store transports by session ID (when using stateful mode)
  const transports: { [sessionId: string]: StreamableHTTPServerTransport } = {};
  
  // Define the MCP endpoint path based on authentication requirement
  const mcpEndpoint = requireApiAuth ? `/:apiKey/mcp` : `/mcp`;
  
  // Handle all HTTP methods for the MCP endpoint
  app.all(mcpEndpoint, async (req: express.Request, res: express.Response) => {
    // If API auth is required, validate the API key
    if (requireApiAuth) {
      const requestApiKey = req.params.apiKey;
      if (!apiKey || requestApiKey !== apiKey) {
        res.status(401).send("Unauthorized: Invalid API key");
        return;
      }
    }
    
    if (stateless) {
      // Stateless mode: Create a new transport for each request
      const transport = new StreamableHTTPServerTransport({
        sessionIdGenerator: undefined, // No session management
      });
      
      res.on("close", () => {
        transport.close();
      });
      
      try {
        // Connect to the server
        await server.connect(transport);
        // Handle the request
        await transport.handleRequest(req, res, req.body);
      } catch (error) {
        console.error("Error handling streamable HTTP request:", error);
        if (!res.headersSent) {
          res.status(500).json({
            jsonrpc: "2.0",
            error: {
              code: -32603,
              message: "Internal server error",
            },
            id: null,
          });
        }
      }
    } else {
      // Stateful mode: Use session management
      const sessionId = req.headers["mcp-session-id"] as string | undefined;
      let transport: StreamableHTTPServerTransport;
      
      if (sessionId && transports[sessionId]) {
        // Reuse existing transport
        transport = transports[sessionId];
      } else if (!sessionId && req.method === "POST") {
        // New initialization request
        transport = new StreamableHTTPServerTransport({
          sessionIdGenerator: () => randomUUID(),
          onsessioninitialized: (sessionId) => {
            // Store the transport by session ID
            transports[sessionId] = transport;
          }
        });
        
        // Clean up transport when closed
        transport.onclose = () => {
          if (transport.sessionId) {
            delete transports[transport.sessionId];
          }
        };
        
        // Connect to the server
        await server.connect(transport);
      } else {
        // Invalid request
        res.status(400).json({
          jsonrpc: "2.0",
          error: {
            code: -32000,
            message: "Bad Request: No valid session ID provided",
          },
          id: null,
        });
        return;
      }
      
      try {
        // Handle the request
        await transport.handleRequest(req, res, req.body);
      } catch (error) {
        console.error("Error handling streamable HTTP request:", error);
        if (!res.headersSent) {
          res.status(500).json({
            jsonrpc: "2.0",
            error: {
              code: -32603,
              message: "Internal server error",
            },
            id: null,
          });
        }
      }
    }
  });
  
  const serverInstance = app.listen(port, () => {
    const baseUrl = `http://localhost:${port}`;
    const mcpUrl = requireApiAuth ? `${baseUrl}/${apiKey}/mcp` : `${baseUrl}/mcp`;
    console.log(`Streamable HTTP server listening on port ${port}`);
    console.log(`MCP endpoint: ${mcpUrl}`);
    console.log(`Mode: ${stateless ? "Stateless" : "Stateful"}`);
  });
  
  // Return cleanup function
  return async () => {
    // Close all active transports
    await Promise.all(
      Object.values(transports).map((transport) => transport.close())
    );
    serverInstance.close();
  };
} 
```

--------------------------------------------------------------------------------
/src/report-tools.ts:
--------------------------------------------------------------------------------

```typescript
import axios from "axios";
import { getMetaMcpApiBaseUrl, getMetaMcpApiKey } from "./utils.js";
import { getMcpServers } from "./fetch-metamcp.js";
import { initSessions, getSession } from "./sessions.js";
import { getSessionKey } from "./utils.js";
import { ListToolsResultSchema } from "@modelcontextprotocol/sdk/types.js";

// Define interface for tool data structure
export interface MetaMcpTool {
  name: string;
  description?: string;
  toolSchema: any;
  mcp_server_uuid: string;
}

// API route handler for submitting tools to MetaMCP
export async function reportToolsToMetaMcp(tools: MetaMcpTool[]) {
  try {
    const apiKey = getMetaMcpApiKey();
    const apiBaseUrl = getMetaMcpApiBaseUrl();

    if (!apiKey) {
      return { error: "API key not set" };
    }

    // Validate that tools is an array
    if (!Array.isArray(tools) || tools.length === 0) {
      return {
        error: "Request must include a non-empty array of tools",
        status: 400,
      };
    }

    // Validate required fields for all tools and prepare for submission
    const validTools = [];
    const errors = [];

    for (const tool of tools) {
      const { name, description, toolSchema, mcp_server_uuid } = tool;

      // Validate required fields for each tool
      if (!name || !toolSchema || !mcp_server_uuid) {
        errors.push({
          tool,
          error:
            "Missing required fields: name, toolSchema, or mcp_server_uuid",
        });
        continue;
      }

      validTools.push({
        name,
        description,
        toolSchema,
        mcp_server_uuid,
      });
    }

    // Submit valid tools to MetaMCP API
    let results: any[] = [];
    if (validTools.length > 0) {
      try {
        const response = await axios.post(
          `${apiBaseUrl}/api/tools`,
          { tools: validTools },
          {
            headers: {
              "Content-Type": "application/json",
              Authorization: `Bearer ${apiKey}`,
            },
          }
        );

        results = response.data.results || [];
      } catch (error: any) {
        if (error.response) {
          // The request was made and the server responded with a status code outside of 2xx
          return {
            error: error.response.data.error || "Failed to submit tools",
            status: error.response.status,
            details: error.response.data,
          };
        } else if (error.request) {
          // The request was made but no response was received
          return {
            error: "No response received from server",
            details: error.request,
          };
        } else {
          // Something happened in setting up the request
          return {
            error: "Error setting up request",
            details: error.message,
          };
        }
      }
    }

    return {
      results,
      errors,
      success: results.length > 0,
      failureCount: errors.length,
      successCount: results.length,
    };
  } catch (error: any) {
    return {
      error: "Failed to process tools request",
      status: 500,
    };
  }
}

// Function to fetch all MCP servers, initialize clients, and report tools to MetaMCP API
export async function reportAllTools() {
  console.log("Fetching all MCPs and initializing clients...");

  // Get all MCP servers
  const serverParams = await getMcpServers();

  // Initialize all sessions
  await initSessions();

  console.log(`Found ${Object.keys(serverParams).length} MCP servers`);

  // For each server, get its tools and report them
  await Promise.allSettled(
    Object.entries(serverParams).map(async ([uuid, params]) => {
      const sessionKey = getSessionKey(uuid, params);
      const session = await getSession(sessionKey, uuid, params);

      if (!session) {
        console.log(`Could not establish session for ${params.name} (${uuid})`);
        return;
      }

      const capabilities = session.client.getServerCapabilities();
      if (!capabilities?.tools) {
        console.log(`Server ${params.name} (${uuid}) does not support tools`);
        return;
      }

      try {
        console.log(`Fetching tools from ${params.name} (${uuid})...`);

        const result = await session.client.request(
          { method: "tools/list", params: {} },
          ListToolsResultSchema
        );

        if (result.tools && result.tools.length > 0) {
          console.log(
            `Reporting ${result.tools.length} tools from ${params.name} to MetaMCP API...`
          );

          const reportResult = await reportToolsToMetaMcp(
            result.tools.map((tool) => ({
              name: tool.name,
              description: tool.description,
              toolSchema: tool.inputSchema,
              mcp_server_uuid: uuid,
            }))
          );

          console.log(
            `Reported tools from ${params.name}: ${reportResult.successCount} succeeded, ${reportResult.failureCount} failed`
          );
        } else {
          console.log(`No tools found for ${params.name}`);
        }
      } catch (error) {
        console.error(`Error reporting tools for ${params.name}:`, error);
      }
    })
  );

  console.log("Finished reporting all tools to MetaMCP API");
  process.exit(0);
}

```

--------------------------------------------------------------------------------
/src/tool-logs.ts:
--------------------------------------------------------------------------------

```typescript
import axios from "axios";
import { getMetaMcpApiBaseUrl, getMetaMcpApiKey } from "./utils.js";
import {
  ProfileCapability,
  getProfileCapabilities,
} from "./fetch-capabilities.js";

// Define status enum for tool execution
export enum ToolExecutionStatus {
  SUCCESS = "SUCCESS",
  ERROR = "ERROR",
  PENDING = "PENDING",
}

// Define interface for tool execution log data
export interface ToolExecutionLog {
  id?: string;
  tool_name: string;
  payload: any;
  status: ToolExecutionStatus;
  result?: any;
  mcp_server_uuid: string;
  error_message?: string | null;
  execution_time_ms: number;
  created_at?: string;
  updated_at?: string;
}

// Response interfaces
export interface ToolLogResponse {
  id?: string;
  success: boolean;
  data?: any;
  error?: string;
  status?: number;
  details?: any;
}

// Class to manage tool execution logs
export class ToolLogManager {
  private static instance: ToolLogManager;
  private logStore: Map<string, ToolExecutionLog> = new Map();

  private constructor() {}

  public static getInstance(): ToolLogManager {
    if (!ToolLogManager.instance) {
      ToolLogManager.instance = new ToolLogManager();
    }
    return ToolLogManager.instance;
  }

  /**
   * Creates a new tool execution log
   * @param toolName Name of the tool
   * @param serverUuid UUID of the MCP server
   * @param payload The input parameters for the tool
   * @returns Log object with tracking ID
   */
  public async createLog(
    toolName: string,
    serverUuid: string,
    payload: any
  ): Promise<ToolExecutionLog> {
    // Check for TOOL_LOGS capability first
    const profileCapabilities = await getProfileCapabilities();
    const hasToolsLogCapability = profileCapabilities.includes(
      ProfileCapability.TOOL_LOGS
    );

    // Generate a temporary ID for tracking
    const tempId = `${Date.now()}-${Math.random()
      .toString(36)
      .substring(2, 9)}`;

    const log: ToolExecutionLog = {
      id: tempId, // Will be replaced with the real ID from the API
      tool_name: toolName,
      mcp_server_uuid: serverUuid,
      payload,
      status: ToolExecutionStatus.PENDING,
      execution_time_ms: 0,
      created_at: new Date().toISOString(),
    };

    // Store in memory
    this.logStore.set(tempId, log);

    // Submit to API only if TOOL_LOGS capability is present
    if (hasToolsLogCapability) {
      const response = await reportToolExecutionLog(log);

      // Update with real ID if available
      if (response.success && response.data?.id) {
        const newId = response.data.id;
        log.id = newId;
        this.logStore.delete(tempId);
        this.logStore.set(newId, log);
      }
    }

    return log;
  }

  /**
   * Updates the status of a tool execution log
   * @param logId ID of the log to update
   * @param status New status
   * @param result Optional result data
   * @param errorMessage Optional error message
   * @param executionTimeMs Optional execution time in milliseconds
   * @returns Updated log
   */
  public async updateLogStatus(
    logId: string,
    status: ToolExecutionStatus,
    result?: any,
    errorMessage?: string | null,
    executionTimeMs?: number
  ): Promise<ToolExecutionLog | null> {
    const log = this.logStore.get(logId);

    if (!log) {
      console.error(`Cannot update log: Log with ID ${logId} not found`);
      return null;
    }

    // Update log properties
    log.status = status;
    if (result !== undefined) log.result = result;
    if (errorMessage !== undefined) log.error_message = errorMessage;
    if (executionTimeMs !== undefined) log.execution_time_ms = executionTimeMs;
    log.updated_at = new Date().toISOString();

    // Update in memory
    this.logStore.set(logId, log);

    // Check for TOOL_LOGS capability before sending update to API
    const profileCapabilities = await getProfileCapabilities();
    const hasToolsLogCapability = profileCapabilities.includes(
      ProfileCapability.TOOL_LOGS
    );

    // Send update to API only if TOOL_LOGS capability is present
    if (hasToolsLogCapability) {
      await updateToolExecutionLog(logId, {
        status,
        result,
        error_message: errorMessage,
        execution_time_ms: executionTimeMs,
      });
    }

    return log;
  }

  /**
   * Get a log by ID
   * @param logId ID of the log
   * @returns Log or null if not found
   */
  public getLog(logId: string): ToolExecutionLog | null {
    return this.logStore.get(logId) || null;
  }

  /**
   * Complete the tool execution log with success status
   * @param logId ID of the log to complete
   * @param result Result data
   * @param executionTimeMs Execution time in milliseconds
   * @returns Updated log
   */
  public async completeLog(
    logId: string,
    result: any,
    executionTimeMs: number
  ): Promise<ToolExecutionLog | null> {
    return this.updateLogStatus(
      logId,
      ToolExecutionStatus.SUCCESS,
      result,
      null,
      executionTimeMs
    );
  }

  /**
   * Mark the tool execution log as failed
   * @param logId ID of the log to fail
   * @param errorMessage Error message
   * @param executionTimeMs Execution time in milliseconds
   * @returns Updated log
   */
  public async failLog(
    logId: string,
    errorMessage: string,
    executionTimeMs: number
  ): Promise<ToolExecutionLog | null> {
    return this.updateLogStatus(
      logId,
      ToolExecutionStatus.ERROR,
      null,
      errorMessage,
      executionTimeMs
    );
  }
}

/**
 * Reports a tool execution log to the MetaMCP API
 * @param logData The tool execution log data
 * @returns Result of the API call
 */
export async function reportToolExecutionLog(
  logData: ToolExecutionLog
): Promise<ToolLogResponse> {
  try {
    // Check for TOOL_LOGS capability first
    const profileCapabilities = await getProfileCapabilities();
    const hasToolsLogCapability = profileCapabilities.includes(
      ProfileCapability.TOOL_LOGS
    );

    if (!hasToolsLogCapability) {
      return { success: false, error: "TOOL_LOGS capability not enabled" };
    }

    const apiKey = getMetaMcpApiKey();
    const apiBaseUrl = getMetaMcpApiBaseUrl();

    if (!apiKey) {
      return { success: false, error: "API key not set" };
    }

    // Validate required fields
    if (!logData.tool_name || !logData.mcp_server_uuid) {
      return {
        success: false,
        error: "Missing required fields: tool_name or mcp_server_uuid",
        status: 400,
      };
    }

    // Submit log to MetaMCP API
    try {
      const response = await axios.post(
        `${apiBaseUrl}/api/tool-execution-logs`,
        logData,
        {
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${apiKey}`,
          },
        }
      );

      return {
        success: true,
        data: response.data,
      };
    } catch (error: any) {
      if (error.response) {
        // The request was made and the server responded with a status code outside of 2xx
        return {
          success: false,
          error:
            error.response.data.error || "Failed to submit tool execution log",
          status: error.response.status,
          details: error.response.data,
        };
      } else if (error.request) {
        // The request was made but no response was received
        return {
          success: false,
          error: "No response received from server",
          details: error.request,
        };
      } else {
        // Something happened in setting up the request
        return {
          success: false,
          error: "Error setting up request",
          details: error.message,
        };
      }
    }
  } catch (error: any) {
    return {
      success: false,
      error: "Failed to process tool execution log request",
      status: 500,
      details: error.message,
    };
  }
}

/**
 * Updates an existing tool execution log
 * @param logId The ID of the log to update
 * @param updateData The updated log data
 * @returns Result of the API call
 */
export async function updateToolExecutionLog(
  logId: string,
  updateData: Partial<ToolExecutionLog>
): Promise<ToolLogResponse> {
  try {
    // Check for TOOL_LOGS capability first
    const profileCapabilities = await getProfileCapabilities();
    const hasToolsLogCapability = profileCapabilities.includes(
      ProfileCapability.TOOL_LOGS
    );

    if (!hasToolsLogCapability) {
      return { success: false, error: "TOOL_LOGS capability not enabled" };
    }

    const apiKey = getMetaMcpApiKey();
    const apiBaseUrl = getMetaMcpApiBaseUrl();

    if (!apiKey) {
      return { success: false, error: "API key not set" };
    }

    if (!logId) {
      return {
        success: false,
        error: "Log ID is required for updates",
      };
    }

    // Submit update to MetaMCP API
    try {
      const response = await axios.put(
        `${apiBaseUrl}/api/tool-execution-logs/${logId}`,
        updateData,
        {
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${apiKey}`,
          },
        }
      );

      return {
        success: true,
        data: response.data,
      };
    } catch (error: any) {
      if (error.response) {
        return {
          success: false,
          error:
            error.response.data.error || "Failed to update tool execution log",
          status: error.response.status,
          details: error.response.data,
        };
      } else if (error.request) {
        return {
          success: false,
          error: "No response received from server",
          details: error.request,
        };
      } else {
        return {
          success: false,
          error: "Error setting up request",
          details: error.message,
        };
      }
    }
  } catch (error: any) {
    return {
      success: false,
      error: "Failed to process update request",
      status: 500,
      details: error.message,
    };
  }
}

/**
 * Simple function to log a tool execution
 * @param toolName Name of the tool
 * @param serverUuid UUID of the MCP server
 * @param payload The input parameters for the tool
 * @param result The result of the tool execution
 * @param status Status of the execution
 * @param errorMessage Optional error message if execution failed
 * @param executionTimeMs Time taken to execute the tool in milliseconds
 * @returns Result of the API call
 */
export async function logToolExecution(
  toolName: string,
  serverUuid: string,
  payload: any,
  result: any = null,
  status: ToolExecutionStatus = ToolExecutionStatus.SUCCESS,
  errorMessage: string | null = null,
  executionTimeMs: number = 0
): Promise<ToolLogResponse> {
  // Check for TOOL_LOGS capability first
  const profileCapabilities = await getProfileCapabilities();
  const hasToolsLogCapability = profileCapabilities.includes(
    ProfileCapability.TOOL_LOGS
  );

  if (!hasToolsLogCapability) {
    return { success: false, error: "TOOL_LOGS capability not enabled" };
  }

  const logData: ToolExecutionLog = {
    tool_name: toolName,
    mcp_server_uuid: serverUuid,
    payload,
    status,
    result,
    error_message: errorMessage,
    execution_time_ms: executionTimeMs,
  };

  return await reportToolExecutionLog(logData);
}

```

--------------------------------------------------------------------------------
/src/mcp-proxy.ts:
--------------------------------------------------------------------------------

```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import {
  CallToolRequestSchema,
  GetPromptRequestSchema,
  ListPromptsRequestSchema,
  ListResourcesRequestSchema,
  ListToolsRequestSchema,
  ReadResourceRequestSchema,
  Tool,
  ListToolsResultSchema,
  ListPromptsResultSchema,
  ListResourcesResultSchema,
  ReadResourceResultSchema,
  ListResourceTemplatesRequestSchema,
  ListResourceTemplatesResultSchema,
  ResourceTemplate,
  CompatibilityCallToolResultSchema,
  GetPromptResultSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import { getMcpServers } from "./fetch-metamcp.js";
import { getSessionKey, sanitizeName } from "./utils.js";
import { cleanupAllSessions, getSession, initSessions } from "./sessions.js";
import { ConnectedClient } from "./client.js";
import { reportToolsToMetaMcp } from "./report-tools.js";
import { getInactiveTools, ToolParameters } from "./fetch-tools.js";
import {
  getProfileCapabilities,
  ProfileCapability,
} from "./fetch-capabilities.js";
import { ToolLogManager } from "./tool-logs.js";

const toolToClient: Record<string, ConnectedClient> = {};
const toolToServerUuid: Record<string, string> = {};
const promptToClient: Record<string, ConnectedClient> = {};
const resourceToClient: Record<string, ConnectedClient> = {};
const inactiveToolsMap: Record<string, boolean> = {};

export const createServer = async () => {
  const server = new Server(
    {
      name: "MetaMCP",
      version: "0.6.5",
    },
    {
      capabilities: {
        prompts: {},
        resources: {},
        tools: {},
      },
    }
  );

  // Initialize sessions in the background when server starts
  initSessions().catch();

  // List Tools Handler
  server.setRequestHandler(ListToolsRequestSchema, async (request) => {
    const profileCapabilities = await getProfileCapabilities(true);
    const serverParams = await getMcpServers(true);

    // Fetch inactive tools only if tools management capability is present
    let inactiveTools: Record<string, ToolParameters> = {};
    if (profileCapabilities.includes(ProfileCapability.TOOLS_MANAGEMENT)) {
      inactiveTools = await getInactiveTools(true);
      // Clear existing inactive tools map before rebuilding
      Object.keys(inactiveToolsMap).forEach(
        (key) => delete inactiveToolsMap[key]
      );
    }

    const allTools: Tool[] = [];

    await Promise.allSettled(
      Object.entries(serverParams).map(async ([uuid, params]) => {
        const sessionKey = getSessionKey(uuid, params);
        const session = await getSession(sessionKey, uuid, params);
        if (!session) return;

        const capabilities = session.client.getServerCapabilities();
        if (!capabilities?.tools) return;

        const serverName = session.client.getServerVersion()?.name || "";
        try {
          const result = await session.client.request(
            {
              method: "tools/list",
              params: { _meta: request.params?._meta },
            },
            ListToolsResultSchema
          );

          const toolsWithSource =
            result.tools
              ?.filter((tool) => {
                // Only filter inactive tools if tools management is enabled
                if (
                  profileCapabilities.includes(
                    ProfileCapability.TOOLS_MANAGEMENT
                  )
                ) {
                  return !inactiveTools[`${uuid}:${tool.name}`];
                }
                return true;
              })
              .map((tool) => {
                const toolName = `${sanitizeName(serverName)}__${tool.name}`;
                toolToClient[toolName] = session;
                toolToServerUuid[toolName] = uuid;
                return {
                  ...tool,
                  name: toolName,
                  description: tool.description,
                };
              }) || [];

          // Update our inactive tools map only if tools management is enabled
          if (
            profileCapabilities.includes(ProfileCapability.TOOLS_MANAGEMENT)
          ) {
            result.tools?.forEach((tool) => {
              const isInactive = inactiveTools[`${uuid}:${tool.name}`];
              if (isInactive) {
                const formattedName = `${sanitizeName(serverName)}__${
                  tool.name
                }`;
                inactiveToolsMap[formattedName] = true;
              }
            });

            // Report full tools for this server
            reportToolsToMetaMcp(
              result.tools.map((tool) => ({
                name: tool.name,
                description: tool.description,
                toolSchema: tool.inputSchema,
                mcp_server_uuid: uuid,
              }))
            ).catch();
          }

          allTools.push(...toolsWithSource);
        } catch (error) {
          console.error(`Error fetching tools from: ${serverName}`, error);
        }
      })
    );

    return { tools: allTools };
  });

  // Call Tool Handler
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;
    const originalToolName = name.split("__")[1];
    const clientForTool = toolToClient[name];
    const toolLogManager = ToolLogManager.getInstance();
    let logId: string | undefined;
    let startTime = Date.now();

    if (!clientForTool) {
      throw new Error(`Unknown tool: ${name}`);
    }

    // Get MCP server UUID for the tool
    const mcpServerUuid = toolToServerUuid[name] || "";

    if (!mcpServerUuid) {
      console.error(`Could not determine MCP server UUID for tool: ${name}`);
    }

    // Get profile capabilities
    const profileCapabilities = await getProfileCapabilities();

    // Only check inactive tools if tools management capability is present
    if (
      profileCapabilities.includes(ProfileCapability.TOOLS_MANAGEMENT) &&
      inactiveToolsMap[name]
    ) {
      throw new Error(`Tool is inactive: ${name}`);
    }

    // Check if TOOL_LOGS capability is enabled
    const hasToolsLogCapability = profileCapabilities.includes(
      ProfileCapability.TOOL_LOGS
    );

    try {
      // Create initial pending log only if TOOL_LOGS capability is present
      if (hasToolsLogCapability) {
        const log = await toolLogManager.createLog(
          originalToolName,
          mcpServerUuid,
          args || {}
        );
        logId = log.id;
      }

      // Reset the timer right before making the actual tool call
      startTime = Date.now();

      // Use the correct schema for tool calls
      const result = await clientForTool.client.request(
        {
          method: "tools/call",
          params: {
            name: originalToolName,
            arguments: args || {},
            _meta: {
              progressToken: request.params._meta?.progressToken,
            },
          },
        },
        CompatibilityCallToolResultSchema
      );

      const executionTime = Date.now() - startTime;

      // Update log with success result only if TOOL_LOGS capability is present
      if (hasToolsLogCapability && logId) {
        try {
          await toolLogManager.completeLog(logId, result, executionTime);
        } catch (logError) {}
      }

      return result;
    } catch (error: any) {
      const executionTime = Date.now() - startTime;

      // Update log with error only if TOOL_LOGS capability is present
      if (hasToolsLogCapability && logId) {
        try {
          await toolLogManager.failLog(
            logId,
            error.message || "Unknown error",
            executionTime
          );
        } catch (logError) {}
      }

      console.error(
        `Error calling tool "${name}" through ${
          clientForTool.client.getServerVersion()?.name || "unknown"
        }:`,
        error
      );
      throw error;
    }
  });

  // Get Prompt Handler
  server.setRequestHandler(GetPromptRequestSchema, async (request) => {
    const { name } = request.params;
    const clientForPrompt = promptToClient[name];

    if (!clientForPrompt) {
      throw new Error(`Unknown prompt: ${name}`);
    }

    try {
      const promptName = name.split("__")[1];
      const response = await clientForPrompt.client.request(
        {
          method: "prompts/get",
          params: {
            name: promptName,
            arguments: request.params.arguments || {},
            _meta: request.params._meta,
          },
        },
        GetPromptResultSchema
      );

      return response;
    } catch (error) {
      console.error(
        `Error getting prompt through ${
          clientForPrompt.client.getServerVersion()?.name
        }:`,
        error
      );
      throw error;
    }
  });

  // List Prompts Handler
  server.setRequestHandler(ListPromptsRequestSchema, async (request) => {
    const serverParams = await getMcpServers(true);
    const allPrompts: z.infer<typeof ListPromptsResultSchema>["prompts"] = [];

    await Promise.allSettled(
      Object.entries(serverParams).map(async ([uuid, params]) => {
        const sessionKey = getSessionKey(uuid, params);
        const session = await getSession(sessionKey, uuid, params);
        if (!session) return;

        const capabilities = session.client.getServerCapabilities();
        if (!capabilities?.prompts) return;

        const serverName = session.client.getServerVersion()?.name || "";
        try {
          const result = await session.client.request(
            {
              method: "prompts/list",
              params: {
                cursor: request.params?.cursor,
                _meta: request.params?._meta,
              },
            },
            ListPromptsResultSchema
          );

          if (result.prompts) {
            const promptsWithSource = result.prompts.map((prompt) => {
              const promptName = `${sanitizeName(serverName)}__${prompt.name}`;
              promptToClient[promptName] = session;
              return {
                ...prompt,
                name: promptName,
                description: prompt.description || "",
              };
            });
            allPrompts.push(...promptsWithSource);
          }
        } catch (error) {
          console.error(`Error fetching prompts from: ${serverName}`, error);
        }
      })
    );

    return {
      prompts: allPrompts,
      nextCursor: request.params?.cursor,
    };
  });

  // List Resources Handler
  server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
    const serverParams = await getMcpServers(true);
    const allResources: z.infer<typeof ListResourcesResultSchema>["resources"] =
      [];

    await Promise.allSettled(
      Object.entries(serverParams).map(async ([uuid, params]) => {
        const sessionKey = getSessionKey(uuid, params);
        const session = await getSession(sessionKey, uuid, params);
        if (!session) return;

        const capabilities = session.client.getServerCapabilities();
        if (!capabilities?.resources) return;

        const serverName = session.client.getServerVersion()?.name || "";
        try {
          const result = await session.client.request(
            {
              method: "resources/list",
              params: {
                cursor: request.params?.cursor,
                _meta: request.params?._meta,
              },
            },
            ListResourcesResultSchema
          );

          if (result.resources) {
            const resourcesWithSource = result.resources.map((resource) => {
              resourceToClient[resource.uri] = session;
              return {
                ...resource,
                name: resource.name || "",
              };
            });
            allResources.push(...resourcesWithSource);
          }
        } catch (error) {
          console.error(`Error fetching resources from: ${serverName}`, error);
        }
      })
    );

    return {
      resources: allResources,
      nextCursor: request.params?.cursor,
    };
  });

  // Read Resource Handler
  server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
    const { uri } = request.params;
    const clientForResource = resourceToClient[uri];

    if (!clientForResource) {
      throw new Error(`Unknown resource: ${uri}`);
    }

    try {
      return await clientForResource.client.request(
        {
          method: "resources/read",
          params: {
            uri,
            _meta: request.params._meta,
          },
        },
        ReadResourceResultSchema
      );
    } catch (error) {
      console.error(
        `Error reading resource through ${
          clientForResource.client.getServerVersion()?.name
        }:`,
        error
      );
      throw error;
    }
  });

  // List Resource Templates Handler
  server.setRequestHandler(
    ListResourceTemplatesRequestSchema,
    async (request) => {
      const serverParams = await getMcpServers(true);
      const allTemplates: ResourceTemplate[] = [];

      await Promise.allSettled(
        Object.entries(serverParams).map(async ([uuid, params]) => {
          const sessionKey = getSessionKey(uuid, params);
          const session = await getSession(sessionKey, uuid, params);
          if (!session) return;

          const capabilities = session.client.getServerCapabilities();
          if (!capabilities?.resources) return;

          try {
            const result = await session.client.request(
              {
                method: "resources/templates/list",
                params: {
                  cursor: request.params?.cursor,
                  _meta: request.params?._meta,
                },
              },
              ListResourceTemplatesResultSchema
            );

            if (result.resourceTemplates) {
              const templatesWithSource = result.resourceTemplates.map(
                (template) => ({
                  ...template,
                  name: template.name || "",
                })
              );
              allTemplates.push(...templatesWithSource);
            }
          } catch (error) {
            return;
          }
        })
      );

      return {
        resourceTemplates: allTemplates,
        nextCursor: request.params?.cursor,
      };
    }
  );

  const cleanup = async () => {
    await cleanupAllSessions();
  };

  return { server, cleanup };
};

```