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

@kuralle-agents/plugins

v0.24.0

Published

Agent Plugins loader and conformance for Kuralle agents

Readme

@kuralle-agents/plugins

Load Agent Plugins v1.0.0 directories into a Kuralle agent — plugin.json, skills/, mcp.json, and (as a host extension) flows/.

Install

npm install @kuralle-agents/plugins

This installs @kuralle-agents/core and @kuralle-agents/fs with it. Every @kuralle-agents/* package versions in lockstep, so install them at the same version.

This package does not depend on @kuralle-agents/mcp. It parses mcp.json into validated config data. Connecting that data to a live client is your explicit step, so a skills-only consumer never pulls an MCP client.

What it does

A plugin is a directory with a fixed layout. A plugin authored for another agent client loads here unmodified.

Key exports:

  • loadAgentPlugin(fs, root, options?) — reads a plugin directory and returns a discriminated result. It never throws. Pass hostTools to scope action-node tool references.
  • expandPluginPlaceholders — expands ${PLUGIN_ROOT} and ${PLUGIN_DATA} per spec §9.2.
  • TypesLoadPluginResult, LoadedPlugin, LoadAgentPluginOptions, PluginManifest, McpServerConfig, Diagnostic, Rejection.

Layout

my-plugin/
  plugin.json                     manifest (required)
  skills/
    invoice-policy/
      SKILL.md                    name must match the directory
      references/rates.md         bundled resource
  flows/
    refund.flow.json              optional — Kuralle host extension
  mcp.json                        optional MCP servers

Agent Plugins 1.0.0 does not define flows/; other hosts ignore the directory. Kuralle returns validated FlowDefinitions on plugin.flows for the host to register via runtime.addDynamicFlows.

Usage

import { defineAgent } from '@kuralle-agents/core';
import { NodeFileSystem } from '@kuralle-agents/fs/node/fs';
import { loadAgentPlugin } from '@kuralle-agents/plugins';

const fs = new NodeFileSystem('/srv/agents');
const loaded = await loadAgentPlugin(fs, '/.agents/plugins/acme');

if (!loaded.ok) {
  throw new Error(loaded.rejection.message);
}

for (const d of loaded.plugin.diagnostics) {
  console.warn(`${d.section} ${d.origin}: ${d.message}`);
}

const agent = defineAgent({
  id: 'billing',
  model,
  instructions: 'Answer billing questions.',
  skills: loaded.plugin.skills,
});

loaded.plugin.mcpServers holds parsed server configs. Pass them to @kuralle-agents/mcp to connect them. loaded.plugin.flows holds validated flow definitions; the host registers them with runtime.addDynamicFlows.

Failure is graded, not binary

A throw cannot express how much of a plugin survived. A discriminated return can. The loader applies five different blast radii:

| What failed | Outcome | Skills | MCP servers | Flows | | --- | --- | --- | --- | --- | | plugin.json | Reject the whole plugin (ok: false) | not loaded | not loaded | not loaded | | mcp.json | Disable MCP for this plugin only | loaded | [] + diagnostic | unchanged | | One skill folder | Skip that skill | the rest load | unchanged | unchanged | | One server entry | Skip that server | unchanged | siblings load | unchanged | | One flow file | Skip that flow | unchanged | unchanged | siblings load |

A missing skills/, mcp.json, or flows/ is not an error. Spec §6.2 makes an absent skills/ or mcp.json legal; flows/ is a host extension with the same missing-component rule.

LoadPluginResult

type LoadPluginResult =
  | { ok: true;  plugin: LoadedPlugin }
  | { ok: false; rejection: Rejection; diagnostics: readonly Diagnostic[] };

interface LoadedPlugin {
  manifest: PluginManifest;
  skills: SkillStoreLike;                  // ready for AgentConfig.skills
  mcpServers: readonly McpServerConfig[];  // parsed, NOT connected
  flows: readonly FlowDefinition[];        // validated, NOT registered
  diagnostics: readonly Diagnostic[];
}

Every Diagnostic carries { section, rule, origin, message }. The section names the spec clause, so you can log a failure without guessing which layer produced it.

Credentials in plugin files

The spec forbids plugin authors from putting secrets in env or headers. It does not instruct a client to reject such a config, and it gives no portable place to reference a credential instead.

So a conformant client loads a secret-bearing config, reports a diagnostic, and continues. This package does that. Rejecting would refuse a config the spec does not authorise us to refuse, and the most common real-world MCP config is a bare npx <server> with an API key in env.

Supply real credentials in code, through the auth resolver on @kuralle-agents/mcp.

PLUGIN_ROOT and PLUGIN_DATA

${PLUGIN_ROOT} and ${PLUGIN_DATA} expand inside a stdio entry's args, env, and cwd, and reach the subprocess as environment variables. Expansion is single-pass: text introduced by one substitution is never rescanned.

PLUGIN_DATA is a sibling of the plugin directory, keyed by plugin name — a plugin at /plugins/acme gets /plugins/data/acme. Keeping it outside the plugin root means writing state never mutates the distributed bundle, so a plugin stays byte-identical to what was published. @kuralle-agents/mcp/node creates it and proves it writable before the subprocess starts.

A command is either a bare token resolved through the platform search path, or a plugin-relative ./… path resolved against the plugin root. cwd defaults to the plugin root when omitted, and cwdRoot records which root it was declared against.

Containment

§4.1 keeps a plugin's declared paths inside the plugin, and defines that against the filesystem-resolved path. A plugin can ship bin/server as a symlink to /usr/bin/curl — the string ./bin/server looks contained.

Two checks run. Parsing catches ../ escapes early. @kuralle-agents/mcp/node re-checks command and cwd through realpath immediately before spawning, which is the first moment ${PLUGIN_DATA} exists — resolving it at parse time would reject the specification's own cwd example.

Either failure invalidates that one server entry (section: "4.1", rule: "path-escapes-plugin-root") and leaves the plugin's skills and other servers loading. A symlink that stays inside the plugin root is permitted; this is containment, not a ban on symlinks.

It is not a sandbox — §4.1 says so. It constrains what a plugin may declare, not what the process it launches may do. Use Policy for that.

Platform limits

stdio servers parse here on every runtime. They only run on Node and Bun, through @kuralle-agents/mcp/node. Cloudflare Workers have no subprocess, so a stdio server there fails with a named error rather than a module-resolution crash.

Related