Skip to content

Keon-Systems/keon-mcp-gateway

Repository files navigation

Keon MCP Gateway

Governed MCP gateway for adding policy enforcement, tenant binding, receipts, and durable memory hooks to AI tool execution with low client adoption friction.

Use it in one of two ways:

  • Official MCP over Streamable HTTP at POST /mcp
  • Official MCP over stdio with --stdio

What This Is

This service fronts the Keon Runtime Gateway and exposes a governed tool surface that:

  • validates JWTs or gateway API keys
  • binds tenant_id and actor_id fail-closed
  • enforces per-tool scopes
  • requires Decide before any Execute
  • validates legacy request and response envelopes against contracts/mcp_gateway.v1.schema.json
  • emits durable ingress spine receipts for directive, intent, and terminal outcome
  • preserves the canonical governed envelope in MCP structuredContent

This is not only a custom HTTP adapter anymore. It now exposes standard MCP methods so existing MCP-capable clients can connect directly.

Transport Support

Use case Transport Path / Mode Status
Remote MCP client Streamable HTTP POST /mcp Supported
Local MCP client stdio --stdio Supported
SSE stream from GET /mcp Streamable HTTP optional feature GET /mcp Not emitted yet

Implemented MCP Methods

  • initialize
  • notifications/initialized
  • ping
  • tools/list
  • tools/call

Supported MCP Protocol Versions

  • 2025-11-25
  • 2025-06-18
  • 2025-03-26

For browser-based HTTP clients, non-loopback origins are blocked by default. Add explicit origins to McpServer:AllowedOrigins when you want browser access outside localhost.

Tool Surface

  • keon.governed.execute.v1
  • keon.launch.hardening.v1

Over MCP:

  • tools/list returns standard MCP tool descriptors
  • tools/call returns summary text in content
  • the full governed Keon envelope is preserved in structuredContent
  • failures return isError=true

Over the legacy HTTP surface:

  • request and response bodies use the canonical Keon schema envelope

Quickstart

Prerequisites

  • .NET 10 SDK
  • Python if you want to use the included JWT demo helper

Build and Test

dotnet restore tests\Keon.McpGateway.Tests\Keon.McpGateway.Tests.csproj
dotnet test tests\Keon.McpGateway.Tests\Keon.McpGateway.Tests.csproj
dotnet build src\Keon.McpGateway\Keon.McpGateway.csproj

Default Local URL

http://localhost:5000

First Successful MCP Call In 2 Minutes

This path uses a bearer token. For JWT mode, tenant_id and actor_id can be derived from claims, so you do not need extra MCP headers.

1. Create a local dev token and keypair

Install the Python dependencies once:

pip install pyjwt cryptography

Mint a token and keypair:

$auth = python .\examples\demo_jwt.py `
  --tenant-id tnt_123 `
  --actor-id usr_456 `
  --scopes keon:mcp:list keon:mcp:invoke keon:execute `
  --private-key "$env:TEMP\keon-mcp-private.pem" `
  --public-key "$env:TEMP\keon-mcp-public.pem" | ConvertFrom-Json

$env:Auth__JwtPublicKeyPem = Get-Content -Raw $auth.public_key
$env:KEON_MCP_BEARER_TOKEN = $auth.token

2. Run the gateway

dotnet run --project src\Keon.McpGateway\Keon.McpGateway.csproj

3. Initialize the MCP session

$headers = @{
  Authorization = "Bearer $env:KEON_MCP_BEARER_TOKEN"
}

Invoke-RestMethod `
  -Uri "http://localhost:5000/mcp" `
  -Method Post `
  -Headers $headers `
  -ContentType "application/json" `
  -Body '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","clientInfo":{"name":"manual-smoke","version":"1.0.0"},"capabilities":{}}}'

4. List tools

$headers["MCP-Protocol-Version"] = "2025-06-18"

