mcp-stdio-mux 0.1.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.
- mcp_stdio_mux-0.1.0/.gitignore +9 -0
- mcp_stdio_mux-0.1.0/LICENSE +21 -0
- mcp_stdio_mux-0.1.0/PKG-INFO +176 -0
- mcp_stdio_mux-0.1.0/README.md +152 -0
- mcp_stdio_mux-0.1.0/docs/README.md +10 -0
- mcp_stdio_mux-0.1.0/docs/specs/2026-11-10-v0.1-scope.md +49 -0
- mcp_stdio_mux-0.1.0/pyproject.toml +54 -0
- mcp_stdio_mux-0.1.0/skills/mcp-stdio-mux/SKILL.md +77 -0
- mcp_stdio_mux-0.1.0/src/mcp_stdio_mux/__init__.py +3 -0
- mcp_stdio_mux-0.1.0/src/mcp_stdio_mux/__main__.py +3 -0
- mcp_stdio_mux-0.1.0/src/mcp_stdio_mux/cli.py +251 -0
- mcp_stdio_mux-0.1.0/src/mcp_stdio_mux/clients.py +416 -0
- mcp_stdio_mux-0.1.0/src/mcp_stdio_mux/config.py +74 -0
- mcp_stdio_mux-0.1.0/src/mcp_stdio_mux/daemon.py +521 -0
- mcp_stdio_mux-0.1.0/src/mcp_stdio_mux/install.py +330 -0
- mcp_stdio_mux-0.1.0/src/mcp_stdio_mux/router.py +309 -0
- mcp_stdio_mux-0.1.0/src/mcp_stdio_mux/runtime.py +436 -0
- mcp_stdio_mux-0.1.0/tests/__init__.py +0 -0
- mcp_stdio_mux-0.1.0/tests/helpers.py +240 -0
- mcp_stdio_mux-0.1.0/tests/test_benchmark.py +21 -0
- mcp_stdio_mux-0.1.0/tests/test_clients.py +465 -0
- mcp_stdio_mux-0.1.0/tests/test_install.py +337 -0
- mcp_stdio_mux-0.1.0/tests/test_router.py +240 -0
- mcp_stdio_mux-0.1.0/tests/test_runtime.py +503 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ahmad Muhyidin
|
|
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,176 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: mcp-stdio-mux
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: One shared process per MCP server across every AI agent session, app and profile on your machine.
|
|
5
|
+
Project-URL: Homepage, https://github.com/techidin/mcp-stdio-mux
|
|
6
|
+
Project-URL: Issues, https://github.com/techidin/mcp-stdio-mux/issues
|
|
7
|
+
Author: Ahmad Muhyidin
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: ai-agents,claude-code,codex,mcp,model-context-protocol,stdio
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: MacOS
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# mcp-stdio-mux
|
|
26
|
+
|
|
27
|
+
One shared process per MCP server across every AI agent session, app and
|
|
28
|
+
profile on your machine.
|
|
29
|
+
|
|
30
|
+
Open four Claude Code sessions and each one starts its own copy of every stdio
|
|
31
|
+
MCP server you configured. Add Codex, Cursor, or a second profile and the copies
|
|
32
|
+
multiply. `mcp-stdio-mux` puts one shared backend behind all of them; each
|
|
33
|
+
session reaches it through a 1.2 MB relay instead of a server of its own.
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
uvx mcp-stdio-mux setup
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Setup finds Claude Code (every profile), Claude Desktop, Codex (every profile),
|
|
42
|
+
Gemini CLI, Cursor, Windsurf and OpenCode, shows what it will change, and asks
|
|
43
|
+
once. It installs itself as a `uv` tool so your configs point at a path that
|
|
44
|
+
stays. Restart your agent sessions afterwards.
|
|
45
|
+
|
|
46
|
+
Without `uv`: `pipx install mcp-stdio-mux && mcp-stdio-mux setup`.
|
|
47
|
+
|
|
48
|
+
Setup also gives Claude Code and Codex an agent skill, so you can ask an agent
|
|
49
|
+
to "share my MCP servers" or "share the server I just added".
|
|
50
|
+
|
|
51
|
+
## Measured
|
|
52
|
+
|
|
53
|
+
Four sessions of one server, every process the sessions caused (relays, daemon,
|
|
54
|
+
backend and its children), macOS 27.2 on Apple silicon with 16 GB, three runs on
|
|
55
|
+
10 November 2026. The unit is `phys_footprint`, the "Memory" column of Activity
|
|
56
|
+
Monitor.
|
|
57
|
+
|
|
58
|
+
| Server | Without a mux | With mcp-stdio-mux | Saved |
|
|
59
|
+
|---|---|---|---|
|
|
60
|
+
| serena (Python) | 679–757 MB | 202–203 MB | 70–73% |
|
|
61
|
+
| tilth (Node) | 57 MB | 36 MB | 37% |
|
|
62
|
+
|
|
63
|
+
Each session's own process drops from 12–94 MB to 1.2 MB. The saving grows with
|
|
64
|
+
the number of sessions and with how heavy the server is. Reproduce it with any
|
|
65
|
+
server:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python scripts/benchmark.py --sessions 4 -- tilth --mcp
|
|
69
|
+
python scripts/benchmark.py --sessions 4 \
|
|
70
|
+
--prefix "mcp-stdio-mux run --domain bench --server tilth" -- tilth --mcp
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## What setup changes, and what it never touches
|
|
74
|
+
|
|
75
|
+
- Each local (stdio) server's command in your user-level config files becomes
|
|
76
|
+
`mcp-stdio-mux run --domain <profile> --server <name> -- <original command>`.
|
|
77
|
+
The original command stays readable after `--`.
|
|
78
|
+
- Every file is backed up to `~/.local/state/mcp-stdio-mux/backups/` first,
|
|
79
|
+
written atomically through symlinks with its permissions kept, then read back.
|
|
80
|
+
- Remote (HTTP or SSE) servers, project `.mcp.json` files, and servers you mark
|
|
81
|
+
`native` are never touched. A file that nothing changes keeps its exact bytes.
|
|
82
|
+
- `mcp-stdio-mux undo` restores each file's exact bytes when the app has not
|
|
83
|
+
written to it since setup, and removes only the wrapper when it has.
|
|
84
|
+
- Running setup a second time changes nothing that is already shared.
|
|
85
|
+
|
|
86
|
+
## Who shares with whom
|
|
87
|
+
|
|
88
|
+
Two sessions share one backend only when all of these match:
|
|
89
|
+
|
|
90
|
+
| Part | Where it comes from |
|
|
91
|
+
|---|---|
|
|
92
|
+
| Domain | The profile name: `~/.claude-work` and `~/.codex-work` are both `work`; default profiles are `default`. |
|
|
93
|
+
| Command | The exact backend command and arguments. |
|
|
94
|
+
| Credentials | A SHA-256 of the environment variables the config declares. Values are never logged. |
|
|
95
|
+
| Workspace | The git root, for servers bound to the directory they start in. |
|
|
96
|
+
|
|
97
|
+
Differently named profiles never share unless you map them to one domain.
|
|
98
|
+
|
|
99
|
+
Each backend also gets a scope:
|
|
100
|
+
|
|
101
|
+
| Scope | When | Backend |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `global` | Default. | One per signature, started in your home directory. |
|
|
104
|
+
| `workspace` | An argument flag names `cwd`, `project`, `workspace` or `root`, an argument is `.`, or the config sets `cwd`. | One per signature and git root. |
|
|
105
|
+
| `session` | The first `tools/list` reply has a tool named like `browser`, `tab`, `page`, `navigate`, `click`, `screenshot`, `session`, `login` or `logout`. | One per session. |
|
|
106
|
+
|
|
107
|
+
Until a server is classified, a second session waits (up to 20 seconds), so no
|
|
108
|
+
session attaches to a stateful server before the mux knows it is one.
|
|
109
|
+
|
|
110
|
+
## Configuration
|
|
111
|
+
|
|
112
|
+
Optional. `~/.config/mcp-stdio-mux/config.toml`:
|
|
113
|
+
|
|
114
|
+
```toml
|
|
115
|
+
[profiles]
|
|
116
|
+
work = "default" # let the work profile share with the default one
|
|
117
|
+
|
|
118
|
+
[servers]
|
|
119
|
+
my-browser = "session" # by server name or executable name
|
|
120
|
+
heavy-indexer = "native" # leave it alone; takes effect on the next setup
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Scopes are `global`, `workspace`, `session` and `native`.
|
|
124
|
+
|
|
125
|
+
## Commands
|
|
126
|
+
|
|
127
|
+
| Command | What it does |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `mcp-stdio-mux status` | Which servers each config shares, and every running backend with its sessions and memory. |
|
|
130
|
+
| `mcp-stdio-mux setup [--dry-run] [--yes]` | Share every stdio server in every detected config. |
|
|
131
|
+
| `mcp-stdio-mux undo [--yes]` | Put every config back and remove the skill. |
|
|
132
|
+
| `mcp-stdio-mux run ...` | What your apps call. You do not run this yourself. |
|
|
133
|
+
|
|
134
|
+
## How it works
|
|
135
|
+
|
|
136
|
+
1. The app starts `mcp-stdio-mux run`, a short-lived shim.
|
|
137
|
+
2. The shim finds or starts the daemon for its launch signature (one lock per
|
|
138
|
+
signature), passes its stdin and stdout to the daemon over the Unix socket
|
|
139
|
+
(`SCM_RIGHTS`), and replaces itself with `/bin/cat` named
|
|
140
|
+
`mcp-stdio-mux: <server>`. That relay is the 1.2 MB the app keeps.
|
|
141
|
+
3. The daemon runs the backend in its own process group. It gives each session's
|
|
142
|
+
JSON-RPC ids and progress tokens a private namespace, answers later
|
|
143
|
+
`initialize` calls from the first result, and sends server requests (roots,
|
|
144
|
+
sampling, elicitation) to a session that supports them.
|
|
145
|
+
4. A session ends when the app closes its stdin, kills the relay, or exits. When
|
|
146
|
+
the last session is gone, the daemon waits five minutes, then stops the
|
|
147
|
+
backend's whole process group, so `npx` children do not outlive it.
|
|
148
|
+
|
|
149
|
+
Sockets live in a directory only you can read (`$XDG_RUNTIME_DIR/mcp-stdio-mux`
|
|
150
|
+
or `/tmp/mcp-stdio-mux-<uid>`), and a Unix socket cannot be reached from another
|
|
151
|
+
machine.
|
|
152
|
+
|
|
153
|
+
## Limits
|
|
154
|
+
|
|
155
|
+
- macOS and Linux. Windows is not supported.
|
|
156
|
+
- A shared backend inherits environment variables the config does not declare
|
|
157
|
+
from the session that started it. Declare anything that differs.
|
|
158
|
+
- A server that keeps per-client state but whose tool names do not show it must
|
|
159
|
+
be marked `session`.
|
|
160
|
+
- When a backend crashes, its in-flight requests fail and are not replayed; the
|
|
161
|
+
next session start launches a new backend.
|
|
162
|
+
- An app that dies without being reaped by its parent keeps its session until
|
|
163
|
+
its relay is killed.
|
|
164
|
+
|
|
165
|
+
## Development
|
|
166
|
+
|
|
167
|
+
Runtime code is Python 3.11+ standard library only.
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
PYTHONPATH=src python -m unittest discover -s tests -t .
|
|
171
|
+
ruff format --check src tests scripts && ruff check src tests scripts
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# mcp-stdio-mux
|
|
2
|
+
|
|
3
|
+
One shared process per MCP server across every AI agent session, app and
|
|
4
|
+
profile on your machine.
|
|
5
|
+
|
|
6
|
+
Open four Claude Code sessions and each one starts its own copy of every stdio
|
|
7
|
+
MCP server you configured. Add Codex, Cursor, or a second profile and the copies
|
|
8
|
+
multiply. `mcp-stdio-mux` puts one shared backend behind all of them; each
|
|
9
|
+
session reaches it through a 1.2 MB relay instead of a server of its own.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
uvx mcp-stdio-mux setup
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Setup finds Claude Code (every profile), Claude Desktop, Codex (every profile),
|
|
18
|
+
Gemini CLI, Cursor, Windsurf and OpenCode, shows what it will change, and asks
|
|
19
|
+
once. It installs itself as a `uv` tool so your configs point at a path that
|
|
20
|
+
stays. Restart your agent sessions afterwards.
|
|
21
|
+
|
|
22
|
+
Without `uv`: `pipx install mcp-stdio-mux && mcp-stdio-mux setup`.
|
|
23
|
+
|
|
24
|
+
Setup also gives Claude Code and Codex an agent skill, so you can ask an agent
|
|
25
|
+
to "share my MCP servers" or "share the server I just added".
|
|
26
|
+
|
|
27
|
+
## Measured
|
|
28
|
+
|
|
29
|
+
Four sessions of one server, every process the sessions caused (relays, daemon,
|
|
30
|
+
backend and its children), macOS 27.2 on Apple silicon with 16 GB, three runs on
|
|
31
|
+
10 November 2026. The unit is `phys_footprint`, the "Memory" column of Activity
|
|
32
|
+
Monitor.
|
|
33
|
+
|
|
34
|
+
| Server | Without a mux | With mcp-stdio-mux | Saved |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| serena (Python) | 679–757 MB | 202–203 MB | 70–73% |
|
|
37
|
+
| tilth (Node) | 57 MB | 36 MB | 37% |
|
|
38
|
+
|
|
39
|
+
Each session's own process drops from 12–94 MB to 1.2 MB. The saving grows with
|
|
40
|
+
the number of sessions and with how heavy the server is. Reproduce it with any
|
|
41
|
+
server:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python scripts/benchmark.py --sessions 4 -- tilth --mcp
|
|
45
|
+
python scripts/benchmark.py --sessions 4 \
|
|
46
|
+
--prefix "mcp-stdio-mux run --domain bench --server tilth" -- tilth --mcp
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## What setup changes, and what it never touches
|
|
50
|
+
|
|
51
|
+
- Each local (stdio) server's command in your user-level config files becomes
|
|
52
|
+
`mcp-stdio-mux run --domain <profile> --server <name> -- <original command>`.
|
|
53
|
+
The original command stays readable after `--`.
|
|
54
|
+
- Every file is backed up to `~/.local/state/mcp-stdio-mux/backups/` first,
|
|
55
|
+
written atomically through symlinks with its permissions kept, then read back.
|
|
56
|
+
- Remote (HTTP or SSE) servers, project `.mcp.json` files, and servers you mark
|
|
57
|
+
`native` are never touched. A file that nothing changes keeps its exact bytes.
|
|
58
|
+
- `mcp-stdio-mux undo` restores each file's exact bytes when the app has not
|
|
59
|
+
written to it since setup, and removes only the wrapper when it has.
|
|
60
|
+
- Running setup a second time changes nothing that is already shared.
|
|
61
|
+
|
|
62
|
+
## Who shares with whom
|
|
63
|
+
|
|
64
|
+
Two sessions share one backend only when all of these match:
|
|
65
|
+
|
|
66
|
+
| Part | Where it comes from |
|
|
67
|
+
|---|---|
|
|
68
|
+
| Domain | The profile name: `~/.claude-work` and `~/.codex-work` are both `work`; default profiles are `default`. |
|
|
69
|
+
| Command | The exact backend command and arguments. |
|
|
70
|
+
| Credentials | A SHA-256 of the environment variables the config declares. Values are never logged. |
|
|
71
|
+
| Workspace | The git root, for servers bound to the directory they start in. |
|
|
72
|
+
|
|
73
|
+
Differently named profiles never share unless you map them to one domain.
|
|
74
|
+
|
|
75
|
+
Each backend also gets a scope:
|
|
76
|
+
|
|
77
|
+
| Scope | When | Backend |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `global` | Default. | One per signature, started in your home directory. |
|
|
80
|
+
| `workspace` | An argument flag names `cwd`, `project`, `workspace` or `root`, an argument is `.`, or the config sets `cwd`. | One per signature and git root. |
|
|
81
|
+
| `session` | The first `tools/list` reply has a tool named like `browser`, `tab`, `page`, `navigate`, `click`, `screenshot`, `session`, `login` or `logout`. | One per session. |
|
|
82
|
+
|
|
83
|
+
Until a server is classified, a second session waits (up to 20 seconds), so no
|
|
84
|
+
session attaches to a stateful server before the mux knows it is one.
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
Optional. `~/.config/mcp-stdio-mux/config.toml`:
|
|
89
|
+
|
|
90
|
+
```toml
|
|
91
|
+
[profiles]
|
|
92
|
+
work = "default" # let the work profile share with the default one
|
|
93
|
+
|
|
94
|
+
[servers]
|
|
95
|
+
my-browser = "session" # by server name or executable name
|
|
96
|
+
heavy-indexer = "native" # leave it alone; takes effect on the next setup
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Scopes are `global`, `workspace`, `session` and `native`.
|
|
100
|
+
|
|
101
|
+
## Commands
|
|
102
|
+
|
|
103
|
+
| Command | What it does |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `mcp-stdio-mux status` | Which servers each config shares, and every running backend with its sessions and memory. |
|
|
106
|
+
| `mcp-stdio-mux setup [--dry-run] [--yes]` | Share every stdio server in every detected config. |
|
|
107
|
+
| `mcp-stdio-mux undo [--yes]` | Put every config back and remove the skill. |
|
|
108
|
+
| `mcp-stdio-mux run ...` | What your apps call. You do not run this yourself. |
|
|
109
|
+
|
|
110
|
+
## How it works
|
|
111
|
+
|
|
112
|
+
1. The app starts `mcp-stdio-mux run`, a short-lived shim.
|
|
113
|
+
2. The shim finds or starts the daemon for its launch signature (one lock per
|
|
114
|
+
signature), passes its stdin and stdout to the daemon over the Unix socket
|
|
115
|
+
(`SCM_RIGHTS`), and replaces itself with `/bin/cat` named
|
|
116
|
+
`mcp-stdio-mux: <server>`. That relay is the 1.2 MB the app keeps.
|
|
117
|
+
3. The daemon runs the backend in its own process group. It gives each session's
|
|
118
|
+
JSON-RPC ids and progress tokens a private namespace, answers later
|
|
119
|
+
`initialize` calls from the first result, and sends server requests (roots,
|
|
120
|
+
sampling, elicitation) to a session that supports them.
|
|
121
|
+
4. A session ends when the app closes its stdin, kills the relay, or exits. When
|
|
122
|
+
the last session is gone, the daemon waits five minutes, then stops the
|
|
123
|
+
backend's whole process group, so `npx` children do not outlive it.
|
|
124
|
+
|
|
125
|
+
Sockets live in a directory only you can read (`$XDG_RUNTIME_DIR/mcp-stdio-mux`
|
|
126
|
+
or `/tmp/mcp-stdio-mux-<uid>`), and a Unix socket cannot be reached from another
|
|
127
|
+
machine.
|
|
128
|
+
|
|
129
|
+
## Limits
|
|
130
|
+
|
|
131
|
+
- macOS and Linux. Windows is not supported.
|
|
132
|
+
- A shared backend inherits environment variables the config does not declare
|
|
133
|
+
from the session that started it. Declare anything that differs.
|
|
134
|
+
- A server that keeps per-client state but whose tool names do not show it must
|
|
135
|
+
be marked `session`.
|
|
136
|
+
- When a backend crashes, its in-flight requests fail and are not replayed; the
|
|
137
|
+
next session start launches a new backend.
|
|
138
|
+
- An app that dies without being reaped by its parent keeps its session until
|
|
139
|
+
its relay is killed.
|
|
140
|
+
|
|
141
|
+
## Development
|
|
142
|
+
|
|
143
|
+
Runtime code is Python 3.11+ standard library only.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
PYTHONPATH=src python -m unittest discover -s tests -t .
|
|
147
|
+
ruff format --check src tests scripts && ruff check src tests scripts
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
Start with the [README](../README.md) for installation and behaviour.
|
|
4
|
+
|
|
5
|
+
| Document | What it holds |
|
|
6
|
+
|---|---|
|
|
7
|
+
| [specs/2026-11-10-v0.1-scope.md](specs/2026-11-10-v0.1-scope.md) | The contract for the first public release. |
|
|
8
|
+
| [../skills/mcp-stdio-mux/SKILL.md](../skills/mcp-stdio-mux/SKILL.md) | The agent skill that setup installs. |
|
|
9
|
+
| [../scripts/benchmark.py](../scripts/benchmark.py) | How the README's memory numbers are measured. |
|
|
10
|
+
| [../AGENTS.md](../AGENTS.md) | Rules for contributors and coding agents. |
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# v0.1 Scope
|
|
2
|
+
|
|
3
|
+
Approved on 10 November 2026. This file is the contract for the first public
|
|
4
|
+
release; a change to a criterion is agreed in writing before the code changes.
|
|
5
|
+
|
|
6
|
+
## Story
|
|
7
|
+
|
|
8
|
+
As a developer who runs several AI agent apps (Claude Code, Codex, and others),
|
|
9
|
+
each with several profiles, I run one command and every stdio MCP server is
|
|
10
|
+
shared safely across my sessions, so duplicate MCP processes stop eating my RAM.
|
|
11
|
+
|
|
12
|
+
## Acceptance Criteria
|
|
13
|
+
|
|
14
|
+
1. **One command.** `uvx mcp-stdio-mux setup` finds every supported app and
|
|
15
|
+
profile, shows which servers it will route through the mux in which file,
|
|
16
|
+
and applies after one confirmation. `--yes` skips the question.
|
|
17
|
+
2. **Detection.** Claude Code (every `~/.claude*` profile and
|
|
18
|
+
`$CLAUDE_CONFIG_DIR`), Claude Desktop, Codex (every `~/.codex*` home and
|
|
19
|
+
`$CODEX_HOME`), Gemini CLI, Cursor, Windsurf, and OpenCode.
|
|
20
|
+
3. **Profiles stay isolated.** Profiles with the same name share backends across
|
|
21
|
+
apps (`~/.claude-work` and `~/.codex-work`); differently named profiles never
|
|
22
|
+
share unless the user maps them to one domain in the config file. Different
|
|
23
|
+
declared credentials never share.
|
|
24
|
+
4. **User configs are safe.** Every file is backed up before it changes, written
|
|
25
|
+
atomically, and re-checked after writing. A second `setup` changes nothing.
|
|
26
|
+
`undo` restores the original bytes when the app has not touched the file
|
|
27
|
+
since, and unwraps in place when it has. HTTP servers are never touched.
|
|
28
|
+
5. **Lighter shims.** The per-session shim uses at least 50% less memory than the
|
|
29
|
+
internal runtime it replaces, measured the same way. All ported tests pass.
|
|
30
|
+
Baseline corrected on 10 November 2026 from 14 MB (a bare import probe) to
|
|
31
|
+
18.2–18.5 MB `phys_footprint`, measured with `scripts/benchmark.py`, the same
|
|
32
|
+
tool that measures the new relay.
|
|
33
|
+
6. **Agent skill.** `SKILL.md` lets a user ask an agent to "share my MCP
|
|
34
|
+
servers": the agent previews, applies after consent, verifies with
|
|
35
|
+
`mcp-stdio-mux status`, and knows how to undo.
|
|
36
|
+
7. **Release.** macOS and Linux CI pass on Python 3.11 and 3.13, the wheel
|
|
37
|
+
installs into a clean environment, the private-data scan is clean, and the
|
|
38
|
+
README carries before/after numbers measured on a real machine.
|
|
39
|
+
|
|
40
|
+
## Out of Scope
|
|
41
|
+
|
|
42
|
+
Windows; project files such as a repository's `.mcp.json` (they belong to a
|
|
43
|
+
team); HTTP and SSE servers; request replay after a backend crash; a dashboard;
|
|
44
|
+
re-wrapping new servers without a command (re-run `setup` or ask the skill).
|
|
45
|
+
|
|
46
|
+
## Definition of Done
|
|
47
|
+
|
|
48
|
+
Each criterion has evidence in the release pull request; CI is green; the
|
|
49
|
+
owner makes the repository public and registers the PyPI trusted publisher.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mcp-stdio-mux"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "One shared process per MCP server across every AI agent session, app and profile on your machine."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
authors = [{ name = "Ahmad Muhyidin" }]
|
|
13
|
+
requires-python = ">=3.11"
|
|
14
|
+
dependencies = []
|
|
15
|
+
keywords = ["mcp", "model-context-protocol", "claude-code", "codex", "ai-agents", "stdio"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Environment :: Console",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"Operating System :: MacOS",
|
|
21
|
+
"Operating System :: POSIX :: Linux",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Topic :: Software Development :: Libraries",
|
|
27
|
+
"Topic :: Utilities",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/techidin/mcp-stdio-mux"
|
|
32
|
+
Issues = "https://github.com/techidin/mcp-stdio-mux/issues"
|
|
33
|
+
|
|
34
|
+
[project.scripts]
|
|
35
|
+
mcp-stdio-mux = "mcp_stdio_mux.cli:main"
|
|
36
|
+
|
|
37
|
+
[tool.hatch.version]
|
|
38
|
+
path = "src/mcp_stdio_mux/__init__.py"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/mcp_stdio_mux"]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
44
|
+
"skills/mcp-stdio-mux/SKILL.md" = "mcp_stdio_mux/SKILL.md"
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.sdist]
|
|
47
|
+
include = ["src", "tests", "skills", "docs", "README.md", "LICENSE"]
|
|
48
|
+
|
|
49
|
+
[tool.ruff]
|
|
50
|
+
line-length = 88
|
|
51
|
+
target-version = "py311"
|
|
52
|
+
|
|
53
|
+
[tool.ruff.lint]
|
|
54
|
+
select = ["E", "F", "W", "I", "B", "UP"]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mcp-stdio-mux
|
|
3
|
+
description: Use when the user wants MCP servers to stop piling up across agent sessions, asks to share or dedupe MCP server processes to save memory, added a new stdio MCP server after mcp-stdio-mux setup, sees an MCP server fail after setup, or wants to undo mcp-stdio-mux.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# mcp-stdio-mux
|
|
7
|
+
|
|
8
|
+
`mcp-stdio-mux` makes every session of every agent app (Claude Code, Claude
|
|
9
|
+
Desktop, Codex, Gemini CLI, Cursor, Windsurf, OpenCode) share one process per
|
|
10
|
+
stdio MCP server instead of starting its own copy. It rewrites each server's
|
|
11
|
+
command in the user's own config files, with backups and a full undo.
|
|
12
|
+
|
|
13
|
+
## Share the user's servers
|
|
14
|
+
|
|
15
|
+
1. Look before changing anything:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
mcp-stdio-mux status # or: uvx mcp-stdio-mux status
|
|
19
|
+
mcp-stdio-mux setup --dry-run
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. Show the user the plan: which apps, profiles, and how many servers. Say that
|
|
23
|
+
profiles with different names never share (unless mapped in the config), and
|
|
24
|
+
that remote (HTTP) servers and project `.mcp.json` files are not touched.
|
|
25
|
+
3. Apply only after the user agrees:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
mcp-stdio-mux setup --yes
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If `mcp-stdio-mux` is not on PATH, `uvx mcp-stdio-mux setup --yes` installs it
|
|
32
|
+
as a tool first, so the configs point at a path that stays.
|
|
33
|
+
4. Tell the user to restart agent sessions; open sessions keep their old servers.
|
|
34
|
+
5. After the restart, `mcp-stdio-mux status` lists each running backend and how
|
|
35
|
+
many sessions it serves.
|
|
36
|
+
|
|
37
|
+
Never wrap or unwrap a server by editing a config file by hand; re-run `setup`.
|
|
38
|
+
|
|
39
|
+
## When the user adds a server later
|
|
40
|
+
|
|
41
|
+
Run `mcp-stdio-mux setup --dry-run`, show the one new line, then `setup --yes`.
|
|
42
|
+
A second `setup` changes nothing that is already shared.
|
|
43
|
+
|
|
44
|
+
## When a server misbehaves
|
|
45
|
+
|
|
46
|
+
- Find its daemon in `mcp-stdio-mux status`; its log is
|
|
47
|
+
`<runtime dir>/<key>.log` (runtime dir: `$MCP_STDIO_MUX_RUNTIME_DIR`, else
|
|
48
|
+
`$XDG_RUNTIME_DIR/mcp-stdio-mux`, else `/tmp/mcp-stdio-mux-<uid>`).
|
|
49
|
+
- A server that keeps per-client state (a browser, a login) must not be shared.
|
|
50
|
+
The mux detects most of them from their tool names. For one it misses, add to
|
|
51
|
+
`~/.config/mcp-stdio-mux/config.toml`:
|
|
52
|
+
|
|
53
|
+
```toml
|
|
54
|
+
[servers]
|
|
55
|
+
my-browser = "session" # one backend per session
|
|
56
|
+
# other scopes: "global", "workspace" (one per git root), "native" (leave alone)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Keys are the server name or the executable name. `native` takes effect after
|
|
60
|
+
`setup` runs again; the other scopes apply at the next session start.
|
|
61
|
+
- To let two differently named profiles share (for example `work` and `default`):
|
|
62
|
+
|
|
63
|
+
```toml
|
|
64
|
+
[profiles]
|
|
65
|
+
work = "default"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
then run `setup` again.
|
|
69
|
+
|
|
70
|
+
## Undo
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
mcp-stdio-mux undo --yes
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
It restores each file's exact bytes when the app has not written to it since
|
|
77
|
+
setup, and removes only the mux wrapper when it has. It also removes this skill.
|