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

@uluops/registry-mcp

v0.18.2

Published

MCP server for the UluOps Registry API — definition management, versioning, and analytics

Readme

UluOps · The operations layer for agentic work


@uluops/registry-mcp

npm version License: MIT Node.js Version TypeScript Tests

MCP (Model Context Protocol) server for the UluOps Registry API. Provides 44 tools and 4 resources that let Claude Code, OpenCode, Gemini CLI, and other MCP-compatible harnesses browse, create, validate, version, and analyze AI workflow definitions (agents, commands, workflows, pipelines).

Requires Node.js 20.3 or newer. Create an API key at app.uluops.ai/settings/api-keys; API keys begin with ulr_.

Quick start

To check the package without changing your harness configuration, run:

npx -y @uluops/registry-mcp --version

To connect the server, add this block to your harness's MCP config (e.g. ~/.claude.json for Claude Code, ~/.config/opencode/opencode.json for OpenCode):

{
  "mcpServers": {
    "uluops-registry": {
      "command": "npx",
      "args": ["-y", "@uluops/registry-mcp"],
      "env": {
        "ULUOPS_API_KEY": "ulr_your-api-key"
      }
    }
  }
}

Replace the example value with your API key, then restart your harness. To have your harness configuration written for you, use @uluops/setup: npx @uluops/setup.

Configuration

All configuration is passed via environment variables in the env block. No .env file is needed.

| Variable | Description | Required | Default | |----------|-------------|----------|---------| | ULUOPS_API_KEY | API key (ulr_ prefix) from API key settings, or a session token | Yes | — | | ULUOPS_ORG_SLUG | Org for definition writes: sent as X-Org-Slug on create, update, publish, deprecate, archive, delete, retranslate, upgrade, and fork (the org the fork goes into), where it says which org's definition a name means. Reads and other calls (validate, render, record execution, user lookups) never carry it (since 0.11.0), so the marketplace stays readable; your org's private definitions still appear by membership. A bare name on a read means the org that first published it, so for a name another org published first, a read and a write of it mean different rows — check namespace (now in the compact list output) before writing back what you read. | No | — | | ULUOPS_REGISTRY_URL | Registry API base URL. Because the API key is sent to this URL on every request, only *.uluops.ai and loopback hosts are accepted unless you opt in below | No | production registry (loopback when NODE_ENV=development) | | ULUOPS_ALLOW_ANY_REGISTRY_HOST | Set to 1 to accept a ULUOPS_REGISTRY_URL host outside *.uluops.ai and loopback (a self-hosted or staging registry). The server warns at startup when it is used | No | — | | ULUOPS_ALLOW_PRIVATE_REGISTRY | Set to 1 to accept a private-network or cloud-metadata host. Required in addition to the previous variable for those addresses | No | — | | ULUOPS_REGISTRY_TIMEOUT | Request timeout (ms) | No | 30000 | | ULUOPS_REGISTRY_RETRIES | Retry attempts | No | 3 | | LOG_LEVEL | Logging level (error, warn, info, debug) | No | info | | ENABLE_FILE_LOGGING | Write logs to disk | No | false | | LOG_DIR | Log file directory | No | ./logs | | VERBOSE_LOGGING | Verbose security decision logging | No | false | | LOG_PERFORMANCE_METRICS | Log performance metrics | No | false | | WORKSPACE_DIR | Base directory for file_path containment (CWE-22 protection). A relative file_path resolves against the server's working directory, not WORKSPACE_DIR, and must still land inside it | No | cwd | | OUTPUT_BASE_DIR | Base directory for output_path containment (CWE-22 protection) | No | cwd |

Design philosophy

Thin client pattern. This MCP server contains zero business logic. All data processing, validation, storage, and rendering are handled by the registry API. The server's sole responsibility is protocol translation between MCP's stdio JSON-RPC and the backend REST API.

Quick examples

Once configured, your harness can invoke these tools through MCP:

// Browse published definitions
list_definitions({ "type": "agent", "status": "published", "limit": 10 })

// Get a specific definition with its rendered YAML
get_definition({ type: "agent", name: "code-validator", include_yaml: true })

// Search across all definition types
search_definitions({ query: "validation", type: "agent" })
search_definitions({ query: "code-validator", match: "exact" })
search_definitions({ query: "code-", match: "prefix", page: 2, limit: 10, sort: "name", order: "asc" })

// Validate YAML before publishing (inline or by file path)
validate_definition({ type: "agent", yaml: "..." })
validate_definition({ type: "agent", file_path: "/path/to/agent.yaml" })

// Create and publish a definition
create_definition({ type: "agent", name: "my-agent", yaml: "..." })
publish_definition({ type: "agent", name: "my-agent", version: "1.0.0" })

