lablink-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.
- lablink_mcp-0.1.0/.github/workflows/ci.yml +31 -0
- lablink_mcp-0.1.0/.gitignore +45 -0
- lablink_mcp-0.1.0/CHANGELOG.md +69 -0
- lablink_mcp-0.1.0/CONTRIBUTING.md +104 -0
- lablink_mcp-0.1.0/LICENSE +21 -0
- lablink_mcp-0.1.0/PKG-INFO +426 -0
- lablink_mcp-0.1.0/README.md +353 -0
- lablink_mcp-0.1.0/docs/ARCHITECTURE.md +515 -0
- lablink_mcp-0.1.0/docs/agent_docs/agent_development.md +136 -0
- lablink_mcp-0.1.0/docs/agent_docs/readme_agent.md +98 -0
- lablink_mcp-0.1.0/docs/assets/banner-spec.md +198 -0
- lablink_mcp-0.1.0/docs/assets/banner.svg +262 -0
- lablink_mcp-0.1.0/examples/.env.example +8 -0
- lablink_mcp-0.1.0/examples/configs/external_saleae.toml +28 -0
- lablink_mcp-0.1.0/examples/configs/python_shell_env.toml +15 -0
- lablink_mcp-0.1.0/examples/configs/rest_daq.toml +28 -0
- lablink_mcp-0.1.0/examples/configs/serial_device.toml +25 -0
- lablink_mcp-0.1.0/examples/configs/ssh_pi.toml +26 -0
- lablink_mcp-0.1.0/examples/configs/visa_scope.toml +25 -0
- lablink_mcp-0.1.0/lablink/__init__.py +0 -0
- lablink_mcp-0.1.0/lablink/base.py +270 -0
- lablink_mcp-0.1.0/lablink/cli.py +135 -0
- lablink_mcp-0.1.0/lablink/config.py +124 -0
- lablink_mcp-0.1.0/lablink/event_logger.py +88 -0
- lablink_mcp-0.1.0/lablink/exceptions.py +9 -0
- lablink_mcp-0.1.0/lablink/interfaces/__init__.py +45 -0
- lablink_mcp-0.1.0/lablink/interfaces/external_mcp/__init__.py +6 -0
- lablink_mcp-0.1.0/lablink/interfaces/external_mcp/config.py +30 -0
- lablink_mcp-0.1.0/lablink/interfaces/external_mcp/driver.py +109 -0
- lablink_mcp-0.1.0/lablink/interfaces/python_shell/__init__.py +4 -0
- lablink_mcp-0.1.0/lablink/interfaces/python_shell/bootstrap.py +144 -0
- lablink_mcp-0.1.0/lablink/interfaces/python_shell/config.py +36 -0
- lablink_mcp-0.1.0/lablink/interfaces/python_shell/driver.py +709 -0
- lablink_mcp-0.1.0/lablink/interfaces/rest/__init__.py +6 -0
- lablink_mcp-0.1.0/lablink/interfaces/rest/config.py +31 -0
- lablink_mcp-0.1.0/lablink/interfaces/rest/driver.py +647 -0
- lablink_mcp-0.1.0/lablink/interfaces/serial/__init__.py +4 -0
- lablink_mcp-0.1.0/lablink/interfaces/serial/config.py +37 -0
- lablink_mcp-0.1.0/lablink/interfaces/serial/driver.py +571 -0
- lablink_mcp-0.1.0/lablink/interfaces/ssh/__init__.py +6 -0
- lablink_mcp-0.1.0/lablink/interfaces/ssh/config.py +22 -0
- lablink_mcp-0.1.0/lablink/interfaces/ssh/driver.py +759 -0
- lablink_mcp-0.1.0/lablink/interfaces/visa/__init__.py +6 -0
- lablink_mcp-0.1.0/lablink/interfaces/visa/config.py +26 -0
- lablink_mcp-0.1.0/lablink/interfaces/visa/driver.py +528 -0
- lablink_mcp-0.1.0/lablink/mcp_server.py +420 -0
- lablink_mcp-0.1.0/lablink/py.typed +0 -0
- lablink_mcp-0.1.0/lablink/session.py +72 -0
- lablink_mcp-0.1.0/pyproject.toml +65 -0
- lablink_mcp-0.1.0/server.json +37 -0
- lablink_mcp-0.1.0/tests/__init__.py +0 -0
- lablink_mcp-0.1.0/tests/conftest.py +19 -0
- lablink_mcp-0.1.0/tests/interfaces/__init__.py +0 -0
- lablink_mcp-0.1.0/tests/interfaces/test_external.py +199 -0
- lablink_mcp-0.1.0/tests/interfaces/test_python_shell.py +828 -0
- lablink_mcp-0.1.0/tests/interfaces/test_rest.py +462 -0
- lablink_mcp-0.1.0/tests/interfaces/test_serial.py +466 -0
- lablink_mcp-0.1.0/tests/interfaces/test_ssh.py +811 -0
- lablink_mcp-0.1.0/tests/interfaces/test_visa.py +295 -0
- lablink_mcp-0.1.0/tests/test_config.py +124 -0
- lablink_mcp-0.1.0/tests/test_dispatch.py +119 -0
- lablink_mcp-0.1.0/tests/test_fastmcp_late_registration.py +38 -0
- lablink_mcp-0.1.0/tests/test_logger.py +75 -0
- lablink_mcp-0.1.0/tests/test_shared_tools.py +197 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install uv
|
|
25
|
+
uses: astral-sh/setup-uv@v4
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv pip install -e ".[dev]" --system
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: pytest tests/ -v
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyc
|
|
6
|
+
|
|
7
|
+
# Virtual environments
|
|
8
|
+
.venv/
|
|
9
|
+
venv/
|
|
10
|
+
env/
|
|
11
|
+
|
|
12
|
+
# Distribution / packaging
|
|
13
|
+
dist/
|
|
14
|
+
build/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
*.egg
|
|
17
|
+
|
|
18
|
+
# Pytest
|
|
19
|
+
.pytest_cache/
|
|
20
|
+
|
|
21
|
+
# Coverage
|
|
22
|
+
.coverage
|
|
23
|
+
htmlcov/
|
|
24
|
+
|
|
25
|
+
# Environment files
|
|
26
|
+
.env
|
|
27
|
+
.env.*
|
|
28
|
+
!examples/.env.example
|
|
29
|
+
uv.lock
|
|
30
|
+
|
|
31
|
+
# Editors
|
|
32
|
+
.vscode/
|
|
33
|
+
.idea/
|
|
34
|
+
*.swp
|
|
35
|
+
*.swo
|
|
36
|
+
|
|
37
|
+
# macOS
|
|
38
|
+
.DS_Store
|
|
39
|
+
|
|
40
|
+
# Claude Code local config
|
|
41
|
+
.claude/
|
|
42
|
+
|
|
43
|
+
# Device configs (user-local, not for version control)
|
|
44
|
+
# ~/.lablink/ lives outside the repo, but guard against accidents
|
|
45
|
+
*.toml.local
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
|
|
5
|
+
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.0] - 2026-05-30
|
|
10
|
+
|
|
11
|
+
First release of LabLink: five protocol drivers on a shared multi-driver
|
|
12
|
+
dispatch core, exposed to AI agents over MCP and to developers over a CLI.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
**Drivers**
|
|
17
|
+
|
|
18
|
+
| Driver | Transport | Operation tools |
|
|
19
|
+
|--------|-----------|----------------|
|
|
20
|
+
| `visa` | PyVISA (USB-TMC, TCPIP, GPIB, Serial-VISA) | `visa_query`, `visa_write` |
|
|
21
|
+
| `ssh` | Paramiko | `ssh_exec`, `ssh_shell_session`, `ssh_start_stream`, `ssh_read_stream`, `ssh_stop_stream` |
|
|
22
|
+
| `rest` | httpx | `rest_get`, `rest_post`, `rest_put`, `rest_patch`, `rest_delete` |
|
|
23
|
+
| `serial` | pyserial (RS232/RS422/RS485) | `serial_query`, `serial_write`, `serial_read`, `serial_flush` |
|
|
24
|
+
| `python_shell` | subprocess | `python_shell_exec`, `python_shell_eval` |
|
|
25
|
+
|
|
26
|
+
An `external` routing stub lets a device be handled by a vendor-supplied MCP
|
|
27
|
+
server, surfacing routing hints to the agent on `connect()`.
|
|
28
|
+
|
|
29
|
+
**SSH streaming.** `ssh_start_stream` / `ssh_read_stream` / `ssh_stop_stream`
|
|
30
|
+
buffer the output of long-running commands (log tails, continuous monitors) in a
|
|
31
|
+
bounded background queue, with clean teardown on stop and disconnect.
|
|
32
|
+
|
|
33
|
+
**python_shell.** A persistent subprocess REPL bound to a user-supplied
|
|
34
|
+
interpreter (`python_path`), bridging to vendor SDKs such as `nidaqmx` and
|
|
35
|
+
`picosdk` that have no VISA or network interface. State persists across calls
|
|
36
|
+
within a session. Communicates over a newline-delimited JSON wire protocol with
|
|
37
|
+
timeout, crash, and busy-state recovery.
|
|
38
|
+
|
|
39
|
+
**Architecture**
|
|
40
|
+
|
|
41
|
+
- Shared lifecycle tools (`connect`, `disconnect`, `list_devices`, `diagnose`)
|
|
42
|
+
dispatch via the config `type` field across all drivers.
|
|
43
|
+
- Per-driver operation tools register only when that driver's Python
|
|
44
|
+
dependencies are installed — missing deps are surfaced by `diagnose()`, never
|
|
45
|
+
a server crash.
|
|
46
|
+
- Driver ABC (`LabLinkDriver[ConfigT]`) with a `Generic[ConfigT]` session model.
|
|
47
|
+
- `AuthConfig` mixin for drivers that need credentials (SSH, REST), referenced by
|
|
48
|
+
environment variable name only — secrets never live in config files.
|
|
49
|
+
- `DocumentedConfig` mixin carrying `techmanual_document_ids` for
|
|
50
|
+
[techmanual.ai](https://techmanual.ai) integration on T&M instruments.
|
|
51
|
+
- JSONL event log with canonical `ts` / `op` / `alias` / `success` fields.
|
|
52
|
+
- Three-state session lookup (missing / wrong type / found) for precise error
|
|
53
|
+
hints.
|
|
54
|
+
- Optional dependency extras per driver (`[visa]`, `[ssh]`, `[rest]`, `[serial]`,
|
|
55
|
+
`[python_shell]`, `[all]`); the server runs with zero drivers installed.
|
|
56
|
+
- CLI mirroring the MCP tool surface for development and debugging, with the same
|
|
57
|
+
dependency gating.
|
|
58
|
+
|
|
59
|
+
### Validated
|
|
60
|
+
|
|
61
|
+
- **VISA** — end-to-end on a Siglent SDS1104X-E (connect / diagnose / query /
|
|
62
|
+
write / device memory / event log).
|
|
63
|
+
- **SSH** — unit tests plus a hardware smoke test on a Raspberry Pi 4 (exec,
|
|
64
|
+
shell session, and live streaming of an `rtl_433` capture).
|
|
65
|
+
- **REST** — live against a public API across all five HTTP verbs.
|
|
66
|
+
- **Serial** — unit tests with a mocked `serial.Serial`.
|
|
67
|
+
- **python_shell** — unit tests plus real-subprocess integration tests exercising
|
|
68
|
+
the JSON wire protocol (exec, eval, exception/traceback, namespace persistence,
|
|
69
|
+
stdout capture, shutdown).
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Contributing to LabLink
|
|
2
|
+
|
|
3
|
+
Thank you for considering a contribution. This document covers the essentials
|
|
4
|
+
for getting oriented, making changes, and submitting them.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Getting started
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
git clone https://github.com/techmanual-ai/lablink-mcp
|
|
12
|
+
cd lablink-mcp
|
|
13
|
+
uv venv
|
|
14
|
+
uv pip install -e ".[dev]"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Run the tests to confirm your environment is clean:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pytest tests/
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
All tests mock hardware drivers — no real instruments required.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Project structure
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
lablink/
|
|
31
|
+
├── mcp_server.py # FastMCP entrypoint; shared lifecycle tools
|
|
32
|
+
├── cli.py # Click root; shared lifecycle commands
|
|
33
|
+
├── base.py # Data models, config dataclasses, driver ABC
|
|
34
|
+
├── config.py # TOML loader
|
|
35
|
+
├── session.py # Session registry
|
|
36
|
+
├── event_logger.py # JSONL event log
|
|
37
|
+
├── exceptions.py # ConfigError, SessionError, DriverError
|
|
38
|
+
└── interfaces/
|
|
39
|
+
├── visa/
|
|
40
|
+
├── ssh/
|
|
41
|
+
├── rest/
|
|
42
|
+
├── serial/
|
|
43
|
+
├── python_shell/
|
|
44
|
+
└── external_mcp/
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the full design.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Adding a driver
|
|
52
|
+
|
|
53
|
+
1. Create `lablink/interfaces/<type>/` with `driver.py`, `config.py`, `__init__.py`.
|
|
54
|
+
2. Subclass `LabLinkDriver[YourConfig]`; implement `connect`, `disconnect`,
|
|
55
|
+
`diagnose`, `register_tools`, `register_cli_commands`.
|
|
56
|
+
3. Subclass `DriverConfig` (`@dataclass(kw_only=True)`).
|
|
57
|
+
4. Register it — one line in each of `DRIVER_REGISTRY` and
|
|
58
|
+
`DRIVER_CONFIG_REGISTRY` in `lablink/interfaces/__init__.py`.
|
|
59
|
+
5. Write `tests/interfaces/test_<type>.py` with full mock coverage.
|
|
60
|
+
6. Add `examples/configs/<type>_device.toml`.
|
|
61
|
+
|
|
62
|
+
No changes to `lablink/mcp_server.py` or `lablink/cli.py` are required.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Coding standards
|
|
67
|
+
|
|
68
|
+
- Python 3.10+, PEP 8, strict type hints on all signatures.
|
|
69
|
+
- Google-style docstrings. MCP tool docstrings are load-bearing: they are
|
|
70
|
+
surfaced to agents as the tool description.
|
|
71
|
+
- Lazy-import all third-party driver deps inside `connect()`. Missing deps must
|
|
72
|
+
return a structured `{"success": false, "error": ..., "hint": ...}` dict —
|
|
73
|
+
never raise across the MCP boundary.
|
|
74
|
+
- Every tool call logs via `event_logger.log_event()`. Logging must never raise.
|
|
75
|
+
- Tests use `unittest.mock` to mock driver libraries. No real connections in CI.
|
|
76
|
+
- `@dataclass(kw_only=True)` is mandatory on every config dataclass and result
|
|
77
|
+
type (see [docs/ARCHITECTURE.md §5.1](docs/ARCHITECTURE.md)).
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Commit style
|
|
82
|
+
|
|
83
|
+
- Imperative mood: `Add REST driver`, `Fix VISA timeout reset`.
|
|
84
|
+
- One feature or fix per commit.
|
|
85
|
+
- Do not skip pre-commit hooks (`--no-verify`).
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Submitting a pull request
|
|
90
|
+
|
|
91
|
+
1. Fork the repo and create a branch from `main`.
|
|
92
|
+
2. Make your changes and add tests.
|
|
93
|
+
3. Run `pytest tests/` and confirm it passes.
|
|
94
|
+
4. Open a pull request with a clear description of what changed and why.
|
|
95
|
+
|
|
96
|
+
For significant changes or new drivers, open an issue first to discuss the
|
|
97
|
+
approach before investing time in implementation.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## License
|
|
102
|
+
|
|
103
|
+
By contributing, you agree that your contributions will be licensed under the
|
|
104
|
+
[MIT License](LICENSE).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 techmanual-ai
|
|
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.
|