Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Build and Publish Gateway Container Image

on:
workflow_dispatch:
push:
branches: [main]

jobs:
build-and-push-image:
runs-on: ubuntu-latest

permissions:
contents: read
packages: write
attestations: write
id-token: write

steps:
- name: Checkout source
uses: actions/checkout@v3

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.x'

- name: Restore dependencies
run: dotnet restore dotnet/Microsoft.McpGateway.sln --runtime linux-x64

- name: Publish and push the container image
run: dotnet publish dotnet/Microsoft.McpGateway.Service/src/Microsoft.McpGateway.Service.csproj --configuration Release --no-restore /p:PublishProfile=github.pubxml /p:ContainerRepository=${{ github.repository }}
238 changes: 191 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

**MCP Gateway** is a reverse proxy and management layer for [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) servers, enabling scalable, session-aware routing and lifecycle management of MCP servers in Kubernetes environments.

## Table of Contents

- [Overview](#overview)
- [Key Concepts](#key-concepts)
- [Architecture](#architecture)
- [Features](#features)
- [Getting Started – Local Deployment](#getting-started---local-deployment)
- [Getting Started – Cloud Deployment (Azure)](#getting-started---cloud-deployment-azure)

## Overview

This project provides:
Expand Down Expand Up @@ -80,73 +89,208 @@ flowchart LR
- Stateless reverse proxy with a distributed session store (production mode).
- Kubernetes-native deployment using StatefulSets and headless services.

## 🚀 Getting Started
## Getting Started - Local Deployment

1. **Prepare Local Development Environment**
- [Install .NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
- [Install Docker Desktop](https://docs.docker.com/desktop/)
- [Install and turn on Kubernetes](https://docs.docker.com/desktop/features/kubernetes/#install-and-turn-on-kubernetes)
### 1. **Prepare Local Development Environment**
- [Install .NET 8 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0)
- [Install Docker Desktop](https://docs.docker.com/desktop/)
- [Install and turn on Kubernetes](https://docs.docker.com/desktop/features/kubernetes/#install-and-turn-on-kubernetes)

2. **Run Local Docker Registry**
### 2. **Run Local Docker Registry**
```sh
docker run -d -p 5000:5000 --name registry registry:2.7
```

3. **Build & Publish MCP Server Images**
Build and push the MCP server images to your local registry (`localhost:5000`).
```sh
docker build -f mcp-example-server/Dockerfile mcp-example-server -t localhost:5000/mcp-example:1.0.0
docker push localhost:5000/mcp-example:1.0.0
```
### 3. **Build & Publish MCP Server Images**
Build and push the MCP server images to your local registry (`localhost:5000`).
```sh
docker build -f mcp-example-server/Dockerfile mcp-example-server -t localhost:5000/mcp-example:1.0.0
docker push localhost:5000/mcp-example:1.0.0
```

4. **Build & Publish MCP Gateway**
(Optional) Open `dotnet/Microsoft.McpGateway.sln` with Visual Studio.
### 4. **Build & Publish MCP Gateway**
(Optional) Open `dotnet/Microsoft.McpGateway.sln` with Visual Studio.

Publish the MCP Gateway image by right-clicking `Publish` on `Microsoft.McpGateway.Service` in Visual Studio, or run:
```sh
dotnet publish dotnet/Microsoft.McpGateway.Service/src/Microsoft.McpGateway.Service.csproj -c Release /p:PublishProfile=localhost_5000.pubxml
```
Publish the MCP Gateway image by right-clicking `Publish` on `Microsoft.McpGateway.Service` in Visual Studio, or run:
```sh
dotnet publish dotnet/Microsoft.McpGateway.Service/src/Microsoft.McpGateway.Service.csproj -c Release /p:PublishProfile=localhost_5000.pubxml
```

5. **Deploy MCP Gateway to Kubernetes Cluster**
Apply the deployment manifests:
```sh
kubectl apply -f k8s/deployment.yml
### 5. **Deploy MCP Gateway to Kubernetes Cluster**
Apply the deployment manifests:
```sh
kubectl apply -f deployment/k8s/local-deployment.yml
```

### 6. **Enable Port Forwarding**
Forward the gateway service port:
```sh
kubectl port-forward -n adapter svc/mcpgateway-service 8000:8000
```

### 7. **Test the API**

- Import the OpenAPI definition from `openapi/mcp-gateway.openapi.json` into tools like [Postman](https://www.postman.com/), [Bruno](https://www.usebruno.com/), or [Swagger Editor](https://editor.swagger.io/).

- Send a request to create a new adapter resource:
```http
POST http://localhost:8000/adapters
Content-Type: application/json
```
```json
{
"name": "mcp-example",
"imageName": "mcp-example",
"imageVersion": "1.0.0",
"description": "test"
}
```

6. **Enable Port Forwarding**
Forward the gateway service port:
- After deploying the MCP server, use a client like [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) to test the connection.

To connect to the deployed `mcp-example` server, use:
- `http://localhost:8000/adapters/mcp-example/mcp` (Streamable HTTP)

For other servers:
- `http://localhost:8000/adapters/{name}/mcp` (Streamable HTTP)
- `http://localhost:8000/adapters/{name}/sse` (SSE)

### 8. **Clean the Environment**
To remove all deployed resources, delete the Kubernetes namespace:
```sh
kubectl port-forward -n adapter svc/mcpgateway-service 8000:8000
kubectl delete namespace adapter
```

7. **Test the API**
## Getting Started - Cloud Deployment (Azure)

- Import the OpenAPI definition from `openapi/mcp-gateway.openapi.json` into tools like [Postman](https://www.postman.com/), [Bruno](https://www.usebruno.com/), or [Swagger Editor](https://editor.swagger.io/).
### Cloud Infrastructure
![Architecture Diagram](infra-diagram.png)

- Send a POST request to `http://localhost:8000/adapters` to create a new adapter resource:
```json
{
"name": "mcp-example",
"imageName": "mcp-example",
"imageVersion": "1.0.0",
"description": "test"
}
```
### 1. Prepare Cloud Development Environment
- An active [Azure subscription](https://azure.microsoft.com) with **Owner** access
- [Install Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli)
- [Install Docker Desktop](https://docs.docker.com/desktop/)

- After deploying the MCP server, use a client like [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) to test the connection.
### 2. Setup Entra ID (Azure Active Directory)
The cloud-deployed service needs authentication. Here we configure the basic bearer token authentication using Azure Entra ID.
- Go to [App Registrations](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade)
- Create a **single-tenant** app registration
- Add a platform - Mobile and desktop applications
- Under **Redirect URIs**, add: `http://localhost`
- Copy the **Application (client) ID** and **Directory (tenant) ID** from the overview page

To connect to the deployed `mcp-example` server, use:
- `http://localhost:8000/adapters/mcp-example/mcp` (Streamable HTTP)
### 3. Deploy Infrastructure Resources

For other servers:
- `http://localhost:8000/adapters/{name}/mcp` (Streamable HTTP)
- `http://localhost:8000/adapters/{name}/sse` (SSE)
Run the deployment script:

8. **Clean up the environment**
To remove all deployed resources, delete the Kubernetes namespace:
```sh
kubectl delete namespace adapter
```
```sh
deployment/azure-deploy.ps1 -ResourceGroupName <resourceGroupName> -ClientId <appClientId> -Location <azureLocation>
```

**Parameters:**

| Name | Description |
|--------------------|-------------------------------------------------------|
| `ResourceGroupName`| All lowercase, letters and numbers only |
| `ClientId` | Client ID from your app registration |
| `Location` | Azure region (default: `westus3`) |

This script will:
- Create a resource group named `<resourceGroupName>`
- Deploy Azure infrastructure via Bicep templates

| Resource Name | Resource Type |
|-----------------------------------|-----------------------------|
| acr\<resourceGroupName> | Container Registry |
| cosmos\<resourceGroupName> | Azure Cosmos DB Account |
| mg-aag-\<resourceGroupName> | Application Gateway |
| mg-ai-\<resourceGroupName> | Application Insights |
| mg-aks-\<resourceGroupName> | Kubernetes Service (AKS) |
| mg-identity-\<resourceGroupName> | Managed Identity |
| mg-pip-\<resourceGroupName> | Public IP Address |
| mg-vnet-\<resourceGroupName> | Virtual Network |

- Deploy Kubernetes resources (including `mcp-gateway`) to the provisioned AKS cluster

> **Note:** It's recommended to use Managed Identity for credential-less authentication. This deployment follows that design.

### 4. Build & Publish MCP Server Images
The gateway service pulls the MCP server image from the newly provisioned Azure Container Registry (ACR) during deployment.

Build and push the MCP server image to ACR:
> **Note:** Ensure that Docker Engine is running before proceeding.

```sh
az acr login -n acr<resourceGroupName>
docker build -f mcp-example-server/Dockerfile mcp-example-server -t acr<resourceGroupName>.azurecr.io/mcp-example:1.0.0
docker push acr<resourceGroupName>.azurecr.io/mcp-example:1.0.0
```

### 5. Test the API

- Import the OpenAPI spec from `openapi/mcp-gateway.openapi.json` into [Postman](https://www.postman.com/), [Bruno](https://www.usebruno.com/), or [Swagger Editor](https://editor.swagger.io/)

- Acquire a bearer token using this python script locally:
```sh
pip install azure-identity
```
```python
from azure.identity import InteractiveBrowserCredential
tenant_id = "<your-tenant-id>"
client_id = "<your-client-id>"
credential = InteractiveBrowserCredential(tenant_id=tenant_id, client_id=client_id)
access_token = credential.get_token(f"{client_id}/.default").token
print(access_token)
```

- Send a POST request to create an adapter resource:
```http
POST http://<resourceGroupName>.<location>.cloudapp.azure.com/adapters
Authorization: Bearer <token>
Content-Type: application/json
```
```json
{
"name": "mcp-example",
"imageName": "mcp-example",
"imageVersion": "1.0.0",
"description": "test"
}
```

- After deploying the MCP server, use a client like [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) to test the connection.
> **Note:** A valid bearer token is still required in the Authorization header when connecting to the server.

- To connect to the deployed `mcp-example` server, use:
- `http://<resourceGroupName>.<location>.cloudapp.azure.com/adapters/mcp-example/mcp` (Streamable HTTP)

- For other servers:
- `http://<resourceGroupName>.<location>.cloudapp.azure.com/adapters/{name}/mcp` (Streamable HTTP)
- `http://<resourceGroupName>.<location>.cloudapp.azure.com/adapters/{name}/sse` (SSE)

### 6. Clean the Environment
To remove all deployed resources, delete the Azure resource group:
```sh
az group delete --name <resourceGroupName> --yes
```

## 7. Production Onboarding (Follow-up)

- **TLS Configuration**
Set up HTTPS on Azure Application Gateway (AAG) listener using valid TLS certificates.

- **Network Security**
Restrict incoming traffic within the virtual network and configure Private Endpoints for enhanced network security.

- **Telemetry**
Enable advanced telemetry, detailed metrics, and alerts to support monitoring and troubleshooting in production.

- **Scaling**
Adjust scaling for `mcp-gateway` services and MCP servers based on expected load.

- **Authentication & Authorization**
Set up OAuth 2.0 with Azure Entra ID (AAD) for authentication.
Implement fine-grained access control using RBAC or custom ACLs for `adapter` level permissions.

## Contributing

Expand Down
45 changes: 45 additions & 0 deletions deployment/azure-deploy.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
param(
[Parameter(Mandatory=$true)]
[ValidateScript({
if ($_ -match '^[a-z0-9]+$') {
$true
} else {
throw "ResourceGroupName must contain only lowercase letters and numbers (no spaces or special characters)."
}
})]
[string]$ResourceGroupName,

[Parameter(Mandatory=$true)]
[ValidateScript({
if ($_ -match '^[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$') {
$true
} else {
throw "ClientId must be a valid GUID in the format 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'."
}
})]
[string]$ClientId,

[string]$Location = "westus3"
)

$TenantId = az account show --query tenantId --output tsv

# Create Resource Group
az group create --name "$ResourceGroupName" --location "$Location"

# Deploy Bicep
$deployment = az deployment group create `
--resource-group $ResourceGroupName `
--template-file deployment/infra/azure-deployment.bicep | ConvertFrom-Json

# Set cloud-deployment.yml
(Get-Content -Raw "deployment/k8s/cloud-deployment-template.yml") `
-replace "{IDENTIFIER}", $ResourceGroupName `
-replace "{TENANT_ID}", $TenantId `
-replace "{CLIENT_ID}", $ClientId `
-replace "{AZURE_CLIENT_ID}", $deployment.properties.outputs.identityClientId.value `
-replace "{APPINSIGHTS_CONNECTION_STRING}", $deployment.properties.outputs.applicationInsightConnectionString.value `
| Set-Content "deployment/k8s/cloud-deployment.yml"

# Deploy to AKS
az aks command invoke -g $ResourceGroupName -n mg-aks-"$ResourceGroupName" --command "kubectl apply -f cloud-deployment.yml" --file deployment/k8s/cloud-deployment.yml
Loading