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.
Files changed (155) hide show
  1. {dosync-0.6.2 → dosync-0.6.3}/PKG-INFO +127 -3
  2. {dosync-0.6.2 → dosync-0.6.3}/README.md +124 -0
  3. {dosync-0.6.2 → dosync-0.6.3}/dosync/__init__.py +1 -1
  4. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/__init__.py +2 -2
  5. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/matter.py +2 -2
  6. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/shelly.py +2 -2
  7. {dosync-0.6.2 → dosync-0.6.3}/dosync/db.py +2 -2
  8. {dosync-0.6.2 → dosync-0.6.3}/dosync/hub.py +2 -2
  9. {dosync-0.6.2 → dosync-0.6.3}/dosync/mcp_server.py +19 -3
  10. {dosync-0.6.2 → dosync-0.6.3}/dosync/security.py +1 -1
  11. {dosync-0.6.2 → dosync-0.6.3}/dosync/server.py +1 -1
  12. {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/PKG-INFO +127 -3
  13. {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/requires.txt +2 -2
  14. {dosync-0.6.2 → dosync-0.6.3}/pyproject.toml +8 -2
  15. {dosync-0.6.2 → dosync-0.6.3}/tests/test_documentation_is_navigable.py +64 -0
  16. {dosync-0.6.2 → dosync-0.6.3}/tests/test_mcp_dynamic_intents.py +46 -0
  17. {dosync-0.6.2 → dosync-0.6.3}/tests/test_no_operator_data.py +20 -1
  18. {dosync-0.6.2 → dosync-0.6.3}/LICENSE +0 -0
  19. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapter_drafting.py +0 -0
  20. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/ble.py +0 -0
  21. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/declarative.py +0 -0
  22. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/homeassistant.py +0 -0
  23. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/mavlink.py +0 -0
  24. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/mqtt.py +0 -0
  25. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/notifications.py +0 -0
  26. {dosync-0.6.2 → dosync-0.6.3}/dosync/adapters/wiz.py +0 -0
  27. {dosync-0.6.2 → dosync-0.6.3}/dosync/audit_backup.py +0 -0
  28. {dosync-0.6.2 → dosync-0.6.3}/dosync/auth.py +0 -0
  29. {dosync-0.6.2 → dosync-0.6.3}/dosync/auth_fastapi.py +0 -0
  30. {dosync-0.6.2 → dosync-0.6.3}/dosync/cert_signing.py +0 -0
  31. {dosync-0.6.2 → dosync-0.6.3}/dosync/certify.py +0 -0
  32. {dosync-0.6.2 → dosync-0.6.3}/dosync/cli.py +0 -0
  33. {dosync-0.6.2 → dosync-0.6.3}/dosync/composite_operations.py +0 -0
  34. {dosync-0.6.2 → dosync-0.6.3}/dosync/config_reference.py +0 -0
  35. {dosync-0.6.2 → dosync-0.6.3}/dosync/dashboard.html +0 -0
  36. {dosync-0.6.2 → dosync-0.6.3}/dosync/declarative.py +0 -0
  37. {dosync-0.6.2 → dosync-0.6.3}/dosync/device_arbiter.py +0 -0
  38. {dosync-0.6.2 → dosync-0.6.3}/dosync/discoverers.py +0 -0
  39. {dosync-0.6.2 → dosync-0.6.3}/dosync/discoverers_mdns.py +0 -0
  40. {dosync-0.6.2 → dosync-0.6.3}/dosync/discoverers_ssdp.py +0 -0
  41. {dosync-0.6.2 → dosync-0.6.3}/dosync/discovery.py +0 -0
  42. {dosync-0.6.2 → dosync-0.6.3}/dosync/ed25519_pure.py +0 -0
  43. {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/__init__.py +0 -0
  44. {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/3d-printer.yaml +0 -0
  45. {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/air-conditioner.yaml +0 -0
  46. {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/building-lighting.json +0 -0
  47. {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/industrial-conveyor.yaml +0 -0
  48. {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/light-generic.yaml +0 -0
  49. {dosync-0.6.2 → dosync-0.6.3}/dosync/examples/declarative/television.yaml +0 -0
  50. {dosync-0.6.2 → dosync-0.6.3}/dosync/executor.py +0 -0
  51. {dosync-0.6.2 → dosync-0.6.3}/dosync/geo.py +0 -0
  52. {dosync-0.6.2 → dosync-0.6.3}/dosync/hub_monitor.py +0 -0
  53. {dosync-0.6.2 → dosync-0.6.3}/dosync/lightweight.py +0 -0
  54. {dosync-0.6.2 → dosync-0.6.3}/dosync/manage.py +0 -0
  55. {dosync-0.6.2 → dosync-0.6.3}/dosync/metrics.py +0 -0
  56. {dosync-0.6.2 → dosync-0.6.3}/dosync/models.py +0 -0
  57. {dosync-0.6.2 → dosync-0.6.3}/dosync/operation_guards.py +0 -0
  58. {dosync-0.6.2 → dosync-0.6.3}/dosync/operation_supervisor.py +0 -0
  59. {dosync-0.6.2 → dosync-0.6.3}/dosync/operations.py +0 -0
  60. {dosync-0.6.2 → dosync-0.6.3}/dosync/paths.py +0 -0
  61. {dosync-0.6.2 → dosync-0.6.3}/dosync/plugins.py +0 -0
  62. {dosync-0.6.2 → dosync-0.6.3}/dosync/policies.py +0 -0
  63. {dosync-0.6.2 → dosync-0.6.3}/dosync/policy_config.py +0 -0
  64. {dosync-0.6.2 → dosync-0.6.3}/dosync/py.typed +0 -0
  65. {dosync-0.6.2 → dosync-0.6.3}/dosync/reconciler.py +0 -0
  66. {dosync-0.6.2 → dosync-0.6.3}/dosync/route_composer.py +0 -0
  67. {dosync-0.6.2 → dosync-0.6.3}/dosync/spec_coverage.py +0 -0
  68. {dosync-0.6.2 → dosync-0.6.3}/dosync/templates/adapter-draft-prompt.md +0 -0
  69. {dosync-0.6.2 → dosync-0.6.3}/dosync/validation.py +0 -0
  70. {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/SOURCES.txt +0 -0
  71. {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/dependency_links.txt +0 -0
  72. {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/entry_points.txt +0 -0
  73. {dosync-0.6.2 → dosync-0.6.3}/dosync.egg-info/top_level.txt +0 -0
  74. {dosync-0.6.2 → dosync-0.6.3}/setup.cfg +0 -0
  75. {dosync-0.6.2 → dosync-0.6.3}/tests/test_adapter_drafting.py +0 -0
  76. {dosync-0.6.2 → dosync-0.6.3}/tests/test_adapters.py +0 -0
  77. {dosync-0.6.2 → dosync-0.6.3}/tests/test_audit_archive.py +0 -0
  78. {dosync-0.6.2 → dosync-0.6.3}/tests/test_audit_backup.py +0 -0
  79. {dosync-0.6.2 → dosync-0.6.3}/tests/test_audit_chain_integrity.py +0 -0
  80. {dosync-0.6.2 → dosync-0.6.3}/tests/test_audit_provenance.py +0 -0
  81. {dosync-0.6.2 → dosync-0.6.3}/tests/test_auth.py +0 -0
  82. {dosync-0.6.2 → dosync-0.6.3}/tests/test_ble_adapter.py +0 -0
  83. {dosync-0.6.2 → dosync-0.6.3}/tests/test_certification_honesty.py +0 -0
  84. {dosync-0.6.2 → dosync-0.6.3}/tests/test_claim_state_machine.py +0 -0
  85. {dosync-0.6.2 → dosync-0.6.3}/tests/test_composite_operations.py +0 -0
  86. {dosync-0.6.2 → dosync-0.6.3}/tests/test_composite_orchestration.py +0 -0
  87. {dosync-0.6.2 → dosync-0.6.3}/tests/test_composition_kind_db.py +0 -0
  88. {dosync-0.6.2 → dosync-0.6.3}/tests/test_composition_kind_endpoint.py +0 -0
  89. {dosync-0.6.2 → dosync-0.6.3}/tests/test_composition_routing.py +0 -0
  90. {dosync-0.6.2 → dosync-0.6.3}/tests/test_dashboard_tells_the_truth.py +0 -0
  91. {dosync-0.6.2 → dosync-0.6.3}/tests/test_db.py +0 -0
  92. {dosync-0.6.2 → dosync-0.6.3}/tests/test_declarative_adapters.py +0 -0
  93. {dosync-0.6.2 → dosync-0.6.3}/tests/test_declarative_quarantine.py +0 -0
  94. {dosync-0.6.2 → dosync-0.6.3}/tests/test_deployment_env_contract.py +0 -0
  95. {dosync-0.6.2 → dosync-0.6.3}/tests/test_deployment_layout.py +0 -0
  96. {dosync-0.6.2 → dosync-0.6.3}/tests/test_device_health.py +0 -0
  97. {dosync-0.6.2 → dosync-0.6.3}/tests/test_device_heartbeat.py +0 -0
  98. {dosync-0.6.2 → dosync-0.6.3}/tests/test_device_identity.py +0 -0
  99. {dosync-0.6.2 → dosync-0.6.3}/tests/test_direct_action_governance.py +0 -0
  100. {dosync-0.6.2 → dosync-0.6.3}/tests/test_discovery_adoption.py +0 -0
  101. {dosync-0.6.2 → dosync-0.6.3}/tests/test_drone_policies.py +0 -0
  102. {dosync-0.6.2 → dosync-0.6.3}/tests/test_ed25519_pure.py +0 -0
  103. {dosync-0.6.2 → dosync-0.6.3}/tests/test_emergency_preemption.py +0 -0
  104. {dosync-0.6.2 → dosync-0.6.3}/tests/test_env_loading_is_explicit.py +0 -0
  105. {dosync-0.6.2 → dosync-0.6.3}/tests/test_evaluation_metrics.py +0 -0
  106. {dosync-0.6.2 → dosync-0.6.3}/tests/test_event_loop_migration.py +0 -0
  107. {dosync-0.6.2 → dosync-0.6.3}/tests/test_explain_consistency.py +0 -0
  108. {dosync-0.6.2 → dosync-0.6.3}/tests/test_explain_resolve_parity.py +0 -0
  109. {dosync-0.6.2 → dosync-0.6.3}/tests/test_geo.py +0 -0
  110. {dosync-0.6.2 → dosync-0.6.3}/tests/test_ha_bridge_hygiene.py +0 -0
  111. {dosync-0.6.2 → dosync-0.6.3}/tests/test_hub_monitor.py +0 -0
  112. {dosync-0.6.2 → dosync-0.6.3}/tests/test_idempotency.py +0 -0
  113. {dosync-0.6.2 → dosync-0.6.3}/tests/test_independent_observation.py +0 -0
  114. {dosync-0.6.2 → dosync-0.6.3}/tests/test_integration_suite.py +0 -0
  115. {dosync-0.6.2 → dosync-0.6.3}/tests/test_lightweight_heartbeat.py +0 -0
  116. {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_adapter.py +0 -0
  117. {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_channels.py +0 -0
  118. {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_listener.py +0 -0
  119. {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_return_home.py +0 -0
  120. {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_single_reader.py +0 -0
  121. {dosync-0.6.2 → dosync-0.6.3}/tests/test_mavlink_telemetry_closure.py +0 -0
  122. {dosync-0.6.2 → dosync-0.6.3}/tests/test_mcp_partial_progress.py +0 -0
  123. {dosync-0.6.2 → dosync-0.6.3}/tests/test_metrics.py +0 -0
  124. {dosync-0.6.2 → dosync-0.6.3}/tests/test_models.py +0 -0
  125. {dosync-0.6.2 → dosync-0.6.3}/tests/test_multihub_endpoints.py +0 -0
  126. {dosync-0.6.2 → dosync-0.6.3}/tests/test_notification_templates.py +0 -0
  127. {dosync-0.6.2 → dosync-0.6.3}/tests/test_operation_guards.py +0 -0
  128. {dosync-0.6.2 → dosync-0.6.3}/tests/test_operation_supervisor.py +0 -0
  129. {dosync-0.6.2 → dosync-0.6.3}/tests/test_operations.py +0 -0
  130. {dosync-0.6.2 → dosync-0.6.3}/tests/test_operations_endpoints.py +0 -0
  131. {dosync-0.6.2 → dosync-0.6.3}/tests/test_operations_persistence.py +0 -0
  132. {dosync-0.6.2 → dosync-0.6.3}/tests/test_operations_wiring.py +0 -0
  133. {dosync-0.6.2 → dosync-0.6.3}/tests/test_panel_polish_2026_07_21.py +0 -0
  134. {dosync-0.6.2 → dosync-0.6.3}/tests/test_policies.py +0 -0
  135. {dosync-0.6.2 → dosync-0.6.3}/tests/test_policy_config.py +0 -0
  136. {dosync-0.6.2 → dosync-0.6.3}/tests/test_public_claims.py +0 -0
  137. {dosync-0.6.2 → dosync-0.6.3}/tests/test_pushed_verification.py +0 -0
  138. {dosync-0.6.2 → dosync-0.6.3}/tests/test_quickstart_is_runnable.py +0 -0
  139. {dosync-0.6.2 → dosync-0.6.3}/tests/test_reachability_cause.py +0 -0
  140. {dosync-0.6.2 → dosync-0.6.3}/tests/test_recall_benchmark_postpolicy.py +0 -0
  141. {dosync-0.6.2 → dosync-0.6.3}/tests/test_reconciler.py +0 -0
  142. {dosync-0.6.2 → dosync-0.6.3}/tests/test_resolution_wiring.py +0 -0
  143. {dosync-0.6.2 → dosync-0.6.3}/tests/test_resolver_scoring.py +0 -0
  144. {dosync-0.6.2 → dosync-0.6.3}/tests/test_resolver_semantics.py +0 -0
  145. {dosync-0.6.2 → dosync-0.6.3}/tests/test_route_composer.py +0 -0
  146. {dosync-0.6.2 → dosync-0.6.3}/tests/test_sensor_kind.py +0 -0
  147. {dosync-0.6.2 → dosync-0.6.3}/tests/test_server.py +0 -0
  148. {dosync-0.6.2 → dosync-0.6.3}/tests/test_simulation_is_declared.py +0 -0
  149. {dosync-0.6.2 → dosync-0.6.3}/tests/test_telemetry_bridge.py +0 -0
  150. {dosync-0.6.2 → dosync-0.6.3}/tests/test_third_party_adapters.py +0 -0
  151. {dosync-0.6.2 → dosync-0.6.3}/tests/test_transport_discoverers.py +0 -0
  152. {dosync-0.6.2 → dosync-0.6.3}/tests/test_universal_intent_contract.py +0 -0
  153. {dosync-0.6.2 → dosync-0.6.3}/tests/test_validation.py +0 -0
  154. {dosync-0.6.2 → dosync-0.6.3}/tests/test_validation_integration.py +0 -0
  155. {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.2
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>=1.0.0; extra == "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>=1.0.0; extra == "all"
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 |
@@ -13,5 +13,5 @@ The two numbers move independently on purpose:
13
13
  __version__ this implementation of the hub (semver)
14
14
  __protocol_version__ the wire contract other implementations must match
15
15
  """
16
- __version__ = "0.6.2"
16
+ __version__ = "0.6.3"
17
17
  __protocol_version__ = "0.4"
@@ -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
- Para agregar un nuevo dispositivo:
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
- Para implementar un adapter nuevo:
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 para dispositivos Matter via python-matter-server o Home Assistant bridge.
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
- Adapter DoSync para dispositivos Matter.
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 para dispositivos Shelly via HTTP local (Gen1 y Gen2).
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
- Adapter DoSync para dispositivos Shelly.
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
- """Carga todos los dispositivos registrados."""
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
- """Carga todos los estados persistidos. Retorna {device_id: state_dict}."""
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
- busca los params correspondientes a este dispositivo y actuator.
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
- Ideal para emergencias donde el orden importa.
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
- "Usar para: emergencias, rutinas, control del ambiente, notificaciones."
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
- "Usar: DOSYNC_AUTH=false para deshabilitar, o "
851
- "DOSYNC_TOKEN=<token> para autenticar."
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
- # Emitir certificado para un adapter nuevo
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
- Para cada dispositivo registrado, detalla:
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.2
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>=1.0.0; extra == "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>=1.0.0; extra == "all"
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>=1.0.0
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>=1.0.0
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
- mcp = ["mcp>=1.0.0"]
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",