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

mcp-thing

v0.2.1

Published

Reflection-based CLI to expose TypeScript classes as MCP tools over stdio.

Readme

mcp-thing

Expose TypeScript classes as MCP (Model Context Protocol) tools over stdio.

mcp-thing is the fast, easy way to create a fully working MCP service: write TypeScript classes, export them, and point the CLI at one service file or a directory of services. Public methods become MCP tools, JSDoc becomes tool documentation, and TypeScript types become MCP JSON schemas.

Requirements

  • Node.js 18+
  • A TypeScript service file or services directory
  • npm install in the service source directory if your services import dependencies

Quick Start With npx

Create a service:

// services/EchoService.ts
export class EchoService {
  /**
   * Returns the provided text.
   * @param text Text to return.
   */
  public echo(text: string): string {
    return text;
  }
}

Run it:

npx mcp-thing --run ./services/EchoService.ts

The exported tool is named EchoService_echo.

You can also use the short binary name:

npx -p mcp-thing mcpt --run ./services

Install

Install globally if you want the mcpt command available everywhere:

npm install -g mcp-thing

Run a service file or services directory:

mcpt --run ./services/EchoService.ts
mcpt --run ./services

You can omit --run and pass the source directly:

mcpt ./services/EchoService.ts
mcpt ./services

Compile

Compile a TypeScript service file or services directory into a standalone JavaScript MCP server. For best startup performance in MCP clients, compile your service and run the generated JavaScript entrypoint instead of parsing TypeScript on every launch:

mcpt --compile ./services/EchoService.ts
mcpt --compile ./services

By default this writes to dist inside the source directory, or the parent directory when the source is a file. You can provide a custom output directory:

mcpt --compile ./services/EchoService.ts ./build/mcp
mcpt --compile ./services ./build/mcp

Run the compiled server:

cd ./services/dist
npm install
node index.js

Compiled output includes:

  • index.js — generated MCP server entrypoint
  • schema-manifest.json — discovered services, tools, schemas, and result modes
  • emitted service .js files
  • package.json with the runtime dependency on mcp-thing

CLI Options

  • --server-name <name> — override MCP serverInfo.name
  • --server-version <version> — override MCP serverInfo.version
  • --result-mode <structured|content> — choose global successful result format
  • --verbose — enable detailed stderr logs
  • --silent — suppress non-error stderr logs, including startup notice

The default result mode is structured.

MCP Client Config

Most MCP clients launch stdio servers with a command and args.

With npx:

{
  "mcpServers": {
    "my-tools": {
      "command": "npx",
      "args": ["mcp-thing", "--run", "/absolute/path/to/services"]
    }
  }
}

With a global install:

{
  "mcpServers": {
    "my-tools": {
      "command": "mcpt",
      "args": ["--run", "/absolute/path/to/services"]
    }
  }
}

With compiled output:

{
  "mcpServers": {
    "my-tools": {
      "command": "node",
      "args": ["/absolute/path/to/services/dist/index.js"]
    }
  }
}

Use --run while iterating locally. Use compiled output when you want a faster, plain JavaScript startup path for MCP clients.

Tool Discovery

Tools are discovered from exported TypeScript classes:

  • Exported classes are services.
  • Public methods are tools.
  • Private methods and constructors are ignored.
  • Classes with no exposed methods are skipped.
  • Use @mcpIgnore to skip an exported helper class or a public method.
/**
 * @mcpIgnore
 */
export class HelperError extends Error {}

export class PackageService {
  /**
   * @mcpIgnore
   */
  public internalDebug(): string {
    return 'debug';
  }
}

Tool metadata comes from names and JSDoc:

  • Default tool name: <ClassName>_<methodName>
  • @mcpToolName <name> overrides the tool name
  • Method JSDoc description becomes the MCP tool description
  • @param descriptions become parameter descriptions
export class GreetingService {
  /**
   * Greets a user.
   * @mcpToolName hello
   * @param name User name.
   */
  public greet(name: string): string {
    return `Hello, ${name}!`;
  }
}

Input Schemas

mcp-thing infers MCP input schemas from TypeScript method parameters.

Supported inference includes:

  • string, number, boolean
  • optional parameters like count?: number
  • optional/nullish unions like number | undefined and string | null
  • arrays like string[] and Array<number>
  • literal unions as enums, like 'name' | 'description'
  • named type aliases for simple supported types
  • TypeScript enums and const enums with string or number values
  • plain object, type alias, and interface shapes with simple properties
  • recursive array item schemas, including enum arrays

Unsupported complex types degrade to a simpler schema and produce a warning instead of silently pretending the schema is precise.

