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-funnel/commands-core

v0.0.8

Published

Core infrastructure for MCP Funnel commands

Readme

@mcp-funnel/commands-core

Core infrastructure for building MCP Funnel commands that work both via CLI and MCP protocol.

Overview

This package provides the base interfaces, classes, and utilities for creating commands that can be:

  • Executed via CLI: Direct command-line usage with npx mcp-funnel run <command>
  • Called via MCP: Exposed through the MCP protocol for AI assistants

Installation

yarn add @mcp-funnel/commands-core

Creating a Command

1. Implement the ICommand Interface

import { ICommand, Tool, CallToolResult } from '@mcp-funnel/commands-core';

export class MyCommand implements ICommand {
  readonly name = 'my-command';
  readonly description = 'Description of what my command does';

  // For MCP execution (returns JSON)
  async executeToolViaMCP(
    toolName: string,
    args: Record<string, unknown>,
  ): Promise<CallToolResult> {
    // Process args and return result
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({ success: true }),
        },
      ],
    };
  }

  // For CLI execution (console output)
  async executeViaCLI(args: string[]): Promise<void> {
    // Parse CLI args and output to console
    console.log('Command executed!');
  }

  // MCP tool definitions
  getMCPDefinitions(): Tool[] {
    return [
      {
        name: this.name,
        description: this.description,
        inputSchema: {
          type: 'object',
          properties: {
            // Define your command's parameters
          },
        },
      },
    ];
  }
}

2. Or Extend BaseCommand

import { BaseCommand } from '@mcp-funnel/commands-core';

export class MyCommand extends BaseCommand {
  readonly name = 'my-command';
  readonly description = 'My command description';

  // BaseCommand provides helpers like:
  // - parseCommonOptions() for parsing --verbose, --dry-run, etc.
  // - log() and logError() for output handling

  async executeToolViaMCP(
    toolName: string,
    args: Record<string, unknown>,
  ): Promise<CallToolResult> {
    const options = this.parseCommonOptions(args);
    // Implementation
  }

  async executeViaCLI(args: string[]): Promise<void> {
    const options = this.parseCommonOptions(args);
    this.log('Processing...', options);
    // Implementation
  }

  getMCPDefinitions(): Tool[] {
    // Command schema
    return [
      {
        name: this.name,
        description: this.description,
        inputSchema: {
          type: 'object',
          properties: {
            // Define your command's parameters
          },
        },
      },
    ];
  }
}

3. Package Structure

Create your command package:

packages/commands/my-command/
├── package.json
├── tsconfig.json
├── src/
│   ├── index.ts      # Export your command
│   └── command.ts    # Command implementation
└── dist/
    └── index.js      # Built output

package.json:

{
  "name": "@mcp-funnel/command-my-command",
  "version": "0.0.1",
  "type": "module",
  "main": "./dist/index.js",
  "exports": {
    ".": "./dist/index.js"
  }
}

src/index.ts:

import { MyCommand } from './command.js';

// Default export for auto-discovery
export default new MyCommand();

// Named export for programmatic use
export { MyCommand };

Command Discovery

The discovery system automatically finds commands in packages/commands/*:

import { discoverCommands } from '@mcp-funnel/commands-core';

const registry = await discoverCommands('./packages/commands');

// Get all discovered commands
const commandNames = registry.getAllCommandNames();

// Get a specific command
const command = registry.getCommandForCLI('my-command');
if (command) {
  await command.executeViaCLI(['--help']);
}

Command Registry

Manually register commands:

import { CommandRegistry } from '@mcp-funnel/commands-core';
import { MyCommand } from './my-command.js';

const registry = new CommandRegistry();
registry.register(new MyCommand());

// For MCP
const mcpTools = registry.getAllMCPDefinitions();

// For CLI
const command = registry.getCommandForCLI('my-command');

API Reference

Interfaces

  • ICommand: Core command interface
  • ICommandMetadata: Command metadata (name, version, author, tags)
  • ICommandOptions: Common command options (verbose, dryRun, etc.)

Classes

  • BaseCommand: Abstract base class with common functionality
  • CommandRegistry: Registry for managing commands

Functions

  • discoverCommands(searchPath): Auto-discover commands from directory
  • discoverCommandsFromDefault(): Discover from default location

Multi-Tool Commands

Commands can expose multiple tools through the MCP protocol while maintaining a single CLI interface. This pattern is useful for commands that provide related functionality grouped under a common domain.

Overview

The multi-tool pattern allows:

  • Logical grouping: Related operations under one command umbrella
  • Shared resources: Common caching, configuration, error handling
  • Flexible control: Enable/disable individual tools via configuration
  • Compact naming: Single-tool commands use commandName, multi-tool commands use commandName_toolName (e.g., npm_lookup, npm_search)

Implementation

To create a multi-tool command, implement executeToolViaMCP instead of executeViaMCP:

import { ICommand, Tool } from '@mcp-funnel/commands-core';
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';

export class MyMultiCommand implements ICommand {
  readonly name = 'multi-example';
  readonly description = 'Example multi-tool command';

  // Handle tool-specific execution via MCP
  async executeToolViaMCP(
    toolName: string,
    args: Record<string, unknown>,
  ): Promise<CallToolResult> {
    switch (toolName) {
      case 'lookup':
        return this.handleLookup(args);
      case 'search':
        return this.handleSearch(args);
      default:
        throw new Error(`Unknown tool: ${toolName}`);
    }
  }

  // CLI interface routes to appropriate subcommand
  async executeViaCLI(args: string[]): Promise<void> {
    const [subcommand, ...subArgs] = args;

    switch (subcommand) {
      case 'lookup':
        return this.cliLookup(subArgs);
      case 'search':
        return this.cliSearch(subArgs);
      case '--help':
      case 'help':
        return this.showHelp();
      default:
        throw new Error(`Unknown subcommand: ${subcommand}`);
    }
  }

  // Return multiple tool definitions
  getMCPDefinitions(): Tool[] {
    return [
      {
        name: 'lookup',
        description: 'Look up specific items',
        inputSchema: {
          type: 'object',
          properties: {
            id: {
              type: 'string',
              description: 'Item ID to lookup',
            },
          },
          required: ['id'],
        },
      },
      {
        name: 'search',
        description: 'Search for items',
        inputSchema: {
          type: 'object',
          properties: {
            query: {
              type: 'string',
              description: 'Search query',
            },
            limit: {
              type: 'number',
              description: 'Maximum results',
              default: 10,
            },
          },
          required: ['query'],
        },
      },
    ];
  }

  private async handleLookup(
    args: Record<string, unknown>,
  ): Promise<CallToolResult> {
    const { id } = args;
    // Implementation
    return {
      content: [{ type: 'text', text: `Looked up: ${id}` }],
    };
  }

  private async handleSearch(
    args: Record<string, unknown>,
  ): Promise<CallToolResult> {
    const { query, limit = 10 } = args;
    // Implementation
    return {
      content: [{ type: 'text', text: `Search results for: ${query}` }],
    };
  }

  private async cliLookup(args: string[]): Promise<void> {
    // CLI implementation
    console.log('Lookup via CLI:', args);
  }

  private async cliSearch(args: string[]): Promise<void> {
    // CLI implementation
    console.log('Search via CLI:', args);
  }

  private showHelp(): void {
    console.log(`
Usage: npx mcp-funnel run ${this.name} <subcommand> [options]

Subcommands:
  lookup <id>        Look up item by ID
  search <query>     Search for items
  help              Show this help

Examples:
  npx mcp-funnel run ${this.name} lookup item-123
  npx mcp-funnel run ${this.name} search "test query"
    `);
  }
}

When to Use Multi-Tool Pattern

Consider multi-tool commands for:

  • API clients: CRUD operations (create, read, update, delete)
  • File operations: Different file manipulations (read, write, search, validate)
  • Development tools: Related dev operations (lint, test, build, deploy)
  • External services: Multiple operations on the same service/API

Single vs Multi-Tool Decision

| Use Single Tool When | Use Multi-Tool When | | -------------------------------- | ------------------------------------------ | | Command has one clear purpose | Command covers multiple related operations | | No shared state or resources | Operations share configuration/caching | | Unlikely to expand functionality | Planning to add related tools | | Simplicity is paramount | Logical grouping provides value |

Examples

  • Single-tool: ts-validate command in packages/commands/ts-validate
  • Multi-tool: npm command in packages/commands/npm-lookup

Contributing

When creating new commands:

  1. Implement both MCP and CLI interfaces
  2. Include comprehensive input validation
  3. Add proper error handling
  4. Document your command's parameters in the schema

License

MIT