Invoke-RestMethod `
  -Uri "http://localhost:5000/mcp" `
  -Method Post `
  -Headers $headers `
  -ContentType "application/json" `
  -Body '{"jsonrpc":"2.0","id":"tools-list","method":"tools/list","params":{}}'

5. Call a governed tool

Invoke-RestMethod `
  -Uri "http://localhost:5000/mcp" `
  -Method Post `
  -Headers $headers `
  -ContentType "application/json" `
  -Body '{
    "jsonrpc":"2.0",
    "id":"call-1",
    "method":"tools/call",
    "params":{
      "name":"keon.governed.execute.v1",
      "arguments":{
        "purpose":"Summarize recent sent emails for weekly status update",
        "action":"summarize",
        "resource":{"type":"email","scope":"mailbox:sent"},
        "params":{"window_days":7,"max_items":25},
        "mode":"decide_then_execute"
      }
    }
  }'

The MCP result will contain:

  • human-readable summary text in result.content
  • the canonical governed envelope in result.structuredContent
  • decision, receipts, and result payloads suitable for memory and audit handling

Use With Existing MCP Clients

Streamable HTTP Client Config

Use this when your client supports remote MCP servers over HTTP.

{
  "type": "streamable-http",
  "url": "http://localhost:5000/mcp",
  "headers": {
    "Authorization": "Bearer <token>",
    "MCP-Protocol-Version": "2025-06-18"
  }
}

Notes:

  • For JWT mode, tenant_id and actor_id can be derived from token claims.
  • For API key mode, include X-Api-Key, X-Keon-Tenant-Id, and X-Keon-Actor-Id.
  • For browser clients, configure McpServer:AllowedOrigins.

stdio Client Config

Use this when your client prefers launching a local MCP server process.

Build first:

dotnet build src\Keon.McpGateway\Keon.McpGateway.csproj

Then point the client at the built DLL:

{
  "type": "stdio",
  "command": "dotnet",
  "args": [
    "D:\\Repos\\keon-omega\\keon-mcp-gateway\\src\\Keon.McpGateway\\bin\\Debug\\net10.0\\Keon.McpGateway.dll",
    "--stdio"
  ],
  "env": {
    "KEON_MCP_BEARER_TOKEN": "<token>"
  }
}

Optional stdio environment variables:

  • KEON_MCP_BEARER_TOKEN
  • KEON_MCP_API_KEY
  • KEON_MCP_TENANT_ID
  • KEON_MCP_ACTOR_ID

Notes:

  • In bearer-token mode, tenant_id and actor_id can be derived from token claims.
  • In API-key mode, set KEON_MCP_TENANT_ID and KEON_MCP_ACTOR_ID.
  • Prefer the built DLL or dotnet run --no-build ... -- --stdio so stdout stays clean for the MCP stream.

Auth Modes

Bearer JWT

Use bearer auth when you want tenant and actor identity bound to signed claims.

The gateway enforces:

  • issuer and audience validation
  • signature validation via Auth:JwtPublicKeyPem or Auth:JwksUrl
  • required gateway scopes plus per-tool scopes
  • fail-closed tenant and actor binding

Typical scopes:

  • keon:mcp:list
  • keon:mcp:invoke
  • keon:execute
  • keon:attest

API Key

Use API key auth when you want low-friction service-to-service adoption with gateway-side entitlement checks.

The gateway enforces:

  • key format and secret validation
  • active API key and environment state
  • tenant entitlement state
  • quota checks for governed execution
  • durable usage-event outbox writes on successful invokes

For HTTP MCP clients, send:

  • X-Api-Key
  • X-Keon-Tenant-Id
  • X-Keon-Actor-Id

For stdio MCP clients, set:

  • KEON_MCP_API_KEY
  • KEON_MCP_TENANT_ID
  • KEON_MCP_ACTOR_ID

Governance And Memory Lifecycle

Every governed invoke follows the same core path:

  1. Accept request and bind tenant and actor
  2. Emit directive
  3. Emit intent
  4. Ask runtime to Decide
  5. If approved and requested, call Execute
  6. Emit terminal outcome

