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
6 changes: 5 additions & 1 deletion distribution/tutorials/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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

54 changes: 54 additions & 0 deletions distribution/tutorials/ai/mcp/10-MCP-Server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 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
#
# 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.
Comment thread
christiangoerdes marked this conversation as resolved.
# 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
name: MCP-Server
flow:
- membraneMCPServer:
maxExchanges: 10000

---
api:
port: 3000
name: Fruitshop
path:
uri: /shop/v2/
target:
url: https://api.predic8.de

---
api:
port: 3001
name: ApiBin
target:
url: https://apibin.io/
37 changes: 37 additions & 0 deletions distribution/tutorials/ai/mcp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Membrane API Gateway Tutorial - MCP

This tutorial shows how to expose Membrane API Gateway as an MCP server for AI clients.
It covers:

- running a local MCP server
- inspecting recent API traffic through MCP

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

Open `Settings` -> `Developer`, edit `claude_desktop_config.json`, and add:

```json
{
"mcpServers": {
"membrane": {
"command": "npx",
"args": [
"mcp-remote",
"http://localhost:2000"
]
}
},
"preferences": {
"coworkScheduledTasksEnabled": true,
"ccdScheduledTasksEnabled": true,
"sidebarMode": "task",
"coworkWebSearchEnabled": true
}
}
Comment thread
christiangoerdes marked this conversation as resolved.
```

Start membrane and restart Claude Desktop.
44 changes: 44 additions & 0 deletions distribution/tutorials/ai/mcp/generate-traffic.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
@echo off
setlocal EnableExtensions EnableDelayedExpansion

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:3000

if "%~1"=="" (
set ROUNDS=20
) else (
set ROUNDS=%~1
Comment thread
christiangoerdes marked this conversation as resolved.
)

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"
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

: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
43 changes: 43 additions & 0 deletions distribution/tutorials/ai/mcp/generate-traffic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env sh
set -eu

FRUIT_BASE="${FRUIT_BASE:-http://localhost:3000/shop/v2}"
APIBIN_BASE="${APIBIN_BASE:-http://localhost:3001}"
ATTACK_BASE="${ATTACK_BASE:-http://localhost:3000}"
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"
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
24 changes: 24 additions & 0 deletions distribution/tutorials/ai/mcp/membrane.cmd
Original file line number Diff line number Diff line change
@@ -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
Comment thread
christiangoerdes marked this conversation as resolved.
21 changes: 21 additions & 0 deletions distribution/tutorials/ai/mcp/membrane.sh
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions distribution/tutorials/ai/mcp/run-docker.cmd
Original file line number Diff line number Diff line change
@@ -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%
Comment thread
christiangoerdes marked this conversation as resolved.
endlocal
Comment thread
christiangoerdes marked this conversation as resolved.
14 changes: 14 additions & 0 deletions distribution/tutorials/ai/mcp/run-docker.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading