dosbox-mcp 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.
Files changed (50) hide show
  1. dosbox_mcp-0.1.0/LICENSE +13 -0
  2. dosbox_mcp-0.1.0/MANIFEST.in +4 -0
  3. dosbox_mcp-0.1.0/PKG-INFO +121 -0
  4. dosbox_mcp-0.1.0/PROTOCOL.md +166 -0
  5. dosbox_mcp-0.1.0/README.md +87 -0
  6. dosbox_mcp-0.1.0/THIRD_PARTY_LICENSES.txt +30 -0
  7. dosbox_mcp-0.1.0/dosbox_mcp/__init__.py +3 -0
  8. dosbox_mcp-0.1.0/dosbox_mcp/capabilities.py +26 -0
  9. dosbox_mcp-0.1.0/dosbox_mcp/cli.py +177 -0
  10. dosbox_mcp-0.1.0/dosbox_mcp/client.py +65 -0
  11. dosbox_mcp-0.1.0/dosbox_mcp/config.py +265 -0
  12. dosbox_mcp-0.1.0/dosbox_mcp/connection.py +208 -0
  13. dosbox_mcp-0.1.0/dosbox_mcp/lifecycle.py +261 -0
  14. dosbox_mcp-0.1.0/dosbox_mcp/protocol.py +85 -0
  15. dosbox_mcp-0.1.0/dosbox_mcp/server.py +128 -0
  16. dosbox_mcp-0.1.0/dosbox_mcp/tools/__init__.py +3 -0
  17. dosbox_mcp-0.1.0/dosbox_mcp/tools/bridge.py +284 -0
  18. dosbox_mcp-0.1.0/dosbox_mcp/tools/cpu.py +40 -0
  19. dosbox_mcp-0.1.0/dosbox_mcp/tools/debug.py +53 -0
  20. dosbox_mcp-0.1.0/dosbox_mcp/tools/freeze.py +93 -0
  21. dosbox_mcp-0.1.0/dosbox_mcp/tools/input.py +188 -0
  22. dosbox_mcp-0.1.0/dosbox_mcp/tools/io.py +81 -0
  23. dosbox_mcp-0.1.0/dosbox_mcp/tools/media.py +49 -0
  24. dosbox_mcp-0.1.0/dosbox_mcp/tools/memory.py +138 -0
  25. dosbox_mcp-0.1.0/dosbox_mcp/tools/screen.py +56 -0
  26. dosbox_mcp-0.1.0/dosbox_mcp/tools/script.py +68 -0
  27. dosbox_mcp-0.1.0/dosbox_mcp/tools/session.py +85 -0
  28. dosbox_mcp-0.1.0/dosbox_mcp.egg-info/PKG-INFO +121 -0
  29. dosbox_mcp-0.1.0/dosbox_mcp.egg-info/SOURCES.txt +48 -0
  30. dosbox_mcp-0.1.0/dosbox_mcp.egg-info/dependency_links.txt +1 -0
  31. dosbox_mcp-0.1.0/dosbox_mcp.egg-info/entry_points.txt +2 -0
  32. dosbox_mcp-0.1.0/dosbox_mcp.egg-info/requires.txt +9 -0
  33. dosbox_mcp-0.1.0/dosbox_mcp.egg-info/top_level.txt +1 -0
  34. dosbox_mcp-0.1.0/feelies/INSTALL.md +69 -0
  35. dosbox_mcp-0.1.0/feelies/MANUAL.md +144 -0
  36. dosbox_mcp-0.1.0/feelies/REFCARD.md +97 -0
  37. dosbox_mcp-0.1.0/feelies/TECH-SUPPORT.md +81 -0
  38. dosbox_mcp-0.1.0/licenses/CC-BY-SA-4.0.txt +428 -0
  39. dosbox_mcp-0.1.0/licenses/GPL-2.0-or-later.txt +339 -0
  40. dosbox_mcp-0.1.0/pyproject.toml +61 -0
  41. dosbox_mcp-0.1.0/setup.cfg +4 -0
  42. dosbox_mcp-0.1.0/tests/test_bridge_tools.py +251 -0
  43. dosbox_mcp-0.1.0/tests/test_capabilities.py +43 -0
  44. dosbox_mcp-0.1.0/tests/test_cli.py +100 -0
  45. dosbox_mcp-0.1.0/tests/test_client.py +68 -0
  46. dosbox_mcp-0.1.0/tests/test_config.py +192 -0
  47. dosbox_mcp-0.1.0/tests/test_connection.py +206 -0
  48. dosbox_mcp-0.1.0/tests/test_lifecycle.py +212 -0
  49. dosbox_mcp-0.1.0/tests/test_protocol.py +78 -0
  50. dosbox_mcp-0.1.0/tests/test_server.py +198 -0
