Network Discovery | Getting Started
Find things on the local network and advertise your own, across the three protocols consumer and
commercial hardware actually uses: mDNS/DNS-SD (Bonjour/Zeroconf), SSDP/UPnP, and
WS-Discovery. Discover printers, casting targets, IoT devices, routers, media servers, ONVIF
cameras, and other instances of your own app. Built on Shiny.Core, so it runs in any Shiny host —
MAUI, console, service, or server.
| GitHub | |
| Downloads |
Which protocol?
Section titled “Which protocol?”Register only what you need — they are independent, and a device that speaks one usually does not speak the others.
| Looking for | Protocol | Register | Docs |
|---|---|---|---|
| Apple devices, AirPlay, IPP printers, peer apps, your own services | mDNS/DNS-SD | AddMdns() |
Browsing · Publishing |
| Routers, UPnP/DLNA media servers, Sonos, Roku, smart TVs | SSDP/UPnP | AddSsdp() |
SSDP & UPnP |
| ONVIF IP cameras, WSD printers and scanners, Windows machines | WS-Discovery | AddWsDiscovery() |
WS-Discovery & ONVIF |
Features
Section titled “Features”- Browse — a live
IAsyncEnumerable<>stream of things appearing and disappearing, or a one-shot scan, for all three protocols. - Resolve — turn an mDNS instance name into a host, port, IPv4/IPv6 addresses, and TXT records.
- Publish — advertise your own mDNS service, UPnP root device, or WS-Discovery target.
- UPnP device descriptions — fetch and parse friendly name, model, icons, and the service list.
- No iOS multicast entitlement for mDNS — Apple platforms go through Bonjour (
NSNetService), not raw multicast sockets. SSDP and WS-Discovery do need it; see Platform Setup. - No Android multicast lock to manage — mDNS goes through
NsdManager, and the lock SSDP/WS-Discovery need is acquired for you. - Actionable permission errors — a missing entitlement or manifest entry throws
DiscoveryPermissionExceptionnaming what to add, instead of silently finding nothing. - Dependency-free managed implementations, no Avahi and no third-party stack.
- AOT & trimmer compatible.
Step 1 — Add the marketplace:
claude plugin marketplace add shinyorg/skillsStep 2 — Install the plugin:
claude plugin install shiny@shinyOne plugin installs all 35 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.
Step 1 — Add the marketplace:
copilot plugin marketplace add https://github.com/shinyorg/skillsStep 2 — Install the plugin:
copilot plugin install shiny@shinyOne plugin installs all 35 Shiny skills. Your agent loads only the skill relevant to what you're building, so there's no cost to having them all available.
Install the package:
dotnet add package Shiny.Net.DiscoveryRegister the manager:
using Shiny;
builder.Services.AddMdns(); // IMdnsManagerbuilder.Services.AddSsdp(); // ISsdpManagerbuilder.Services.AddWsDiscovery(); // IWsDiscoveryManagerThen inject the manager you need.
Find services on the network
Section titled “Find services on the network”public class PrinterFinder(IMdnsManager mdns){ public async Task<IReadOnlyList<MdnsService>> FindPrinters(CancellationToken ct) => await mdns.BrowseOnce("_ipp._tcp", TimeSpan.FromSeconds(5), ct);}BrowseOnce collects for a fixed window and returns everything still present when it closes — ideal
for a “scan” button. For a list that updates as devices come and go, use the streaming API:
await foreach (var result in mdns.Browse("_ipp._tcp", ct)){ if (result.Status == MdnsBrowseStatus.Found) this.printers[result.Service.FullName] = result.Service; else this.printers.Remove(result.Service.FullName);}Browse never completes on its own — it runs until the CancellationToken is cancelled.
Use what you found
Section titled “Use what you found”var service = found.First();
Console.WriteLine(service.InstanceName); // "Kitchen Printer"Console.WriteLine(service.HostName); // "kitchen-printer.local"Console.WriteLine(service.Port); // 631Console.WriteLine(service.GetTxt("rp")); // TXT record lookup
if (service.IsResolved) await socket.ConnectAsync(service.GetEndPoint()!);Advertise your own service
Section titled “Advertise your own service”await using var publication = await mdns.Publish( new MdnsServiceRegistration("Allan's Laptop", "_myapp._tcp", 8080) { TxtRecords = new Dictionary<string, string> { ["version"] = "2" } }, ct);
// the responder renames on conflict, so read the live name backlogger.LogInformation("advertising as {Name}", publication.InstanceName);Publishing only advertises the port — you are still responsible for having a listener bound to it. Disposing the publication sends a goodbye packet and stops advertising.
Next steps
Section titled “Next steps”- Browsing & Resolving — streaming vs one-shot, TXT records, resolve timeouts
- Publishing — TXT metadata, conflict renaming, lifetime
- Platform Setup — Info.plist, manifest, firewall, and what backs each platform