// One-call create-or-update-and-publish
update_and_publish({ type: "agent", name: "my-agent", version: "1.1.0", yaml: "..." })

// Compare versions in three different formats
diff_versions({ type: "agent", name: "code-validator", from: "1.0.0", to: "1.1.0" })                  // section summary
diff_versions({ type: "agent", name: "code-validator", from: "1.0.0", to: "1.1.0", format: "fields" })  // structural diff + suggested bump
diff_versions({ type: "agent", name: "code-validator", from: "1.0.0", to: "1.1.0", format: "unified", diff_contract: "combined-v1", full: true }) // patch + exact YAML

// Analytics
get_effectiveness({ type: "agent", name: "code-validator", version: "1.1.0" })
compare_effectiveness({ type: "agent", name: "code-validator", versions: ["1.0.0", "1.1.0"] })

Available tools

Core tools (P0)

| Tool | Description | |------|-------------| | list_definitions | List definitions with filters (type, status, domain, visibility, search, name, match, tags, pagination). format: compact (default — type, name, namespace, version, status, visibility, description and optional relevance per item), full (all catalog fields) | | get_definition | Get a single definition by type + name, optionally with YAML / runtime / refs | | search_definitions | Keyword search by default; match: exact, prefix, or text. Supports page, limit (default 20, max 100), sort and order | | list_models | List AI models with optional filters | | resolve_alias | Resolve an alias (e.g. sonnet) to provider + modelId | | validate_definition | Validate YAML without storing (accepts yaml or file_path) | | render_definition | Get rendered markdown for a definition. output_path writes directly to a file |

render_definition uses the uluops-full render profile when render_profile is omitted: the full prompt the registry stores. render_profile: "core" is declared-only (registry on definition-factory ≥ 0.81.0): only what the definition declares, with no factory-supplied output contract, scoring instruction or tone. Before that factory, core meant the full prompt minus the UluOps blocks. Inline and file-write results report the effective profile.

Since 0.16.0 (registry-sdk 0.62.0) results also carry the renderer fields:

  • factoryVersion and renderStack ({ factory, nunjucks, yaml });
  • for agents, resolvedProfile, scaffolding and declaredOutputShape.

Pin a core hash on the definition version plus renderStack. scaffolding: false means the render is declared-only.

output_path writes on the MCP server host, within OUTPUT_BASE_DIR (default: server process cwd). Relative paths resolve from that cwd, even when a different root is configured. The root must exist; missing directories below it are created. Paths outside the root and traversal escaping it are refused. Existing symlinks in the file or directories below the root are checked and refused before rendering. Existing files require overwrite: true. Remote clients should omit output_path and consume markdown inline.

File responses retain the inline rendering metadata (target, renderProfile, selectedModel, promptHash, warnings, and any SDK metadata when supplied) alongside success, output_path, and UTF-8 bytes; markdown is written to disk. Warnings and optional metadata have the same presence and values as inline output.

// Inline output for remote clients
render_definition({ type: "agent", name: "code-validator", target: "codex" })
// Write beneath the server cwd (or use an absolute path inside OUTPUT_BASE_DIR)
render_definition({ type: "agent", name: "code-validator", target: "codex", output_path: "output/code-validator.toml" })
// Explicit replacement retains rendering diagnostics
render_definition({ type: "agent", name: "code-validator", target: "codex", output_path: "output/code-validator.toml", overwrite: true })

Definition management (P1)

| Tool | Description | |------|-------------| | create_definition | Create a new draft definition (accepts yaml or file_path) | | update_definition | Update a draft definition. Smart version-up: if target version is published or doesn't exist and YAML is provided, automatically creates a new draft | | publish_definition | Publish a draft definition | | deprecate_definition | Deprecate with reason and optional successor | | archive_definition | Archive a deprecated definition (terminal lifecycle state) | | delete_definition | Delete a definition version, including published, deprecated, or archived versions when no dependents or forks block deletion. A blocked delete returns DELETE_BLOCKED with a recovery action; hidden references are not disclosed |

Composite workflows (P1)

| Tool | Description | |------|-------------| | update_and_publish | Update a draft and publish it in one call. Inherits smart version-up + create fallback from update_definition | | batch_publish | Publish up to 20 definition versions in one call. Continues on individual failures, returns both published and failed items |

Versions & dependencies (P1)

| Tool | Description | |------|-------------| | list_versions | List all versions of a definition | | diff_versions | Compare two versions. format: sections (default), fields (structural + suggested bump), unified. Select diff_contract="combined-v1" for an applicable patch; full=true adds exact YAML. | | get_dependencies | Forward dependency graph | | get_dependents | Reverse dependency graph | | get_execution_stats | Execution statistics for a definition version | | list_forks | List forks of a definition |

Forks (P2)

