mech.app

The mech.app newsletter

Agentic AI, minus the noise.

Get practical field notes on AI agents, automation, developer tools and security delivered to your inbox.

No spam. Unsubscribe anytime.

Dev Tools

Agent Plugin Manifests: Why Strict JSON Schema Validation Prevents Runtime Tool Failures

How Agent Plugins 1.0.0 uses JSON Schema with additionalProperties: false to enforce contract boundaries and what happens when clients ignore validation.

Source: dev.to
Agent Plugin Manifests: Why Strict JSON Schema Validation Prevents Runtime Tool Failures

Agent Plugins 1.0.0 ships with a JSON Schema for plugin.json manifests. The schema sets additionalProperties: false, which should make validation simple. You parse the file, run it through a validator, and reject on failure. Four lines of code.

Except the spec says that’s wrong. Section 5.2 requires clients to report and ignore unknown top-level fields, then continue loading if the rest of the manifest is valid. Section 8.1 repeats the same rule for malformed extensions fields. Every other schema violation is fatal.

A standard JSON Schema validator returns one boolean. The spec wants three different outcomes: tolerate unknown fields, reject malformed extensions, and fail hard on everything else. The natural implementation is non-conformant in exactly two cases and correct everywhere else. This is the kind of bug that doesn’t surface in your tests. It surfaces six months later when a plugin works in one client but not another.

The Conformance Gap in Practice

This has already happened. Multiple times.

Codex loaded any directory with a root plugin.json through its Agent Plugins loader, which had no hook support. Every hook declared in .codex-plugin/plugin.json silently stopped running. Two plugins were dead for a week before anyone noticed.

oh-my-pi routed packages declaring an agent-plugins.org $schema to a strict provider that dropped any SKILL.md with an extra frontmatter key. A plugin went from 33 skills to 3. The fix was to delete $schema from the manifest, so conforming to the standard cost them the standard.

dotnet/skills shipped manifests with no $schema and with skills, agents, and mcpServers as top-level fields. Kiro refused them. Adding $schema got past the rejection, then triggered a different failure path.

In each case, the client implemented what looked like correct validation. The spec’s nuance around unknown fields created a conformance boundary that standard tooling doesn’t enforce.

What additionalProperties: false Actually Does

additionalProperties: false tells the validator to reject any key not explicitly defined in the schema. For a plugin manifest, that means:

  • name, version, description, author, license are allowed.
  • skills, agents, mcpServers, extensions are allowed.
  • Anything else triggers a validation error.

The problem is that the spec wants you to warn and continue for unknown top-level fields. The schema says reject. The spec says tolerate. A naive validator picks the schema.

Here’s the broken loader:

const manifest = JSON.parse(await readFile(join(dir, 'plugin.json')));
if (!validate(manifest)) return reject('invalid manifest');

This rejects manifests with unknown fields. It’s non-conformant.

Here’s what conformance looks like:

const manifest = JSON.parse(await readFile(join(dir, 'plugin.json')));
const result = validate(manifest);

if (!result.valid) {
  const unknownFields = result.errors.filter(e => 
    e.keyword === 'additionalProperties' && e.instancePath === ''
  );
  const otherErrors = result.errors.filter(e => 
    !(e.keyword === 'additionalProperties' && e.instancePath === '')
  );

  if (otherErrors.length > 0) {
    return reject('invalid manifest', otherErrors);
  }

  if (unknownFields.length > 0) {
    console.warn('Unknown fields in manifest:', unknownFields);
  }
}

You have to split the error set. Unknown top-level fields get logged and ignored. Everything else is fatal.

Failure Modes When Validation Is Wrong

When a client rejects a valid manifest with unknown fields:

  • Plugin doesn’t load. The agent runtime never sees the tools.
  • No error context. The user sees “invalid manifest” with no indication that an unknown field caused the rejection.
  • Silent divergence. The plugin works in one client, fails in another, and the manifest looks identical.

When a client accepts an invalid manifest:

  • Runtime tool failures. The agent tries to call a tool that doesn’t exist or has malformed parameters.
  • State corruption. If the manifest declares hooks or lifecycle events incorrectly, the plugin may partially initialize.
  • Security boundary violations. If permissions or capabilities are malformed, the plugin may run with incorrect privileges.

