dosync 0.6.2__tar.gz → 0.6.3__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.
- {dosync-0.6.2 → dosync-0.6.3}/PKG-INFO +127 -3
- {dosync-0.6.2 → dosync-0.6.3}/README.md +124 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/__init__.py +1 -1
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/__init__.py +2 -2
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/matter.py +2 -2
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/shelly.py +2 -2
- {dosync-0.6.2 → dosync-0.6.3}/dosync/db.py +2 -2
- {dosync-0.6.2 → dosync-0.6.3}/dosync/hub.py +2 -2
- {dosync-0.6.2 → dosync-0.6.3}/dosync/mcp_server.py +19 -3
- {dosync-0.6.2 → dosync-0.6.3}/dosync/security.py +1 -1
- {dosync-0.6.2 → dosync-0.6.3}/dosync/server.py +1 -1
- {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/PKG-INFO +127 -3
- {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/requires.txt +2 -2
- {dosync-0.6.2 → dosync-0.6.3}/pyproject.toml +8 -2
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_documentation_is_navigable.py +64 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_mcp_dynamic_intents.py +46 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_no_operator_data.py +20 -1
- {dosync-0.6.2 → dosync-0.6.3}/LICENSE +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapter_drafting.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/ble.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/declarative.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/homeassistant.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/mavlink.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/mqtt.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/notifications.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/wiz.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/audit_backup.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/auth.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/auth_fastapi.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/cert_signing.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/certify.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/cli.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/composite_operations.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/config_reference.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/dashboard.html +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/declarative.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/device_arbiter.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/discoverers.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/discoverers_mdns.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/discoverers_ssdp.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/discovery.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/ed25519_pure.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/__init__.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/3d-printer.yaml +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/air-conditioner.yaml +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/building-lighting.json +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/industrial-conveyor.yaml +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/light-generic.yaml +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/television.yaml +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/executor.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/geo.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/hub_monitor.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/lightweight.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/manage.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/metrics.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/models.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/operation_guards.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/operation_supervisor.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/operations.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/paths.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/plugins.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/policies.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/policy_config.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/py.typed +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/reconciler.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/route_composer.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/spec_coverage.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/templates/adapter-draft-prompt.md +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync/validation.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/SOURCES.txt +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/dependency_links.txt +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/entry_points.txt +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/top_level.txt +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/setup.cfg +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_adapter_drafting.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_adapters.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_audit_archive.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_audit_backup.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_audit_chain_integrity.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_audit_provenance.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_auth.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_ble_adapter.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_certification_honesty.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_claim_state_machine.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_composite_operations.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_composite_orchestration.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_composition_kind_db.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_composition_kind_endpoint.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_composition_routing.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_dashboard_tells_the_truth.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_db.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_declarative_adapters.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_declarative_quarantine.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_deployment_env_contract.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_deployment_layout.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_device_health.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_device_heartbeat.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_device_identity.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_direct_action_governance.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_discovery_adoption.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_drone_policies.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_ed25519_pure.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_emergency_preemption.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_env_loading_is_explicit.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_evaluation_metrics.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_event_loop_migration.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_explain_consistency.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_explain_resolve_parity.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_geo.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_ha_bridge_hygiene.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_hub_monitor.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_idempotency.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_independent_observation.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_integration_suite.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_lightweight_heartbeat.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_adapter.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_channels.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_listener.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_return_home.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_single_reader.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_telemetry_closure.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_mcp_partial_progress.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_metrics.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_models.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_multihub_endpoints.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_notification_templates.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_operation_guards.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_operation_supervisor.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_operations.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_operations_endpoints.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_operations_persistence.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_operations_wiring.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_panel_polish_2026_07_21.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_policies.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_policy_config.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_public_claims.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_pushed_verification.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_quickstart_is_runnable.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_reachability_cause.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_recall_benchmark_postpolicy.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_reconciler.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_resolution_wiring.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_resolver_scoring.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_resolver_semantics.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_route_composer.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_sensor_kind.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_server.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_simulation_is_declared.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_telemetry_bridge.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_third_party_adapters.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_transport_discoverers.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_universal_intent_contract.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_validation.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_validation_integration.py +0 -0
- {dosync-0.6.2 → dosync-0.6.3}/tests/test_wiring_audit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dosync
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: The semantic layer between AI agents and physical devices
|
|
5
5
|
Author-email: Rodrigo Giuliani <rgiuliani@dosync.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -45,7 +45,7 @@ Requires-Dist: twilio>=8.0.0; extra == "sms"
|
|
|
45
45
|
Provides-Extra: mavlink
|
|
46
46
|
Requires-Dist: pymavlink>=2.4.0; extra == "mavlink"
|
|
47
47
|
Provides-Extra: mcp
|
|
48
|
-
Requires-Dist: mcp
|
|
48
|
+
Requires-Dist: mcp<2.0,>=1.0.0; extra == "mcp"
|
|
49
49
|
Provides-Extra: all
|
|
50
50
|
Requires-Dist: pywizlight>=0.5.0; extra == "all"
|
|
51
51
|
Requires-Dist: paho-mqtt>=2.0.0; extra == "all"
|
|
@@ -53,7 +53,7 @@ Requires-Dist: aiohttp>=3.9.0; extra == "all"
|
|
|
53
53
|
Requires-Dist: bleak>=0.21; extra == "all"
|
|
54
54
|
Requires-Dist: twilio>=8.0.0; extra == "all"
|
|
55
55
|
Requires-Dist: pymavlink>=2.4.0; extra == "all"
|
|
56
|
-
Requires-Dist: mcp
|
|
56
|
+
Requires-Dist: mcp<2.0,>=1.0.0; extra == "all"
|
|
57
57
|
Provides-Extra: dev
|
|
58
58
|
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
59
59
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -788,6 +788,130 @@ test, so it is named rather than recommended.
|
|
|
788
788
|
|
|
789
789
|
---
|
|
790
790
|
|
|
791
|
+
## Connecting an agent
|
|
792
|
+
|
|
793
|
+
This page has said "MCP" a dozen times — a badge at the top, a section on why
|
|
794
|
+
MCP is the channel rather than the rival, a drone that flew a mission through
|
|
795
|
+
it — and never once explained how to connect an agent. This is that.
|
|
796
|
+
|
|
797
|
+
DoSync ships an MCP server. It is not a separate product: it exposes the hub's
|
|
798
|
+
intents and devices as tools, and every call it makes lands in the same policy
|
|
799
|
+
engine and the same audit chain as a call from `curl`. An agent that reaches a
|
|
800
|
+
device through it is governed exactly like anything else.
|
|
801
|
+
|
|
802
|
+
### What DoSync provides
|
|
803
|
+
|
|
804
|
+
The server is a module rather than a command, and it needs the MCP SDK, which
|
|
805
|
+
the hub does not install:
|
|
806
|
+
|
|
807
|
+
```bash
|
|
808
|
+
pip install "mcp>=1.0.0,<2.0" # or: pipx inject dosync "mcp>=1.0.0,<2.0"
|
|
809
|
+
python -m dosync.mcp_server
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
The upper bound is not caution. The 2.x SDK changed how tools are registered
|
|
813
|
+
and this server is written against 1.x; without the cap, a fresh install gets
|
|
814
|
+
2.x and the server exits at import. It says so plainly if that happens.
|
|
815
|
+
|
|
816
|
+
It speaks over stdin/stdout, so a successful start prints nothing and waits.
|
|
817
|
+
Typing into that terminal will produce JSON parse errors — that is the protocol
|
|
818
|
+
rejecting your keystrokes, not a fault.
|
|
819
|
+
|
|
820
|
+
It reads three environment variables:
|
|
821
|
+
|
|
822
|
+
| Variable | Meaning |
|
|
823
|
+
|---|---|
|
|
824
|
+
| `DOSYNC_HUB_URL` | Where the hub is. Default `http://localhost:47200` |
|
|
825
|
+
| `DOSYNC_TOKEN` | The hub's bearer token |
|
|
826
|
+
| `DOSYNC_CA_CERT` | CA certificate, when the hub runs over TLS |
|
|
827
|
+
|
|
828
|
+
That is the whole of DoSync's side. Everything below is about getting a client
|
|
829
|
+
to launch that command — which is the client's business, not the protocol's.
|
|
830
|
+
|
|
831
|
+
### What the client provides
|
|
832
|
+
|
|
833
|
+
MCP clients are configured with a JSON file naming a command to run and the
|
|
834
|
+
environment to run it in. The shape is standard:
|
|
835
|
+
|
|
836
|
+
```json
|
|
837
|
+
{
|
|
838
|
+
"mcpServers": {
|
|
839
|
+
"dosync": {
|
|
840
|
+
"command": "/absolute/path/to/python",
|
|
841
|
+
"args": ["-m", "dosync.mcp_server"],
|
|
842
|
+
"env": {
|
|
843
|
+
"DOSYNC_HUB_URL": "http://localhost:47200",
|
|
844
|
+
"DOSYNC_TOKEN": "your-token-here"
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
**That file holds a credential.** It grants whatever the token grants. This
|
|
852
|
+
project shipped a live token in a public repository for three months, so the
|
|
853
|
+
warning is not rhetorical: keep it out of version control and off shared
|
|
854
|
+
drives.
|
|
855
|
+
|
|
856
|
+
Use an absolute path to the interpreter that has DoSync installed. A client
|
|
857
|
+
launches the command with its own environment, not yours, so `python` may not
|
|
858
|
+
resolve to what you expect. With `pipx`, the interpreter is inside its venv —
|
|
859
|
+
on Linux and macOS `~/.local/share/pipx/venvs/dosync/bin/python`, on Windows
|
|
860
|
+
`%LOCALAPPDATA%\pipx\pipx\venvs\dosync\Scripts\python.exe`.
|
|
861
|
+
|
|
862
|
+
Where the file goes and how it reloads is documented by your client, not here.
|
|
863
|
+
|
|
864
|
+
### Two things Windows does to this file
|
|
865
|
+
|
|
866
|
+
Neither belongs to any particular client, and both fail without a useful error,
|
|
867
|
+
so they are worth knowing before you spend an afternoon on them.
|
|
868
|
+
|
|
869
|
+
**A packaged application does not see the path you wrote to.** Applications
|
|
870
|
+
installed from the Microsoft Store run with a virtualised filesystem: when they
|
|
871
|
+
read `%APPDATA%\Something`, Windows redirects them to
|
|
872
|
+
`%LOCALAPPDATA%\Packages\<package-id>\LocalCache\Roaming\Something`. A file
|
|
873
|
+
written to the literal path is invisible to the application, and nothing
|
|
874
|
+
reports this — the client simply behaves as though no configuration exists. If
|
|
875
|
+
your client offers a button that opens its configuration folder, use it and
|
|
876
|
+
write there.
|
|
877
|
+
|
|
878
|
+
**PowerShell 5.1 writes a byte-order mark.** `Set-Content -Encoding UTF8` puts
|
|
879
|
+
three bytes at the start of the file that JSON parsers reject, and the error
|
|
880
|
+
names an invisible character: `Unexpected token ''`. Write it without one:
|
|
881
|
+
|
|
882
|
+
```powershell
|
|
883
|
+
[System.IO.File]::WriteAllText($path, $json, [System.Text.UTF8Encoding]::new($false))
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
Check the first byte is `123` (`{`) and not `239`:
|
|
887
|
+
|
|
888
|
+
```powershell
|
|
889
|
+
[System.IO.File]::ReadAllBytes($path)[0]
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### What it looks like when it works
|
|
893
|
+
|
|
894
|
+
*Verified 29 August 2026: Windows 11 ARM64, Python 3.14, DoSync 0.6.2 installed
|
|
895
|
+
with `pipx`, MCP SDK 1.29.1, Claude Desktop from the Microsoft Store. Named
|
|
896
|
+
because a result you cannot reproduce is not evidence — not as a
|
|
897
|
+
recommendation. Any client that speaks MCP works; the details above are what
|
|
898
|
+
that combination required, and clients change theirs without notice.*
|
|
899
|
+
|
|
900
|
+
Asked in plain language which devices were registered, the agent listed six —
|
|
901
|
+
three WiZ bulbs with their actions, and three adopted devices with none. What
|
|
902
|
+
it said about the second group is the part worth reading:
|
|
903
|
+
|
|
904
|
+
> Estos tres últimos están registrados pero sin tags ni acciones definidas — el
|
|
905
|
+
> hub sabe que existen pero no puede actuar sobre ellos todavía.
|
|
906
|
+
|
|
907
|
+
It did not invent capabilities for devices that have none. That is not the
|
|
908
|
+
model being careful: the hub reports an undeclared device as undeclared, so
|
|
909
|
+
there was nothing to invent from. Then it offered to describe one — the drafting
|
|
910
|
+
flow in [`/v1/devices/{id}/describe`](#keeping-the-hub-running), reached from
|
|
911
|
+
the other end.
|
|
912
|
+
|
|
913
|
+
---
|
|
914
|
+
|
|
791
915
|
## What's built today
|
|
792
916
|
|
|
793
917
|
| Component | Status |
|
|
@@ -726,6 +726,130 @@ test, so it is named rather than recommended.
|
|
|
726
726
|
|
|
727
727
|
---
|
|
728
728
|
|
|
729
|
+
## Connecting an agent
|
|
730
|
+
|
|
731
|
+
This page has said "MCP" a dozen times — a badge at the top, a section on why
|
|
732
|
+
MCP is the channel rather than the rival, a drone that flew a mission through
|
|
733
|
+
it — and never once explained how to connect an agent. This is that.
|
|
734
|
+
|
|
735
|
+
DoSync ships an MCP server. It is not a separate product: it exposes the hub's
|
|
736
|
+
intents and devices as tools, and every call it makes lands in the same policy
|
|
737
|
+
engine and the same audit chain as a call from `curl`. An agent that reaches a
|
|
738
|
+
device through it is governed exactly like anything else.
|
|
739
|
+
|
|
740
|
+
### What DoSync provides
|
|
741
|
+
|
|
742
|
+
The server is a module rather than a command, and it needs the MCP SDK, which
|
|
743
|
+
the hub does not install:
|
|
744
|
+
|
|
745
|
+
```bash
|
|
746
|
+
pip install "mcp>=1.0.0,<2.0" # or: pipx inject dosync "mcp>=1.0.0,<2.0"
|
|
747
|
+
python -m dosync.mcp_server
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
The upper bound is not caution. The 2.x SDK changed how tools are registered
|
|
751
|
+
and this server is written against 1.x; without the cap, a fresh install gets
|
|
752
|
+
2.x and the server exits at import. It says so plainly if that happens.
|
|
753
|
+
|
|
754
|
+
It speaks over stdin/stdout, so a successful start prints nothing and waits.
|
|
755
|
+
Typing into that terminal will produce JSON parse errors — that is the protocol
|
|
756
|
+
rejecting your keystrokes, not a fault.
|
|
757
|
+
|
|
758
|
+
It reads three environment variables:
|
|
759
|
+
|
|
760
|
+
| Variable | Meaning |
|
|
761
|
+
|---|---|
|
|
762
|
+
| `DOSYNC_HUB_URL` | Where the hub is. Default `http://localhost:47200` |
|
|
763
|
+
| `DOSYNC_TOKEN` | The hub's bearer token |
|
|
764
|
+
| `DOSYNC_CA_CERT` | CA certificate, when the hub runs over TLS |
|
|
765
|
+
|
|
766
|
+
That is the whole of DoSync's side. Everything below is about getting a client
|
|
767
|
+
to launch that command — which is the client's business, not the protocol's.
|
|
768
|
+
|
|
769
|
+
### What the client provides
|
|
770
|
+
|
|
771
|
+
MCP clients are configured with a JSON file naming a command to run and the
|
|
772
|
+
environment to run it in. The shape is standard:
|
|
773
|
+
|
|
774
|
+
```json
|
|
775
|
+
{
|
|
776
|
+
"mcpServers": {
|
|
777
|
+
"dosync": {
|
|
778
|
+
"command": "/absolute/path/to/python",
|
|
779
|
+
"args": ["-m", "dosync.mcp_server"],
|
|
780
|
+
"env": {
|
|
781
|
+
"DOSYNC_HUB_URL": "http://localhost:47200",
|
|
782
|
+
"DOSYNC_TOKEN": "your-token-here"
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
```
|
|
788
|
+
|
|
789
|
+
**That file holds a credential.** It grants whatever the token grants. This
|
|
790
|
+
project shipped a live token in a public repository for three months, so the
|
|
791
|
+
warning is not rhetorical: keep it out of version control and off shared
|
|
792
|
+
drives.
|
|
793
|
+
|
|
794
|
+
Use an absolute path to the interpreter that has DoSync installed. A client
|
|
795
|
+
launches the command with its own environment, not yours, so `python` may not
|
|
796
|
+
resolve to what you expect. With `pipx`, the interpreter is inside its venv —
|
|
797
|
+
on Linux and macOS `~/.local/share/pipx/venvs/dosync/bin/python`, on Windows
|
|
798
|
+
`%LOCALAPPDATA%\pipx\pipx\venvs\dosync\Scripts\python.exe`.
|
|
799
|
+
|
|
800
|
+
Where the file goes and how it reloads is documented by your client, not here.
|
|
801
|
+
|
|
802
|
+
### Two things Windows does to this file
|
|
803
|
+
|
|
804
|
+
Neither belongs to any particular client, and both fail without a useful error,
|
|
805
|
+
so they are worth knowing before you spend an afternoon on them.
|
|
806
|
+
|
|
807
|
+
**A packaged application does not see the path you wrote to.** Applications
|
|
808
|
+
installed from the Microsoft Store run with a virtualised filesystem: when they
|
|
809
|
+
read `%APPDATA%\Something`, Windows redirects them to
|
|
810
|
+
`%LOCALAPPDATA%\Packages\<package-id>\LocalCache\Roaming\Something`. A file
|
|
811
|
+
written to the literal path is invisible to the application, and nothing
|
|
812
|
+
reports this — the client simply behaves as though no configuration exists. If
|
|
813
|
+
your client offers a button that opens its configuration folder, use it and
|
|
814
|
+
write there.
|
|
815
|
+
|
|
816
|
+
**PowerShell 5.1 writes a byte-order mark.** `Set-Content -Encoding UTF8` puts
|
|
817
|
+
three bytes at the start of the file that JSON parsers reject, and the error
|
|
818
|
+
names an invisible character: `Unexpected token ''`. Write it without one:
|
|
819
|
+
|
|
820
|
+
```powershell
|
|
821
|
+
[System.IO.File]::WriteAllText($path, $json, [System.Text.UTF8Encoding]::new($false))
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
Check the first byte is `123` (`{`) and not `239`:
|
|
825
|
+
|
|
826
|
+
```powershell
|
|
827
|
+
[System.IO.File]::ReadAllBytes($path)[0]
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
### What it looks like when it works
|
|
831
|
+
|
|
832
|
+
*Verified 29 August 2026: Windows 11 ARM64, Python 3.14, DoSync 0.6.2 installed
|
|
833
|
+
with `pipx`, MCP SDK 1.29.1, Claude Desktop from the Microsoft Store. Named
|
|
834
|
+
because a result you cannot reproduce is not evidence — not as a
|
|
835
|
+
recommendation. Any client that speaks MCP works; the details above are what
|
|
836
|
+
that combination required, and clients change theirs without notice.*
|
|
837
|
+
|
|
838
|
+
Asked in plain language which devices were registered, the agent listed six —
|
|
839
|
+
three WiZ bulbs with their actions, and three adopted devices with none. What
|
|
840
|
+
it said about the second group is the part worth reading:
|
|
841
|
+
|
|
842
|
+
> Estos tres últimos están registrados pero sin tags ni acciones definidas — el
|
|
843
|
+
> hub sabe que existen pero no puede actuar sobre ellos todavía.
|
|
844
|
+
|
|
845
|
+
It did not invent capabilities for devices that have none. That is not the
|
|
846
|
+
model being careful: the hub reports an undeclared device as undeclared, so
|
|
847
|
+
there was nothing to invent from. Then it offered to describe one — the drafting
|
|
848
|
+
flow in [`/v1/devices/{id}/describe`](#keeping-the-hub-running), reached from
|
|
849
|
+
the other end.
|
|
850
|
+
|
|
851
|
+
---
|
|
852
|
+
|
|
729
853
|
## What's built today
|
|
730
854
|
|
|
731
855
|
| Component | Status |
|
|
@@ -6,7 +6,7 @@ Translation layer between the DoSync protocol and real physical devices.
|
|
|
6
6
|
Modelo:
|
|
7
7
|
DoSync Hub → AdapterExecutor → [WiZAdapter | GPIOAdapter | ShellyAdapter | ...]
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
To add a new device type:
|
|
10
10
|
1. Crear adapters/mi_marca.py implementando DoSyncAdapter
|
|
11
11
|
2. Register the device with adapter="my_brand" in its CapabilityManifest
|
|
12
12
|
3. The hub treats it like any other device — no core changes needed
|
|
@@ -55,7 +55,7 @@ class DoSyncAdapter(ABC):
|
|
|
55
55
|
Cada adapter traduce acciones DoSync al protocolo nativo
|
|
56
56
|
del dispositivo (UDP, HTTP, GPIO, BLE, etc.).
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
To implement a new adapter:
|
|
59
59
|
|
|
60
60
|
class MyBrandAdapter(DoSyncAdapter):
|
|
61
61
|
async def execute(self, action, urgency):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
DoSync — Matter Adapter
|
|
3
3
|
=======================
|
|
4
|
-
Adapter
|
|
4
|
+
Adapter for Matter devices, via python-matter-server or Home Assistant bridge.
|
|
5
5
|
|
|
6
6
|
Matter is the Connectivity Standards Alliance IoT interoperability standard.
|
|
7
7
|
Este adapter soporta dos modos:
|
|
@@ -164,7 +164,7 @@ class _MatterViaHA:
|
|
|
164
164
|
|
|
165
165
|
class MatterAdapter(DoSyncAdapter):
|
|
166
166
|
"""
|
|
167
|
-
|
|
167
|
+
DoSync adapter for Matter devices.
|
|
168
168
|
|
|
169
169
|
Modo actual: via Home Assistant bridge (v0.2).
|
|
170
170
|
Modo futuro: via python-matter-server standalone (v0.3).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
DoSync — Shelly Adapter
|
|
3
3
|
=======================
|
|
4
|
-
Adapter
|
|
4
|
+
Adapter for Shelly devices over local HTTP (Gen1 and Gen2).
|
|
5
5
|
|
|
6
6
|
Features:
|
|
7
7
|
- Fully local communication — no cloud, no internet required
|
|
@@ -199,7 +199,7 @@ class ShellyAdapter(DoSyncAdapter):
|
|
|
199
199
|
adapter_kind = "reference"
|
|
200
200
|
|
|
201
201
|
"""
|
|
202
|
-
|
|
202
|
+
DoSync adapter for Shelly devices.
|
|
203
203
|
|
|
204
204
|
Soporta Gen1 (API REST) y Gen2 (API RPC).
|
|
205
205
|
Sin dependencias externas — usa requests.
|
|
@@ -232,7 +232,7 @@ class DoSyncDB:
|
|
|
232
232
|
log.debug("Deleted device: %s", device_id)
|
|
233
233
|
|
|
234
234
|
def load_devices(self) -> list[dict]:
|
|
235
|
-
"""
|
|
235
|
+
"""Load every registered device."""
|
|
236
236
|
with self._cursor() as cur:
|
|
237
237
|
cur.execute("SELECT manifest_json FROM devices ORDER BY registered_at")
|
|
238
238
|
rows = cur.fetchall()
|
|
@@ -552,7 +552,7 @@ class DoSyncDB:
|
|
|
552
552
|
return {}
|
|
553
553
|
|
|
554
554
|
def load_all_device_states(self) -> dict:
|
|
555
|
-
"""
|
|
555
|
+
"""Load every persisted state. Returns {device_id: state_dict}."""
|
|
556
556
|
with self._cursor() as cur:
|
|
557
557
|
cur.execute("SELECT device_id, state_json FROM device_state")
|
|
558
558
|
rows = cur.fetchall()
|
|
@@ -685,7 +685,7 @@ class CapabilityMatchingResolver(BaseResolver):
|
|
|
685
685
|
actuator_type: str, intent: Intent) -> dict | None:
|
|
686
686
|
"""
|
|
687
687
|
When the intent carries explicit profile actions in its context,
|
|
688
|
-
|
|
688
|
+
finds the params belonging to this device and actuator.
|
|
689
689
|
Returns None when there is no match — the caller uses the defaults.
|
|
690
690
|
"""
|
|
691
691
|
profile_actions = intent.context.get("actions", [])
|
|
@@ -3553,7 +3553,7 @@ class DoSyncHub:
|
|
|
3553
3553
|
"""
|
|
3554
3554
|
Ejecuta un PhasedActionPlan: cada fase en paralelo,
|
|
3555
3555
|
the phases in sequence, with a delay between them.
|
|
3556
|
-
|
|
3556
|
+
Suited to emergencies where ordering matters.
|
|
3557
3557
|
"""
|
|
3558
3558
|
all_results = []
|
|
3559
3559
|
|
|
@@ -128,6 +128,22 @@ def fmt(data: dict) -> str:
|
|
|
128
128
|
|
|
129
129
|
server = Server("dosync-hub")
|
|
130
130
|
|
|
131
|
+
# Checked here rather than left to fail at the first decorator. The 2.x SDK
|
|
132
|
+
# removed `Server.list_tools()`, so an operator with that version installed got
|
|
133
|
+
# `AttributeError: 'Server' object has no attribute 'list_tools'` from inside a
|
|
134
|
+
# module they never opened — a true statement about the wrong thing. The
|
|
135
|
+
# dependency is capped at `<2.0` now, but a cap does not help anyone who
|
|
136
|
+
# already has 2.x in their environment, which is exactly who this message is
|
|
137
|
+
# for.
|
|
138
|
+
if not hasattr(server, "list_tools"):
|
|
139
|
+
import mcp as _mcp
|
|
140
|
+
raise RuntimeError(
|
|
141
|
+
"This MCP server is written against the 1.x SDK and the installed "
|
|
142
|
+
f"version is {getattr(_mcp, '__version__', 'unknown')}. The 2.x SDK "
|
|
143
|
+
"changed how tools are registered, so nothing here can start.\n\n"
|
|
144
|
+
" pipx inject --force dosync 'mcp>=1.0.0,<2.0'\n\n"
|
|
145
|
+
"Porting to 2.x is tracked work, not a configuration problem.")
|
|
146
|
+
|
|
131
147
|
|
|
132
148
|
async def _intent_property_schema() -> dict:
|
|
133
149
|
"""Build the JSON-schema fragment for the `intent` argument by reading the
|
|
@@ -179,7 +195,7 @@ async def list_tools() -> list[types.Tool]:
|
|
|
179
195
|
"Execute a semantic intent on the DoSync hub. "
|
|
180
196
|
"The hub resolves which devices act and how, "
|
|
181
197
|
"from their declared capabilities. "
|
|
182
|
-
"
|
|
198
|
+
"Use for: emergencies, routines, environment control, notifications."
|
|
183
199
|
),
|
|
184
200
|
inputSchema={
|
|
185
201
|
"type": "object",
|
|
@@ -847,8 +863,8 @@ async def main():
|
|
|
847
863
|
log.warning(
|
|
848
864
|
"DOSYNC_TOKEN is not set — requests to the hub may fail "
|
|
849
865
|
"if authentication is enabled. "
|
|
850
|
-
"
|
|
851
|
-
"
|
|
866
|
+
"Set DOSYNC_TOKEN=<token> to authenticate, or run the hub with "
|
|
867
|
+
"DOSYNC_AUTH=false to turn authentication off (development only)."
|
|
852
868
|
)
|
|
853
869
|
|
|
854
870
|
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
|
@@ -25,7 +25,7 @@ Quick start:
|
|
|
25
25
|
# Generar PKI completa (primera vez)
|
|
26
26
|
python3 -m dosync.security setup
|
|
27
27
|
|
|
28
|
-
#
|
|
28
|
+
# Issue a certificate for a new adapter
|
|
29
29
|
python3 -m dosync.security issue --name gpio --ip 127.0.0.1
|
|
30
30
|
|
|
31
31
|
# Verify the certificates are valid and have not expired
|
|
@@ -1294,7 +1294,7 @@ async def explain_intent(
|
|
|
1294
1294
|
"""
|
|
1295
1295
|
Explainability endpoint — the resolver's reasoning for one intent.
|
|
1296
1296
|
|
|
1297
|
-
|
|
1297
|
+
For each registered device, it details:
|
|
1298
1298
|
- Score total y desglose (tag overlap, location bonus, emergency bonus, actuator match)
|
|
1299
1299
|
- Why it was included in or excluded from the ActionPlan
|
|
1300
1300
|
- Which tags matched the intent's resolution tags
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dosync
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.3
|
|
4
4
|
Summary: The semantic layer between AI agents and physical devices
|
|
5
5
|
Author-email: Rodrigo Giuliani <rgiuliani@dosync.dev>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -45,7 +45,7 @@ Requires-Dist: twilio>=8.0.0; extra == "sms"
|
|
|
45
45
|
Provides-Extra: mavlink
|
|
46
46
|
Requires-Dist: pymavlink>=2.4.0; extra == "mavlink"
|
|
47
47
|
Provides-Extra: mcp
|
|
48
|
-
Requires-Dist: mcp
|
|
48
|
+
Requires-Dist: mcp<2.0,>=1.0.0; extra == "mcp"
|
|
49
49
|
Provides-Extra: all
|
|
50
50
|
Requires-Dist: pywizlight>=0.5.0; extra == "all"
|
|
51
51
|
Requires-Dist: paho-mqtt>=2.0.0; extra == "all"
|
|
@@ -53,7 +53,7 @@ Requires-Dist: aiohttp>=3.9.0; extra == "all"
|
|
|
53
53
|
Requires-Dist: bleak>=0.21; extra == "all"
|
|
54
54
|
Requires-Dist: twilio>=8.0.0; extra == "all"
|
|
55
55
|
Requires-Dist: pymavlink>=2.4.0; extra == "all"
|
|
56
|
-
Requires-Dist: mcp
|
|
56
|
+
Requires-Dist: mcp<2.0,>=1.0.0; extra == "all"
|
|
57
57
|
Provides-Extra: dev
|
|
58
58
|
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
59
59
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
@@ -788,6 +788,130 @@ test, so it is named rather than recommended.
|
|
|
788
788
|
|
|
789
789
|
---
|
|
790
790
|
|
|
791
|
+
## Connecting an agent
|
|
792
|
+
|
|
793
|
+
This page has said "MCP" a dozen times — a badge at the top, a section on why
|
|
794
|
+
MCP is the channel rather than the rival, a drone that flew a mission through
|
|
795
|
+
it — and never once explained how to connect an agent. This is that.
|
|
796
|
+
|
|
797
|
+
DoSync ships an MCP server. It is not a separate product: it exposes the hub's
|
|
798
|
+
intents and devices as tools, and every call it makes lands in the same policy
|
|
799
|
+
engine and the same audit chain as a call from `curl`. An agent that reaches a
|
|
800
|
+
device through it is governed exactly like anything else.
|
|
801
|
+
|
|
802
|
+
### What DoSync provides
|
|
803
|
+
|
|
804
|
+
The server is a module rather than a command, and it needs the MCP SDK, which
|
|
805
|
+
the hub does not install:
|
|
806
|
+
|
|
807
|
+
```bash
|
|
808
|
+
pip install "mcp>=1.0.0,<2.0" # or: pipx inject dosync "mcp>=1.0.0,<2.0"
|
|
809
|
+
python -m dosync.mcp_server
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
The upper bound is not caution. The 2.x SDK changed how tools are registered
|
|
813
|
+
and this server is written against 1.x; without the cap, a fresh install gets
|
|
814
|
+
2.x and the server exits at import. It says so plainly if that happens.
|
|
815
|
+
|
|
816
|
+
It speaks over stdin/stdout, so a successful start prints nothing and waits.
|
|
817
|
+
Typing into that terminal will produce JSON parse errors — that is the protocol
|
|
818
|
+
rejecting your keystrokes, not a fault.
|
|
819
|
+
|
|
820
|
+
It reads three environment variables:
|
|
821
|
+
|
|
822
|
+
| Variable | Meaning |
|
|
823
|
+
|---|---|
|
|
824
|
+
| `DOSYNC_HUB_URL` | Where the hub is. Default `http://localhost:47200` |
|
|
825
|
+
| `DOSYNC_TOKEN` | The hub's bearer token |
|
|
826
|
+
| `DOSYNC_CA_CERT` | CA certificate, when the hub runs over TLS |
|
|
827
|
+
|
|
828
|
+
That is the whole of DoSync's side. Everything below is about getting a client
|
|
829
|
+
to launch that command — which is the client's business, not the protocol's.
|
|
830
|
+
|
|
831
|
+
### What the client provides
|
|
832
|
+
|
|
833
|
+
MCP clients are configured with a JSON file naming a command to run and the
|
|
834
|
+
environment to run it in. The shape is standard:
|
|
835
|
+
|
|
836
|
+
```json
|
|
837
|
+
{
|
|
838
|
+
"mcpServers": {
|
|
839
|
+
"dosync": {
|
|
840
|
+
"command": "/absolute/path/to/python",
|
|
841
|
+
"args": ["-m", "dosync.mcp_server"],
|
|
842
|
+
"env": {
|
|
843
|
+
"DOSYNC_HUB_URL": "http://localhost:47200",
|
|
844
|
+
"DOSYNC_TOKEN": "your-token-here"
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
**That file holds a credential.** It grants whatever the token grants. This
|
|
852
|
+
project shipped a live token in a public repository for three months, so the
|
|
853
|
+
warning is not rhetorical: keep it out of version control and off shared
|
|
854
|
+
drives.
|
|
855
|
+
|
|
856
|
+
Use an absolute path to the interpreter that has DoSync installed. A client
|
|
857
|
+
launches the command with its own environment, not yours, so `python` may not
|
|
858
|
+
resolve to what you expect. With `pipx`, the interpreter is inside its venv —
|
|
859
|
+
on Linux and macOS `~/.local/share/pipx/venvs/dosync/bin/python`, on Windows
|
|
860
|
+
`%LOCALAPPDATA%\pipx\pipx\venvs\dosync\Scripts\python.exe`.
|
|
861
|
+
|
|
862
|
+
Where the file goes and how it reloads is documented by your client, not here.
|
|
863
|
+
|
|
864
|
+
### Two things Windows does to this file
|
|
865
|
+
|
|
866
|
+
Neither belongs to any particular client, and both fail without a useful error,
|
|
867
|
+
so they are worth knowing before you spend an afternoon on them.
|
|
868
|
+
|
|
869
|
+
**A packaged application does not see the path you wrote to.** Applications
|
|
870
|
+
installed from the Microsoft Store run with a virtualised filesystem: when they
|
|
871
|
+
read `%APPDATA%\Something`, Windows redirects them to
|
|
872
|
+
`%LOCALAPPDATA%\Packages\<package-id>\LocalCache\Roaming\Something`. A file
|
|
873
|
+
written to the literal path is invisible to the application, and nothing
|
|
874
|
+
reports this — the client simply behaves as though no configuration exists. If
|
|
875
|
+
your client offers a button that opens its configuration folder, use it and
|
|
876
|
+
write there.
|
|
877
|
+
|
|
878
|
+
**PowerShell 5.1 writes a byte-order mark.** `Set-Content -Encoding UTF8` puts
|
|
879
|
+
three bytes at the start of the file that JSON parsers reject, and the error
|
|
880
|
+
names an invisible character: `Unexpected token ''`. Write it without one:
|
|
881
|
+
|
|
882
|
+
```powershell
|
|
883
|
+
[System.IO.File]::WriteAllText($path, $json, [System.Text.UTF8Encoding]::new($false))
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
Check the first byte is `123` (`{`) and not `239`:
|
|
887
|
+
|
|
888
|
+
```powershell
|
|
889
|
+
[System.IO.File]::ReadAllBytes($path)[0]
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
### What it looks like when it works
|
|
893
|
+
|
|
894
|
+
*Verified 29 August 2026: Windows 11 ARM64, Python 3.14, DoSync 0.6.2 installed
|
|
895
|
+
with `pipx`, MCP SDK 1.29.1, Claude Desktop from the Microsoft Store. Named
|
|
896
|
+
because a result you cannot reproduce is not evidence — not as a
|
|
897
|
+
recommendation. Any client that speaks MCP works; the details above are what
|
|
898
|
+
that combination required, and clients change theirs without notice.*
|
|
899
|
+
|
|
900
|
+
Asked in plain language which devices were registered, the agent listed six —
|
|
901
|
+
three WiZ bulbs with their actions, and three adopted devices with none. What
|
|
902
|
+
it said about the second group is the part worth reading:
|
|
903
|
+
|
|
904
|
+
> Estos tres últimos están registrados pero sin tags ni acciones definidas — el
|
|
905
|
+
> hub sabe que existen pero no puede actuar sobre ellos todavía.
|
|
906
|
+
|
|
907
|
+
It did not invent capabilities for devices that have none. That is not the
|
|
908
|
+
model being careful: the hub reports an undeclared device as undeclared, so
|
|
909
|
+
there was nothing to invent from. Then it offered to describe one — the drafting
|
|
910
|
+
flow in [`/v1/devices/{id}/describe`](#keeping-the-hub-running), reached from
|
|
911
|
+
the other end.
|
|
912
|
+
|
|
913
|
+
---
|
|
914
|
+
|
|
791
915
|
## What's built today
|
|
792
916
|
|
|
793
917
|
| Component | Status |
|
|
@@ -15,7 +15,7 @@ aiohttp>=3.9.0
|
|
|
15
15
|
bleak>=0.21
|
|
16
16
|
twilio>=8.0.0
|
|
17
17
|
pymavlink>=2.4.0
|
|
18
|
-
mcp
|
|
18
|
+
mcp<2.0,>=1.0.0
|
|
19
19
|
|
|
20
20
|
[ble]
|
|
21
21
|
bleak>=0.21
|
|
@@ -32,7 +32,7 @@ aiohttp>=3.9.0
|
|
|
32
32
|
pymavlink>=2.4.0
|
|
33
33
|
|
|
34
34
|
[mcp]
|
|
35
|
-
mcp
|
|
35
|
+
mcp<2.0,>=1.0.0
|
|
36
36
|
|
|
37
37
|
[mqtt]
|
|
38
38
|
paho-mqtt>=2.0.0
|
|
@@ -89,11 +89,17 @@ ha = ["aiohttp>=3.9.0"]
|
|
|
89
89
|
ble = ["bleak>=0.21"]
|
|
90
90
|
sms = ["twilio>=8.0.0"]
|
|
91
91
|
mavlink = ["pymavlink>=2.4.0"]
|
|
92
|
-
|
|
92
|
+
# `<2.0` is not caution, it is a measured incompatibility: the 2.x SDK removed
|
|
93
|
+
# `Server.list_tools()`, which `dosync/mcp_server.py` decorates at import time,
|
|
94
|
+
# so `python -m dosync.mcp_server` dies with an AttributeError before it can
|
|
95
|
+
# serve anything. An open-ended `>=1.0.0` was correct the day it was written
|
|
96
|
+
# and stopped being correct when 2.0 shipped — the same shape of mistake as the
|
|
97
|
+
# plain `uvicorn` pin that left the hub without a WebSocket library.
|
|
98
|
+
mcp = ["mcp>=1.0.0,<2.0"]
|
|
93
99
|
# Everything a fully-loaded deployment needs.
|
|
94
100
|
all = [
|
|
95
101
|
"pywizlight>=0.5.0", "paho-mqtt>=2.0.0", "aiohttp>=3.9.0",
|
|
96
|
-
"bleak>=0.21", "twilio>=8.0.0", "pymavlink>=2.4.0", "mcp>=1.0.0",
|
|
102
|
+
"bleak>=0.21", "twilio>=8.0.0", "pymavlink>=2.4.0", "mcp>=1.0.0,<2.0",
|
|
97
103
|
]
|
|
98
104
|
dev = [
|
|
99
105
|
"pytest>=7.4", "pytest-asyncio>=0.23", "httpx>=0.27",
|