| Tool | Description | |------|-------------| | fork_definition | Fork a definition | | is_forkable | Check if a definition version can be forked | | get_fork_lineage | Fork ancestry chain |

Translation (P2)

| Tool | Description | |------|-------------| | retranslate_definition | Retranslate with the latest translator version | | upgrade_definition | Upgrade a definition from legacy format (accepts yaml or file_path) | | get_translator_version | Get current translator version |

upgrade_definition applies to legacy definitions without storage/translation metadata and creates a new major version. Current-format drafts and already translated definitions return a structured refusal with applicationState: not_applied, a reason, and recovery guidance. Use publish or retranslate when indicated.

If an upgrade response fails validation, the result carries applicationState: unknown: the write may already have committed. Read the definition and list its versions before retrying. Successful responses preserve the previous version and translated artifact metadata. These upgrade semantics require Registry API 0.59.4 or newer.

Lifecycle refusals now carry allowedTransitions; blocked deletion carries blockingResources: { present: true } and a specific recovery action without exposing hidden references. A bad render target lists available targets once. For any write whose SDK response validation fails, applicationState: unknown means read current state before retrying.

Models & languages (P2)

| Tool | Description | |------|-------------| | get_model | Get specific model details by provider + modelId | | list_providers | List AI providers | | list_aliases | List all model aliases | | list_languages | List supported definition languages (ADL, CDL, WDL, PDL) | | get_language | Get a definition language with its JSON Schema. format: compact (default — condensed digest sufficient to author a definition, ~50–75% smaller), full (complete JSON Schema with patterns, bounds, and examples) |

Execution & users (P2)

| Tool | Description | |------|-------------| | record_execution | Record a definition execution (idempotent). Admin-only — executions are recorded automatically by the runtime; user keys get a 403 explaining this | | get_user | Get public user profile | | batch_users | Batch user lookup (max 100); optional format: 'envelope' returns {data,foundIds,missingIds} |

Analytics (P3)

| Tool | Description | |------|-------------| | get_effectiveness | Effectiveness metrics: voter-weighted quality (one actor, one vote) with the provenance block — independent vs selfReported split, confidence label. Agents are score-only (passRate: null). Pools every version run in the window, whatever version you pass; the description and each response carry a pooled-versions caveat | | get_health | Health grade and issue profile for a definition; the pass-rate factor requires 3+ qualifying actors. Derived from get_effectiveness, so it pools every version too | | get_ecosystem_overview | Ecosystem-wide analytics overview | | get_lineage | Lineage graph for a definition (versions + forks as a tree) | | get_evolution | Version-over-version metrics with trend detection | | get_translation_analytics | Versions grouped by translator version with aggregate metrics | | compare_effectiveness | Compare effectiveness across 2–5 definition versions side-by-side | | get_diff_impact | Structural diff combined with metric deltas between two versions |

Session

| Tool | Description | |------|-------------| | set_default_type | Set (or clear) a session-level default for the type parameter. When set, definition tools use this type unless explicitly overridden |

Available resources

MCP resources provide read-only access to registry data via the registry:// URI scheme.

| Resource | URI | Description | |----------|-----|-------------| | Definitions | registry://definitions | Published definitions (up to 100) | | Models | registry://models | AI model catalog | | Definition types | registry://definition-types | Static list: agent, command, workflow, pipeline | | Providers | registry://providers | AI provider list |

read_resource("registry://definitions")
read_resource("registry://models")
read_resource("registry://definition-types")

Rate limiting

This server uses mcp-secure-server with configuration tuned for typical harness usage patterns. Source of truth: src/config/server-options.ts (global), src/config/limits.ts (the shared envelope), src/config/tool-registry.ts (per-tool caps and quotas), and tool-policies.json (per-tool security level and relaxed fields — loaded explicitly since 0.8.2; yaml is not pattern-scanned at this layer, the registry API scans definitions at publish).

| Setting | Value | Notes | |---------|-------|-------| | Security level | basic | | | Max requests/min | 120 | Global rate limit | | Max message size | 1 MiB + 64 KiB (1,114,112 bytes) | Layer 1 envelope | | Max string length | 1,114,112 characters | Per-string cap. It counts characters, not bytes, and can never bind before the message size | | Max param bytes | 1,114,112 bytes | Layer 2 serialized-params cap (requires mcp-secure-server >= 0.0.19-security) | | Suspicious message size | 1,114,112 bytes | Layer 3 hard block, not a log-only flag | | Burst threshold | 15 | Requests within burst window | | Burst window | 5000 ms | | | Automation detection | Disabled | The calling harness is trusted automation |

The four size settings share one value, ENVELOPE_BYTES. Because a string's characters can never outnumber the message's bytes, and the params are part of the message, the message size is the one that binds; the others never fire first. The per-tool maxArgsSize in tool-registry.ts is the tool-scoped gate beneath them (never above; exactly at it for the YAML tools). Each tool's maxEgressBytes is kept ≥ 16 × its maxArgsSize, because mcp-secure-server checks egress at request time as argument bytes × 16.

