llm-mitm-proxy 2026.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. llm_mitm_proxy-2026.9.0/.devcontainer/devcontainer.json +12 -0
  2. llm_mitm_proxy-2026.9.0/.dockerignore +9 -0
  3. llm_mitm_proxy-2026.9.0/.github/workflows/checks.yml +64 -0
  4. llm_mitm_proxy-2026.9.0/.github/workflows/deploy-docker.yml +65 -0
  5. llm_mitm_proxy-2026.9.0/.github/workflows/prune-ghcr.yml +55 -0
  6. llm_mitm_proxy-2026.9.0/.github/workflows/publish-pypi.yml +49 -0
  7. llm_mitm_proxy-2026.9.0/.gitignore +12 -0
  8. llm_mitm_proxy-2026.9.0/Dockerfile +48 -0
  9. llm_mitm_proxy-2026.9.0/LICENSE.md +21 -0
  10. llm_mitm_proxy-2026.9.0/PKG-INFO +116 -0
  11. llm_mitm_proxy-2026.9.0/README.md +93 -0
  12. llm_mitm_proxy-2026.9.0/docker-compose.yml +11 -0
  13. llm_mitm_proxy-2026.9.0/docs/CONVERSATION.md +377003 -0
  14. llm_mitm_proxy-2026.9.0/docs/PLAN.md +662 -0
  15. llm_mitm_proxy-2026.9.0/docs/screenshot.png +0 -0
  16. llm_mitm_proxy-2026.9.0/eslint.config.mjs +24 -0
  17. llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/PKG-INFO +116 -0
  18. llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/SOURCES.txt +65 -0
  19. llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/dependency_links.txt +1 -0
  20. llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/entry_points.txt +2 -0
  21. llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/requires.txt +7 -0
  22. llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/scm_file_list.json +57 -0
  23. llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/scm_version.json +8 -0
  24. llm_mitm_proxy-2026.9.0/llm_mitm_proxy.egg-info/top_level.txt +1 -0
  25. llm_mitm_proxy-2026.9.0/llm_proxy/__init__.py +10 -0
  26. llm_mitm_proxy-2026.9.0/llm_proxy/api/__init__.py +1 -0
  27. llm_mitm_proxy-2026.9.0/llm_proxy/api/ui.py +162 -0
  28. llm_mitm_proxy-2026.9.0/llm_proxy/app.py +346 -0
  29. llm_mitm_proxy-2026.9.0/llm_proxy/config.py +60 -0
  30. llm_mitm_proxy-2026.9.0/llm_proxy/dissectors/__init__.py +10 -0
  31. llm_mitm_proxy-2026.9.0/llm_proxy/dissectors/base.py +75 -0
  32. llm_mitm_proxy-2026.9.0/llm_proxy/dissectors/generic.py +35 -0
  33. llm_mitm_proxy-2026.9.0/llm_proxy/dissectors/openai.py +109 -0
  34. llm_mitm_proxy-2026.9.0/llm_proxy/dump.py +62 -0
  35. llm_mitm_proxy-2026.9.0/llm_proxy/hub.py +164 -0
  36. llm_mitm_proxy-2026.9.0/llm_proxy/logconf.py +63 -0
  37. llm_mitm_proxy-2026.9.0/llm_proxy/model/__init__.py +1 -0
  38. llm_mitm_proxy-2026.9.0/llm_proxy/model/conversation.py +98 -0
  39. llm_mitm_proxy-2026.9.0/llm_proxy/model/ir.py +48 -0
  40. llm_mitm_proxy-2026.9.0/llm_proxy/proxy/__init__.py +1 -0
  41. llm_mitm_proxy-2026.9.0/llm_proxy/proxy/pipeline.py +718 -0
  42. llm_mitm_proxy-2026.9.0/llm_proxy/proxy/router.py +20 -0
  43. llm_mitm_proxy-2026.9.0/llm_proxy/proxy/sse.py +108 -0
  44. llm_mitm_proxy-2026.9.0/llm_proxy/proxy/util.py +19 -0
  45. llm_mitm_proxy-2026.9.0/llm_proxy/store/__init__.py +1 -0
  46. llm_mitm_proxy-2026.9.0/llm_proxy/store/memory.py +126 -0
  47. llm_mitm_proxy-2026.9.0/llm_proxy/tests/__init__.py +1 -0
  48. llm_mitm_proxy-2026.9.0/llm_proxy/tests/helpers.py +89 -0
  49. llm_mitm_proxy-2026.9.0/llm_proxy/tests/mock_upstream.py +221 -0
  50. llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_cli.py +77 -0
  51. llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_dissectors.py +77 -0
  52. llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_proxy.py +287 -0
  53. llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_shutdown.py +116 -0
  54. llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_ui_api.py +246 -0
  55. llm_mitm_proxy-2026.9.0/llm_proxy/tests/test_ws.py +396 -0
  56. llm_mitm_proxy-2026.9.0/llm_proxy/web/app.js +90 -0
  57. llm_mitm_proxy-2026.9.0/llm_proxy/web/favicon.svg +5 -0
  58. llm_mitm_proxy-2026.9.0/llm_proxy/web/index.html +69 -0
  59. llm_mitm_proxy-2026.9.0/llm_proxy/web/styles.css +1 -0
  60. llm_mitm_proxy-2026.9.0/package-lock.json +1485 -0
  61. llm_mitm_proxy-2026.9.0/package.json +25 -0
  62. llm_mitm_proxy-2026.9.0/pyproject.toml +63 -0
  63. llm_mitm_proxy-2026.9.0/setup.cfg +4 -0
  64. llm_mitm_proxy-2026.9.0/ui/app.js +970 -0
  65. llm_mitm_proxy-2026.9.0/ui/favicon.svg +5 -0
  66. llm_mitm_proxy-2026.9.0/ui/index.html +69 -0
  67. llm_mitm_proxy-2026.9.0/ui/styles.css +458 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "llm-mitm-proxy",
