20 min read
Intermediate
Client Integration

MCP with ChatGPT and OpenAI

Connect MCP servers to ChatGPT and OpenAI applications using bridge adapters and function calling

MCPgee Team

MCP Expert

An existing MCP server (TypeScript or Python)OpenAI API keyBasic understanding of OpenAI function callingNode.js 18+ or Python 3.10+

MCP with ChatGPT and OpenAI

Introduction

While MCP was created by Anthropic for Claude, its open protocol design means it can work with any AI platform, including ChatGPT and OpenAI's ecosystem. This tutorial shows you how to bridge MCP servers with OpenAI's function calling system, enabling your MCP tools to work with ChatGPT and other OpenAI-powered applications.

For an overview of all supported clients, visit our clients directory.

How MCP and OpenAI Connect

OpenAI uses function calling (also called tool use) to let models interact with external systems. The approach is conceptually similar to MCP tools but uses a different protocol. To bridge them, you need an adapter layer that:

  1. Connects to your MCP server as a client
  2. Translates MCP tool definitions into OpenAI function schemas
  3. Routes OpenAI function calls to MCP tool executions
  4. Returns MCP responses back to OpenAI
plaintext
┌──────────────┐     ┌─────────────────┐     ┌──────────────┐
│   OpenAI     │ --> │   MCP-OpenAI    │ --> │  MCP Server  │
│   API/Chat   │ <-- │   Bridge        │ <-- │  (Your Tools)│
└──────────────┘     └─────────────────┘     └──────────────┘

Building a TypeScript Bridge

Step 1: Set Up the Project

bash
mkdir mcp-openai-bridge
cd mcp-openai-bridge
npm init -y
npm install @modelcontextprotocol/sdk openai zod
npm install -D typescript @types/node
npx tsc --init

Step 2: Create the MCP Client

typescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import OpenAI from 'openai';

class MCPOpenAIBridge {
  private mcpClient: Client;
  private openai: OpenAI;
  private tools: OpenAI.ChatCompletionTool[] = [];

  constructor() {
    this.mcpClient = new Client({
      name: 'openai-bridge',
      version: '1.0.0',
    });
    this.openai = new OpenAI();
  }

  async connectToMCPServer(command: string, args: string[]) {
    const transport = new StdioClientTransport({ command, args });
    await this.mcpClient.connect(transport);

    // Discover MCP tools and convert to OpenAI format
    const mcpTools = await this.mcpClient.listTools();
    this.tools = mcpTools.tools.map((tool) => ({
      type: 'function' as const,
      function: {
        name: tool.name,
        description: tool.description || '',
        parameters: tool.inputSchema as Record<string, unknown>,
      },
    }));

    console.log(`Connected. Found ${this.tools.length} tools.`);
  }

  async chat(userMessage: string): Promise<string> {
    const messages: OpenAI.ChatCompletionMessageParam[] = [
      { role: 'user', content: userMessage },
    ];

    const response = await this.openai.chat.completions.create({
      model: 'gpt-4',
      messages,
      tools: this.tools,
    });

    const choice = response.choices[0];
    if (choice.finish_reason === 'tool_calls' && choice.message.tool_calls) {
      // Execute MCP tools for each function call
      messages.push(choice.message);

      for (const toolCall of choice.message.tool_calls) {
        const result = await this.mcpClient.callTool(
          toolCall.function.name,
          JSON.parse(toolCall.function.arguments)
        );

        const textContent = result.content
          .filter((c): c is { type: 'text'; text: string } => c.type === 'text')
          .map((c) => c.text)
          .join('\n');

        messages.push({
          role: 'tool',
          tool_call_id: toolCall.id,
          content: textContent,
        });
      }

      // Get final response with tool results
      const finalResponse = await this.openai.chat.completions.create({
        model: 'gpt-4',
        messages,
      });

      return finalResponse.choices[0].message.content || '';
    }

    return choice.message.content || '';
  }

  async disconnect() {
    await this.mcpClient.close();
  }
}

Step 3: Use the Bridge

typescript
async function main() {
  const bridge = new MCPOpenAIBridge();

  // Connect to your MCP server
  await bridge.connectToMCPServer('node', ['./my-mcp-server.js']);

  // Chat using OpenAI with MCP tools
  const response = await bridge.chat('List the files in my workspace');
  console.log('Response:', response);

  await bridge.disconnect();
}

main().catch(console.error);

Building a Python Bridge

Step 1: Install Dependencies

bash
pip install mcp openai

Step 2: Create the Bridge

python
import asyncio
import json
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from openai import OpenAI

class MCPOpenAIBridge:
    def __init__(self):
        self.openai = OpenAI()
        self.session = None
        self.tools = []

    async def connect(self, command: str, args: list[str]):
        server_params = StdioServerParameters(
            command=command,
            args=args,
        )
        self._stdio = stdio_client(server_params)
        self._read, self._write = await self._stdio.__aenter__()
        self.session = ClientSession(self._read, self._write)
        await self.session.__aenter__()
        await self.session.initialize()

        # Convert MCP tools to OpenAI format
        mcp_tools = await self.session.list_tools()
        self.tools = [
            {
                "type": "function",
                "function": {
                    "name": tool.name,
                    "description": tool.description or "",
                    "parameters": tool.inputSchema,
                },
            }
            for tool in mcp_tools.tools
        ]
        print(f"Connected. Found {len(self.tools)} tools.")

    async def chat(self, user_message: str) -> str:
        messages = [{"role": "user", "content": user_message}]

        response = self.openai.chat.completions.create(
            model="gpt-4",
            messages=messages,
            tools=self.tools if self.tools else None,
        )

        choice = response.choices[0]
        if choice.finish_reason == "tool_calls" and choice.message.tool_calls:
            messages.append(choice.message)

            for tool_call in choice.message.tool_calls:
                args = json.loads(tool_call.function.arguments)
                result = await self.session.call_tool(
                    tool_call.function.name, arguments=args
                )

                text_content = "\n".join(
                    c.text for c in result.content if c.type == "text"
                )
                messages.append({
                    "role": "tool",
                    "tool_call_id": tool_call.id,
                    "content": text_content,
                })

            final_response = self.openai.chat.completions.create(
                model="gpt-4",
                messages=messages,
            )
            return final_response.choices[0].message.content or ""

        return choice.message.content or ""

    async def disconnect(self):
        if self.session:
            await self.session.__aexit__(None, None, None)
        await self._stdio.__aexit__(None, None, None)

async def main():
    bridge = MCPOpenAIBridge()
    await bridge.connect("python", ["my_mcp_server.py"])

    response = await bridge.chat("What tools do you have available?")
    print("Response:", response)

    await bridge.disconnect()

asyncio.run(main())

Connecting to Remote MCP Servers

For MCP servers deployed with Streamable HTTP transport, use the HTTP client transport:

typescript
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const transport = new StreamableHTTPClientTransport(
  new URL('https://your-mcp-server.example.com/mcp')
);
await bridge.mcpClient.connect(transport);

This is especially useful when your MCP server is deployed in Docker or on AWS Lambda.

Multi-Turn Conversations

For ongoing conversations that maintain context:

typescript
class ConversationalBridge extends MCPOpenAIBridge {
  private conversationHistory: OpenAI.ChatCompletionMessageParam[] = [];

  async chat(userMessage: string): Promise<string> {
    this.conversationHistory.push({ role: 'user', content: userMessage });

    const response = await this.openai.chat.completions.create({
      model: 'gpt-4',
      messages: [
        {
          role: 'system',
          content: 'You have access to MCP tools. Use them when appropriate.',
        },
        ...this.conversationHistory,
      ],
      tools: this.tools,
    });

    const choice = response.choices[0];

    if (choice.finish_reason === 'tool_calls' && choice.message.tool_calls) {
      this.conversationHistory.push(choice.message);

      for (const toolCall of choice.message.tool_calls) {
        const result = await this.mcpClient.callTool(
          toolCall.function.name,
          JSON.parse(toolCall.function.arguments)
        );

        const textContent = result.content
          .filter((c): c is { type: 'text'; text: string } => c.type === 'text')
          .map((c) => c.text)
          .join('\n');

        this.conversationHistory.push({
          role: 'tool',
          tool_call_id: toolCall.id,
          content: textContent,
        });
      }

      const finalResponse = await this.openai.chat.completions.create({
        model: 'gpt-4',
        messages: [
          { role: 'system', content: 'You have access to MCP tools.' },
          ...this.conversationHistory,
        ],
      });

      const reply = finalResponse.choices[0].message.content || '';
      this.conversationHistory.push({ role: 'assistant', content: reply });
      return reply;
    }

    const reply = choice.message.content || '';
    this.conversationHistory.push({ role: 'assistant', content: reply });
    return reply;
  }

  clearHistory() {
    this.conversationHistory = [];
  }
}

Schema Translation Details

MCP and OpenAI use slightly different schema formats. Key differences to handle:

FeatureMCPOpenAI
Schema formatJSON SchemaJSON Schema (subset)
Required fieldsinputSchema.requiredparameters.required
Descriptiontool.descriptionfunction.description
Nested objectsFull supportFull support
Enum typesSupportedSupported
Most MCP tool schemas translate directly to OpenAI function schemas since both use JSON Schema. Edge cases include:
  • Recursive schemas: Not supported by OpenAI, flatten if needed
  • Format keywords: Some may not be validated by OpenAI
  • Default values: Supported by both but handled differently

Error Handling Best Practices

typescript
async function safeMCPCall(
  client: Client,
  toolName: string,
  args: Record<string, unknown>
): Promise<string> {
  try {
    const result = await client.callTool(toolName, args);

    if (result.isError) {
      return JSON.stringify({ error: 'MCP tool returned an error', details: result.content });
    }

    return result.content
      .filter((c): c is { type: 'text'; text: string } => c.type === 'text')
      .map((c) => c.text)
      .join('\n');
  } catch (error) {
    return JSON.stringify({
      error: 'Failed to call MCP tool',
      message: error instanceof Error ? error.message : 'Unknown error',
    });
  }
}

Security Considerations

When bridging MCP with OpenAI:

  1. API key management: Store both OpenAI and MCP credentials securely
  2. Input validation: Validate function call arguments before passing to MCP
  3. Rate limiting: Both OpenAI and your MCP server should have rate limits
  4. Audit logging: Log all tool calls for security review

For comprehensive security guidance, see our security fundamentals tutorial.

Conclusion

Bridging MCP with OpenAI's ecosystem allows you to build once and use your tools across multiple AI platforms. The MCP-OpenAI bridge translates between protocols seamlessly, letting you maintain a single set of MCP tools while supporting both Claude and ChatGPT clients.

Explore our clients directory for more integration options, or learn about VS Code integration for editor-based workflows.

Code Examples

MCP-to-OpenAI Tool Translationtypescript
// Convert MCP tool definitions to OpenAI function format
function mcpToolsToOpenAI(mcpTools: McpTool[]): OpenAI.ChatCompletionTool[] {
  return mcpTools.map((tool) => ({
    type: 'function' as const,
    function: {
      name: tool.name,
      description: tool.description || '',
      parameters: tool.inputSchema as Record<string, unknown>,
    },
  }));
}
Python Bridge Usagepython
import asyncio
from mcp_openai_bridge import MCPOpenAIBridge

async def main():
    bridge = MCPOpenAIBridge()
    await bridge.connect("python", ["my_server.py"])

    # The bridge translates OpenAI function calls to MCP tool calls
    response = await bridge.chat("Search for Python files in my project")
    print(response)

    await bridge.disconnect()

asyncio.run(main())
Remote MCP Server Connectiontypescript
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const client = new Client({ name: 'openai-bridge', version: '1.0.0' });
const transport = new StreamableHTTPClientTransport(
  new URL('https://mcp-server.example.com/mcp')
);
await client.connect(transport);

const tools = await client.listTools();
console.log('Available MCP tools:', tools.tools.map(t => t.name));

Key Takeaways

  • MCP tools can be bridged to OpenAI function calling with a translation layer
  • Both TypeScript and Python bridges are straightforward to implement
  • MCP tool schemas map almost directly to OpenAI function schemas
  • Multi-turn conversations require maintaining message history across tool calls
  • The bridge pattern lets you maintain one set of tools for multiple AI platforms

Troubleshooting

OpenAI rejects the function schema from MCP

Some MCP schemas use JSON Schema features that OpenAI does not support, such as recursive definitions or certain format keywords. Simplify the schema by removing unsupported keywords or flattening nested structures.

Tool call results are truncated in ChatGPT responses

OpenAI has context window limits. If MCP tool responses are very large, truncate or summarize them before returning. Consider returning only the most relevant portion of large results.

Bridge hangs when connecting to MCP server

Ensure your MCP server is running and accessible. For stdio transport, verify the command and arguments are correct. For HTTP transport, check that the URL is reachable and the server supports Streamable HTTP.

Next Steps

  • Build a bridge for your existing MCP servers
  • Explore VS Code integration for editor-based MCP workflows
  • Deploy your MCP server remotely for broader access
  • Add authentication for production bridge deployments

Was this helpful?

Share tutorial:

Stay Updated with MCP Insights

Join 5,000+ developers and get weekly insights on MCP development, new server releases, and implementation strategies delivered to your inbox.

We respect your privacy. Unsubscribe at any time.

MCPgee Team

We write in-depth guides, tutorials, and reviews to help developers get the most out of the Model Context Protocol ecosystem.

Frequently Asked Questions

Explore MCP Servers

Browse our directory of 33,000+ MCP servers. Find the perfect tools for your AI-powered workflows.