Receipt keys are stable in the canonical envelope. Values are nullable when the lifecycle stage did not occur or a reference is unavailable:

  • directive
  • intent
  • request when execute is attempted and request emission occurred
  • decision
  • execution when execute succeeds
  • outcome once terminal outcome emission succeeds
  • evidence_pack when available

This is the basis for governance and memory:

  • the agent gets a standard MCP result
  • the application gets structured receipts and terminal status
  • the platform gets durable memory hooks and audit material

Example MCP Tool Result

tools/call returns a standard MCP result. The governed Keon envelope is preserved inside structuredContent.

{
  "jsonrpc": "2.0",
  "id": "call-1",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "keon.governed.execute.v1 completed with decision approved. Receipts are available in structuredContent."
      }
    ],
    "structuredContent": {
      "correlation_id": "c01J9Z8Q6X4J5Y2P9H3K8M7N6",
      "tool": "keon.governed.execute.v1",
      "ok": true,
      "decision": {
        "status": "approved",
        "policy_hash": "sha256:9c1af02e"
      },
      "result": {
        "summary": "done"
      },
      "receipts": {
        "directive": "rcpt_dir_01J9Z9",
        "intent": "rcpt_int_01J9Z9",
        "request": "rcpt_req_01J9Z9",
        "decision": "rcpt_dec_01J9Z9",
        "execution": "rcpt_exe_01J9Z9",
        "outcome": "rcpt_out_01J9Z9",
        "evidence_pack": null
      }
    },
    "isError": false
  }
}

Legacy Compatibility Surface

The original HTTP endpoints are still available for systems that already integrated the canonical Keon envelope directly.

POST /mcp/tools/list

Request schema: ToolsListRequest

Response schema: ToolsListResponse

POST /mcp/tools/invoke

Request schema: ToolsInvokeRequest

Response schema: ToolsInvokeResponse

See contracts/README.md and contracts/mcp_gateway.v1.schema.json.

Configuration

src/Keon.McpGateway/appsettings.json

{
  "Runtime": {
    "BaseUrl": "http://localhost:8080",
    "TimeoutSeconds": 5,
    "MaxRetries": 2
  },
  "ControlPlane": {
    "BaseUrl": "http://localhost:5000",
    "TimeoutSeconds": 3
  },
  "IngressSpine": {
    "Mode": "Off",
    "ConnectionString": "Data Source=ingress-spine.db"
  },
  "RateLimiting": {
    "Enabled": false,
    "PermitLimit": 20,
    "WindowSeconds": 60
  },
  "McpServer": {
    "ServerName": "Keon MCP Gateway",
    "SupportedProtocolVersions": [ "2025-11-25", "2025-06-18", "2025-03-26" ],
    "AllowedOrigins": [],
    "DefaultTenantId": "",
    "DefaultActorId": "",
    "DefaultBearerToken": "",
    "DefaultApiKey": ""
  },
  "Auth": {
    "Issuer": "keon-auth",
    "Audience": "keon-mcp-gateway",
    "JwksUrl": "",
    "JwtPublicKeyPem": "",
    "RequiredScopes": []
  }
}

Important Settings

IngressSpine:Mode

  • Off: no ingress persistence
  • BestEffort: append failures are logged and never block request completion
  • Required: append failures are fail-closed; if directive append fails the runtime is never called

McpServer:AllowedOrigins

  • empty means only non-browser and loopback browser origins are accepted
  • add explicit origins for browser-based remote clients

McpServer:DefaultTenantId and McpServer:DefaultActorId

  • useful for stdio mode when the calling client cannot inject custom MCP headers
  • not required for bearer tokens that already carry tenant_id and actor_id

McpServer:DefaultBearerToken and McpServer:DefaultApiKey

  • intended for local development or tightly controlled launcher environments
  • prefer environment variables over checked-in config for secrets