export class SearchService {
  /**
   * Searches packages.
   * @param keyword Search term.
   * @param by Search field.
   * @param count Maximum number of results.
   */
  public search(
    keyword: string,
    by: 'name' | 'description' = 'name',
    count?: number,
  ) {
    return { packages: [], total: 0 };
  }
}

This exposes by as a string enum and count as an optional number.

Named simple types work too:

type SearchBy = 'name' | 'description' | 'maintainer';

enum SortBy {
  Name = 'name',
  Votes = 'votes',
}

interface SearchOptions {
  keyword: string;
  by: SearchBy;
  sort: SortBy;
  count?: number;
}

export class SearchService {
  public search(options: SearchOptions) {
    return { packages: [], total: 0 };
  }
}

This exposes options as an object schema, by and sort as enums, and count as an optional number.

Intentionally unsupported complex TypeScript features include generics, mapped types, conditional types, recursive types, inheritance-heavy classes, and utility types like Partial<T>, Pick<T>, and Record<K, V>.

Optional Schema Constraints

You can enrich inferred input schemas with optional JSDoc tags. These tags are not required; use them only when clients should discover validation details.

Supported parameter constraint tags:

  • @minLength <param> <number>
  • @maxLength <param> <number>
  • @minimum <param> <number>
  • @maximum <param> <number>
  • @exclusiveMinimum <param> <number>
  • @exclusiveMaximum <param> <number>
  • @default <param> <value>
  • @minItems <param> <number>
  • @maxItems <param> <number>
  • @pattern <param> <regex>
  • @format <param> <format>

Example:

export class AurService {
  /**
   * Searches AUR packages.
   * @param keyword Search term.
   * @minLength keyword 2
   * @param by Search field.
   * @default by name
   * @param count Maximum result count.
   * @minimum count 1
   * @maximum count 500
   * @default count 50
   */
  public search(
    keyword: string,
    by: 'name' | 'description' | 'maintainer' = 'name',
    count?: number,
  ) {
    return { packages: [], total: 0 };
  }
}

Clients will see keyword.minLength, by.enum, by.default, count.minimum, count.maximum, and count.default in the input schema.

Output Schemas

mcp-thing also infers output schemas from simple return types, including plain object aliases and interfaces.

public packageInfo(name: string): { name: string; version: string } {
  return { name, version: '1.0.0' };
}

This emits an MCP outputSchema for:

{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "version": { "type": "string" }
  },
  "required": ["name", "version"]
}

Primitive and array returns are wrapped as { "result": value } in structured responses, so their output schemas are wrapped the same way.

public names(): string[] {
  return ['yay', 'paru'];
}

Produces structured content shaped like:

{
  "result": ["yay", "paru"]
}

Complex return types may still be returned as structured data at runtime, but outputSchema is only emitted when the shape is simple enough to infer.

Tool Results

Successful tool calls default to structured MCP results:

public info(name: string) {
  return { name, version: '1.0.0' };
}

Default MCP result:

{
  "content": [],
  "structuredContent": {
    "name": "yay",
    "version": "1.0.0"
  }
}

This avoids duplicating JSON into both text content and structured content. content is still present because MCP requires it to be an array.

Return behavior in structured mode:

  • plain objects become structuredContent as-is
  • strings, numbers, booleans, arrays, and null are wrapped as { "result": value }
  • undefined becomes empty structured content {}
  • thrown errors become { isError: true, content: [{ type: "text", text: message }] }

Content Mode

Use content mode when a client only reads regular text content.

Globally:

mcpt --run ./services --result-mode content

Per tool:

export class ExplainService {
  /**
   * Explains a package.
   * @mcpResultMode content
   */
  public explain(name: string): string {
    return `Package ${name} is maintained by ...`;
  }
}

In content mode, structuredContent is omitted and non-string values are serialized as JSON text.

Explicit ToolResult

Most methods should return normal values. For advanced cases, a method can return a full MCP-style tool result to control content, structuredContent, and isError directly.

import type { ToolResult } from 'mcp-thing';

export class ImportService {
  public importPackages(): ToolResult {
    return {
      isError: true,
      content: [{ type: 'text', text: 'Imported 8 packages, failed 2.' }],
      structuredContent: {
        imported: 8,
        failed: ['pkg-a', 'pkg-b'],
      },
    };
  }
}

This is optional and mainly useful for partial success or custom error formatting. For normal failures, throw an error.

Server Identity

Set serverInfo.name and serverInfo.version with class-level JSDoc:

/**
 * @mcpServerName My MCP Server
 * @mcpServerVersion 1.2.3
 */
export class MyService {
  public ping(): string {
    return 'pong';
  }
}

CLI flags take precedence over JSDoc values.

Examples

See examples/CalculatorService.ts for a small service with server identity, parameter descriptions, and a custom tool name.