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

@backstack-io/example

v1.0.0

Published

Reference implementation demonstrating MCP YAML bridge patterns with mathematical functions

Downloads

6

Readme

@backstack/example

License: MIT Node.js Version

Reference implementation for creating MCP tools with the YAML bridge

A production-quality example package demonstrating how to create custom MCP (Model Context Protocol) tools using the @backstack/mcp-yaml-bridge. This package showcases best practices for function implementation, YAML configuration, testing, and documentation.

📖 Table of Contents

What is This?

This package demonstrates how to create MCP tools that can be used by AI agents in the Backstack platform. Instead of implementing the full MCP protocol, you simply:

  1. Write standard JavaScript/TypeScript functions
  2. Define them in a mcp-tools.yaml file
  3. Publish to NPM

The @backstack/mcp-yaml-bridge handles all the MCP protocol details for you.

Who Should Use This?

  • Developers building custom tools for Backstack workspaces
  • Team leads wanting to extend AI agent capabilities
  • Anyone looking to understand MCP tool development patterns

What You'll Learn

  • How to structure an MCP tool package
  • Function implementation patterns with async/await
  • JSON Schema design for input validation
  • Environment variable management
  • Testing strategies for MCP tools
  • Documentation best practices

Quick Start

Installation

npm install @backstack/example

Testing Locally

You can test this package locally with the MCP YAML bridge:

# Install the bridge globally
npm install -g @backstack/mcp-yaml-bridge

# Run the package
mcp-yaml-bridge @backstack/example

The bridge will start a JSON-RPC server on stdin/stdout that implements the MCP protocol.

Example Usage

// Import a function directly (for testing)
const { addNumbers } = require('@backstack/example/src/math/basic-math');

async function demo() {
  const result = await addNumbers({ a: 5, b: 3 });
  console.log(result);
  // {
  //   operation: 'addition',
  //   operands: [5, 3],
  //   result: 8,
  //   timestamp: '2024-01-14T12:00:00.000Z'
  // }
}

demo();

Using with MCP Bridge

# Send a tools/list request
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | mcp-yaml-bridge @backstack/example

# Call add_numbers tool
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"add_numbers","arguments":{"a":5,"b":3}}}' | mcp-yaml-bridge @backstack/example

Available Tools

This package includes four mathematical tools demonstrating different patterns:

1. add_numbers

Purpose: Demonstrates the simplest possible MCP tool implementation.

Parameters: | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | a | number | Yes | The first number to add | | b | number | Yes | The second number to add |

Example:

const result = await addNumbers({ a: 5, b: 3 });

Output:

{
  "operation": "addition",
  "operands": [5, 3],
  "result": 8,
  "timestamp": "2024-01-14T12:00:00.000Z"
}

Common Errors:

  • Missing parameters → Schema validation error
  • Non-numeric values → Schema validation error

2. multiply_numbers

Purpose: Demonstrates default values and enum constraints.

Parameters: | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | a | number | Yes | - | The first number to multiply | | b | number | Yes | - | The second number to multiply | | precision | integer | No | 2 | Decimal places for rounding (0-5) |

Example:

const result = await multiplyNumbers({ a: 1.234, b: 2, precision: 3 });

Output:

{
  "operation": "multiplication",
  "operands": [1.234, 2],
  "result": 2.468,
  "precision": 3,
  "timestamp": "2024-01-14T12:00:00.000Z"
}

Common Errors:

  • precision outside 0-5 range → Schema validation error
  • Invalid precision type → Schema validation error

3. calculate_statistics

Purpose: Demonstrates array input handling and error validation.

Parameters: | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | numbers | array | Yes | Array of numbers to analyze (1-1000 items) |

Example:

const result = await calculateStatistics({ numbers: [1, 2, 3, 4, 5] });

Output:

{
  "count": 5,
  "sum": 15,
  "mean": 3,
  "median": 3,
  "min": 1,
  "max": 5,
  "timestamp": "2024-01-14T12:00:00.000Z"
}