Point At Keon SaaS Runtime

$env:Runtime__BaseUrl = "https://api.keon.systems"
dotnet run --project src\Keon.McpGateway\Keon.McpGateway.csproj

Point At Enterprise Runtime

$env:Runtime__BaseUrl = "https://keon-runtime.internal"
dotnet run --project src\Keon.McpGateway\Keon.McpGateway.csproj

Docker

Build:

docker build -t keon-mcp-gateway .

Run:

docker run --rm -p 8080:8080 `
  -e Runtime__BaseUrl=http://host.docker.internal:8080 `
  -e Auth__JwtPublicKeyPem="<public-key-pem>" `
  keon-mcp-gateway

Container port:

http://localhost:8080

Health, Rate Limiting, And Operational Notes

  • GET /health verifies the downstream runtime status and returns both gateway and runtime state
  • rate limiting is applied to the public MCP routes when RateLimiting:Enabled=true
  • /mcp currently supports request/response Streamable HTTP; SSE streaming from GET /mcp is not emitted yet
  • stdio mode is intended for local MCP client launches and should be run without extra stdout noise

Examples And Demos

Included examples:

Trust-failure demos:

  • .\examples\demo_trust_failure_runtime_down.ps1
  • .\examples\demo_policy_deny_blocks_summarize.ps1

Expected outcomes:

  • runtime-down path fails closed
  • deny path does not execute
  • ingress spine still records directive -> intent -> outcome

Tool Schemas

Minimal tool metadata and legacy schemas live in:

Test Coverage

The automated suite covers:

  • schema fixture validation for 6 golden payloads
  • approve and execute
  • deny without execute
  • missing scope
  • tenant mismatch
  • runtime unavailable
  • correlation preservation through decide and execute
  • required spine behavior
  • best-effort spine behavior
  • rate limiting
  • MCP initialize
  • MCP tools/list
  • MCP tools/call
  • MCP origin enforcement

Deferred

  • SSE response streaming from GET /mcp
  • MCP resources, prompts, and other non-tool capabilities
  • live spine persistence for Directive, Intent, Request, and Outcome receipts beyond the current SQLite-backed sink
  • real keon.launch.hardening.v1 execution wiring beyond the current governed decision stub
  • remote git hosting and PR publication

CI/CD

Workflows

  • PR validation: .github/workflows/pr-validation.yml
  • Staging deploy: .github/workflows/deploy-staging.yml

Required GitHub Secrets

  • AZURE_CLIENT_ID: Azure federated identity app client ID for GitHub OIDC login
  • AZURE_TENANT_ID: Azure tenant ID for OIDC login
  • AZURE_SUBSCRIPTION_ID: Azure subscription ID used for ACR and Container Apps operations

Required GitHub Repository Variables

  • ACR_NAME: Azure Container Registry name without FQDN
  • ACR_LOGIN_SERVER: ACR login server, for example myregistry.azurecr.io
  • ACR_IMAGE_REPOSITORY: image repo path in ACR, for example keon-mcp-gateway
  • ACA_RESOURCE_GROUP: resource group containing the Container App
  • ACA_APP_NAME: Azure Container App name for staging
  • STAGING_HEALTHCHECK_URL: optional override for the health URL

Branch Protection And Required Checks

Protect main and require:

  • Restore, Build, Test, Python Smoke

Recommended:

  • require pull requests before merge
  • require linear history or squash merge
  • restrict direct pushes to main
  • require status checks before merge

Fork PR Safety

  • staging deploy does not run on pull requests
  • deploy runs only on protected main or manual workflow_dispatch against protected main
  • staging environment approvals can gate deployment

Break-Glass Rollback Runbook

  1. Identify the last known-good image tag.
  2. Authenticate to Azure with least-privilege operator credentials.
  3. Roll back the Container App image.
  4. Run smoke checks against /health.
  5. Capture rollback details in incident notes.

About

MCP gateway for Keon Systems backend

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors