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

@ata-project/mcp

v0.1.2

Published

JSON Schema validation provider for the Model Context Protocol TypeScript SDK, for runtimes that do not allow code generation

Readme

@ata-project/mcp

A JSON Schema validation provider for the Model Context Protocol TypeScript SDK, for runtimes that do not allow code generation.

Why this exists

The MCP SDK validates tool call arguments against each tool's inputSchema, and exposes jsonSchemaValidator as the extension point for how that is done. It ships two providers: one on ajv, and one on @cfworker/json-schema. The second one is there because ajv compiles a schema with new Function, and Cloudflare Workers, Vercel Edge and anything under a strict CSP do not allow that.

This is a third provider for the same slot. It needs no code generation either, and it is faster in that environment.

Use

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { AtaJsonSchemaValidator } from '@ata-project/mcp'

const server = new McpServer(
  { name: 'my-server', version: '1.0.0' },
  { validator: new AtaJsonSchemaValidator() }
)

Nothing here imports the SDK. The interface is structural, so the only dependency is the validator itself.

On an edge runtime, compile first

The provider above compiles each tool's schema on first use. That is the right trade on a server and the wrong one on a Worker, where an isolate cold starts often and the per-call microseconds are invisible next to a model round trip.

Five tool schemas, minified and gzipped, and cold start measured as module evaluation plus the first validation in a fresh process:

| | bundle | cold start | |---|---|---| | compiled ahead of time | 6.6 kB | 0.74 ms | | cfworker provider | 6.4 kB | 1.36 ms | | this provider at runtime | 85 kB | 12.07 ms |

So compile the tools at build time and serve them:

// build step
import { compileTools } from '@ata-project/mcp/build'

await compileTools({
  tools: { search: searchSchema, send_email: sendEmailSchema },
  outDir: './compiled',
})
// worker
import { AtaAotJsonSchemaValidator } from '@ata-project/mcp'
import tools from './compiled/index.mjs'

const server = new McpServer(info, { validator: new AtaAotJsonSchemaValidator(tools) })

The lookup is by a hash of the schema rather than by name or by object identity. A name has to be kept in step by hand and identity does not survive a bundler, while the hash comes from the schema itself: if the server declares a schema that was never compiled, getValidator says so instead of validating against the wrong one. Pass onMissing: 'compile' to fall back to compiling at run time, though that needs code generation and will not work in the runtime this exists for.

A constructor you registered yourself in withKeywords.CONSTRUCTORS cannot be compiled, because a standalone module imports nothing and there is nowhere to put your class. Those schemas are refused rather than silently compiled without the check.

Verified inside the SDK

e2e-sdk.mjs runs a real Server and Client from @modelcontextprotocol/sdk (1.30.0) over an in-memory transport, with a tool declaring an outputSchema. With code generation allowed, the SDK's default provider, the cfworker provider and this one accept the same valid result and reject the same invalid one with the same MCP error. With code generation blocked (node --disallow-code-generation-from-strings, the restriction Workers and strict-CSP pages enforce), the default provider fails the tool call even when the result is valid; cfworker and ata keep answering.

Measured

Node 25, ata-validator 1.36.0, an eight-field tool schema with a nested array of objects, 20000 calls per round, medians of 7 interleaved rounds.

With code generation blocked, which is the case this provider is for:

| provider | valid | invalid | |---|---|---| | ata | 181 ns | 509 ns | | cfworker | 2906 ns | 869 ns | | ajv | throws EvalError | throws EvalError |

With code generation allowed:

| provider | valid | invalid | |---|---|---| | ata | 19 ns | 192 ns | | ajv | 54 ns | 34 ns | | cfworker | 2845 ns | 860 ns |

ajv is the one to beat on the failing path when it can compile, and it is not beaten there. Rejecting is where it stops early and reports almost nothing; the numbers above include building the message string, which is the work a provider actually does.

Error messages

A tool call that fails validation usually goes back to the model to be retried, so the message is not a log line, it is part of the next prompt.

The default wording matches the providers the SDK already ships, so swapping does not change the strings anything downstream is reading:

/limit: must be <= 100

detailed says what was expected and what arrived, which is what a model needs in order to fix the call rather than guess at it:

new AtaJsonSchemaValidator({ detailed: true })
/mode: expected one of ["read", "write", "append"], found "delete"
/limit: expected ≤100, found 250

I have not measured that this reduces retries. It is a mechanism, not a result.

Options

| option | default | meaning | |---|---|---| | allErrors | false | report every failure instead of stopping at the first | | detailed | false | messages name the expectation and the value that arrived | | formats | none | custom format checkers, passed through to ata |

Drop-in

npm test compares this provider's verdicts against both of the SDK's own providers over 280 tool calls each, and checks that the verdicts are identical with code generation switched off. A provider that quietly accepted a call the others rejected would be worse than no provider at all.