The second failure mode is worse. A rejected manifest fails fast. An accepted invalid manifest fails at runtime, possibly after the agent has already made decisions based on the tool’s declared capabilities.

Schema Validation Architecture

Here’s how validation fits into the plugin loading flow:

StageResponsibilityFailure Mode
ParseRead plugin.json from diskFile not found, invalid JSON
ValidateRun JSON Schema validationSchema violation
FilterSeparate unknown fields from fatal errorsConformance bug
LoadInstantiate tools, hooks, lifecycle handlersRuntime error
RegisterAdd tools to agent runtimeDuplicate tool names, capability conflicts

The filter stage is where most clients fail. Standard validators don’t distinguish between “unknown field” and “malformed required field.” You have to inspect the error set manually.

When to Fail Fast vs. Degrade Gracefully

The spec’s decision to tolerate unknown fields is a forward-compatibility strategy. If Agent Plugins 2.0.0 adds a new top-level field, 1.0.0 clients should still load the manifest. The unknown field gets ignored, and the plugin runs with 1.0.0 semantics.

This only works if the new field is optional. If 2.0.0 adds a required field, 1.0.0 clients will silently ignore it and the plugin will misbehave.

The spec doesn’t address this. It assumes all future fields will be optional, which is a fragile assumption.

For extensions, the spec is stricter. If extensions is present but not an object, the client must reject the manifest. This is because extensions is a namespace for vendor-specific metadata. A malformed extensions field suggests the manifest was hand-edited incorrectly or generated by a broken tool.

Here’s the decision tree:

  • Unknown top-level field: Warn and continue.
  • Malformed extensions: Reject.
  • Missing required field: Reject.
  • Invalid field type: Reject.
  • Invalid enum value: Reject.

Observability for Validation Failures

When validation fails, you need to know:

  • Which field caused the failure.
  • What the validator expected.
  • What the manifest provided.
  • Whether the failure is fatal or a warning.

Standard JSON Schema validators provide this in the error object. The problem is surfacing it to the user. Most plugin loaders log “invalid manifest” and stop. That’s not enough.

A conformant loader should:

  • Log the full error set with field paths and expected types.
  • Distinguish between warnings (unknown fields) and errors (schema violations).
  • Provide a link to the schema documentation.
  • Show the exact line in plugin.json where the error occurred.

The last point requires mapping JSON Schema paths back to source locations, which most validators don’t do. You need a separate parser that tracks line and column numbers.

Testing Conformance

You can’t test conformance with a single valid manifest. You need a suite of edge cases:

  • Manifest with an unknown top-level field.
  • Manifest with a malformed extensions field.
  • Manifest with a missing required field.
  • Manifest with an invalid enum value.
  • Manifest with a valid $schema but invalid content.
  • Manifest with no $schema and valid content.

For each case, the test should verify:

  • Whether the loader accepts or rejects the manifest.
  • What error or warning it produces.
  • Whether the plugin loads and runs correctly.

The third point is critical. A loader that accepts an invalid manifest but then fails at runtime is worse than a loader that rejects it immediately.

Technical Verdict

Use strict JSON Schema validation with manual error filtering if you’re building an agent runtime that loads plugins from untrusted sources. The spec’s requirement to tolerate unknown fields is a forward-compatibility strategy, but it requires you to inspect the error set and separate warnings from fatal errors.

Avoid naive validation (parse, validate, reject on failure) because it’s non-conformant in exactly two cases: unknown top-level fields and malformed extensions. Those cases are rare enough that they won’t show up in basic testing, but common enough that they’ll cause production failures.

Fail fast on everything except unknown top-level fields. The spec’s decision to tolerate unknown fields is fragile. If a future version of the spec adds a required field, 1.0.0 clients will silently ignore it. The only way to avoid this is to version the schema and reject manifests that declare a newer $schema than the client supports.

Invest in observability. When validation fails, log the full error set with field paths, expected types, and source locations. “Invalid manifest” is not enough. The user needs to know which field is wrong and why.

Tags

agentic-ai orchestration infrastructure

Primary Source

dev.to