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

@stone-js/mcp

v0.8.19

Published

MCP for Stone.js: your existing routes become tools an AI agent can call, through the chain that already protects them.

Readme

Stone.js - MCP

npm license npm version npm downloads Maintenance CI Release Quality Gate Status Coverage Security Policy CodeQL Dependabot Status Conventional Commits

MCP for Stone.js. Your routes become tools an AI agent can call, through the chain that already protects them.

A tool is a route that said so. Nothing is registered twice, nothing is described twice, and a tool call is a real request: the rate limit, the authentication, the authorization and the validation that guard the route are the ones that guard the tool.


Installation

npm install @stone-js/mcp

# optional, to derive tool arguments from your validation schemas:
npm install @stone-js/openapi

Peer dependencies: @stone-js/core and @stone-js/http-core. @stone-js/openapi is optional.

Enable it

Declarative:

import { Mcp } from '@stone-js/mcp'
import { StoneApp } from '@stone-js/core'

@Mcp()
@Routing()
@StoneApp({ name: 'app' })
export class Application {}

Imperative, through stone.mcp:

import { defineConfig, defineStoneApp } from '@stone-js/core'
import { mcpBlueprint } from '@stone-js/mcp'

export const App = defineStoneApp({ name: 'app' }, [mcpBlueprint])

export const AppConfig = defineConfig((blueprint) => blueprint.set('stone.mcp', {
  instructions: 'Tools for managing notes. Read before you write.',
  route: { auth: true }
}))

Enabling the module adds one route, /mcp, and changes nothing else. No route becomes a tool until it says so.

Declare a tool

The short form names it:

@Post('/notes', { mcp: 'create-note' })
create (event: IncomingHttpEvent) { … }

The long form states what the short form would have to derive:

@Post('/notes', {
  mcp: {
    name: 'create-note',
    description: 'Create a note for the signed-in user.',
    annotations: { destructiveHint: false }
  }
})

On the handler instead, the same shape as @Validate and @Returns:

class NotesController {
  @Tool({ name: 'create-note', description: 'Create a note for the signed-in user.' })
  @Post('/notes')
  create (event: IncomingHttpEvent) { … }
}

Both are read. The route wins when both are present, because with a router in play a route is the single description of itself.

What is derived, and from where

| Field | Stated on the declaration | Otherwise derived from | |---|---|---| | name | mcp: 'create-note' or mcp.name | the route's name | | description | mcp.description | the route's openapi summary or description | | inputSchema | mcp.inputSchema | the route's validation schema, converted; failing that, its path parameters | | outputSchema | mcp.outputSchema | nothing. A shape nobody promised is not sent |

Every source is optional. An application with openapi and validation writes almost nothing. An application with neither still gets working tools, built from what a route always has: a path, a method, and its parameters. That degradation is the design, not a fallback.

A tool with no description is exposed and logged: an agent reading a bare name will guess, and it guesses worst on the routes that write. Set stone.mcp.requireDescription to leave those out instead.

What happens on a call

tools/call  ->  a real request to the route  ->  rate limit -> auth -> authz -> validation -> handler

The caller's headers travel with it, so the bearer an agent was given is the bearer the route authenticates. An agent acts for someone, and that someone is the principal. Nothing here carries a permission model of its own: a second set of rules would be a second thing to keep in step with the first.

A tool that fails answers a result with isError, not a protocol error. An agent reads it, explains it and tries something else, which is what should happen when an authorization refuses.

Protecting the endpoint

The endpoint is a route, so it is guarded like one:

blueprint.set('stone.mcp.route', {
  auth: true,
  rateLimit: { max: 60, window: 60, by: 'user' }
})

Configuration

| Key | Meaning | |---|---| | path | Where the endpoint is served. Defaults to /mcp. | | name / version | What the server calls itself during initialize. | | instructions | Handed to the agent once, alongside the tool list. | | requireDescription | Leave out a tool with no description. Defaults to false. | | route | Anything to put on the endpoint's route: auth, authz, rateLimit, middleware. | | filter | The last word on which tools are exposed, for what a declaration cannot know. |

Why there is no stream

An MCP server over HTTP is one POST endpoint: the client posts JSON-RPC, the server answers JSON, the connection closes. A stream is only needed for what a server sends unprompted, progress on a long tool or a server-initiated sampling request, and an API exposing its own routes sends none of them.

That is why this runs unchanged on a long-lived Node server, on a Lambda, or at the edge. There is nothing to keep open, and no session to hold.

Documentation

See the official documentation for the full guide.

License

MIT