How To Access Local MCP Servers Through a Secure Tunnel
MCP servers are excellent candidates for exposing data from traditional databases as context to large language models (LLMs). Since these MCP servers reside on-premises closer to the data sources, we need a mechanism to expose them to remote LLMs and agents.
In this tutorial, we will explore how to utilize Ngrok to securely expose MCP servers to hosted LLMs. We will first build an MCP server based on streamable HTTP transport that returns data. We will then expose it through an Ngrok tunnel, and finally create an MCP client based on the Anthropic Claude API.
The diagram below explains this approach:

Step 1: Create an MCP Server
We will create a simple MCP server that returns employee data in JSON. This can be easily replaced with a database query to fetch the data from an existing database. Install the latest FastMCP Python module before proceeding.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
from fastmcp import FastMCP mcp = FastMCP("Employee Server") @mcp.tool() def get_employees() -> list: """ Returns a list of 5 employee records. Each record is a dictionary containing: - id: Unique identifier for the employee - name: Employee's name - role: Employee's job title """ #You can access any internal datasource here instead of sending dummy data return [ {"id": 1, "name": "Alice", "role": "Engineer"}, {"id": 2, "name": "Bob", "role": "Designer"}, {"id": 3, "name": "Charlie", "role": "Manager"}, {"id": 4, "name": "Diana", "role": "Analyst"}, {"id": 5, "name": "Eve", "role": "Intern"}, ] if __name__ == "__main__": mcp.run(transport="streamable-http") |
Notice that we are using streamable HTTP as the transport.
Run the MCP server and ensure it is listening for incoming requests.

Step 2: Expose the MCP Server Through an Ngrok Tunnel
Start by installing Ngrok on your machine. This tutorial shows how to install and run it on macOS. Refer to the Ngrok documentation for other environments.
|
1 |
brew install ngrok |
Get your Authtoken from the Ngrok dashboard and initialize the CLI.
|
1 |
ngrok config add-authtoken $YOUR_AUTHTOKEN |
We are now ready to open a secure tunnel to expose our MCP server.
|
1 |
ngrok http http://localhost:8000 |

Our MCP server is now accessible at the URL exposed by Ngrok. In my case, it is https://c45c-49-205-249-147.ngrok-free.app/mcp. We are now ready to build the MCP client that talks to this endpoint.
Step 3: Building an MCP Client with Claude API
The Claude API, which is in beta, supports invoking tools exposed by an MCP server directly within the request. Refer to the documentation on the latest API specification.
The code below demonstrates how to access remote MCP servers from Claude’s API. It assumes you have the API key from Anthropic and have installed the required Python modules.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
import anthropic client = anthropic.Anthropic( api_key="YOUR_ANTHROPIC_API_KEY", default_headers={ "anthropic-beta": "mcp-client-2025-04-04" } ) response = client.beta.messages.create( model="claude-sonnet-4-20250514", max_tokens=1000, messages=[ { "role": "user", "content": "Who is the intern within the organization?" } ], mcp_servers=[ { "type": "url", "url": "https://c45c-49-205-249-147.ngrok-free.app/mcp", "name": "emp-server", } ] ) blocks=response.content #print(blocks) final_output = None for block in reversed(blocks): if hasattr(block, 'text'): final_output = block.text break print(final_output) |
The code is self-explanatory, as it points to the remote MCP server via the API. The prompt is about identifying the intern within the company, and this forces Claude to invoke the tools within the MCP server.

Claude successfully identified the intern and showed the details of the employee. This confirms that it is indeed accessing the remote MCP server.
In the upcoming tutorials of this series, we will explore how to implement OAuth to secure MCP servers. Stay tuned.