Common Errors:

  • Empty array → Runtime error: "Array must contain at least one number"
  • Array too large (>1000) → Schema validation error
  • Non-numeric array items → Schema validation error

4. format_currency

Purpose: Demonstrates environment variable usage and complex defaults.

Parameters: | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | amount | number | Yes | - | The numeric amount to format | | currency | string | No | USD | ISO 4217 code (USD, EUR, GBP, JPY, CAD, AUD) | | locale | string | No | en-US | Locale for formatting (en-US, en-GB, de-DE, fr-FR, ja-JP) |

Environment Variables:

  • CURRENCY_API_KEY (optional): Enables exchange rate conversion

Example:

const result = await formatCurrency({
  amount: 100,
  currency: 'EUR',
  locale: 'de-DE'
});

Output:

{
  "originalAmount": 100,
  "currency": "EUR",
  "locale": "de-DE",
  "formatted": "85,00 €",
  "exchangeRate": 0.85,
  "apiKeyUsed": true,
  "timestamp": "2024-01-14T12:00:00.000Z"
}

Common Errors:

  • Invalid currency code → Schema validation error
  • Invalid locale → Schema validation error
  • Missing CURRENCY_API_KEY when expected → Function works but uses exchangeRate of 1.0

Creating Your Own Package

Follow these steps to create your own MCP tool package based on this example:

Step 1: Initialize Your NPM Package

# Create a new directory
mkdir my-mcp-tools
cd my-mcp-tools

# Initialize package.json
npm init -y

# Update package.json with proper metadata
npm pkg set name="@myorg/my-tools"
npm pkg set description="My custom MCP tools"
npm pkg set version="1.0.0"
npm pkg set license="MIT"

# Create directory structure
mkdir -p src tests

Step 2: Create mcp-tools.yaml

Create a mcp-tools.yaml file in your package root:

version: "1.0"
name: "@myorg/my-tools"
description: "Description of your tools"

tools:
  - name: my_tool_name
    description: "What your tool does (10+ chars)"
    implementation: ./src/my-function.js
    function: myFunction
    inputSchema:
      type: object
      properties:
        param1:
          type: string
          description: "Parameter description"
      required:
        - param1
      additionalProperties: false

Key points:

  • Tool names use snake_case
  • Function names use camelCase
  • Descriptions must be 10+ characters
  • Always set additionalProperties: false

Step 3: Implement Your Functions

Create your implementation file (e.g., src/my-function.js):

/**
 * Brief description of what the function does.
 *
 * @param {Object} args - Function arguments
 * @param {string} args.param1 - Description
 * @returns {Promise<Object>} Result object
 */
async function myFunction(args) {
  const { param1 } = args;

  // Your implementation here

  return {
    result: 'your result',
    timestamp: new Date().toISOString()
  };
}

module.exports = { myFunction };

Key points:

  • Always use async functions for consistency
  • Destructure the args object
  • Return plain objects (not classes or special types)
  • Include a timestamp for debugging
  • Add JSDoc comments

Step 4: Define JSON Schemas

Your inputSchema should validate all inputs:

inputSchema:
  type: object
  properties:
    stringParam:
      type: string
      description: "A string parameter"
    numberParam:
      type: number
      description: "A number parameter"
    optionalParam:
      type: string
      enum: [option1, option2, option3]
      default: option1
      description: "An optional parameter"
  required:
    - stringParam
    - numberParam
  additionalProperties: false

Supported types: string, number, integer, boolean, array, object

Useful keywords: enum, default, minItems, maxItems, minimum, maximum

Step 5: Add Tests

Create tests to verify your functions work:

const { myFunction } = require('../src/my-function');

describe('myFunction', () => {
  it('should handle typical case', async () => {
    const result = await myFunction({ param1: 'test' });
    expect(result).toBeDefined();
  });

  it('should handle edge cases', async () => {
    // Test edge cases
  });
});

Run tests with:

npm test

Step 6: Test Locally

Install the bridge and test your package:

# Install dependencies
npm install

# Test with the bridge
npx @backstack/mcp-yaml-bridge .

Send test requests:

echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | npx @backstack/mcp-yaml-bridge .

Step 7: Publish to NPM

When ready to publish:

# Login to NPM
npm login

# Publish your package
npm publish --access public

# For scoped packages (@org/name), you need --access public

After publishing, you can use it in Backstack by registering the NPM package name.

Best Practices

Function Design

DO:

  • Use async functions consistently
  • Destructure args with defaults
  • Return plain objects
  • Include timestamps
  • Handle errors with clear messages
  • Validate critical inputs beyond schema

DON'T:

  • Use sync functions (inconsistent with patterns)
  • Mutate input arguments
  • Return class instances or complex types
  • Use console.log (throw errors instead)
  • Rely solely on schema validation

Schema Design

DO:

  • Always set additionalProperties: false
  • Use descriptive property names
  • Provide helpful descriptions (10+ chars)
  • Use enums for limited choices
  • Set defaults for optional parameters
  • Use appropriate constraints (min, max, minItems, etc.)

DON'T:

  • Allow additional properties (security risk)
  • Use vague descriptions
  • Over-constrain inputs unnecessarily
  • Forget required fields array

Error Handling

DO:

  • Throw descriptive errors
  • Validate edge cases
  • Include context in error messages
  • Document common errors in README

DON'T:

  • Silently fail or return error codes
  • Use generic error messages
  • Catch errors without rethrowing
  • Assume inputs are always valid

Testing

DO:

  • Test success cases
  • Test edge cases (empty, zero, negative, etc.)
  • Test error scenarios
  • Use descriptive test names
  • Aim for high coverage

DON'T:

  • Only test happy paths
  • Skip integration tests
  • Use unclear test descriptions

Environment Variables

DO:

  • Declare required vars in YAML environment array
  • Check if vars exist before use
  • Provide sensible defaults when possible
  • Document required variables

DON'T:

  • Forget to declare vars in YAML
  • Assume vars are always set
  • Store secrets in code

Architecture Patterns

Sync vs Async Functions

Always use async functions, even if they don't need await:

// Good
async function myFunction(args) {
  const result = doSomething();
  return { result };
}

// Avoid
function myFunction(args) {
  return { result: doSomething() };
}

Why? Consistency with the pattern makes code predictable and future-proof.

Destructuring Args

Always destructure with defaults:

// Good
async function myFunction(args) {
  const { param1, param2 = 'default' } = args;
  // ...
}

// Avoid
async function myFunction(args) {
  const param1 = args.param1;
  const param2 = args.param2 || 'default';
  // ...
}

Return Value Formats

Return plain objects with consistent structure:

// Good
return {
  operation: 'action_name',
  inputs: { param1, param2 },
  result: computedValue,
  metadata: { /* ... */ },
  timestamp: new Date().toISOString()
};

// Avoid
return computedValue; // Too simple
return new ResultClass(value); // Don't use classes

Error Propagation

Let errors bubble up with clear messages:

// Good
if (invalidCondition) {
  throw new Error('Clear message explaining what went wrong');
}

// Avoid
if (invalidCondition) {
  return { error: 'Something failed' };
}

Advanced Topics

Using External Dependencies

You can use any NPM package in your functions:

const axios = require('axios');
const _ = require('lodash');

async function myFunction(args) {
  const response = await axios.get('https://api.example.com');
  const processed = _.groupBy(response.data, 'category');
  return { result: processed };
}

Just add dependencies to your package.json:

npm install axios lodash

TypeScript Support

You can write functions in TypeScript:

  1. Install TypeScript: npm install --save-dev typescript ts-node
  2. Write .ts files instead of .js
  3. Update mcp-tools.yaml to point to .ts files
  4. The bridge will automatically compile with ts-node

Example:

interface Args {
  param1: string;
  param2?: number;
}

async function myFunction(args: Args): Promise<object> {
  const { param1, param2 = 0 } = args;
  return { result: param1, count: param2 };
}

export { myFunction };

Multi-File Organization

Organize complex packages with multiple files:

src/
├── utils/
│   ├── validators.js
│   └── formatters.js
├── tools/
│   ├── tool1.js
│   ├── tool2.js
│   └── tool3.js
└── index.js

Import utilities in your tool files:

const { validateEmail } = require('../utils/validators');

async function myTool(args) {
  if (!validateEmail(args.email)) {
    throw new Error('Invalid email format');
  }
  // ...
}

Environment Variable Management

Best practices for handling secrets:

async function myFunction(args) {
  // Check if required env var exists
  const apiKey = process.env.MY_API_KEY;
  if (!apiKey) {
    throw new Error('MY_API_KEY environment variable is required');
  }

  // Optional env var with fallback
  const apiUrl = process.env.API_URL || 'https://api.default.com';

  // Use the variables
  const response = await fetch(apiUrl, {
    headers: { 'Authorization': `Bearer ${apiKey}` }
  });

  return { result: response.data };
}

Declare in YAML:

environment:
  - MY_API_KEY  # Required
  # API_URL is optional, don't list it

Validation Strategies

Layer validation for robustness:

async function myFunction(args) {
  // 1. Schema validates basic types
  // 2. Add business logic validation
  const { email, age } = args;

  if (!email.includes('@')) {
    throw new Error('Email must contain @ symbol');
  }

  if (age < 0 || age > 150) {
    throw new Error('Age must be between 0 and 150');
  }

  // 3. Proceed with validated inputs
  return { valid: true };
}

Troubleshooting

Common Issues

1. "Cannot find module" Error

Problem: Bridge can't locate your package or implementation file.

Solution:

  • Verify implementation path in YAML is correct (e.g., ./src/my-file.js)
  • Ensure file exists at specified path
  • Check file extension matches (.js, .ts, .mjs, .cjs)

2. "Function not found" Error

Problem: Bridge can't find the exported function.

Solution:

  • Verify function name in YAML matches export: function: myFunction
  • Check you're using module.exports = { myFunction }
  • Ensure function name is spelled correctly (case-sensitive)

3. Schema Validation Fails

Problem: Input arguments don't match schema.

Solution:

  • Check required fields are provided
  • Verify types match (number vs string)
  • Ensure enum values are exact matches
  • Remove any extra properties if additionalProperties: false

4. Missing Environment Variable

Problem: Function expects env var that isn't set.

Solution:

  • Set the variable: export MY_VAR=value
  • Add to YAML environment array if required
  • Check for typos in variable name

5. Invalid YAML Syntax

Problem: YAML file can't be parsed.

Solution:

  • Check indentation (use spaces, not tabs)
  • Verify colons have spaces after them: name: value
  • Ensure strings with special chars are quoted
  • Validate YAML with online validator

6. Tests Failing

Problem: Jest tests don't pass.

Solution:

  • Run npm install to install dependencies
  • Check test file paths are correct
  • Verify function exports match imports
  • Review error messages for specifics

7. Bridge Hangs or Doesn't Respond

Problem: No output from bridge command.

Solution:

  • Ensure you're piping valid JSON-RPC to stdin
  • Check for syntax errors in request
  • Verify package name is correct
  • Try running with explicit package path: mcp-yaml-bridge ./

8. Unexpected Tool Behavior

Problem: Tool returns wrong results.

Solution:

  • Add logging: console.error('Debug:', value) (goes to stderr)
  • Write unit tests to isolate issue
  • Check for async/await issues
  • Verify input destructuring is correct

9. TypeScript Errors

Problem: .ts files don't work with bridge.

Solution:

  • Install: npm install --save-dev ts-node typescript
  • Verify TypeScript config exists (tsconfig.json)
  • Check ts-node is in devDependencies
  • Try compiling manually: npx tsc

10. NPM Publish Fails

Problem: Can't publish package to NPM.

Solution:

  • Login first: npm login
  • For scoped packages: npm publish --access public
  • Check package name isn't taken
  • Verify version number is incremented

Debugging Tips

Enable verbose logging:

NODE_DEBUG=* mcp-yaml-bridge @backstack/example

Test function directly:

const { myFunction } = require('./src/my-file');
myFunction({ test: 'args' }).then(console.log);

Validate YAML:

npm install -g js-yaml
js-yaml mcp-tools.yaml

Check JSON-RPC format:

// Valid format:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "my_tool",
    "arguments": { "param": "value" }
  }
}

API Reference

YAML Schema Reference

Complete specification for mcp-tools.yaml:

version: "1.0"  # Required: Must be "1.0"
name: "string"  # Required: Package name (should match package.json)
description: "string"  # Optional: Package description

tools:  # Required: Array of tool definitions (min 1)
  - name: "string"  # Required: Tool name (snake_case, lowercase, must match ^[a-z_][a-z0-9_]*$)
    description: "string"  # Required: Min 10 characters, used by AI agents
    implementation: "string"  # Required: Relative path to .js/.ts file (e.g., ./src/tool.js)
    function: "string"  # Required: Exported function name (camelCase)
    inputSchema:  # Required: JSON Schema object
      type: object
      properties:  # Optional: Object defining parameters
        paramName:
          type: "string|number|integer|boolean|array|object"
          description: "string"
          enum: []  # Optional: Allowed values
          default: value  # Optional: Default value
          # ... other JSON Schema keywords
      required: []  # Optional: Array of required property names
      additionalProperties: false  # Recommended: Reject unknown properties
    environment:  # Optional: Array of required environment variable names
      - "ENV_VAR_NAME"

Supported JSON Schema Keywords

Type Keywords:

  • type: string, number, integer, boolean, array, object, null
  • enum: Array of allowed values
  • const: Single allowed value

Validation Keywords:

  • Numbers: minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf
  • Strings: minLength, maxLength, pattern (regex)
  • Arrays: minItems, maxItems, uniqueItems
  • Objects: required, properties, additionalProperties, minProperties, maxProperties

Annotation Keywords:

  • description: Human-readable description
  • title: Short title
  • default: Default value (applied by validator)
  • examples: Array of example values

Combining Schemas:

  • allOf: Must match all schemas
  • anyOf: Must match at least one schema
  • oneOf: Must match exactly one schema
  • not: Must not match schema

MCP Bridge Behavior

Tool Discovery:

  1. Bridge resolves package using Node.js require.resolve()
  2. Reads mcp-tools.yaml from package root
  3. Validates YAML against JSON schema
  4. Checks all implementation files exist

Tool Execution:

  1. Validates arguments against inputSchema (with type coercion and defaults)
  2. Checks required environment variables exist
  3. Dynamically imports implementation module
  4. Executes named function with args object
  5. Returns result in MCP format

Error Handling:

  • YAML validation errors → Detailed error with line number
  • Missing files → "Implementation file not found" with path
  • Missing function → "Function X not found in module Y"
  • Schema validation errors → Detailed JSON Schema error
  • Runtime errors → Stack trace in response

Contributing

We welcome contributions to improve this example package!

Reporting Issues

Found a bug or have a suggestion?

  1. Check existing issues: https://github.com/backstack-io/backstack-v2/issues
  2. Create a new issue with:
    • Clear description
    • Steps to reproduce (for bugs)
    • Expected vs actual behavior
    • Environment details (Node version, OS)

Pull Requests

Want to improve the code or documentation?

  1. Fork the repository
  2. Create a feature branch: git checkout -b feature/my-improvement
  3. Make your changes
  4. Add tests if applicable
  5. Run tests: npm test
  6. Commit with clear message: git commit -m "feat: add X"
  7. Push and create a pull request

Code of Conduct

Be respectful and constructive. We're all here to learn and build great tools together.

License

MIT License - see LICENSE file for details.

Copyright (c) 2024 Backstack Team


Need Help?

  • Documentation: https://docs.backstack.io
  • Issues: https://github.com/backstack-io/backstack-v2/issues
  • Discord: Join our community for questions and discussions

Made with ❤️ by the Backstack Team