@@ -0,0 +1,13 @@
1
+ dosbox-mcp License
2
+ ------------------
3
+
4
+ dosbox-mcp is licensed under the GNU General Public License
5
+ version 2 or later (GPL-2.0-or-later).
6
+
7
+ The full license text is in licenses/GPL-2.0-or-later.txt.
8
+
9
+ Third-party dependencies are listed in THIRD_PARTY_LICENSES.txt.
10
+
11
+ Exception: PROTOCOL.md (the dosbox-mcp protocol specification) is
12
+ licensed under Creative Commons Attribution-ShareAlike 4.0
13
+ (CC-BY-SA-4.0). The full license text is in licenses/CC-BY-SA-4.0.txt.
@@ -0,0 +1,4 @@
1
+ include PROTOCOL.md
2
+ include THIRD_PARTY_LICENSES.txt
3
+ recursive-include feelies *.md
4
+ recursive-include licenses *.txt
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: dosbox-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for dosbox-automation: drive a DOS machine from an AI agent
5
+ Author-email: dosbox-mcp contributors <dosbox-mcp@trinity2k.net>
6
+ License-Expression: GPL-2.0-or-later
7
+ Project-URL: Homepage, https://dosbox-automation.org
8
+ Project-URL: Repository, https://github.com/dosbox-automation/dosbox-mcp
9
+ Project-URL: Issues, https://github.com/dosbox-automation/dosbox-mcp/issues
10
+ Keywords: dosbox,mcp,model-context-protocol,automation,emulator,dos
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3 :: Only
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: System :: Emulators
19
+ Requires-Python: >=3.11
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ License-File: licenses/GPL-2.0-or-later.txt
23
+ License-File: licenses/CC-BY-SA-4.0.txt
24
+ License-File: THIRD_PARTY_LICENSES.txt
25
+ Requires-Dist: mcp>=1.2
26
+ Requires-Dist: httpx>=0.27
27
+ Requires-Dist: platformdirs>=4
28
+ Requires-Dist: tomlkit>=0.13
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest; extra == "dev"
31
+ Requires-Dist: ruff; extra == "dev"
32
+ Requires-Dist: bandit; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # dosbox-mcp
36
+
37
+ MCP server for [dosbox-automation](https://dosbox-automation.org):
38
+ drive a DOS machine from an AI agent.
39
+
40
+ dosbox-automation is a DOSBox fork with a built-in automation API.
41
+ This bridge connects any MCP-capable agent runtime (Claude Code and
42
+ friends) to a running emulator. The agent can type into DOS programs,
43
+ read the screen, capture frames and video, run sandboxed Lua scripts,
44
+ and inspect memory: everything the automation API offers, as MCP tools.
45
+
46
+ ## Quick start
47
+
48
+ 1. Install dosbox-automation and start it with the API enabled. The
49
+ [manual](https://dosbox-automation.org) covers setup for all
50
+ platforms.
51
+
52
+ 2. Add the bridge to your MCP client. For Claude Code:
53
+
54
+ ```
55
+ claude mcp add dosbox -- uvx dosbox-mcp
56
+ ```
57
+
58
+ Or in a JSON MCP configuration:
59
+
60
+ ```json
61
+ {
62
+ "mcpServers": {
63
+ "dosbox": { "command": "uvx", "args": ["dosbox-mcp"] }
64
+ }
65
+ }
66
+ ```
67
+
68
+ `pipx install dosbox-mcp` or `pip install dosbox-mcp` work just as
69
+ well; the command is `dosbox-mcp` either way.
70
+
71
+ That's it. The bridge finds the running emulator, checks what it can
72
+ do, and registers the matching tools. Ask your agent for `bridge_status`
73
+ to confirm the connection.
74
+
75
+ ## Configuration
76
+
77
+ Optional. Write a commented config file to tune the port, spawn an
78
+ emulator on demand, or limit what an agent may do:
79
+
80
+ ```
81
+ dosbox-mcp setup --init
82
+ ```
83
+
84
+ The comments in the generated file explain each setting. Environment
85
+ variables `DOSBOX_API_URL` and `DOSBOX_API_TOKEN` override it. The
86
+ bridge talks to loopback addresses only; it will not reach out to a
87
+ remote machine.
88
+
89
+ ## What the agent gets
90
+
91
+ Tools for session control, screen text and captures, video recording,
92
+ Lua scripting, keyboard and mouse input, and memory access. You choose
93
+ how much reach an agent has, from read-only observation to full
94
+ control.
95
+
96
+ ## An open protocol
97
+
98
+ The contract between this bridge and the emulator is written down in
99
+ [PROTOCOL.md](PROTOCOL.md) and versioned independently. dosbox-automation
100
+ is the reference implementation; any DOSBox variant that speaks the
101
+ protocol works with this bridge.
102
+
103
+ ## In the box
104
+
105
+ Like the games it drives, this one ships with its paper. The `feelies/`
106
+ folder holds the manual, a reference card for every tool, an install
107
+ sheet, and a tech-support page.
108
+
109
+ ## Requirements
110
+
111
+ - Python 3.11 or newer
112
+ - A running dosbox-automation (0.84-da2 or newer) on the same machine
113
+
114
+ ## License
115
+
116
+ GPL-2.0-or-later. See LICENSE. The protocol specification
117
+ (PROTOCOL.md) is licensed CC-BY-SA-4.0.
118
+
119
+ ---
120
+ This project is developed with tooled assistance, but tested, reviewed and
121
+ signed off by a human developer.
@@ -0,0 +1,166 @@
1
+ # The dosbox-mcp Protocol
2
+
3
+ Version 1.0.0 (2026-07-17)
4
+
5
+ This document specifies the contract between an MCP bridge and a
6
+ DOSBox-family emulator that exposes the automation REST API. The bridge
7
+ declares what the protocol can do; the engine delivers it. The reference
8
+ implementation of the engine side is dosbox-automation
9
+ (https://dosbox-automation.org); any other DOSBox variant that
10
+ implements this contract is a valid peer.
11
+
12
+ License: this specification is licensed under Creative Commons
13
+ Attribution-ShareAlike 4.0 International (CC-BY-SA-4.0). You may
14
+ implement it under any license you like - the license covers this
15
+ document, not your code. If you publish a modified version of the
16
+ specification itself, the modification must carry the same license and
17
+ credit the dosbox-mcp project. Full text: licenses/CC-BY-SA-4.0.txt in
18
+ this repository, or https://creativecommons.org/licenses/by-sa/4.0/
19
+
20
+
21
+ ## Versioning
22
+
23
+ The specification is versioned MAJOR.MINOR.PATCH:
24
+
25
+ - MAJOR changes break existing shapes. Peers with different majors are
26
+ incompatible.
27
+ - MINOR changes are additive: a new route group, route, or field.
28
+ Nothing existing changes meaning.
29
+ - PATCH changes fix or clarify the text without changing behavior.
30
+ They never appear on the wire.
31
+
32
+ On the wire, only "major.minor" travels. An engine advertises its
33
+ version in the info payload (below) as the string field `mcp_protocol`,
34
+ for example `"1.0"`. Two peers are compatible when their majors match;
35
+ the effective contract between them is the smaller of the two minors.
36
+
37
+ Backward compatibility rule: an engine whose info payload has no
38
+ `mcp_protocol` field but does have a `features` object is treated as
39
+ implicit version 1.0, because 1.0 is defined as exactly the surface
40
+ those engines ship. An info payload with neither field is not a
41
+ protocol peer.
42
+
43
+
44
+ ## Transport and security
45
+
46
+ - HTTP (optionally HTTPS) on a loopback address only. Engines must bind
47
+ to 127.0.0.1 (or ::1); bridges must refuse any non-loopback URL.
48
+ Operation across a network is out of scope for protocol 1.x.
49
+ - Every request except the hello route (below) carries a bearer token:
50
+ `Authorization: Bearer <token>`. The engine generates the token at
51
+ startup; with the engine setting `webserver_token_file = true` it is
52
+ written to a token file that local clients read. The token must never
53
+ be passed on a command line or echoed in error responses.
54
+ - Clients should identify themselves with an `X-Client` header (the
55
+ bridge sends `X-Client: mcp`). Engines may use it to drive on-screen
56
+ activity indicators; they must not grant anything based on it.
57
+
58
+
59
+ ## Discovery
60
+
61
+ ### GET /api/v1/hello (unauthenticated)
62
+
63
+ Added in 1.0 as an optional route; engines that predate it simply
64
+ answer 404 and are treated as implicit 1.0. When implemented, it
65
+ returns exactly three fields and nothing else:
66
+
67
+ ```json
68
+ {"name": "dosbox-automation", "version": "0.84-da3", "mcp_protocol": "1.0"}
69
+ ```
70
+
71
+ Its purpose is diagnostics before authentication: a client can
72
+ distinguish "no instance on this port" from "instance present, token
73
+ missing" and report precisely. Because it is unauthenticated, it must
74
+ never expose configuration, state, or anything beyond these three
75
+ fields.
76
+
77
+ ### GET /api/v1/dosbox/info (authenticated)
78
+
79
+ The identification payload. Required fields for a protocol peer:
80
+
81
+ - `version` (string): the engine's own version.
82
+ - `features` (object): capability flags, see below.
83
+ - `mcp_protocol` (string, "major.minor"): the protocol version.
84
+ Absent on pre-1.0-advertisement engines (implicit 1.0 rule).
85
+
86
+ ### Feature flags
87
+
88
+ `features` reports which optional capability groups the running build
89
+ supports. Version 1.0 defines these flags, all boolean:
90
+
91
+ | Flag | Grants |
92
+ |---|---|
93
+ | memory | memory read/write/search, freeze, DOS memory map |
94
+ | input | keyboard and input injection |
95
+ | cpu_registers | CPU register reads |
96
+ | cpu_control | CPU register writes |
97
+ | port_io | x86 I/O port read/write |
98
+ | freeze | per-frame value locks |
99
+ | debugger | execution control (reserved; no 1.0 engine ships it) |
100
+
101
+ A flag that is absent counts as false. Flags gate at call time: a
102
+ client may know a route exists in the contract while the running build
103
+ has it disabled.
104
+
105
+
106
+ ## The 1.0 contract surface
107
+
108
+ The route groups below, under the `/api/v1/` prefix, are the 1.0
109
+ contract. The machine-readable OpenAPI document served at
110
+ `/openapi.json` is part of the contract surface: it is normative for
111
+ the exact request and response schemas of these routes, and clients
112
+ verify the advertised contract against it.
113
+
114
+ | Group | Routes | Notes |
115
+ |---|---|---|
116
+ | session | `status`, `program/state`, `dosbox/info`, `dosbox/shutdown` | machine and program state; shutdown is irreversible |
117
+ | screen | `video/frame`, `video/frame/info`, `video/text` | frame capture (clean emulator output) and text-mode screen reading |
118
+ | capture | `capture/video/start`, `capture/video/stop`, `capture/video/status` | video recording control |
119
+ | input | `input/sequence`, `input/type` | named-key sequences; paced string typing (feature: input) |
120
+ | memory | `memory/{offset}/{length}` GET, `memory/{offset}` PUT, `memory/search` | guest physical memory (feature: memory) |
121
+ | freeze | `memory/freeze` POST/GET/DELETE | per-frame value locks (feature: freeze) |
122
+ | dos | `dos/internals` | DOS internals incl. the MCB memory map (feature: memory) |
123
+ | cpu | `cpu/register` PUT | register writes (feature: cpu_control) |
124
+ | io | `io/port` GET/PUT | port I/O (feature: port_io) |
125
+ | script | `script/load`, `script/start`, `script/status`, `script/stop` | sandboxed Lua; `script/load` takes the raw source as a text/plain body |
126
+ | media | `drive/swap`, mount routes | disk image swapping; mount policy applies underneath |
127
+
128
+ Semantics that are part of the contract, not just the schemas:
129
+
130
+ - Memory routes address guest physical memory with plain integer
131
+ offsets; JSON output (base64 payload) is selected with an
132
+ `Accept: application/json` header, binary is the default.
133
+ - The frame returned by `video/frame` is the clean emulator output:
134
+ on-screen overlays the engine draws for the human watching are never
135
+ in it.
136
+ - Validation lives engine-side. The engine is the trust boundary;
137
+ a client talking HTTP directly must be subject to exactly the same
138
+ limits as one going through a bridge.
139
+
140
+
141
+ ## Conformance
142
+
143
+ An engine conforms to protocol 1.0 when it:
144
+
145
+ 1. serves the session, screen, capture, and script groups,
146
+ 2. serves `dosbox/info` with a `features` object and (from now on)
147
+ the `mcp_protocol` field,
148
+ 3. gates optional groups behind their feature flags,
149
+ 4. authenticates every route except `hello` with the bearer token,
150
+ 5. binds to loopback only,
151
+ 6. serves its OpenAPI document at `/openapi.json`.
152
+
153
+ A client (bridge) conforms when it negotiates the version as specified,
154
+ refuses non-loopback targets, treats feature flags as call-time gates,
155
+ and keeps the token out of command lines, logs, and outputs.
156
+
157
+
158
+ ## Changelog
159
+
160
+ Every revision of this specification gets an entry here stating what
161
+ changed and the version it produces.
162
+
163
+ - **1.0.0 (2026-07-17)** - initial specification: versioning and
164
+ negotiation rules, transport and token requirements, hello route,
165
+ feature flags, and the 1.0 route groups as shipped by
166
+ dosbox-automation 0.84.
@@ -0,0 +1,87 @@
1
+ # dosbox-mcp
2
+
3
+ MCP server for [dosbox-automation](https://dosbox-automation.org):
4
+ drive a DOS machine from an AI agent.
5
+
6
+ dosbox-automation is a DOSBox fork with a built-in automation API.
7
+ This bridge connects any MCP-capable agent runtime (Claude Code and
8
+ friends) to a running emulator. The agent can type into DOS programs,
9
+ read the screen, capture frames and video, run sandboxed Lua scripts,
10
+ and inspect memory: everything the automation API offers, as MCP tools.
11
+
12
+ ## Quick start
13
+
14
+ 1. Install dosbox-automation and start it with the API enabled. The
15
+ [manual](https://dosbox-automation.org) covers setup for all
16
+ platforms.
17
+
18
+ 2. Add the bridge to your MCP client. For Claude Code:
19
+
20
+ ```
21
+ claude mcp add dosbox -- uvx dosbox-mcp
22
+ ```
23
+
24
+ Or in a JSON MCP configuration:
25
+
26
+ ```json
27
+ {
28
+ "mcpServers": {
29
+ "dosbox": { "command": "uvx", "args": ["dosbox-mcp"] }
30
+ }
31
+ }
32
+ ```
33
+
34
+ `pipx install dosbox-mcp` or `pip install dosbox-mcp` work just as
35
+ well; the command is `dosbox-mcp` either way.
36
+
37
+ That's it. The bridge finds the running emulator, checks what it can
38
+ do, and registers the matching tools. Ask your agent for `bridge_status`
39
+ to confirm the connection.
40
+
41
+ ## Configuration
42
+
43
+ Optional. Write a commented config file to tune the port, spawn an
44
+ emulator on demand, or limit what an agent may do:
45
+
46
+ ```
47
+ dosbox-mcp setup --init
48
+ ```
49
+
50
+ The comments in the generated file explain each setting. Environment
51
+ variables `DOSBOX_API_URL` and `DOSBOX_API_TOKEN` override it. The
52
+ bridge talks to loopback addresses only; it will not reach out to a
53
+ remote machine.
54
+
55
+ ## What the agent gets
56
+
57
+ Tools for session control, screen text and captures, video recording,
58
+ Lua scripting, keyboard and mouse input, and memory access. You choose
59
+ how much reach an agent has, from read-only observation to full
60
+ control.
61
+
62
+ ## An open protocol
63
+
64
+ The contract between this bridge and the emulator is written down in
65
+ [PROTOCOL.md](PROTOCOL.md) and versioned independently. dosbox-automation
66
+ is the reference implementation; any DOSBox variant that speaks the
67
+ protocol works with this bridge.
68
+
69
+ ## In the box
70
+
71
+ Like the games it drives, this one ships with its paper. The `feelies/`
72
+ folder holds the manual, a reference card for every tool, an install
73
+ sheet, and a tech-support page.
74
+
75
+ ## Requirements
76
+
77
+ - Python 3.11 or newer
78
+ - A running dosbox-automation (0.84-da2 or newer) on the same machine
79
+
80
+ ## License
81
+
82
+ GPL-2.0-or-later. See LICENSE. The protocol specification
83
+ (PROTOCOL.md) is licensed CC-BY-SA-4.0.
84
+
85
+ ---
86
+ This project is developed with tooled assistance, but tested, reviewed and
87
+ signed off by a human developer.
@@ -0,0 +1,30 @@
1
+ ================================================================================
2
+ dosbox-mcp THIRD-PARTY LICENSES
3
+ ================================================================================
4
+
5
+ Runtime dependencies, installed from PyPI:
6
+
7
+ httpx HTTP client for Python
8
+ Copyright (c) Tom Christie
9
+ License: BSD-3-Clause
10
+ Homepage: https://github.com/encode/httpx
11
+
12
+ mcp Model Context Protocol SDK
13
+ Copyright (c) Anthropic, PBC.
14
+ License: MIT
15
+ Homepage: https://modelcontextprotocol.io
16
+
17
+ platformdirs Platform-specific user directories
18
+ Copyright (c) 2010-202x The platformdirs developers
19
+ License: MIT
20
+ Homepage: https://github.com/tox-dev/platformdirs
21
+
22
+ tomlkit Style-preserving TOML library
23
+ Copyright (c) Sebastien Eustace
24
+ License: MIT
25
+ Homepage: https://github.com/python-poetry/tomlkit
26
+
27
+ All licenses are compatible with GPL-2.0-or-later.
28
+
29
+ END OF THIRD-PARTY LICENSES
30
+ ================================================================================
@@ -0,0 +1,3 @@
1
+ # This file is part of the dosbox-mcp Project.
2
+ # License: GPL-2.0-or-later. Contact: dosbox-mcp@trinity2k.net
3
+ #
@@ -0,0 +1,26 @@
1
+ # This file is part of the dosbox-mcp Project.
2
+ # License: GPL-2.0-or-later. Contact: dosbox-mcp@trinity2k.net
3
+ #
4
+
5
+ # Maps a bridge tool group to the feature flag that must be true for it
6
+ # to register. Groups always present (session, screen, script, media)
7
+ # are not gated.
8
+ GROUP_CAPABILITY = {
9
+ "memory": "memory",
10
+ "input": "input",
11
+ "freeze": "freeze",
12
+ "cpu_registers": "cpu_registers",
13
+ "cpu_control": "cpu_control",
14
+ "port_io": "port_io",
15
+ "debugger": "debugger",
16
+ }
17
+
18
+ ALWAYS_ON = {"session", "screen", "script", "media"}
19
+
20
+
21
+ def registered_groups(features: dict) -> set:
22
+ groups = set(ALWAYS_ON)
23
+ for group, flag in GROUP_CAPABILITY.items():
24
+ if features.get(flag) is True:
25
+ groups.add(group)
26
+ return groups
@@ -0,0 +1,177 @@
1
+ # This file is part of the dosbox-mcp Project.
2
+ # License: GPL-2.0-or-later. Contact: dosbox-mcp@trinity2k.net
3
+ #
4
+
5
+ """Command line entry point.
6
+
7
+ `dosbox-mcp` with no arguments serves MCP over stdio (what an MCP client
8
+ spawns). `setup` and `probe` are the human-facing configuration
9
+ commands; there is deliberately no interactive dialog - the commented
10
+ config template plays that role.
11
+ """
12
+
13
+ import argparse
14
+ import json
15
+ import sys
16
+
17
+
18
+ from .client import DosboxClient
19
+ from .config import (
20
+ Config,
21
+ default_config_path,
22
+ update_config_file,
23
+ write_config_template,
24
+ )
25
+ from .connection import Connection, NotConnected
26
+ from .lifecycle import InstanceManager, LifecycleError
27
+ from .protocol import BRIDGE_PROTOCOL
28
+
29
+
30
+ def _serve() -> int:
31
+ # Local import: the MCP server pulls in asyncio machinery the other
32
+ # subcommands never need.
33
+ from .server import main as serve_main
34
+ serve_main()
35
+ return 0
36
+
37
+
38
+ def _connection_factory(config: Config) -> Connection:
39
+ return Connection(config)
40
+
41
+
42
+ def _cmd_setup(args) -> int:
43
+ path = default_config_path()
44
+ if args.init:
45
+ try:
46
+ write_config_template(path)
47
+ except FileExistsError as e:
48
+ print(str(e), file=sys.stderr)
49
+ return 2
50
+ print(f"wrote {path} - edit it to suit; commented lines show defaults")
51
+ return 0
52
+
53
+ changes = {}
54
+ for key in ("binary", "port", "protocol", "mode", "token_file"):
55
+ value = getattr(args, key)
56
+ if value is not None:
57
+ changes[key] = value
58
+ if args.headless is not None:
59
+ changes["headless"] = args.headless
60
+
61
+ if not changes:
62
+ config = Config.load()
63
+ current = {
64
+ "config_file": str(path),
65
+ "binary": str(config.binary) if config.binary else None,
66
+ "port": config.port,
67
+ "headless": config.headless,
68
+ "protocol": config.protocol,
69
+ "mode": config.mode,
70
+ "token_file": str(config.token_file) if config.token_file else None,
71
+ }
72
+ print(json.dumps(current, indent=2))
73
+ return 0
74
+
75
+ try:
76
+ update_config_file(path, changes)
77
+ except ValueError as e:
78
+ print(str(e), file=sys.stderr)
79
+ return 2
80
+ print(f"saved to {path}")
81
+ return 0
82
+
83
+
84
+ def _cmd_probe(args) -> int:
85
+ try:
86
+ config = Config.load()
87
+ except ValueError as e:
88
+ print(str(e), file=sys.stderr)
89
+ return 2
90
+
91
+ print(f"bridge protocol: {BRIDGE_PROTOCOL}")
92
+ print(f"config file: {default_config_path()}")
93
+ print(f"target: {config.base_url} (mode {config.mode})")
94
+
95
+ if config.binary is not None:
96
+ return _probe_binary(config)
97
+ print("no binary configured - trying to attach to a running instance")
98
+ return _probe_attach(config)
99
+
100
+
101
+ def _probe_binary(config: Config) -> int:
102
+ def attach(base_url, token):
103
+ client = DosboxClient(base_url, token)
104
+ return client.get("/api/v1/dosbox/info")
105
+
106
+ manager = InstanceManager(config, attach=attach)
107
+ try:
108
+ info = manager.start()
109
+ except LifecycleError as e:
110
+ print(str(e), file=sys.stderr)
111
+ return 1
112
+ try:
113
+ print(f"binary: {config.binary}")
114
+ print(f"engine version: {info.get('version', '?')}")
115
+ print(f"engine protocol: {info.get('mcp_protocol', 'implicit 1.0')}")
116
+ finally:
117
+ manager.stop()
118
+ return 0
119
+
120
+
121
+ def _probe_attach(config: Config) -> int:
122
+ conn = _connection_factory(config)
123
+ try:
124
+ conn.ensure_connected()
125
+ except NotConnected as e:
126
+ print(f"not connected: {e}")
127
+ return 1
128
+ status = conn.status()
129
+ print(f"engine version: {status['engine_version']}")
130
+ print(f"protocol: {status['protocol']}")
131
+ print(f"features: {json.dumps(status['features'])}")
132
+ return 0
133
+
134
+
135
+ def _build_parser() -> argparse.ArgumentParser:
136
+ parser = argparse.ArgumentParser(
137
+ prog="dosbox-mcp",
138
+ description="MCP bridge for dosbox. Without a subcommand, serves "
139
+ "MCP over stdio.",
140
+ )
141
+ sub = parser.add_subparsers(dest="command")
142
+
143
+ setup = sub.add_parser(
144
+ "setup",
145
+ help="write config values; --init writes a commented template; "
146
+ "no flags prints the current configuration",
147
+ )
148
+ setup.add_argument("--init", action="store_true",
149
+ help="write the commented default config")
150
+ setup.add_argument("--binary", help="dosbox binary that bridge_start spawns")
151
+ setup.add_argument("--port", type=int, help="webserver port on 127.0.0.1")
152
+ setup.add_argument("--headless", action=argparse.BooleanOptionalAction,
153
+ default=None, help="spawn without a window")
154
+ setup.add_argument("--protocol", help='protocol pin, "major.minor"')
155
+ setup.add_argument("--mode", help="capability mode: observe/interact/full")
156
+ setup.add_argument("--token-file", dest="token_file",
157
+ help="token file of an already-running instance")
158
+
159
+ sub.add_parser(
160
+ "probe",
161
+ help="validate the config and probe the binary (or a running "
162
+ "instance) for version and protocol",
163
+ )
164
+ return parser
165
+
166
+
167
+ def main(argv=None) -> int:
168
+ args = _build_parser().parse_args(argv)
169
+ if args.command == "setup":
170
+ return _cmd_setup(args)
171
+ if args.command == "probe":
172
+ return _cmd_probe(args)
173
+ return _serve()
174
+
175
+
176
+ if __name__ == "__main__":
177
+ sys.exit(main())