npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

easy-onvif-mcp

v0.2.0

Published

MCP server for ONVIF IP cameras and NVRs, compiled from the Dart easy_onvif stack with dart2js. Runs over stdio: device information, media profiles, RTSP and snapshot URIs, PTZ control, and WS-Discovery.

Readme

easy_onvif_mcp

An MCP (Model Context Protocol) server that exposes ONVIF IP cameras and NVRs as tools, built on easy_onvif.

Point a model at a camera: list profiles, resolve RTSP and snapshot URIs, drive PTZ, read device information, and discover devices over WS-Discovery — without writing a single SOAP request by hand.

Pub Version

How it is built

The tools are declared once, as @Tool-annotated methods on EasyOnvifMcpServer. easy_api_generator turns those annotations into the stdio dispatcher, so there is no hand-maintained schema and no separate registry to keep in step:

cd packages/easy_onvif_mcp
dart run build_runner build   # regenerates src/easy_onvif_mcp_server.mcp.dart

| Artifact | Entry point | How you run it | | --- | --- | --- | | pub.dev / Dart VM | bin/easy_onvif_mcp_server.dart (generated dispatcher) | dart pub global activate easy_onvif_mcp | | npm / Node.js | lib/src/js/entry.dart → dist/ (compiled with dart compile js) | npx -y easy-onvif-mcp |

The generated dispatcher imports dart:io, which cannot be compiled to JS, so the npm artifact keeps a small registry of tool names and JSON schemas in lib/src/js/tools.dart and calls the same annotated methods. test/tool_defs_test.dart fails if the two lists ever diverge.

Credentials

Resolved lazily on the first tool call, environment winning over the config file:

  1. ONVIF_HOST, ONVIF_USERNAME, ONVIF_PASSWORD
  2. $HOME/.onvif/credentials.json — the same file easy_onvif_cli reads, so onvif authorize sets up this server too. Override the path with ONVIF_CONFIG_FILE.
  3. Or call the onvif_connect tool explicitly. On the Dart VM it takes host and username, and reads the password from the sources above; the npm build accepts host, username and password directly, because an npx process has no reliable home directory for the credentials file.

ONVIF_HOST may carry a port (192.168.1.10:8080) or a scheme. Nothing is read until a tool needs it, so the server always starts.

Client configuration

{
  "mcpServers": {
    "onvif": {
      "command": "npx",
      "args": ["-y", "easy-onvif-mcp"],
      "env": {
        "ONVIF_HOST": "192.168.1.10",
        "ONVIF_USERNAME": "admin",
        "ONVIF_PASSWORD": "secret"
      }
    }
  }
}

With the Dart VM binary, use "command": "dart", "args": ["run", "easy_onvif_mcp:easy_onvif_mcp_server"].

Tools

52 tools, all prefixed onvif_. Media and PTZ tools take a profile_token; get one from onvif_get_profiles. Run dart run example/serve_stdio.dart --list-tools to print the live list with each tool's required arguments.

Connection — onvif_connect, onvif_disconnect, onvif_is_connected, onvif_status, onvif_probe

Device management (read-only) — onvif_get_device_information, onvif_get_services, onvif_get_capabilities, onvif_get_system_date_and_time, onvif_get_hostname, onvif_get_dns, onvif_get_ntp, onvif_get_dynamic_dns, onvif_get_network_protocols, onvif_get_users, onvif_get_relay_outputs, onvif_get_storage_configurations, onvif_get_discovery_mode, onvif_get_endpoint_reference

Media — onvif_get_profiles, onvif_get_profile, onvif_get_stream_uri, onvif_get_snapshot_uri, onvif_get_video_sources, onvif_get_audio_sources, onvif_get_media_service_capabilities

PTZ — onvif_ptz_get_status, onvif_ptz_get_presets, onvif_ptz_get_configurations, onvif_ptz_goto_preset, onvif_ptz_set_preset, onvif_ptz_remove_preset, onvif_ptz_absolute_move, onvif_ptz_relative_move, onvif_ptz_continuous_move, onvif_ptz_stop, onvif_ptz_move, onvif_ptz_move_up, onvif_ptz_move_down, onvif_ptz_move_left, onvif_ptz_move_right, onvif_ptz_zoom, onvif_ptz_get_service_capabilities

Imaging (read-only) — onvif_imaging_get_status, onvif_imaging_get_presets, onvif_imaging_get_current_preset

Recordings / replay / search — onvif_get_recordings, onvif_get_recording_jobs, onvif_get_replay_uri, onvif_get_recording_summary, onvif_find_recordings, onvif_get_recording_search_results

What is deliberately not exposed

Device-mutating operations — system_reboot, user creation/deletion, hostname, DNS, NTP, network protocol and IP-filter writes — are not tools here. A stdio server holding live camera credentials should not hand a model a reboot button by default. Everything exposed is either a read or a camera movement.

Two reads the ONVIF spec defines have no counterpart in the easy_onvif API and so are absent: GetScopes, and imaging options/settings beyond ImagingStatus20 (onvif_imaging_get_status is the closest available). Add them once the core exposes those requests — one ToolDef entry each.

Credentials are never echoed back

dart_mcp's automatic argument validation quotes the rejected value in its failure message — Value `12345` is not of type `String` at path #root["password"] — and numeric default passwords are common on ONVIF cameras. The password is therefore never a schema-validated argument: the Dart VM build does not accept one at all (it comes from the environment or the credentials file), and the npm build registers onvif_connect with validateArguments: false and validates in-handler. Errors name the offending field only.

One device at a time

Tool calls are serialized. easy_onvif's SOAP layer keeps its clock offset and XML builder in static state (Transport.timeDelta, Transport.builder), so two in-flight requests would corrupt each other's WS-Security timestamps and XML fragments. Serializing keeps correctness at the cost of throughput, and means this server talks to one device per process — connect to a second camera by running a second server.

Diagnostics

stdout is the JSON-RPC channel, so every log line, including SOAP failures and stack traces, goes to stderr. Tool failures come back as a normal MCP result with isError: true and a one-line message.

Development

dart test
dart run build_runner build   # after touching any @Tool method

To poke the server by hand:

{ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' '{"jsonrpc":"2.0","method":"notifications/initialized"}' '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'; sleep 2; } \
  | dart run bin/easy_onvif_mcp_server.dart

The sleep matters: closing stdin ends the session, and a piped heredoc closes before the async reply is written.