From 76975bc02899d8c6c6ac58731ddc092421239f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6rdes?= Date: Fri, 22 May 2026 10:54:51 +0200 Subject: [PATCH 1/5] add mcp server tutorial --- .../tutorials/ai/mcp/10-MCP-Server.yaml | 24 ++++++++++ .../ai/mcp/20-MCP-Server-Protected.yaml | 46 +++++++++++++++++++ distribution/tutorials/ai/mcp/README.md | 45 ++++++++++++++++++ .../tutorials/ai/mcp/generate-traffic.cmd | 38 +++++++++++++++ .../tutorials/ai/mcp/generate-traffic.sh | 37 +++++++++++++++ 5 files changed, 190 insertions(+) create mode 100644 distribution/tutorials/ai/mcp/10-MCP-Server.yaml create mode 100644 distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml create mode 100644 distribution/tutorials/ai/mcp/README.md create mode 100644 distribution/tutorials/ai/mcp/generate-traffic.cmd create mode 100644 distribution/tutorials/ai/mcp/generate-traffic.sh diff --git a/distribution/tutorials/ai/mcp/10-MCP-Server.yaml b/distribution/tutorials/ai/mcp/10-MCP-Server.yaml new file mode 100644 index 0000000000..2b6b89e8c7 --- /dev/null +++ b/distribution/tutorials/ai/mcp/10-MCP-Server.yaml @@ -0,0 +1,24 @@ +# Exposes the Membrane MCP server directly on http://localhost:2000. +# Claude Desktop connects to this endpoint through mcp-remote. +api: + port: 2000 + name: MCP-Server + flow: + - membraneMCPServer: + # Keep enough recent exchanges so the AI can inspect traffic generated + # through the demo APIs below. + maxExchanges: 10000 + +--- +api: + port: 3000 + name: Fruit-Shop-Demo + target: + url: https://api.predic8.de/shop/v2/ + +--- +api: + port: 3001 + name: ApiBin-Demo + target: + url: https://apibin.io/ \ No newline at end of file diff --git a/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml b/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml new file mode 100644 index 0000000000..c28a81eaae --- /dev/null +++ b/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml @@ -0,0 +1,46 @@ +# Claude Desktop still connects to http://localhost:2000. +# This local proxy adds the API key before forwarding requests to the protected MCP server. +api: + port: 2000 + name: MCP-Proxy + flow: + - setHeader: + name: Authorization + value: apikey 12345 + target: + url: http://localhost:8080/mcp + +--- +# The real MCP server is protected by the apiKey interceptor. +# The expected header value is: Authorization: apikey 12345 +api: + port: 8080 + name: MCP-Server-Protected + path: + uri: /mcp + flow: + - apiKey: + stores: + - simple: + - secret: + value: apikey 12345 + extractors: + - header: Authorization + - membraneMCPServer: + # Keep enough recent exchanges so the AI can inspect traffic generated + # through the demo APIs below. + maxExchanges: 10000 + +--- +api: + port: 3000 + name: Fruit-Shop-Demo + target: + url: https://api.predic8.de/shop/v2/ + +--- +api: + port: 3001 + name: ApiBin-Demo + target: + url: https://apibin.io/ \ No newline at end of file diff --git a/distribution/tutorials/ai/mcp/README.md b/distribution/tutorials/ai/mcp/README.md new file mode 100644 index 0000000000..66f3bd34ae --- /dev/null +++ b/distribution/tutorials/ai/mcp/README.md @@ -0,0 +1,45 @@ +# Membrane MCP Server Example + +Start one of the example configs: + +- `10-MCP-Server.yaml` exposes the MCP server directly. +- `20-MCP-Server-Protected.yaml` protects the MCP server with an API key and uses a local proxy for Claude Desktop. + +Generate some traffic first: + +```bash +./generate-traffic.sh 20 +``` +On Windows: + +```shell +generate-traffic.cmd 20 +``` + +# Setup MCP for Claude Desktop + +1. Open Claude Desktop +2. Open `Settings` -> `Developer` +3. Under `Local MCP servers` click on `Edit Config` +4. Open `claude_desktop_config.json` and paste the following: + +```json +{ + "mcpServers": { + "membrane": { + "command": "npx", + "args": [ + "mcp-remote", + "http://localhost:2000" + ] + } + }, + "preferences": { + "coworkScheduledTasksEnabled": true, + "ccdScheduledTasksEnabled": true, + "sidebarMode": "task", + "coworkWebSearchEnabled": true + } +} +``` + diff --git a/distribution/tutorials/ai/mcp/generate-traffic.cmd b/distribution/tutorials/ai/mcp/generate-traffic.cmd new file mode 100644 index 0000000000..6a4969853a --- /dev/null +++ b/distribution/tutorials/ai/mcp/generate-traffic.cmd @@ -0,0 +1,38 @@ +@echo off +setlocal EnableExtensions EnableDelayedExpansion + +if "%FRUIT_BASE%"=="" set FRUIT_BASE=http://localhost:3000 +if "%APIBIN_BASE%"=="" set APIBIN_BASE=http://localhost:3001 + +if "%~1"=="" ( + set ROUNDS=20 +) else ( + set ROUNDS=%~1 +) + +for /L %%i in (1,1,%ROUNDS%) do ( + call :request GET "%FRUIT_BASE%/products/" + call :request GET "%FRUIT_BASE%/products/4" + call :request GET "%FRUIT_BASE%/categories/" + + call :request GET "%APIBIN_BASE%/analyze?round=%%i&delay=50" + call :request GET "%APIBIN_BASE%/faker?profile=order&count=2&locale=de-DE&seed=%%i" + call :request POST "%APIBIN_BASE%/echo" "hello apibin round %%i" +) + +exit /b 0 + +:request +set METHOD=%~1 +set URL=%~2 +set BODY=%~3 + +echo %METHOD% %URL% + +if "%BODY%"=="" ( + curl -sS -o NUL -w " -^> %%{http_code}\n" -X %METHOD% "%URL%" +) else ( + curl -sS -o NUL -w " -^> %%{http_code}\n" -X %METHOD% "%URL%" -H "Content-Type: text/plain" --data "%BODY%" +) + +exit /b 0 \ No newline at end of file diff --git a/distribution/tutorials/ai/mcp/generate-traffic.sh b/distribution/tutorials/ai/mcp/generate-traffic.sh new file mode 100644 index 0000000000..63814de68a --- /dev/null +++ b/distribution/tutorials/ai/mcp/generate-traffic.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env sh +set -eu + +FRUIT_BASE="${FRUIT_BASE:-http://localhost:3000}" +APIBIN_BASE="${APIBIN_BASE:-http://localhost:3001}" +ROUNDS="${1:-20}" + +request() { + method="$1" + url="$2" + body="${3:-}" + + printf "%s %s" "$method" "$url" + + if [ -n "$body" ]; then + curl -sS -o /dev/null -w " -> %{http_code}\n" \ + -X "$method" "$url" \ + -H "Content-Type: text/plain" \ + --data "$body" || true + else + curl -sS -o /dev/null -w " -> %{http_code}\n" \ + -X "$method" "$url" || true + fi +} + +i=1 +while [ "$i" -le "$ROUNDS" ]; do + request GET "$FRUIT_BASE/products/" + request GET "$FRUIT_BASE/products/4" + request GET "$FRUIT_BASE/categories/" + + request GET "$APIBIN_BASE/analyze?round=$i&delay=50" + request GET "$APIBIN_BASE/faker?profile=order&count=2&locale=de-DE&seed=$i" + request POST "$APIBIN_BASE/echo" "hello apibin round $i" + + i=$((i + 1)) +done \ No newline at end of file From b8961c27b3397d42c7812bbfcd852340887bbf38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6rdes?= Date: Fri, 22 May 2026 11:03:29 +0200 Subject: [PATCH 2/5] improvements --- distribution/tutorials/README.md | 6 +++- .../tutorials/ai/mcp/10-MCP-Server.yaml | 32 +++++++++++++++--- .../ai/mcp/20-MCP-Server-Protected.yaml | 33 ++++++++++++++++--- distribution/tutorials/ai/mcp/README.md | 29 +++++++++------- distribution/tutorials/ai/mcp/membrane.cmd | 24 ++++++++++++++ distribution/tutorials/ai/mcp/membrane.sh | 21 ++++++++++++ distribution/tutorials/ai/mcp/run-docker.cmd | 15 +++++++++ distribution/tutorials/ai/mcp/run-docker.sh | 14 ++++++++ 8 files changed, 152 insertions(+), 22 deletions(-) create mode 100644 distribution/tutorials/ai/mcp/membrane.cmd create mode 100755 distribution/tutorials/ai/mcp/membrane.sh create mode 100644 distribution/tutorials/ai/mcp/run-docker.cmd create mode 100755 distribution/tutorials/ai/mcp/run-docker.sh diff --git a/distribution/tutorials/README.md b/distribution/tutorials/README.md index 8f40e98376..6e0d82988d 100644 --- a/distribution/tutorials/README.md +++ b/distribution/tutorials/README.md @@ -25,6 +25,11 @@ If your APIs use XML as input or output, this tutorial provides useful configura Learn how to use Membrane in more advanced scenarios. Topics include path rewriting, scripting, conditions and more. +## [AI / MCP](ai/mcp) + +Expose Membrane as an MCP server for AI clients, inspect recent API traffic, and protect the MCP endpoint with an API key. + + ## [SOAP Web Services (Legacy)](soap) If you need to integrate legacy SOAP Web Services, this tutorial provides examples and practical guidance. @@ -43,4 +48,3 @@ If you have questions, feedback or run into any issues, we’re happy to help. You can also join the Membrane discussions on GitHub: https://github.com/membrane/api-gateway/discussions - diff --git a/distribution/tutorials/ai/mcp/10-MCP-Server.yaml b/distribution/tutorials/ai/mcp/10-MCP-Server.yaml index 2b6b89e8c7..7b2ae818e3 100644 --- a/distribution/tutorials/ai/mcp/10-MCP-Server.yaml +++ b/distribution/tutorials/ai/mcp/10-MCP-Server.yaml @@ -1,12 +1,34 @@ -# Exposes the Membrane MCP server directly on http://localhost:2000. -# Claude Desktop connects to this endpoint through mcp-remote. +# yaml-language-server: $schema=https://www.membrane-api.io/v7.2.1.json +# +# Tutorial: MCP Server +# +# This configuration exposes the Membrane MCP server directly on +# http://localhost:2000 and starts two demo APIs that generate traffic +# the AI client can inspect through MCP. +# +# 1.) Start Membrane: +# +# Linux/Mac: +# ./membrane.sh -c 10-MCP-Server.yaml +# Windows: +# membrane.cmd -c 10-MCP-Server.yaml +# +# 2.) Generate some demo traffic in a second terminal: +# +# Linux/Mac: +# ./generate-traffic.sh 20 +# Windows: +# generate-traffic.cmd 20 +# +# 3.) Configure Claude Desktop as described in README.md. +# +# 4.) Ask the AI client to inspect recent exchanges or list the available APIs. + api: port: 2000 name: MCP-Server flow: - membraneMCPServer: - # Keep enough recent exchanges so the AI can inspect traffic generated - # through the demo APIs below. maxExchanges: 10000 --- @@ -21,4 +43,4 @@ api: port: 3001 name: ApiBin-Demo target: - url: https://apibin.io/ \ No newline at end of file + url: https://apibin.io/ diff --git a/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml b/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml index c28a81eaae..bfdbabfd65 100644 --- a/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml +++ b/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml @@ -1,5 +1,32 @@ +# yaml-language-server: $schema=https://www.membrane-api.io/v7.2.1.json +# +# Tutorial: Protected MCP Server +# # Claude Desktop still connects to http://localhost:2000. -# This local proxy adds the API key before forwarding requests to the protected MCP server. +# A local proxy adds the API key before forwarding requests to the +# protected MCP server at http://localhost:8080/mcp. +# +# 1.) Start Membrane: +# +# Linux/Mac: +# ./membrane.sh -c 20-MCP-Server-Protected.yaml +# Windows: +# membrane.cmd -c 20-MCP-Server-Protected.yaml +# +# 2.) Generate some demo traffic in a second terminal: +# +# Linux/Mac: +# ./generate-traffic.sh 20 +# Windows: +# generate-traffic.cmd 20 +# +# 3.) Configure Claude Desktop as described in README.md. +# +# 4.) Ask the AI client to inspect recent exchanges. +# +# The client talks to the local proxy on port 2000. +# The protected MCP endpoint remains hidden behind the API key interceptor. + api: port: 2000 name: MCP-Proxy @@ -27,8 +54,6 @@ api: extractors: - header: Authorization - membraneMCPServer: - # Keep enough recent exchanges so the AI can inspect traffic generated - # through the demo APIs below. maxExchanges: 10000 --- @@ -43,4 +68,4 @@ api: port: 3001 name: ApiBin-Demo target: - url: https://apibin.io/ \ No newline at end of file + url: https://apibin.io/ diff --git a/distribution/tutorials/ai/mcp/README.md b/distribution/tutorials/ai/mcp/README.md index 66f3bd34ae..1999d2a3a3 100644 --- a/distribution/tutorials/ai/mcp/README.md +++ b/distribution/tutorials/ai/mcp/README.md @@ -1,27 +1,33 @@ -# Membrane MCP Server Example +# Membrane API Gateway Tutorial - MCP -Start one of the example configs: +This tutorial shows how to expose Membrane API Gateway as an MCP server for AI clients. +It covers: -- `10-MCP-Server.yaml` exposes the MCP server directly. -- `20-MCP-Server-Protected.yaml` protects the MCP server with an API key and uses a local proxy for Claude Desktop. +- running a local MCP server +- inspecting recent API traffic through MCP +- protecting the MCP endpoint with an API key +- forwarding Claude Desktop traffic through a local proxy -Generate some traffic first: +To begin, open [10-MCP-Server.yaml](10-MCP-Server.yaml) and follow the instructions in the file. +Then continue with [20-MCP-Server-Protected.yaml](20-MCP-Server-Protected.yaml). + +If you want to inspect API traffic from Claude Desktop, generate a few sample requests first: + +Linux/macOS: ```bash ./generate-traffic.sh 20 ``` -On Windows: + +Windows: ```shell generate-traffic.cmd 20 ``` -# Setup MCP for Claude Desktop +## Claude Desktop Setup -1. Open Claude Desktop -2. Open `Settings` -> `Developer` -3. Under `Local MCP servers` click on `Edit Config` -4. Open `claude_desktop_config.json` and paste the following: +Open `Settings` -> `Developer`, edit `claude_desktop_config.json`, and add: ```json { @@ -42,4 +48,3 @@ generate-traffic.cmd 20 } } ``` - diff --git a/distribution/tutorials/ai/mcp/membrane.cmd b/distribution/tutorials/ai/mcp/membrane.cmd new file mode 100644 index 0000000000..8d2d64e9cf --- /dev/null +++ b/distribution/tutorials/ai/mcp/membrane.cmd @@ -0,0 +1,24 @@ +@echo off +setlocal EnableExtensions + +set "SCRIPT_DIR=%~dp0" +if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%" + +set "dir=%SCRIPT_DIR%" + +:search_up +if exist "%dir%\LICENSE.txt" if exist "%dir%\scripts\run-membrane.cmd" goto found +for %%A in ("%dir%\..") do set "next=%%~fA" +if /I "%next%"=="%dir%" goto notfound +set "dir=%next%" +goto search_up + +:found +set "MEMBRANE_HOME=%dir%" +set "MEMBRANE_CALLER_DIR=%SCRIPT_DIR%" +call "%MEMBRANE_HOME%\scripts\run-membrane.cmd" %* +exit /b %ERRORLEVEL% + +:notfound +>&2 echo Could not locate Membrane root. Ensure directory structure is correct. +exit /b 1 diff --git a/distribution/tutorials/ai/mcp/membrane.sh b/distribution/tutorials/ai/mcp/membrane.sh new file mode 100755 index 0000000000..b472b6cca5 --- /dev/null +++ b/distribution/tutorials/ai/mcp/membrane.sh @@ -0,0 +1,21 @@ +#!/bin/sh +# Default: ./proxies.xml (next to this script); fallback -> $MEMBRANE_HOME/conf/proxies.xml +# JAVA_OPTS: relative -D paths are auto-resolved against $MEMBRANE_HOME (absolute/URI unchanged). +# Examples: +# export JAVA_OPTS='-Dlog4j.configurationFile=examples/logging/access/log4j2_access.xml' +# export JAVA_OPTS='-Dlog4j.configurationFile=/abs/path/log4j2.xml' + +SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P) + +dir="$SCRIPT_DIR" +while [ "$dir" != "/" ]; do + if [ -f "$dir/LICENSE.txt" ] && [ -f "$dir/scripts/run-membrane.sh" ]; then + export MEMBRANE_HOME="$dir" + export MEMBRANE_CALLER_DIR="$SCRIPT_DIR" + exec sh "$dir/scripts/run-membrane.sh" "$@" + fi + dir=$(dirname "$dir") +done + +echo "Could not locate Membrane root. Ensure directory structure is correct." >&2 +exit 1 diff --git a/distribution/tutorials/ai/mcp/run-docker.cmd b/distribution/tutorials/ai/mcp/run-docker.cmd new file mode 100644 index 0000000000..844911377d --- /dev/null +++ b/distribution/tutorials/ai/mcp/run-docker.cmd @@ -0,0 +1,15 @@ +@echo off +setlocal enabledelayedexpansion + +set "DIR=%~dp0" +set "IMAGE=predic8/membrane:7.2.1" + +for /f "delims=" %%i in ('docker create -p 2000-2010:2000-2010 %IMAGE% %*') do set "CID=%%i" + +set "CLEANUP_CMD=docker rm -f %CID% >nul 2>nul" + +docker cp "%DIR%." "%CID%:/opt/membrane/" >nul +docker start -a "%CID%" + +%CLEANUP_CMD% +endlocal diff --git a/distribution/tutorials/ai/mcp/run-docker.sh b/distribution/tutorials/ai/mcp/run-docker.sh new file mode 100755 index 0000000000..9d342e8614 --- /dev/null +++ b/distribution/tutorials/ai/mcp/run-docker.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -euo pipefail + +DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + +cid="$(docker create -it -p 2000-2010:2000-2010 predic8/membrane:7.2.1 "$@")" + +cleanup() { + docker rm -f "$cid" >/dev/null 2>&1 || true +} +trap cleanup EXIT INT TERM + +docker cp "${DIR}/." "${cid}:/opt/membrane/" +docker start -a "$cid" From 42e40243a7974ea3a85c877f079bf14ecd1fd1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6rdes?= Date: Fri, 22 May 2026 11:08:40 +0200 Subject: [PATCH 3/5] clarify mcp server tutorial details --- .../ai/mcp/20-MCP-Server-Protected.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml b/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml index bfdbabfd65..9ca51f0342 100644 --- a/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml +++ b/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml @@ -2,9 +2,11 @@ # # Tutorial: Protected MCP Server # -# Claude Desktop still connects to http://localhost:2000. -# A local proxy adds the API key before forwarding requests to the -# protected MCP server at http://localhost:8080/mcp. +# This setup keeps the real MCP server protected with an API key while Claude +# Desktop still talks to a simple local endpoint on http://localhost:2000. The +# local proxy adds the Authorization header and forwards the request to the +# actual MCP server on http://localhost:8080/mcp, so the client setup stays +# simple and the protected endpoint is never exposed without authentication. # # 1.) Start Membrane: # @@ -23,9 +25,7 @@ # 3.) Configure Claude Desktop as described in README.md. # # 4.) Ask the AI client to inspect recent exchanges. -# -# The client talks to the local proxy on port 2000. -# The protected MCP endpoint remains hidden behind the API key interceptor. +# From the client perspective, it still uses port 2000. api: port: 2000 @@ -38,8 +38,8 @@ api: url: http://localhost:8080/mcp --- -# The real MCP server is protected by the apiKey interceptor. -# The expected header value is: Authorization: apikey 12345 +# This is the real MCP endpoint. It accepts only requests with +# Authorization: apikey 12345. api: port: 8080 name: MCP-Server-Protected From 8016f82469a7648333351dd83476f64808dc3f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6rdes?= Date: Fri, 22 May 2026 11:32:35 +0200 Subject: [PATCH 4/5] simulate WordPress/PHP probe traffic in MCP tutorials and scripts --- .../tutorials/ai/mcp/10-MCP-Server.yaml | 12 ++++++++++++ .../ai/mcp/20-MCP-Server-Protected.yaml | 12 ++++++++++++ distribution/tutorials/ai/mcp/README.md | 19 ++----------------- .../tutorials/ai/mcp/generate-traffic.cmd | 8 +++++++- .../tutorials/ai/mcp/generate-traffic.sh | 8 +++++++- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/distribution/tutorials/ai/mcp/10-MCP-Server.yaml b/distribution/tutorials/ai/mcp/10-MCP-Server.yaml index 7b2ae818e3..98262e172d 100644 --- a/distribution/tutorials/ai/mcp/10-MCP-Server.yaml +++ b/distribution/tutorials/ai/mcp/10-MCP-Server.yaml @@ -20,6 +20,9 @@ # Windows: # generate-traffic.cmd 20 # +# The script also sends a few typical WordPress/PHP probe requests to a +# local endpoint on port 3002 that always responds with 404. +# # 3.) Configure Claude Desktop as described in README.md. # # 4.) Ask the AI client to inspect recent exchanges or list the available APIs. @@ -44,3 +47,12 @@ api: name: ApiBin-Demo target: url: https://apibin.io/ + +--- +# Simulates common background probe traffic such as WordPress/PHP scans. +api: + port: 3002 + name: 404 Fallback + flow: + - return: + status: 404 diff --git a/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml b/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml index 9ca51f0342..c3b0aba9fd 100644 --- a/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml +++ b/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml @@ -22,6 +22,9 @@ # Windows: # generate-traffic.cmd 20 # +# The script also sends a few typical WordPress/PHP probe requests to a +# local endpoint on port 3002 that always responds with 404. +# # 3.) Configure Claude Desktop as described in README.md. # # 4.) Ask the AI client to inspect recent exchanges. @@ -69,3 +72,12 @@ api: name: ApiBin-Demo target: url: https://apibin.io/ + +--- +# Simulates common background probe traffic such as WordPress/PHP scans. +api: + port: 3002 + name: 404 Fallback + flow: + - return: + status: 404 diff --git a/distribution/tutorials/ai/mcp/README.md b/distribution/tutorials/ai/mcp/README.md index 1999d2a3a3..34f5c09784 100644 --- a/distribution/tutorials/ai/mcp/README.md +++ b/distribution/tutorials/ai/mcp/README.md @@ -8,23 +8,6 @@ It covers: - protecting the MCP endpoint with an API key - forwarding Claude Desktop traffic through a local proxy -To begin, open [10-MCP-Server.yaml](10-MCP-Server.yaml) and follow the instructions in the file. -Then continue with [20-MCP-Server-Protected.yaml](20-MCP-Server-Protected.yaml). - -If you want to inspect API traffic from Claude Desktop, generate a few sample requests first: - -Linux/macOS: - -```bash -./generate-traffic.sh 20 -``` - -Windows: - -```shell -generate-traffic.cmd 20 -``` - ## Claude Desktop Setup Open `Settings` -> `Developer`, edit `claude_desktop_config.json`, and add: @@ -48,3 +31,5 @@ Open `Settings` -> `Developer`, edit `claude_desktop_config.json`, and add: } } ``` + +Start membrane and restart Claude Desktop. \ No newline at end of file diff --git a/distribution/tutorials/ai/mcp/generate-traffic.cmd b/distribution/tutorials/ai/mcp/generate-traffic.cmd index 6a4969853a..687dfb06fb 100644 --- a/distribution/tutorials/ai/mcp/generate-traffic.cmd +++ b/distribution/tutorials/ai/mcp/generate-traffic.cmd @@ -3,6 +3,7 @@ setlocal EnableExtensions EnableDelayedExpansion if "%FRUIT_BASE%"=="" set FRUIT_BASE=http://localhost:3000 if "%APIBIN_BASE%"=="" set APIBIN_BASE=http://localhost:3001 +if "%ATTACK_BASE%"=="" set ATTACK_BASE=http://localhost:3002 if "%~1"=="" ( set ROUNDS=20 @@ -18,6 +19,11 @@ for /L %%i in (1,1,%ROUNDS%) do ( call :request GET "%APIBIN_BASE%/analyze?round=%%i&delay=50" call :request GET "%APIBIN_BASE%/faker?profile=order&count=2&locale=de-DE&seed=%%i" call :request POST "%APIBIN_BASE%/echo" "hello apibin round %%i" + call :request GET "%ATTACK_BASE%/wp-login.php" + call :request GET "%ATTACK_BASE%/xmlrpc.php" + call :request GET "%ATTACK_BASE%/wp-admin/install.php" + call :request GET "%ATTACK_BASE%/.env" + call :request GET "%ATTACK_BASE%/phpinfo.php" ) exit /b 0 @@ -35,4 +41,4 @@ if "%BODY%"=="" ( curl -sS -o NUL -w " -^> %%{http_code}\n" -X %METHOD% "%URL%" -H "Content-Type: text/plain" --data "%BODY%" ) -exit /b 0 \ No newline at end of file +exit /b 0 diff --git a/distribution/tutorials/ai/mcp/generate-traffic.sh b/distribution/tutorials/ai/mcp/generate-traffic.sh index 63814de68a..3193bc47d4 100644 --- a/distribution/tutorials/ai/mcp/generate-traffic.sh +++ b/distribution/tutorials/ai/mcp/generate-traffic.sh @@ -3,6 +3,7 @@ set -eu FRUIT_BASE="${FRUIT_BASE:-http://localhost:3000}" APIBIN_BASE="${APIBIN_BASE:-http://localhost:3001}" +ATTACK_BASE="${ATTACK_BASE:-http://localhost:3002}" ROUNDS="${1:-20}" request() { @@ -32,6 +33,11 @@ while [ "$i" -le "$ROUNDS" ]; do request GET "$APIBIN_BASE/analyze?round=$i&delay=50" request GET "$APIBIN_BASE/faker?profile=order&count=2&locale=de-DE&seed=$i" request POST "$APIBIN_BASE/echo" "hello apibin round $i" + request GET "$ATTACK_BASE/wp-login.php" + request GET "$ATTACK_BASE/xmlrpc.php" + request GET "$ATTACK_BASE/wp-admin/install.php" + request GET "$ATTACK_BASE/.env" + request GET "$ATTACK_BASE/phpinfo.php" i=$((i + 1)) -done \ No newline at end of file +done From 2b6779eb5f3dff69454bfd5de98c58db1c594032 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20G=C3=B6rdes?= Date: Fri, 22 May 2026 12:15:34 +0200 Subject: [PATCH 5/5] remove protected MCP server tutorial and update existing examples --- .../tutorials/ai/mcp/10-MCP-Server.yaml | 24 +++--- .../ai/mcp/20-MCP-Server-Protected.yaml | 83 ------------------- distribution/tutorials/ai/mcp/README.md | 8 +- .../tutorials/ai/mcp/generate-traffic.cmd | 4 +- .../tutorials/ai/mcp/generate-traffic.sh | 4 +- 5 files changed, 19 insertions(+), 104 deletions(-) delete mode 100644 distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml diff --git a/distribution/tutorials/ai/mcp/10-MCP-Server.yaml b/distribution/tutorials/ai/mcp/10-MCP-Server.yaml index 98262e172d..d7261ac844 100644 --- a/distribution/tutorials/ai/mcp/10-MCP-Server.yaml +++ b/distribution/tutorials/ai/mcp/10-MCP-Server.yaml @@ -20,12 +20,15 @@ # Windows: # generate-traffic.cmd 20 # -# The script also sends a few typical WordPress/PHP probe requests to a -# local endpoint on port 3002 that always responds with 404. +# The script also sends a few typical WordPress/PHP probe requests. # # 3.) Configure Claude Desktop as described in README.md. # # 4.) Ask the AI client to inspect recent exchanges or list the available APIs. +# Example prompts: +# - List the available APIs exposed by this Membrane MCP server. +# - Show me the most recent exchanges and summarize what happened. +# - Which endpoints received the most requests in the recent traffic? api: port: 2000 @@ -37,22 +40,15 @@ api: --- api: port: 3000 - name: Fruit-Shop-Demo + name: Fruitshop + path: + uri: /shop/v2/ target: - url: https://api.predic8.de/shop/v2/ + url: https://api.predic8.de --- api: port: 3001 - name: ApiBin-Demo + name: ApiBin target: url: https://apibin.io/ - ---- -# Simulates common background probe traffic such as WordPress/PHP scans. -api: - port: 3002 - name: 404 Fallback - flow: - - return: - status: 404 diff --git a/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml b/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml deleted file mode 100644 index c3b0aba9fd..0000000000 --- a/distribution/tutorials/ai/mcp/20-MCP-Server-Protected.yaml +++ /dev/null @@ -1,83 +0,0 @@ -# yaml-language-server: $schema=https://www.membrane-api.io/v7.2.1.json -# -# Tutorial: Protected MCP Server -# -# This setup keeps the real MCP server protected with an API key while Claude -# Desktop still talks to a simple local endpoint on http://localhost:2000. The -# local proxy adds the Authorization header and forwards the request to the -# actual MCP server on http://localhost:8080/mcp, so the client setup stays -# simple and the protected endpoint is never exposed without authentication. -# -# 1.) Start Membrane: -# -# Linux/Mac: -# ./membrane.sh -c 20-MCP-Server-Protected.yaml -# Windows: -# membrane.cmd -c 20-MCP-Server-Protected.yaml -# -# 2.) Generate some demo traffic in a second terminal: -# -# Linux/Mac: -# ./generate-traffic.sh 20 -# Windows: -# generate-traffic.cmd 20 -# -# The script also sends a few typical WordPress/PHP probe requests to a -# local endpoint on port 3002 that always responds with 404. -# -# 3.) Configure Claude Desktop as described in README.md. -# -# 4.) Ask the AI client to inspect recent exchanges. -# From the client perspective, it still uses port 2000. - -api: - port: 2000 - name: MCP-Proxy - flow: - - setHeader: - name: Authorization - value: apikey 12345 - target: - url: http://localhost:8080/mcp - ---- -# This is the real MCP endpoint. It accepts only requests with -# Authorization: apikey 12345. -api: - port: 8080 - name: MCP-Server-Protected - path: - uri: /mcp - flow: - - apiKey: - stores: - - simple: - - secret: - value: apikey 12345 - extractors: - - header: Authorization - - membraneMCPServer: - maxExchanges: 10000 - ---- -api: - port: 3000 - name: Fruit-Shop-Demo - target: - url: https://api.predic8.de/shop/v2/ - ---- -api: - port: 3001 - name: ApiBin-Demo - target: - url: https://apibin.io/ - ---- -# Simulates common background probe traffic such as WordPress/PHP scans. -api: - port: 3002 - name: 404 Fallback - flow: - - return: - status: 404 diff --git a/distribution/tutorials/ai/mcp/README.md b/distribution/tutorials/ai/mcp/README.md index 34f5c09784..bf606aaf2b 100644 --- a/distribution/tutorials/ai/mcp/README.md +++ b/distribution/tutorials/ai/mcp/README.md @@ -5,8 +5,10 @@ It covers: - running a local MCP server - inspecting recent API traffic through MCP -- protecting the MCP endpoint with an API key -- forwarding Claude Desktop traffic through a local proxy + +To begin, open [10-MCP-Server.yaml](10-MCP-Server.yaml) and follow the instructions in the file. + +If you want to protect the MCP endpoint, put the `membraneMCPServer` behind an `apiKey` interceptor and expose it only through the network path you actually want clients to use. If the client should keep talking to a simple local URL, you can also place a small local proxy in front of the protected MCP endpoint and let that proxy add the required authentication details. ## Claude Desktop Setup @@ -32,4 +34,4 @@ Open `Settings` -> `Developer`, edit `claude_desktop_config.json`, and add: } ``` -Start membrane and restart Claude Desktop. \ No newline at end of file +Start membrane and restart Claude Desktop. diff --git a/distribution/tutorials/ai/mcp/generate-traffic.cmd b/distribution/tutorials/ai/mcp/generate-traffic.cmd index 687dfb06fb..b8a61f7274 100644 --- a/distribution/tutorials/ai/mcp/generate-traffic.cmd +++ b/distribution/tutorials/ai/mcp/generate-traffic.cmd @@ -1,9 +1,9 @@ @echo off setlocal EnableExtensions EnableDelayedExpansion -if "%FRUIT_BASE%"=="" set FRUIT_BASE=http://localhost:3000 +if "%FRUIT_BASE%"=="" set FRUIT_BASE=http://localhost:3000/shop/v2 if "%APIBIN_BASE%"=="" set APIBIN_BASE=http://localhost:3001 -if "%ATTACK_BASE%"=="" set ATTACK_BASE=http://localhost:3002 +if "%ATTACK_BASE%"=="" set ATTACK_BASE=http://localhost:3000 if "%~1"=="" ( set ROUNDS=20 diff --git a/distribution/tutorials/ai/mcp/generate-traffic.sh b/distribution/tutorials/ai/mcp/generate-traffic.sh index 3193bc47d4..58d5805958 100644 --- a/distribution/tutorials/ai/mcp/generate-traffic.sh +++ b/distribution/tutorials/ai/mcp/generate-traffic.sh @@ -1,9 +1,9 @@ #!/usr/bin/env sh set -eu -FRUIT_BASE="${FRUIT_BASE:-http://localhost:3000}" +FRUIT_BASE="${FRUIT_BASE:-http://localhost:3000/shop/v2}" APIBIN_BASE="${APIBIN_BASE:-http://localhost:3001}" -ATTACK_BASE="${ATTACK_BASE:-http://localhost:3002}" +ATTACK_BASE="${ATTACK_BASE:-http://localhost:3000}" ROUNDS="${1:-20}" request() {