Why this envelope: it is the registry API's own body limit on definition routes (1 MiB, express.json({ limit: '1mb' })) plus 64 KiB for the JSON-RPC wrapper, so any body the API accepts fits. Until 0.18.0 it was 500 KiB, which a draft create with a CJK YAML at the 153,600-character limit and maximal provenance (50 contributors with CJK names, about 540 KB) did not fit.

Tool schemas mirror the API's field caps (src/config/api-limits.ts), so an over-limit request is refused locally, by field name, instead of after a round trip:

  • yaml at most 153,600 characters on create, update, validate, upgrade and update-and-publish, including YAML read from file_path;
  • on update_and_publish only, yaml also at most 153,600 UTF-8 bytes. The registry API limits YAML in two units: every route counts characters, while validation and publishing also count UTF-8 bytes, so non-ASCII YAML reaches the byte limit first (a CJK YAML over 51,200 characters can be saved as a draft but not published). update_and_publish checks bytes before it writes anything, so it cannot leave a draft whose publish then fails. validate_definition passes the API's valid: false verdict (PAYLOAD_TOO_LARGE) through instead;
  • description at most 1000 chars;
  • tags at most 32, each at most 50 chars;
  • deprecation reason at most 500 chars, successor as name or [email protected];
  • fork new_name at most 200 chars, lowercase letters, digits and hyphens;
  • execution source at most 32 chars, run_id at most 64.

Here "character" means a UTF-16 code unit (JavaScript string length), as the API counts it.

Per-tool quotas are configured in src/config/tool-registry.ts. Read-heavy tools (list, get, search) allow up to 240 req/min. Write tools (create, update, publish) are 30–60 req/min.

Development

git clone [email protected]:Uluops/-uluops-registry-mcp.git
cd -uluops-registry-mcp
npm install
npm run build
npm test
npm run typecheck
npm run lint

License

MIT

Quality metric contracts (F04)

compare_effectiveness and get_diff_impact accept optional quality_contract: "nullable-v1". Select it to preserve absent gate rates/deltas as null with run-weighted basis, gate denominator, and fraction units. Agent gate rates are null. Omission keeps legacy behavior. Requires the F04-capable Registry SDK and producer capability; unsupported selection returns UNSUPPORTED_CONTRACT instead of legacy numbers. Release the tolerant SDK and producer before enabling this option.

Applicable unified diffs

Call diff_versions with format: "unified" and diff_contract: "combined-v1". Add full: true to receive exact sourceYaml/targetYaml alongside unified. An unchanged pair returns an empty patch. The SDK negotiates support and refuses unsupported servers without falling back. Legacy omission keeps full=true precedence (raw fromYaml/toYaml, no patch) until a future major release with at least 90 days notice.

Definition search semantics

list_definitions({ name: 'code-validator' }) defaults to exact identifier matching; match: 'prefix' selects a literal prefix. Identifiers are trimmed and lowercased, with 1–100 printable ASCII characters before trimming; punctuation, quotes, %, _, and backslashes are preserved. name cannot combine with search or match: 'text'. For search_definitions, exact/prefix modes use the required query as the name. Omitting match retains existing keyword behavior; explicit match: 'text' requires a nonblank search/query of 1–100 printable ASCII characters before trimming. New name or explicit match requests negotiate the server's name-v1 capability and refuse unsupported servers without fallback.

Keyword terms of at least three characters after sanitization use FULLTEXT over name, display_name, and description. Short original searches fall back to name/display_name; longer searches whose sanitized terms are shorter than three characters also fall back across description. Tags filter independently by OR-any and are not keyword searched. Actual FULLTEXT rows may include numeric relevance, retained in compact list output. Search responses retain the full list rows. A list request with page and no limit explicitly uses limit 50, matching the API's default. list_definitions and search_definitions cap limit at 100; list_versions and list_providers at 200.

batch_users defaults to the existing map (format: 'map' is also accepted). With format: 'envelope', UUID inputs are normalized to lowercase and deduplicated in request order; both ID arrays use that order and canonical spelling. Unknown, deleted, and null profiles appear in missingIds. The 100-input limit applies before deduplication. The SDK requires consistent producer found/notFound metadata and returns a response-validation error when it is absent or invalid. In envelope format, fields selects public profile fields inside data while retaining user keys and both foundIds and missingIds. For example, fields: ['id'] trims profiles to their IDs; fields: ['data'] keeps complete profiles. Public profile fields remain selectable for all-missing batches, and unknown fields are rejected. Selecting only metadata leaves empty profile objects under their user keys. Default map field selection retains its existing behavior.