Skip to main content

Utility Functions

The @leanmcp/utils package provides a collection of helper utilities and shared functions for building LeanMCP servers.

Installation

npm install @leanmcp/utils

API Reference

Validation

validateSchema()

Validate JSON data against a schema. Basic implementation for common use cases.
import { validateSchema } from '@leanmcp/utils';

const { valid, errors } = validateSchema(
  { name: 'John', age: 25 },
  {
    type: 'object',
    required: ['name', 'age'],
    properties: {
      name: { type: 'string' },
      age: { type: 'number' }
    }
  }
);

if (!valid) {
  console.error('Validation errors:', errors);
}
For advanced validation, consider using ajv or similar libraries in your project.
data
any
required
Data to validate
schema
any
required
JSON Schema to validate against
return
{ valid: boolean; errors?: any[] }
Validation result with optional error array

Formatting

formatResponse()

Format response data based on render type.
import { formatResponse } from '@leanmcp/utils';

const data = { users: [{ name: 'John' }, { name: 'Jane' }] };

const json = formatResponse(data, 'json');
const markdown = formatResponse(data, 'markdown');
const html = formatResponse(data, 'html');
const table = formatResponse(data, 'table');
data
any
required
Data to format
format
string
required
Output formatOptions: json | markdown | html | table
return
string
Formatted string

Examples

// JSON with pretty printing
formatResponse({ name: 'John' }, 'json')
// Returns:
// {
//   "name": "John"
// }

// Markdown code block
formatResponse({ name: 'John' }, 'markdown')
// Returns:
// ```json
// {
//   "name": "John"
// }
// ```

// HTML pre block
formatResponse({ name: 'John' }, 'html')
// Returns: <pre>{"name":"John"}</pre>

formatAsTable()

Format array data as a markdown table.
import { formatAsTable } from '@leanmcp/utils';

const users = [
  { name: 'John', age: 25, role: 'Admin' },
  { name: 'Jane', age: 30, role: 'User' },
];

const table = formatAsTable(users);
console.log(table);
// | name | age | role |
// | --- | --- | --- |
// | John | 25 | Admin |
// | Jane | 30 | User |
data
any[]
required
Array of objects to format as table
return
string
Markdown table string

Object Utilities

deepMerge()

Deep merge multiple objects.
import { deepMerge } from '@leanmcp/utils';

const target = { a: 1, b: { c: 2 } };
const source1 = { b: { d: 3 } };
const source2 = { e: 4 };

const result = deepMerge(target, source1, source2);
// { a: 1, b: { c: 2, d: 3 }, e: 4 }
target
T extends Record<string, any>
required
Target object (will be mutated)
...sources
Partial<T>[]
Source objects to merge
return
T
Merged object (same reference as target)

isObject()

Check if value is a plain object.
import { isObject } from '@leanmcp/utils';

isObject({})              // true
isObject({ a: 1 })        // true
isObject([])              // false
isObject(null)            // false
isObject(new Date())      // true (caution!)
item
any
required
Value to check
return
boolean
true if value is a plain object, false otherwise

Async Utilities

retry()

Retry a function with exponential backoff.
import { retry } from '@leanmcp/utils';

const result = await retry(
  async () => {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) throw new Error('Request failed');
    return response.json();
  },
  {
    maxRetries: 3,
    delayMs: 1000,
    backoff: 2
  }
);
fn
() => Promise<T>
required
Async function to retry
options
object
Retry options
maxRetries
number
default:3
Maximum number of retry attempts
delayMs
number
default:1000
Initial delay in milliseconds
backoff
number
default:2
Backoff multiplier (delay = delayMs * backoff^attempt)
return
Promise<T>
Result from successful execution

Throws

Throws the last error if all retry attempts fail.

sleep()

Sleep for a given duration.
import { sleep } from '@leanmcp/utils';

await sleep(1000); // Wait 1 second
console.log('Done waiting');
ms
number
required
Duration in milliseconds
return
Promise<void>
Promise that resolves after the delay

timeout()

Add timeout to a promise.
import { timeout } from '@leanmcp/utils';

try {
  const result = await timeout(
    fetch('https://api.example.com/slow'),
    5000,
    'API request timed out'
  );
} catch (error) {
  console.error(error); // 'API request timed out' after 5s
}
promise
Promise<T>
required
Promise to add timeout to
ms
number
required
Timeout duration in milliseconds
errorMessage
string
default:"Operation timed out"
Error message for timeout
return
Promise<T>
Promise that rejects if timeout is reached

Function Utilities

debounce()

Create a debounced function.
import { debounce } from '@leanmcp/utils';

const debouncedSave = debounce(async (data: string) => {
  await saveToDatabase(data);
}, 500);

// Called multiple times, but only executes once after 500ms
debouncedSave('a');
debouncedSave('ab');
debouncedSave('abc'); // Only this executes
func
T extends (...args: any[]) => any
required
Function to debounce
waitMs
number
required
Wait duration in milliseconds
return
(...args: Parameters<T>) => void
Debounced function

throttle()

Create a throttled function.
import { throttle } from '@leanmcp/utils';

const throttledLog = throttle((message: string) => {
  console.log(message);
}, 1000);

// Called multiple times, but only first call executes
throttledLog('a');   // Executes
throttledLog('b');   // Ignored
throttledLog('c');   // Ignored
// Wait 1s
throttledLog('d');   // Executes
func
T extends (...args: any[]) => any
required
Function to throttle
limitMs
number
required
Throttle limit in milliseconds
return
(...args: Parameters<T>) => void
Throttled function

String Utilities

truncate()

Truncate string to maximum length.
import { truncate } from '@leanmcp/utils';

truncate('Hello World', 8)          // 'Hello...'
truncate('Hello World', 8, '…')     // 'Hello W…'
truncate('Short', 10)                // 'Short'
str
string
required
String to truncate
maxLength
number
required
Maximum length including suffix
suffix
string
default:"..."
Suffix to append
return
string
Truncated string

camelToKebab()

Convert camelCase to kebab-case.
import { camelToKebab } from '@leanmcp/utils';

camelToKebab('helloWorld')      // 'hello-world'
camelToKebab('getUserById')     // 'get-user-by-id'
camelToKebab('XMLParser')       // 'xml-parser'
str
string
required
camelCase string
return
string
kebab-case string

kebabToCamel()

Convert kebab-case to camelCase.
import { kebabToCamel } from '@leanmcp/utils';

kebabToCamel('hello-world')      // 'helloWorld'
kebabToCamel('get-user-by-id')   // 'getUserById'
kebabToCamel('xml-parser')       // 'xmlParser'
str
string
required
kebab-case string
return
string
camelCase string

capitalize()

Capitalize first letter of string.
import { capitalize } from '@leanmcp/utils';

capitalize('hello')      // 'Hello'
capitalize('world')      // 'World'
capitalize('UPPER')      // 'UPPER'
str
string
required
String to capitalize
return
string
String with first letter capitalized

Environment Utilities

parseEnv()

Parse environment variables with type coercion.
import { parseEnv } from '@leanmcp/utils';

// String (default)
const host = parseEnv(process.env.HOST, 'localhost', 'string');

// Number
const port = parseEnv(process.env.PORT, 3000, 'number');

// Boolean
const debug = parseEnv(process.env.DEBUG, false, 'boolean');
value
string | undefined
required
Environment variable value
defaultValue
any
required
Default value if undefined or parse fails
type
'string' | 'number' | 'boolean'
default:"string"
Type to coerce to
return
any
Parsed value or default

Examples

// Number parsing
parseEnv('3000', 8080, 'number')      // 3000
parseEnv('invalid', 8080, 'number')   // 8080 (fallback)
parseEnv(undefined, 8080, 'number')   // 8080 (fallback)

// Boolean parsing
parseEnv('true', false, 'boolean')    // true
parseEnv('false', true, 'boolean')    // false
parseEnv('yes', false, 'boolean')     // false (not 'true')

generateId()

Generate a unique ID.
import { generateId } from '@leanmcp/utils';

const id1 = generateId();              // 'l3k5j2h-a8d9f2g'
const id2 = generateId('user');        // 'user-l3k5j2h-a8d9f2g'
const id3 = generateId('session');     // 'session-l3k5j2h-b7e1k3m'
prefix
string
default:""
Optional prefix for the ID
return
string
Generated unique ID

Usage Examples

Tool Result Formatting

import { formatResponse, formatAsTable } from '@leanmcp/utils';
import { Tool } from '@leanmcp/core';

class DataService {
  @Tool({ description: 'Get user list' })
  async getUsers() {
    const users = await db.getUsers();
    
    // Return formatted table
    return formatAsTable(users);
  }
  
  @Tool({ description: 'Get user details' })
  async getUser(args: { id: string }) {
    const user = await db.getUser(args.id);
    
    // Return formatted JSON
    return formatResponse(user, 'markdown');
  }
}

Retry with Exponential Backoff

import { retry } from '@leanmcp/utils';
import { Tool } from '@leanmcp/core';

class ExternalApiService {
  @Tool({ description: 'Fetch data from external API' })
  async fetchData(args: { endpoint: string }) {
    return await retry(
      async () => {
        const response = await fetch(`https://api.example.com/${args.endpoint}`);
        if (!response.ok) throw new Error('Request failed');
        return response.json();
      },
      { maxRetries: 3, delayMs: 1000, backoff: 2 }
    );
  }
}
import { debounce } from '@leanmcp/utils';

class SearchService {
  private debouncedSearch = debounce(async (query: string) => {
    return await this.performSearch(query);
  }, 300);
  
  @Tool({ description: 'Search' })
  async search(args: { query: string }) {
    return await this.debouncedSearch(args.query);
  }
  
  private async performSearch(query: string) {
    // Actual search implementation
    return await db.search(query);
  }
}

Configuration with Environment Variables

import { parseEnv, deepMerge } from '@leanmcp/utils';

const defaultConfig = {
  server: {
    host: 'localhost',
    port: 3000,
  },
  features: {
    debug: false,
  },
};

const config = deepMerge(defaultConfig, {
  server: {
    host: parseEnv(process.env.HOST, 'localhost', 'string'),
    port: parseEnv(process.env.PORT, 3000, 'number'),
  },
  features: {
    debug: parseEnv(process.env.DEBUG, false, 'boolean'),
  },
});

Best Practices

1. Use Retry for External APIs

Wrap unreliable external API calls in retry logic:
await retry(() => externalApi.call(), { maxRetries: 3 });

2. Debounce User Input

Prevent excessive API calls from user input:
const search = debounce(performSearch, 300);

3. Add Timeouts to Long Operations

Prevent hanging requests:
await timeout(longOperation(), 30000, 'Operation took too long');

4. Validate Tool Inputs

Use schema validation for tool arguments:
const { valid, errors } = validateSchema(args, schema);
if (!valid) throw new Error(`Invalid input: ${errors}`);

See Also

Build docs developers (and LLMs) love