Metadata-Version: 2.4
Name: crowddrop-sdk
Version: 0.3.1
Summary: CrowdDrop's SDK for embodied-agent hardware. This release covers cloud_brain: the LLM stays in the cloud, the device is a thin actuator/sensor bridge over Pub/Sub.
Project-URL: Repository, https://github.com/crowddrop-ai/crowddrop_ai_agents
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: cloud-brain
Requires-Dist: crowddrop-pubsub-sdk<1.0.0,>=0.2.0; extra == "cloud-brain"
Requires-Dist: requests<3.0.0,>=2.28.0; extra == "cloud-brain"
Requires-Dist: google-auth<3.0.0,>=2.29.0; extra == "cloud-brain"
Provides-Extra: mcp
Requires-Dist: mcp<2.0.0,>=1.2.0; extra == "mcp"
Requires-Dist: requests<3.0.0,>=2.28.0; extra == "mcp"
Provides-Extra: embodiment
Requires-Dist: langchain-core<0.4.0,>=0.3.0; extra == "embodiment"
Requires-Dist: pydantic<3.0,>=2.0; extra == "embodiment"

# crowddrop-sdk

CrowdDrop's SDK for connecting embodied-agent hardware to the CrowdDrop
platform. It splits into two architecturally different scenarios:

- **`cloud_brain`** (this release) - the LLM/tool-calling brain stays in
  CrowdDrop's cloud backend; your device is a thin actuator/sensor bridge.
  This is the right choice if your hardware can't run an LLM locally but can
  run a real Python process. Two transports, same scenario: the default is
  Google Cloud Pub/Sub (`cloud-brain` extra, below); an alternative is
  hosting your own MCP server that CrowdDrop's cloud brain calls directly
  (`mcp` extra, further down) - better if you'd rather write plain MCP
  tools than publish to a fixed Pub/Sub topic, at the cost of needing your
  device reachable from the internet (a tunnel, typically).
- **`edge_brain`** (planned, not yet released) - the LLM itself runs on your
  device. A different SDK surface entirely, for hardware with real local
  inference capacity.

If you're building a companion-computer-class device (e.g. Raspberry-Pi
class, full Linux + Python) that receives commands and reports telemetry,
you want the `cloud-brain` extra:

```bash
pip install "crowddrop-sdk[cloud-brain]"
```

This pulls in `crowddrop-pubsub-sdk` (CrowdDrop's Pub/Sub transport library)
as its only dependency - nothing else, so it stays light on constrained
hardware. The base `crowddrop-sdk` install (no extra) has no dependencies at
all.

## Getting credentials

Two tiers, both self-service:

- A **developer credential** - reusable, lets you create and update any
  number of your own personas yourself via `POST /agents/create` (see
  "Registering an agent persona self-service" below) - no further manual step per
  persona. Get one with a name and email address:
  ```bash
  curl -X POST "$BACKEND_URL/developers/register" \
    -H "Content-Type: application/json" \
    -d '{"developer_name": "Acme Logistics", "email": "dev@acme.example"}'
  # -> {"developer_name": "...", "developer_credential": "...", ...}
  ```
  **MVP note:** this is currently open, with no email verification - the
  credential is minted and returned immediately in this one call. That's a
  deliberate, temporary trade-off
  (see `docs/plans/first_citizen_drone_connectivity/04-code-implementation.md`'s
  Workstream J in the `crowddrop_ai_agents` repo for the real,
  email-verified design this is a placeholder for), not the intended end
  state. The credential is shown exactly once, in that response - store it
  immediately (see the
  [`crowddrop-ai/crowddrop-sdk-examples`](https://github.com/crowddrop-ai/crowddrop-sdk-examples)
  repo's `register_developer.py` for a runnable version that writes it
  straight to a local `.env` file).
  CrowdDrop can still issue one directly instead
  (`scripts/issue_developer_credential.py`, a human-run fallback for
  internal accounts) - both paths produce an equivalent credential.
- An **agent device key** - one per physical device/persona, either minted
  automatically the first time you self-service-create a persona, or (the
  older path, still supported for personas CrowdDrop's own team defines)
  handed to you directly along with the URL of the backend's token-vending
  endpoint.

Your device never handles a raw GCP service-account key file either way: it
trades its device key for a short-lived GCP access token by calling the
token-vending endpoint (see `cloud_brain/drone/edge_agent.py`'s
`fetch_gcp_access_token`), and refreshes that token automatically as it
nears expiry.

## API reference (`cloud_brain`)

- **`crowddrop_sdk.cloud_brain.events.DRONE_COMMANDS`** - the eight
  supported movement primitives, each a fixed pulse with no
  duration/distance parameter: `take_off`, `land`, `forward`, `backward`,
  `strafe_left`, `strafe_right`, `turn_left`, `turn_right`.
- **`DroneCommandEvent(drone_id, command, issued_at, sequence)`** - what you
  receive, one per command.
- **`DroneTelemetryEvent(drone_id, latitude, longitude, heading,
  battery_level, reported_at, sequence)`** - what you publish back.
- **`crowddrop_sdk.cloud_brain.drone.channels.DroneCommandSubscriber`** -
  pull-based subscriber for commands (you always initiate the connection
  outward - nothing is ever pushed to your device).
- **`crowddrop_sdk.cloud_brain.drone.channels.DroneTelemetryPublisher`** -
  publisher for telemetry, with a `publish_telemetry(drone_id, latitude,
  longitude, heading, battery_level)` convenience method.
- **Two integration points you implement** - `handle_command(event)` (map
  each command to your real flight-controller call) and
  `read_battery_and_gps()` (return a real sensor reading). Both are stand-ins
  in the example below; this SDK doesn't know your hardware's API.

## Quickstart

See [`cloud_brain/drone/README.md`](crowddrop_sdk/cloud_brain/drone/README.md)
for a runnable example (`edge_agent.py`) and the exact env vars it needs.

## Registering an agent persona self-service (`POST /agents/create`)

Once you have a developer credential, bring a new persona onto CrowdDrop
yourself - no YAML PR, no redeploy:

```bash
curl -X POST "$BACKEND_URL/agents/create" \
  -H "X-Developer-Credential: $DEVELOPER_CREDENTIAL" \
  -H "Content-Type: application/json" \
  -d '{
        "config_key": "MyWarehouseDrone",
        "default_name": "My Warehouse Drone",
        "about_me": "A warehouse picking drone.",
        "persona_body": "You are a warehouse picking drone...",
        "organization": {"name": "Acme Logistics", "support_contact": "support@acme.example"},
        "robot": [{
          "identifier": "ACME-DRONE-001", "model_name": "Acme-X1",
          "home_lat": 52.42, "home_lon": 13.05,
          "initial_lat": 52.42, "initial_lon": 13.05,
          "initial_heading": 0, "initial_battery": 100
        }]
      }'
```

The response's `device_key` field is shown exactly once - store it
somewhere durable immediately (see the `crowddrop-ai/crowddrop-sdk-examples`
repo's `cloud_brain/drone/register_agent_persona.py` for a runnable
version of the call above that writes it straight to a local `.env`
file). Calling this again for the same
`config_key` updates it in place (no new device key minted) - the running
persona picks up the change immediately, no restart needed. The persona
this creates has no built-in capabilities of its own - see "Alternative:
bring your own MCP server" right below for how you give it any.

## Alternative: bring your own MCP server (`mcp` extra)

Instead of publishing commands/telemetry over Pub/Sub, you can run your own
[MCP](https://modelcontextprotocol.io/) server exposing your device's
actions as tools, and have CrowdDrop's cloud brain call them directly. This
needs your server reachable from CrowdDrop's cloud - a tunnel (e.g.
`cloudflared`) is the easiest way to get that without a public IP or port
forwarding, which is what `start_cloudflared_tunnel()`/`serve_and_register()`
below are for.

```bash
pip install "crowddrop-sdk[mcp]"
```

**Recommended starting point: `crowddrop_sdk.robot_protocol.RobotProtocol`.**
Subclass it, implement `identify_device_type()`/`get_device_status()` (the
two names CrowdDrop directs the LLM to call for "what are you"/"what's
your status"), add whatever action methods your own device actually
supports, and pass an instance to
`command_handlers_from_robot()` to get the dict `build_command_mcp_server()`
below expects. Real `abstractmethod` enforcement means Python refuses to
instantiate an incomplete subclass, instead of a silent "tool not found"
the first time the LLM tries to call a name you forgot - see
`crowddrop_sdk/tests/test_robot_protocol.py` and the
`crowddrop-ai/crowddrop-sdk-examples` repo's
`cloud_brain/drone/mcp/dummy_robot.py` for a worked example. No new
dependency - `robot_protocol.py` is plain stdlib
(`abc`/`inspect`/`typing`), so it's importable and unit-testable without
the `mcp` extra at all. Using a plain `{name: callable}` dict directly still
works too, if you'd rather not use a class.

- **`crowddrop_sdk.mcp_tools.build_command_mcp_server(name, command_handlers)`**
  - wraps a `{command_name: handler}` dict (the same shape
  `command_handlers_from_robot()` above produces, or the Pub/Sub example's
  `COMMAND_HANDLERS`) as an MCP server, one tool per entry - a handler with
  typed parameters becomes a properly parameterized tool automatically, from
  its own function signature.
- **`start_cloudflared_tunnel(local_port)`** - runs Cloudflare's anonymous
  "quick tunnel" (no account needed) and returns its public URL. Requires
  the `cloudflared` binary on PATH.
- **`register_mcp_server(model_id, device_key, mcp_sse_url, backend_url)`** -
  registers that URL with CrowdDrop, authenticated with the same agent
  device key the Pub/Sub path uses - scoped server-side to your `model_id`.
- **`serve_and_register(name, command_handlers, model_id, device_key,
  backend_url)`** - the one-call path: builds the server, runs it, tunnels
  it, registers it, and blocks until Ctrl-C. Use the pieces above
  individually for more control.

This mechanism is verified end-to-end against the real MCP client
(`langchain_mcp_adapters.MultiServerMCPClient`) CrowdDrop's backend uses -
see `crowddrop_sdk/tests/test_mcp_tools.py`. The Pub/Sub path remains the
better fit if your device has no way to expose a local server at all.

## Embodiment (`embodiment` extra) - contributing a body class

Everything above lets your own server drive a persona from the outside -
this is the rarer, opposite direction: contributing a body class for
CrowdDrop to run *inside* its own backend process, alongside personas like
Humanoid/Drone/Car. Publishing
**`crowddrop_sdk.cloud_brain.embodiment.EmbodiedBodyProtocol`** (plus
`BodyTraits`/`LocationModel`/`MoveCommandInput`) documents that contract -
`pip install "crowddrop-sdk[embodiment]"` pulls in the `langchain_core`/
`pydantic` it's built on. Most developers want `POST /agents/create` +
your own MCP server above instead - this is only for the case where a
device needs body-level logic CrowdDrop's own `GenericMCPEmbodiment` (a
single class every self-service persona already shares) doesn't cover, and
you want CrowdDrop's team to run your class for you rather than exposing it
yourself over MCP.

## Releasing (publishing a new version to PyPI)

Releases are tag-triggered via `.github/workflows/publish-crowddrop-sdk.yml`,
using PyPI's **Trusted Publishing** (OIDC) — no API token is stored as a
GitHub secret.

1. Bump `version` in `crowddrop_sdk/pyproject.toml`.
2. Commit that change (on a branch, via the normal PR flow).
3. Once merged, tag the merge commit and push the tag:
   ```bash
   git tag crowddrop-sdk-v<version>   # e.g. crowddrop-sdk-v0.1.1
   git push origin crowddrop-sdk-v<version>
   ```
   The tag push is what fires the workflow — it builds `crowddrop_sdk/` and
   uploads it to [pypi.org/project/crowddrop-sdk](https://pypi.org/project/crowddrop-sdk/).
   No other trigger publishes this package.

If `cloud-brain`'s dependency on `crowddrop-pubsub-sdk` (see `pyproject.toml`)
needs bumping too, release `pubsub_sdk` first — see its own README's
"Releasing" section, same mechanism, separate workflow/tag prefix
(`pubsub-sdk-v*`).

**One-time setup, not yet done as of this writing — needed before the first
tag push, and again only if this ever moves to a different PyPI
account/org:**
- Register a **pending publisher** for `crowddrop-sdk` at
  https://pypi.org/manage/account/publishing/ — this can be done before the
  PyPI project exists, so it covers the *first-ever* release too, not just
  subsequent ones. Fill in: PyPI project name `crowddrop-sdk`, repo owner
  `crowddrop-ai`, repo name `crowddrop_ai_agents`, workflow filename
  `publish-crowddrop-sdk.yml`, environment name `pypi`. Requires a PyPI
  account with 2FA enabled — no API token to generate or store.
- The `pypi` GitHub Environment referenced by the workflow is created
  automatically the first time the workflow runs against it; create it
  manually in this repo's Settings → Environments beforehand only if you
  want a required-reviewer protection rule (so a tag push pauses for human
  approval before it actually publishes).
- `../scripts/publish_python_packages.sh` (manual `build` + `twine upload`)
  is kept as a fallback/local-dry-run tool only — with a pending publisher
  registered, it's no longer needed even for the first release.

Versioning is manual — nothing cross-checks the tag against
`pyproject.toml`'s `version`. Bump the file first, commit, *then* tag that
exact commit; tagging a commit whose `pyproject.toml` still has an
already-published version will fail the upload (PyPI rejects re-uploading an
existing version).
