diff --git a/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_index.md b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_index.md
new file mode 100644
index 0000000000..e0cd3a1673
--- /dev/null
+++ b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_index.md
@@ -0,0 +1,56 @@
+---
+title: Connect Devices and AI agents through Device Connect Server
+
+draft: true
+cascade:
+ draft: true
+
+minutes_to_complete: 30
+
+who_is_this_for: This is a follow-on topic for developers who have a working Device Connect mesh and want to add a server layer on top. The server gives you a persistent device registry, distributed state, and security primitives (commissioning, ACLs) so you can operate a multi-network fleet from one place.
+
+learning_objectives:
+ - Understand what the Device Connect server adds on top of the edge SDK and when you'd reach for it
+ - Provision a hosted tenant on the Device Connect portal and download per-device NATS credentials
+ - Commission two simulated devices against your tenant using the credentials the portal issues
+ - Discover and invoke commissioned devices from a Python client using `device-connect-agent-tools`
+ - Connect a Strands AI agent to the same tenant
+
+prerequisites:
+ - Familiarity with the Device Connect edge SDK (the [device-to-device Learning Path](/learning-paths/embedded-and-microcontrollers/device-connect-d2d/) is a good starting point)
+ - An account on the [Device Connect portal](https://portal.deviceconnect.dev/)
+ - Basic familiarity with Python and the command line
+
+author:
+ - Kavya Sri Chennoju
+ - Annie Tallund
+
+### Tags
+skilllevels: Introductory
+subjects: Libraries
+armips:
+ - Cortex-A
+ - Neoverse
+operatingsystems:
+ - Linux
+ - macOS
+tools_software_languages:
+ - Python
+ - Docker
+
+further_reading:
+ - resource:
+ title: Device Connect
+ link: https://deviceconnect.dev/
+ type: website
+ - resource:
+ title: device-connect-server package
+ link: https://github.com/arm/device-connect/tree/main/packages/device-connect-server
+ type: documentation
+
+### FIXED, DO NOT MODIFY
+# ================================================================================
+weight: 1
+layout: "learningpathall"
+learning_path_main_page: "yes"
+---
diff --git a/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_next-steps.md b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_next-steps.md
new file mode 100644
index 0000000000..cfca5cb4ea
--- /dev/null
+++ b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/_next-steps.md
@@ -0,0 +1,17 @@
+---
+# ================================================================================
+# FIXED, DO NOT MODIFY THIS FILE
+# ================================================================================
+weight: 21
+title: "Next Steps"
+layout: "learningpathall"
+---
+
+## Where to go next
+
+From this baseline you can extend the deployment in a few directions:
+
+- swap Zenoh dev mode for [Zenoh with TLS](https://github.com/arm/device-connect/tree/main/packages/device-connect-server#secure--zenoh-tls) using the `generate_tls_certs.sh` script in `security_infra/`
+- swap to a NATS backend with [JWT authentication](https://github.com/arm/device-connect/tree/main/packages/device-connect-server#authenticated--nats-jwt-auth) for per-device credentials
+- explore the [multi-tenant deployment](https://github.com/arm/device-connect/tree/main/packages/device-connect-server#multi-tenant-deployment) flow when several teams or workshops share the same infrastructure
+- replace the simulated number-generator with a real sensor or robot driver using the same `DeviceDriver` pattern from the edge SDK
diff --git a/content/learning-paths/embedded-and-microcontrollers/device-connect-server/background.md b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/background.md
new file mode 100644
index 0000000000..13a1fcec62
--- /dev/null
+++ b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/background.md
@@ -0,0 +1,89 @@
+---
+title: Why add a Device Connect server
+weight: 2
+
+# FIXED, DO NOT MODIFY
+layout: learningpathall
+---
+
+## From device-to-device (D2D) to server
+
+The [device-to-device Learning Path](/learning-paths/embedded-and-microcontrollers/device-connect-d2d/) showed Device Connect in its simplest shape. Each runtime is a peer on the same local network. Devices find each other automatically, exchange typed events, and call each other's remote procedure calls (RPCs) directly. There is no broker, registry, or cloud service to run.
+
+That model is useful when everything is close together. It works well for prototypes, lab demos, and small fleets on one local area network (LAN). It is also a good fit when a cloud round-trip would add unnecessary delay.
+
+As soon as the fleet grows, D2D mode starts to run out of road. You may need devices on different networks to talk to each other. You may need a registry that remembers devices after they disconnect. You may also need stronger identity, credential rotation, or audit logs.
+
+Use a Device Connect server when you need:
+
+- a **persistent registry** that survives device reboots and lets new clients list known devices
+- **distributed state** shared between devices and agents, with leases and watches
+- **multi-network reach** for devices and agents on different LANs, behind network address translation (NAT), or in the cloud
+- **fleet-wide operations** such as commissioning devices and rotating credentials
+- **stronger security controls** such as per-device identity, JSON Web Token (JWT) credentials, role-based access control lists (ACLs), and audit logs
+
+The [`device-connect-server`](https://github.com/arm/device-connect/tree/main/packages/device-connect-server) package is the layer that adds those capabilities on top of the edge software development kit (SDK) you already know.
+
+## What the server adds
+
+The server does not change how you write device code. Devices still use `DeviceDriver`, `@rpc`, `@emit`, `@periodic`, and `@on`. Clients and artificial intelligence (AI) agents still use `discover_devices()` and `invoke_device()`.
+
+The server changes how devices find and trust each other. In D2D mode, each runtime discovers nearby peers directly. With a server, every device and agent connects to a shared service.
+
+That shared service gives you a few fleet-level building blocks:
+
+- **routing** so devices on different networks can join the same mesh
+- **registry** so clients can see known devices, their capabilities, and their last-known status
+- **shared state** so devices and agents can coordinate through a common store
+- **commissioning** so each device gets its own trusted credential
+- **security controls** such as Transport Layer Security (TLS), mutual TLS (mTLS), JSON Web Token (JWT) authentication, role-based access control, and audit logging
+
+Device Connect supports different messaging backends, including Zenoh, NATS, and Message Queuing Telemetry Transport (MQTT). In this Learning Path, you will use the hosted portal with NATS credentials. That lets you focus on the device and agent code instead of running the server yourself.
+
+## A note on commissioning
+
+Commissioning means giving a device or agent a trusted identity before it joins the mesh. Without commissioning, a process is just code trying to connect. With commissioning, the server can decide whether that process is allowed to publish, subscribe, register itself, or call an RPC.
+
+The credential format depends on the messaging backend:
+
+- with **Zenoh**, commissioning means issuing the device a client TLS certificate signed by a shared certificate authority (CA)
+- with **NATS**, commissioning means issuing the device a JWT credential bound to its tenant
+
+In both cases, the credential answers the same question: is this identity allowed on this mesh?
+
+In this Learning Path, you will use the [Device Connect portal](https://portal.deviceconnect.dev/) to download NATS credentials for three default identities on your tenant. Two identities will run simulated robot arms. The third identity will run the Python client or agent.
+
+## Where this sits in the architecture
+
+In the diagram, pub/sub means publish/subscribe, KV means key-value, LC means LangChain, and MCP means Model Context Protocol.
+
+```
+┌──────────────────┐ ┌──────────────────────┐ ┌─────────────────────┐
+│ Devices │ │ Device Connect │ │ AI agents │
+│ (edge SDK) │ │ server │ │ (agent-tools) │
+│ DeviceDriver │◄───►│ Pub/sub · Registry│◄───►│ discover_devices() │
+│ @rpc @emit │ │ KV · Security│ │ invoke_device() │
+│ @periodic @on │ │ │ │ Strands / LC / MCP │
+└──────────────────┘ └──────────────────────┘ └─────────────────────┘
+```
+
+Devices and agents still talk to each other through the same primitives (`@rpc`, `@emit`, `discover_devices`, `invoke_device`). The Device Connect server runs the pub/sub messaging, the persistent registry, the shared key-value store, and the security layer in one place.
+
+The animation below shows the same idea end to end. A Device Connect server runs in Berlin. Robots in San Francisco and Tokyo install `device-connect-edge` and register with it. An AI agent in Bangalore installs `device-connect-agent-tools` and drives both robots through the server. Every `invoke_device` call and every event flows through the server.
+
+
+
+## What you'll do in this Learning Path
+
+In the rest of this Learning Path you will:
+
+- sign in to the Device Connect portal and identify your tenant slug
+- download credentials for the three default device identities
+- run two simulated robot arms with `device-connect-edge`
+- discover and invoke both devices from a Python client using `device-connect-agent-tools`
+- optionally attach a Strands AI agent to the same tenant
+
+The next section walks through the setup.
diff --git a/content/learning-paths/embedded-and-microcontrollers/device-connect-server/server-setup.md b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/server-setup.md
new file mode 100644
index 0000000000..a1457aa8f8
--- /dev/null
+++ b/content/learning-paths/embedded-and-microcontrollers/device-connect-server/server-setup.md
@@ -0,0 +1,288 @@
+---
+title: Run devices and an orchestrating agent on a Device Connect server
+weight: 3
+
+# FIXED, DO NOT MODIFY
+layout: learningpathall
+---
+
+## Setup of Device Connect portal
+
+The Device Connect portal is operated by Arm as a hosted developer service for the open-source [Device Connect](https://github.com/arm/device-connect) framework. Signing in creates a private **tenant** for your account. A tenant is an isolated namespace on the shared Device Connect service: devices and agents commissioned to your tenant can discover and invoke each other, but other tenants cannot see or use them.
+
+Only your account email and the cryptographic identities (device IDs, JWTs, public keys) you create on the portal are stored. The contents of messages your devices exchange are not stored, seen, or proxied. Treat each `.creds.json` file like a private key, and do not put secrets, API keys, or personal data into device names or descriptions. For questions or issues, open one at [`arm/device-connect`](https://github.com/arm/device-connect/issues).
+
+The portal page header shows your tenant slug in `Manage device credentials for tenant `. For a new account, the slug is your username. If your account has a different slug, use the value shown in the portal. The portal prefixes every device identity with this slug, so this Learning Path uses names such as `${TENANT}-device-001`.
+
+New tenants include three device identities by default:
+
+- `${TENANT}-device-001`
+- `${TENANT}-device-002`
+- `${TENANT}-device-003`
+
+This Learning Path uses `device-001` and `device-002` for the simulated robot arms, and `device-003` for the Python client or AI agent. You can create your own device identities in the portal if you prefer, but using the default names lets you run the commands below without editing the device names.
+
+1. Open [`https://portal.deviceconnect.dev/`](https://portal.deviceconnect.dev/) and sign in.
+2. Open **My Devices** and copy the tenant slug from the page header.
+3. Download the credentials for the three default identities.
+
+Export the tenant settings so the rest of the walkthrough can reuse them. Replace `` with the slug from the portal.
+
+```bash
+export TENANT=
+export NATS_URL=nats://portal.deviceconnect.dev:4222
+export MESSAGING_BACKEND=nats
+```
+
+Now save the downloaded credentials to a stable directory:
+
+```bash
+mkdir -p ~/.device-connect/credentials
+mv ~/Downloads/${TENANT}-device-001.creds.json ~/.device-connect/credentials/
+mv ~/Downloads/${TENANT}-device-002.creds.json ~/.device-connect/credentials/
+mv ~/Downloads/${TENANT}-device-003.creds.json ~/.device-connect/credentials/
+chmod 600 ~/.device-connect/credentials/*.creds.json
+```
+
+Verify that all three credential files are in place:
+
+```bash
+ls ~/.device-connect/credentials/
+```
+
+The portal also exposes a **Coding Agents** tab that can download credentials on your behalf. The manual flow above is useful to walk through once so you understand what the agent automates.
+
+## Install the Device Connect packages
+
+Create a virtual environment and install the edge runtime and the agent tools:
+
+```bash
+mkdir -p ~/device-connect-fabric && cd ~/device-connect-fabric
+python3 -m venv .venv
+source .venv/bin/activate
+pip install device-connect-edge device-connect-agent-tools
+```
+
+{{% notice Note %}}
+Fabric is the hosted Device Connect service used by the portal. In this Learning Path, Fabric runs the NATS router and registry for you, so you do not install or run `device-connect-server` locally.
+
+If you would rather self-host, install `device-connect-server` with `pip install device-connect-server` and run the router and registry yourself. See the [device-connect-server README](https://github.com/arm/device-connect/tree/main/packages/device-connect-server) for the Docker Compose deployment options.
+{{% /notice %}}
+
+## Create a simulated robot arm
+
+Create a file called `robot_arm.py`. The driver pretends to be a 6-DOF robot arm: it exposes RPCs for moving to a target pose and homing, tracks its current position, and emits a `motion_completed` event after every move.
+
+```python
+import argparse
+import asyncio
+import random
+
+from device_connect_edge import DeviceRuntime
+from device_connect_edge.drivers import DeviceDriver, emit, rpc
+from device_connect_edge.types import DeviceIdentity, DeviceStatus
+
+
+class RobotArmDriver(DeviceDriver):
+ device_type = "robot_arm"
+
+ def __init__(self):
+ super().__init__()
+ self._position = {"x": 0.0, "y": 0.0, "z": 0.0}
+
+ @property
+ def identity(self) -> DeviceIdentity:
+ return DeviceIdentity(
+ device_type="robot_arm",
+ manufacturer="Device Connect",
+ model="SIM-ARM-6DOF",
+ description="Simulated 6-DOF robot arm",
+ )
+
+ @property
+ def status(self) -> DeviceStatus:
+ return DeviceStatus(availability="available", location="simulator")
+
+ @rpc()
+ async def move_to(self, x: float, y: float, z: float) -> dict:
+ # pretend to physically traverse to the target
+ await asyncio.sleep(random.uniform(0.2, 0.6))
+ self._position = {"x": x, "y": y, "z": z}
+ await self.motion_completed(target=self._position)
+ return {"position": self._position}
+
+ @rpc()
+ async def home(self) -> dict:
+ return await self.move_to(0.0, 0.0, 0.0)
+
+ @rpc()
+ async def get_position(self) -> dict:
+ return self._position
+
+ @emit()
+ async def motion_completed(self, target: dict):
+ return None
+
+
+async def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--device-id", required=True)
+ args = parser.parse_args()
+
+ runtime = DeviceRuntime(driver=RobotArmDriver(), device_id=args.device_id)
+ await runtime.run()
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
+```
+
+Note that there is no `allow_insecure=True` on the runtime. The runtime will only join the mesh if it has valid credentials, which is what makes commissioning meaningful.
+
+## Connect the device to the server
+
+You will use three terminals: two for simulated devices and one for the Python client. Each terminal must have the virtual environment active and the shared tenant enviroment variables exported.
+
+| Terminal | Purpose | Credential |
+|----------|---------|------------|
+| 1 | First simulated robot arm | `${TENANT}-device-001.creds.json` |
+| 2 | Second simulated robot arm | `${TENANT}-device-002.creds.json` |
+| 3 | Python client or AI agent | `${TENANT}-device-003.creds.json` |
+
+In every new terminal, run:
+
+```bash
+cd ~/device-connect-fabric
+source .venv/bin/activate
+export TENANT=
+export NATS_URL=nats://portal.deviceconnect.dev:4222
+export MESSAGING_BACKEND=nats
+```
+
+Replace `` with the slug from the portal.
+
+For each process, `NATS_URL` points at the hosted NATS server and `NATS_CREDENTIALS_FILE` selects the identity that process will use. The `--device-id` value must match the identity inside the credentials file.
+
+In terminal 1, start the first simulated robot arm:
+
+```bash
+NATS_CREDENTIALS_FILE=~/.device-connect/credentials/${TENANT}-device-001.creds.json \
+ python robot_arm.py --device-id ${TENANT}-device-001
+```
+
+On startup, the runtime presents its JWT to NATS. NATS verifies it against your tenant's signing key, and the device is allowed to publish, subscribe, and register itself. From this moment it shows up in the portal under your tenant, with its identity, capabilities, and live status.
+
+The output should look like:
+
+```output
+2026-05-12 14:26:01,582 - device_connect_edge.device.-device-001 - INFO - Registering device
+2026-05-12 14:26:01,761 - device_connect_edge.device.-device-001 - INFO - Device registered: registration_id=16d550ee-b287-41af-ac69-d3faabfc8178
+2026-05-12 14:26:01,762 - device_connect_edge.device.-device-001 - INFO - Subscribed to commands on device-connect..-device-001.cmd
+```
+
+In terminal 2, start the second simulated robot arm:
+
+```bash
+NATS_CREDENTIALS_FILE=~/.device-connect/credentials/${TENANT}-device-002.creds.json \
+ python robot_arm.py --device-id ${TENANT}-device-002
+```
+
+The output should be similar to that in the first terminal. You now have two commissioned devices on your tenant.
+
+## Discover and invoke from Python
+
+In terminal 3, run a short client with the third credential. This is the same `device-connect-agent-tools` API you would use from a Strands or LangChain agent.
+
+The agent-tools `connect()` function reads `TENANT` only when its `zone` argument is left unset, so call it as `connect(zone=os.environ["TENANT"])` in the Python snippets below.
+
+```bash
+NATS_CREDENTIALS_FILE=~/.device-connect/credentials/${TENANT}-device-003.creds.json \
+ python - <<'PY'
+import os
+from device_connect_agent_tools import connect, discover_devices, invoke_device
+
+tenant = os.environ["TENANT"]
+connect(zone=tenant)
+
+devices = discover_devices()
+print(f"Found {len(devices)} device(s) on tenant")
+for d in devices:
+ print(f" {d['device_id']:24} {d['device_type']:20} {d.get('status', {}).get('availability', '?')}")
+
+# Drive both arms through the server. The server routes each call to the right device.
+for d in devices:
+ print(f"\nhome on {d['device_id']}:")
+ print(invoke_device(d['device_id'], 'home'))
+
+print(f"\nmove {tenant}-device-001 to (0.2, 0.1, 0.3):")
+print(invoke_device(f'{tenant}-device-001', 'move_to', {'x': 0.2, 'y': 0.1, 'z': 0.3}))
+
+print(f"\nposition of {tenant}-device-002:")
+print(invoke_device(f'{tenant}-device-002', 'get_position'))
+PY
+```
+
+You should see `${TENANT}-device-001` and `${TENANT}-device-002` listed, then each one home, then the first arm move, then the second arm's position read. The client never needs to know which network the arms are on; it only needs the tenant's NATS URL and its own credentials. Your output should end with confirmation that one of the device positions has been updated:
+
+```output
+move -device-001 to (0.2, 0.1, 0.3):
+{'success': True, 'result': {'position': {'x': 0.2, 'y': 0.1, 'z': 0.3}}}
+
+position of -device-002:
+{'success': True, 'result': {'x': 0.0, 'y': 0.0, 'z': 0.0}}
+```
+
+## (Optional) Attach a Strands AI agent
+
+`device-connect-agent-tools` ships an adapter that turns the same Device Connect mesh into a Strands tool surface:
+
+```bash
+pip install "device-connect-agent-tools[strands]"
+```
+
+```python
+import asyncio
+from device_connect_agent_tools.adapters.strands_agent import StrandsDeviceConnectAgent
+
+async def main():
+ agent = StrandsDeviceConnectAgent(
+ goal="Coordinate the two robot arms: home them, then plan and execute moves on user request",
+ model_id="claude-sonnet-4-20250514",
+ )
+ async with agent:
+ await agent.run()
+
+asyncio.run(main())
+```
+
+Run it with the agent's credentials and an Anthropic API key:
+
+```bash
+NATS_CREDENTIALS_FILE=~/.device-connect/credentials/${TENANT}-device-003.creds.json \
+ ANTHROPIC_API_KEY="sk-ant-..." \
+ python my_agent.py
+```
+
+The agent subscribes to events on your tenant, batches them over a short window, and prompts Claude to react. Claude can call back into devices using `invoke_device()` and `get_device_status()`.
+
+## Tear down
+
+Stop the running terminals with `Ctrl-C`. Your tenant and credentials remain valid until you revoke them from the portal, so there is nothing on your machine to clean up beyond the virtual environment:
+
+```bash
+deactivate
+rm -rf ~/device-connect-fabric/.venv
+```
+
+To rotate credentials, regenerate them from the portal and replace the `.creds.json` files. Old credentials are revoked the moment new ones are issued.
+
+## What you've built
+
+You now have a working Device Connect deployment with a hosted server in the loop:
+
+- a hosted NATS router and persistent registry on your tenant
+- two commissioned simulated robot arms, each authenticated by its own JWT credential
+- a third credential used by a Python client with `device-connect-agent-tools` to discover the arms and drive them through the server
+- (optionally) a Strands agent coordinating both arms over the same mesh
+
+This is the same shape of deployment you would use to put real robot arms, conveyors, cameras, or actuators on the mesh; only the driver code and the credential names change.