3
+ "image": "mcr.microsoft.com/devcontainers/python:3.14",
4
+ "features": {
5
+ "ghcr.io/devcontainers/features/node:1": {
6
+ "version": "lts"
7
+ },
8
+ "ghcr.io/devcontainers/features/git:1": {}
9
+ },
10
+ "forwardPorts": [8081, 9090],
11
+ "postCreateCommand": "pip install -e '.[dev]' && npm install && npm run build"
12
+ }
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ node_modules/
3
+ llm_proxy/web/
4
+ **/__pycache__/
5
+ *.pyc
6
+ .devcontainer/
7
+ build/
8
+ *.egg-info/
9
+ dist/
@@ -0,0 +1,64 @@
1
+ name: Code Quality Checks
2
+ permissions:
3
+ contents: read
4
+
5
+ on:
6
+ push:
7
+ branches: ["main"]
8
+ pull_request:
9
+
10
+ # Kill existing jobs
11
+ concurrency:
12
+ group: ${{ github.workflow }}-${{ github.ref }}
13
+ cancel-in-progress: true
14
+
15
+ jobs:
16
+ style:
17
+ runs-on: ubuntu-latest
18
+ steps:
19
+ - uses: actions/checkout@v6
20
+ - uses: actions/setup-python@v6
21
+ with:
22
+ python-version: "3.14"
23
+ - run: |
24
+ python -m pip install --upgrade pip
25
+ pip install black isort
26
+ - run: black --check --diff llm_proxy
27
+ - run: isort --check-only --diff llm_proxy
28
+ - uses: actions/setup-node@v6
29
+ with:
30
+ node-version: "22"
31
+ cache: npm
32
+ - run: npm ci
33
+ - run: npm run verify:js
34
+
35
+ run-unittest:
36
+ runs-on: ubuntu-latest
37
+ strategy:
38
+ max-parallel: 1
39
+ fail-fast: true
40
+ matrix:
41
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
42
+
43
+ steps:
44
+ - uses: actions/checkout@v6
45
+ with:
46
+ # History/tags required to generate version info
47
+ fetch-depth: 0
48
+ - uses: actions/setup-python@v6
49
+ with:
50
+ python-version: ${{ matrix.python-version }}
51
+ cache: "pip"
52
+ - run: python -c "import sys; print(sys.version)"
53
+ # Build the UI so the in-package serving + packaging tests run (they
54
+ # skip when llm_proxy/web is absent).
55
+ - uses: actions/setup-node@v6
56
+ with:
57
+ node-version: "22"
58
+ cache: npm
59
+ - run: npm ci
60
+ - run: npm run build
61
+ - run: |
62
+ python -m pip install --upgrade pip
63
+ pip install --editable .
64
+ - run: python -m unittest
@@ -0,0 +1,65 @@
1
+ name: Publish Docker Images
2
+ permissions:
3
+ contents: read
4
+
5
+ on:
6
+ push:
7
+ branches: ["main"]
8
+ tags: ["*"]
9
+
10
+ pull_request:
11
+ branches: ["main"]
12
+
13
+ # Kill existing jobs
14
+ concurrency:
15
+ group: ${{ github.workflow }}-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ env:
19
+ REGISTRY: ghcr.io
20
+ IMAGE_NAME: mill1000/llm-mitm-proxy
21
+ # No linux/arm/v7: esbuild (the UI build stage) has no armv7 binary.
22
+ PLATFORMS: linux/amd64,linux/arm64
23
+
24
+ jobs:
25
+ build-and-push-image:
26
+ runs-on: ubuntu-latest
27
+
28
+ permissions:
29
+ packages: write
30
+
31
+ steps:
32
+ - uses: actions/checkout@v6
33
+ with:
34
+ # History/tags required to generate version info
35
+ fetch-depth: 0
36
+
37
+ - uses: docker/metadata-action@v6
38
+ id: meta
39
+ with:
40
+ images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
41
+ # Generate tags for latest commit, version tags and PRs
42
+ tags: |
43
+ type=edge
44
+ type=pep440,pattern={{version}}
45
+ type=ref,event=pr
46
+
47
+ - uses: docker/setup-qemu-action@v4
48
+ with:
49
+ platforms: ${{ env.PLATFORMS }}
50
+
51
+ - uses: docker/setup-buildx-action@v4
52
+
53
+ - uses: docker/login-action@v4
54
+ with:
55
+ registry: ${{ env.REGISTRY }}
56
+ username: ${{ github.actor }}
57
+ password: ${{ secrets.GITHUB_TOKEN }}
58
+
59
+ - uses: docker/build-push-action@v7
60
+ with:
61
+ context: .
62
+ platforms: ${{ env.PLATFORMS }}
63
+ push: true
64
+ tags: ${{ steps.meta.outputs.tags }}
65
+ labels: ${{ steps.meta.outputs.labels }}
@@ -0,0 +1,55 @@
1
+ name: Prune GHCR
2
+ permissions:
3
+ contents: read
4
+
5
+ on:
6
+ pull_request:
7
+ types: [closed]
8
+
9
+ schedule:
10
+ - cron: "20 4 * * 0"
11
+
12
+ workflow_dispatch:
13
+
14
+ env:
15
+ CONTAINER_NAME: llm-mitm-proxy
16
+
17
+ jobs:
18
+ prune-pr-images:
19
+ if: github.event_name == 'pull_request'
20
+ runs-on: ubuntu-latest
21
+ permissions:
22
+ packages: write
23
+ steps:
24
+ - uses: vlaurin/action-ghcr-prune@v0.6.0
25
+ with:
26
+ container: ${{ env.CONTAINER_NAME }}
27
+ token: ${{ secrets.GITHUB_TOKEN }}
28
+ prune-tags-regexes: ^pr-${{github.event.pull_request.number}}
29
+ # dry-run: true
30
+
31
+ prune-old-images:
32
+ if: github.event_name != 'pull_request'
33
+ runs-on: ubuntu-latest
34
+ permissions:
35
+ packages: write
36
+ steps:
37
+ - uses: vlaurin/action-ghcr-prune@v0.6.0
38
+ with:
39
+ container: ${{ env.CONTAINER_NAME }}
40
+ token: ${{ secrets.GITHUB_TOKEN }}
41
+ # Keep PRs, CalVer releases, latest and edge
42
+ keep-tags-regexes: |
43
+ ^pr-
44
+ ^\d{4}\.\d{2}\.\d{2}
45
+ keep-tags: |
46
+ edge
47
+ latest
48
+ # dry-run: true
49
+
50
+ - uses: dataaxiom/ghcr-cleanup-action@v1
51
+ with:
52
+ package: ${{ env.CONTAINER_NAME }}
53
+ token: ${{ secrets.GITHUB_TOKEN }}
54
+ # dry-run: true
55
+ validate: true
@@ -0,0 +1,49 @@
1
+ name: Publish Python Package
2
+ permissions:
3
+ contents: read
4
+
5
+ on:
6
+ release:
7
+ types: [published]
8
+
9
+ jobs:
10
+ build-package:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v6
14
+ with:
15
+ # History/tags required to generate version info
16
+ fetch-depth: 0
17
+ - uses: actions/setup-python@v6
18
+ with:
19
+ python-version: "3.14"
20
+ # The wheel ships the WebUI as in-package build output (llm_proxy/web),
21
+ # so the UI must be built before the wheel.
22
+ - uses: actions/setup-node@v4
23
+ with:
24
+ node-version: "22"
25
+ cache: npm
26
+ - run: |
27
+ python -m pip install --upgrade pip
28
+ pip install build
29
+ - run: npm ci
30
+ - run: npm run build
31
+ - run: python -m build
32
+ - uses: actions/upload-artifact@v4
33
+ with:
34
+ name: dist
35
+ path: dist
36
+
37
+ publish-package:
38
+ needs: build-package
39
+ runs-on: ubuntu-latest
40
+ permissions:
41
+ id-token: write # Mandatory for trusted publishing
42
+ steps:
43
+ - uses: actions/download-artifact@v8
44
+ with:
45
+ name: dist
46
+ path: dist
47
+ - uses: pypa/gh-action-pypi-publish@release/v1
48
+ # with:
49
+ # repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ node_modules/
9
+ llm_proxy/web/
10
+ .env
11
+ .DS_Store
12
+ devcontainer-lock.json
@@ -0,0 +1,48 @@
1
+ # UI build
2
+ FROM node:alpine AS ui
3
+
4
+ WORKDIR /src
5
+ COPY package.json package-lock.json ./
6
+ RUN npm ci
7
+ COPY ui ./ui
8
+ RUN npm run build
9
+
10
+ # Backend build
11
+ FROM python:3.14-alpine AS build
12
+
13
+ RUN apk add --no-cache --update git
14
+ RUN pip install --no-cache-dir build
15
+
16
+ ARG VERSION
17
+ ENV SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION}
18
+
19
+ WORKDIR /app
20
+ COPY . .
21
+ COPY --from=ui /src/llm_proxy/web llm_proxy/web
22
+ RUN python -m build --wheel
23
+
24
+ # Runtime
25
+ FROM alpine:3.24
26
+
27
+ RUN apk add --no-cache --update python3 pipx tini
28
+
29
+ ENV PYTHONDONTWRITEBYTECODE=1
30
+ ENV PYTHONUNBUFFERED=1
31
+
32
+ ENV PIPX_HOME=/opt/pipx
33
+ ENV PIPX_BIN_DIR=/usr/bin
34
+
35
+ COPY --from=build /app/dist/llm_mitm_proxy-*.whl /tmp/
36
+ RUN pipx install /tmp/llm_mitm_proxy-*.whl && rm /tmp/llm_mitm_proxy-*.whl
37
+
38
+ RUN adduser -D -u 1000 -h /dev/null -s /sbin/nologin appuser
39
+ USER appuser
40
+
41
+ EXPOSE 8081 9090
42
+
43
+ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s \
44
+ CMD python3 -c "import urllib.request;urllib.request.urlopen('http://127.0.0.1:9090/health', timeout=8)"
45
+
46
+ ENTRYPOINT ["/sbin/tini", "--"]
47
+
48
+ CMD ["/bin/sh", "-c", "llm-mitm-proxy ${UPSTREAM_BASE_URL:+$UPSTREAM_BASE_URL} ${UPSTREAM_API_KEY:+--upstream-api-key $UPSTREAM_API_KEY} ${LISTEN_HOST:+--host $LISTEN_HOST} ${PROXY_PORT:+--proxy-port $PROXY_PORT} ${WEB_PORT:+--web-port $WEB_PORT} ${LOG_LEVEL:+--log-level $LOG_LEVEL}"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tucker Kern
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: llm-mitm-proxy
3
+ Version: 2026.9.0
4
+ Summary: A simple transparent MITM proxy for LLM APIs with a live Web UI
5
+ Project-URL: Repository, https://github.com/mill1000/llm-mitm-proxy
6
+ Project-URL: Issues, https://github.com/mill1000/llm-mitm-proxy/issues
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Classifier: Operating System :: OS Independent
13
+ Requires-Python: >=3.11
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE.md
16
+ Requires-Dist: fastapi>=0.110
17
+ Requires-Dist: uvicorn[standard]>=0.29
18
+ Requires-Dist: httpx2>=2
19
+ Provides-Extra: dev
20
+ Requires-Dist: isort>=5; extra == "dev"
21
+ Requires-Dist: black>=24; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # llm-mitm-proxy
25
+ A simple transparent MITM proxy for LLM APIs with a live Web UI for inspecting, replaying, and exporting "conversations" between clients and an upstream server.
26
+
27
+ **Trusted networks or testing only**
28
+ There is no authentication, no SSL/TLS termination, or other "reverse proxy" feature set. Its primary intent is to provide visibility between an agent and the server.
29
+
30
+ ![llm-mitm-proxy WebUI](./docs/screenshot.png)
31
+
32
+ ## Client Identification
33
+ Clients are identified by their source IP, user agent, and API key (if provided), which determines which "conversation" traffic appears under in the Web UI. An API key is not required, IP and user agent are enough to tell many clients apart, but an API key can be used as a further split for clients sharing the same IP, and user agent.
34
+
35
+ ## Dissectors
36
+ The proxy forwards all traffic verbatim and decodes requests and responses for the Web UI with per-request dissectors that match on method + path.
37
+
38
+ Supported dissectors:
39
+ - `openai`: OpenAI-compatible chat completions
40
+ - `generic`: everything else, captured as raw data
41
+
42
+ ## Vibe Warning
43
+ This project was entirely **vibe coded** with Qwen 3.8 27B and the Zed Agent on local hardware.
44
+
45
+ Work proceeded milestone by milestone according to the [PLAN.md](./docs/PLAN.md) with design, key decisions, and open questions. Occasional updates were made to the plan as direction shifted. Each milestone was tested against a llama.cpp server by a human before committed.
46
+
47
+ The full agent conversation log is preserved in [CONVERSATION.md](./docs/CONVERSATION.md).
48
+
49
+ ## Quick Start
50
+ ### Docker Compose
51
+ ```yaml
52
+ services:
53
+ llm-mitm-proxy:
54
+ image: ghcr.io/mill1000/llm-mitm-proxy:latest
55
+ restart: unless-stopped
56
+ ports:
57
+ - "8081:8081" # LLM API
58
+ - "9090:9090" # Web UI
59
+ environment:
60
+ UPSTREAM_BASE_URL: "http://<your-llamacpp>:8080"
61
+ extra_hosts:
62
+ - "host.docker.internal:host-gateway"
63
+ ```
64
+
65
+ ### Docker
66
+ ```bash
67
+ docker run -d --name llm-mitm-proxy -p 8081:8081 -p 9090:9090 -e UPSTREAM_BASE_URL=http://<your-llamacpp>:8080 ghcr.io/mill1000/llm-mitm-proxy:latest
68
+ ```
69
+
70
+ ## pipx/uvx
71
+ ```bash
72
+ # uvx
73
+ uvx llm-mitm-proxy http://<your-llamacpp>:8080
74
+
75
+ # pipx
76
+ pipx install llm-mitm-proxy
77
+ llm-mitm-proxy http://<your-llamacpp>:8080
78
+ ```
79
+
80
+ ## Usage
81
+ 1. Start the proxy
82
+ 2. Point an agent at the proxy
83
+ 3. Open the Web UI and look at the requests go brrr.
84
+
85
+ See [Configuration](#configuration) for more information on specifying an upstream API key, changing ports, and other options.
86
+
87
+ ## Configuration
88
+ ### Command Line
89
+ ```
90
+ usage: llm-mitm-proxy [-h] [--version] [--host HOST] [--proxy-port PORT] [--web-port PORT]
91
+ [--upstream-api-key KEY] [--log-level LEVEL] [--ui-dir DIR]
92
+ [UPSTREAM_BASE_URL]
93
+ ```
94
+
95
+ | Arg | Notes |
96
+ |---|---|
97
+ | `UPSTREAM_BASE_URL` (positional) | upstream base URL (default `http://host.docker.internal:8080`) |
98
+ | `--host` | listen host for both listeners (default `0.0.0.0`) |
99
+ | `--proxy-port` | proxy listener port (default `8081`, the transparent catch-all; llama.cpp's own default is `8080`) |
100
+ | `--web-port` | WebUI + `/api/*` + `/ws` + `/health` listener port (default `9090`) |
101
+ | `--upstream-api-key` | optional server-side fallback key, injected only when a client sends no key |
102
+ | `--log-level` | app loggers, incl. the `llm_proxy.ws` connection trace at `debug` (connect/focus/disconnect) |
103
+ | `--ui-dir` | static UI directory (default: the in-package `llm_proxy/web` build) |
104
+ | `--help` / `--version` | usage / package version |
105
+
106
+ ### Docker Environment Variables
107
+
108
+ The Docker image maps the following environment variables to command line options:
109
+ | Var | CLI arg |
110
+ |---|---|
111
+ | `UPSTREAM_BASE_URL` | positional upstream |
112
+ | `UPSTREAM_API_KEY` | `--upstream-api-key` |
113
+ | `LISTEN_HOST` | `--host` |
114
+ | `PROXY_PORT` | `--proxy-port` |
115
+ | `WEB_PORT` | `--web-port` |
116
+ | `LOG_LEVEL` | `--log-level` |
@@ -0,0 +1,93 @@
1
+ # llm-mitm-proxy
2
+ A simple transparent MITM proxy for LLM APIs with a live Web UI for inspecting, replaying, and exporting "conversations" between clients and an upstream server.
3
+
4
+ **Trusted networks or testing only**
5
+ There is no authentication, no SSL/TLS termination, or other "reverse proxy" feature set. Its primary intent is to provide visibility between an agent and the server.
6
+
7
+ ![llm-mitm-proxy WebUI](./docs/screenshot.png)
8
+
9
+ ## Client Identification
10
+ Clients are identified by their source IP, user agent, and API key (if provided), which determines which "conversation" traffic appears under in the Web UI. An API key is not required, IP and user agent are enough to tell many clients apart, but an API key can be used as a further split for clients sharing the same IP, and user agent.
11
+
12
+ ## Dissectors
13
+ The proxy forwards all traffic verbatim and decodes requests and responses for the Web UI with per-request dissectors that match on method + path.
14
+
15
+ Supported dissectors:
16
+ - `openai`: OpenAI-compatible chat completions
17
+ - `generic`: everything else, captured as raw data
18
+
19
+ ## Vibe Warning
20
+ This project was entirely **vibe coded** with Qwen 3.8 27B and the Zed Agent on local hardware.
21
+
22
+ Work proceeded milestone by milestone according to the [PLAN.md](./docs/PLAN.md) with design, key decisions, and open questions. Occasional updates were made to the plan as direction shifted. Each milestone was tested against a llama.cpp server by a human before committed.
23
+
24
+ The full agent conversation log is preserved in [CONVERSATION.md](./docs/CONVERSATION.md).
25
+
26
+ ## Quick Start
27
+ ### Docker Compose
28
+ ```yaml
29
+ services:
30
+ llm-mitm-proxy:
31
+ image: ghcr.io/mill1000/llm-mitm-proxy:latest
32
+ restart: unless-stopped
33
+ ports:
34
+ - "8081:8081" # LLM API
35
+ - "9090:9090" # Web UI
36
+ environment:
37
+ UPSTREAM_BASE_URL: "http://<your-llamacpp>:8080"
38
+ extra_hosts:
39
+ - "host.docker.internal:host-gateway"
40
+ ```
41
+
42
+ ### Docker
43
+ ```bash
44
+ docker run -d --name llm-mitm-proxy -p 8081:8081 -p 9090:9090 -e UPSTREAM_BASE_URL=http://<your-llamacpp>:8080 ghcr.io/mill1000/llm-mitm-proxy:latest
45
+ ```
46
+
47
+ ## pipx/uvx
48
+ ```bash
49
+ # uvx
50
+ uvx llm-mitm-proxy http://<your-llamacpp>:8080
51
+
52
+ # pipx
53
+ pipx install llm-mitm-proxy
54
+ llm-mitm-proxy http://<your-llamacpp>:8080
55
+ ```
56
+
57
+ ## Usage
58
+ 1. Start the proxy
59
+ 2. Point an agent at the proxy
60
+ 3. Open the Web UI and look at the requests go brrr.
61
+
62
+ See [Configuration](#configuration) for more information on specifying an upstream API key, changing ports, and other options.
63
+
64
+ ## Configuration
65
+ ### Command Line
66
+ ```
67
+ usage: llm-mitm-proxy [-h] [--version] [--host HOST] [--proxy-port PORT] [--web-port PORT]
68
+ [--upstream-api-key KEY] [--log-level LEVEL] [--ui-dir DIR]
69
+ [UPSTREAM_BASE_URL]
70
+ ```
71
+
72
+ | Arg | Notes |
73
+ |---|---|
74
+ | `UPSTREAM_BASE_URL` (positional) | upstream base URL (default `http://host.docker.internal:8080`) |
75
+ | `--host` | listen host for both listeners (default `0.0.0.0`) |
76
+ | `--proxy-port` | proxy listener port (default `8081`, the transparent catch-all; llama.cpp's own default is `8080`) |
77
+ | `--web-port` | WebUI + `/api/*` + `/ws` + `/health` listener port (default `9090`) |
78
+ | `--upstream-api-key` | optional server-side fallback key, injected only when a client sends no key |
79
+ | `--log-level` | app loggers, incl. the `llm_proxy.ws` connection trace at `debug` (connect/focus/disconnect) |
80
+ | `--ui-dir` | static UI directory (default: the in-package `llm_proxy/web` build) |
81
+ | `--help` / `--version` | usage / package version |
82
+
83
+ ### Docker Environment Variables
84
+
85
+ The Docker image maps the following environment variables to command line options:
86
+ | Var | CLI arg |
87
+ |---|---|
88
+ | `UPSTREAM_BASE_URL` | positional upstream |
89
+ | `UPSTREAM_API_KEY` | `--upstream-api-key` |
90
+ | `LISTEN_HOST` | `--host` |
91
+ | `PROXY_PORT` | `--proxy-port` |
92
+ | `WEB_PORT` | `--web-port` |
93
+ | `LOG_LEVEL` | `--log-level` |
@@ -0,0 +1,11 @@
1
+ services:
2
+ llm-mitm-proxy:
3
+ image: ghcr.io/mill1000/llm-mitm-proxy:latest
4
+ restart: unless-stopped
5
+ ports:
6
+ - "8081:8081" # LLM API
7
+ - "9090:9090" # Web UI
8
+ environment:
9
+ UPSTREAM_BASE_URL: "http://<your-llamacpp>:8080"
10
+ extra_hosts:
11
+ - "host.docker.internal:host-gateway"