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

@commercetools/processors

v0.0.0

Published

A collection of output processors for commercetools MCP server

Readme

@commercetools/processors

Output processors for MCP-style tool payloads and similar JSON: declarative field filtering (drop keys) and redaction (mask values, including query strings inside URL-shaped strings), plus transform helpers that turn structured tool results into tabular or JSON text for LLMs.

The library is domain-agnostic: you pass plain objects/arrays/primitives (anything you would normally JSON.parse). Rules use dot paths from the root of the value you pass into filterFields.


Install

npm add @commercetools/processors

or

yarn add @commercetools/processors

You can also use npm or yarn with the same package name.

Runtime: Node ≥ 18.


Package layout (what each area does)

| Area | Responsibility | | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | FieldFilteringHandler | Walks a value recursively. On objects, applies rules per key path; on arrays, maps each element (path rules do not add array indices—see below). On strings, if the string is a valid URL, runs the same rule logic on query parameter names; other strings are left as-is. | | FieldFilteringManagerConfig | Declarative lists of rules (paths, properties, includes) plus optional whitelist paths and custom redaction placeholder strings for JSON vs URL query values. | | FieldFilteringManager | Interface: filterFields + filterUrlFields. Swap in your own implementation (e.g. remote policy) and use isFieldFilteringManager at runtime to tell config from instance. | | transformToolOutput | Converts data into a string: default tabular (indented, property names via transformPropertyName, booleans as Yes/No, tables for arrays of objects where applicable); optional json stringifies JSON. | | transformPropertyName | Turns identifiers like orderLineId or order_line_id into human-oriented labels (used internally by tabular output and titles). | | urlHelpers | isValidUrl, normaliseUrl, generateQueryString — small utilities shared with URL handling in the handler. | | defaultFilteringRules, defaultJsonRedactionText, defaultUrlRedactionText | Defaults for redaction placeholders when you omit them in config ([REDACTED] / REDACTED). |


Usage overview

import {
  FieldFilteringHandler,
  type FieldFilteringManagerConfig,
  type FieldFilteringRule,
  type FieldFilteringManager,
  isFieldFilteringManager,
  defaultFilteringRules,
  defaultJsonRedactionText,
  defaultUrlRedactionText,
  transformToolOutput,
  transformPropertyName,
  isValidUrl,
  generateQueryString,
  normaliseUrl,
} from '@commercetools/processors';

Typical flow: clone (or parse fresh JSON) → new FieldFilteringHandler(config).filterFields(data) → optionally transformToolOutput({ data: safe, ... }) before sending to a model, agent or client.


Field filtering and redaction

Why cloning matters

filterFields mutates object graphs in place (delete keys, replace values). If the same object is referenced elsewhere (cache, logger, retry), those views change too. Always structuredClone, JSON.parse(JSON.stringify(x)), or another deep copy before filtering when you need isolation.

How rules are evaluated (mental model)

For each object key, the handler builds a path like parent.child.leaf. Then, for filter or redact separately:

  1. Whitelist — if the current path matches a whitelist entry, no filter/redact on that path from the declarative lists (exact / case rules as in tests).
  2. paths — full path match; filter removes that property; redact replaces the entire value at that path with jsonRedactionText (the subtree is not traversed further for path-redact in the same way as nested keys—see handler tests for edge cases).
  3. properties — compares the last segment of the path (the key name) to each rule.
  4. includes — substring match on that last segment (e.g. secret matches clientSecret when case-insensitive).

Arrays: elements are processed with the same currentPath as the parent array (no .0, .1 in the path). Rules keyed on property names or includes still apply inside objects inside arrays; full paths rules that assume items.0.field will not match the stock handler—use properties/includes or a custom FieldFilteringManager.

FieldFilteringManagerConfig (reference)

Every top-level field is optional; supply only what you need.

| Option | Type | Role | | ----------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | paths | FieldFilteringRule[] | Match the full dot-path from the root of the value passed to filterFields. type: 'filter' removes the key. type: 'redact' replaces that node’s value with jsonRedactionText (often a whole object becomes the placeholder string). | | properties | FieldFilteringRule[] | Match the leaf key name at any depth (e.g. every token key). | | includes | FieldFilteringRule[] | Match when the leaf name contains rule.value (substring), with optional case folding per rule. | | whitelistPaths | Omit<FieldFilteringRule, 'type'>[] | Paths that suppress filter/redact from the lists above when the evaluated path matches (see tests for exact vs case-insensitive behaviour). | | jsonRedactionText | string? | Replacement for redacted JSON values (default [REDACTED]). | | urlRedactionText | string? | Replacement for redacted URL query values (default REDACTED). |

FieldFilteringRule

| Field | Meaning | | ------------------- | -------------------------------------------------------------------------------------------- | | value | Path, leaf name, or substring needle (depending on which array the rule sits in). | | caseSensitive | If false, comparisons use case-insensitive matching where implemented. | | type | 'filter' — remove key (or query param). 'redact' — keep key/name, replace value. |

FieldFilteringManager and isFieldFilteringManager

Use a custom class that implements filterFields / filterUrlFields when you need logging, remote policy, or different path semantics than FieldFilteringHandler. isFieldFilteringManager(x) returns true when x has both methods (duck typing), so you can accept either plain config or a ready-made manager from configuration:

import {
  FieldFilteringHandler,
  isFieldFilteringManager,
  type FieldFilteringManager,
  type FieldFilteringManagerConfig,
} from '@commercetools/processors';

function getHandler(
  input: FieldFilteringManagerConfig | FieldFilteringManager
): FieldFilteringManager {
  if (isFieldFilteringManager(input)) {
    return input;
  }
  return new FieldFilteringHandler(input);
}

const handler = getHandler({
  properties: [{value: 'x', caseSensitive: true, type: 'filter'}],
});
const out = handler.filterFields({x: 1, y: 2});

// Expected `out`:
// { y: 2 }

Examples: FieldFilteringHandler on JSON

import {
  FieldFilteringHandler,
  type FieldFilteringManagerConfig,
} from '@commercetools/processors';

const config: FieldFilteringManagerConfig = {
  // Full path: redact only this branch's value (here the string becomes "[REDACTED]")
  paths: [
    {value: 'credentials.accessToken', caseSensitive: true, type: 'redact'},
  ],
  // Any key named apiKey (any casing): remove entirely
  properties: [{value: 'apiKey', caseSensitive: false, type: 'filter'}],
  // Any leaf name containing "secret": redact value, keep key
  includes: [{value: 'secret', caseSensitive: false, type: 'redact'}],
};

const toolResult = {
  credentials: {accessToken: 'tok', refresh: 'r'},
  apiKey: 'k',
  clientSecret: 's',
};

const safe = structuredClone(toolResult);
new FieldFilteringHandler(config).filterFields(safe);

// Expected `safe` (JSON-serialized for clarity):
// {"credentials":{"accessToken":"[REDACTED]","refresh":"r"},"clientSecret":"[REDACTED]"}
// - paths: credentials.accessToken value replaced; refresh unchanged
// - properties: apiKey key removed
// - includes: clientSecret value redacted (leaf name contains "secret")

Examples: filterUrlFields (query string)

Rules use the same machinery: filter drops a query pair; redact keeps the name and sets the value to urlRedactionText.

import {FieldFilteringHandler} from '@commercetools/processors';

const redacted = new FieldFilteringHandler({
  properties: [{value: 'sig', caseSensitive: false, type: 'redact'}],
}).filterUrlFields('https://x.example/y?sig=abc123&ok=1');

// Expected `redacted`:
// "https://x.example/y?sig=REDACTED&ok=1"

const stripped = new FieldFilteringHandler({
  properties: [{value: 'token', caseSensitive: false, type: 'filter'}],
}).filterUrlFields('https://api.example.com/callback?token=abc&ok=1');

// Expected `stripped`:
// "https://api.example.com/callback?ok=1"

Examples: transformToolOutput

format defaults to 'tabular'. With a title, the title is uppercased (after transformPropertyName) and used as a heading. Booleans become Yes/No; nested objects get indented lines; empty plain objects yield no properties (with optional title prefix—see below).

Tabular, with title

import {transformToolOutput} from '@commercetools/processors';

const text = transformToolOutput({
  data: {orderId: '123', total: {currency: 'EUR', centAmount: 4200}},
  title: 'Order summary',
  format: 'tabular',
});

// Expected `text` (string; newlines shown as \n here):
// "ORDER SUMMARY\nOrder Id: 123\nTotal:\n\tCurrency: EUR\n\tCent Amount: 4200"

Tabular, no title

const plain = transformToolOutput({
  data: {a: 1},
  format: 'tabular',
});

// Expected `plain`:
// "A: 1"

Tabular, empty object

const empty = transformToolOutput({data: {}, format: 'tabular'});

// Expected `empty`:
// "no properties"

Tabular, boolean

const stock = transformToolOutput({
  data: {inStock: true},
  format: 'tabular',
});

// Expected `stock`:
// "In Stock: Yes"

JSON format

With format: 'json', output is JSON.stringify of the data (or a single-key object whose key is the transformed title when title is set).

const json = transformToolOutput({
  data: {orderId: '123', total: {currency: 'EUR', centAmount: 4200}},
  title: 'Order summary',
  format: 'json',
});

// Expected `json` (parsed shape):
// {"ORDER SUMMARY":{"orderId":"123","total":{"currency":"EUR","centAmount":4200}}}
// Exact string spacing follows JSON.stringify.

Examples: transformPropertyName

Splits camelCase, PascalCase, snake_case, and kebab-case into words and applies light title-style casing (see unit tests for acronym edge cases).

import {transformPropertyName} from '@commercetools/processors';

transformPropertyName('customerId');
// Expected: "Customer Id"

transformPropertyName('order_line_id');
// Expected: "Order Line Id"

transformPropertyName('propertyNameSDK');
// Expected: "Property Name SDK"

Examples: URL helpers

import {
  isValidUrl,
  normaliseUrl,
  generateQueryString,
} from '@commercetools/processors';

isValidUrl('https://example.com/path?q=1');
// Expected: true

isValidUrl('not a url');
// Expected: false

normaliseUrl('https://a.com//b/../c?x=1');
// Example output (string; normalises slashes/host segment style — see tests for full matrix):
// "https://a.com/b/../c?x=1"

generateQueryString({a: 1, b: [2, 3]});
// Expected (qs indices format; leading `?`):
// "?a=1&b[0]=2&b[1]=3"

More cases: processors/test/field-filtering/urlHelpers.test.ts.


Defaults export

import {
  defaultFilteringRules,
  defaultJsonRedactionText,
  defaultUrlRedactionText,
} from '@commercetools/processors';

// `defaultFilteringRules` is a FieldFilteringManagerConfig-shaped object with only the default redaction strings set.
// `defaultJsonRedactionText` === '[REDACTED]'
// `defaultUrlRedactionText` === 'REDACTED'

Build from source (monorepo)

pnpm --filter @commercetools/processors install
pnpm --filter @commercetools/processors run build
pnpm --filter @commercetools/processors run test

Further reading

Behavioural details (whitelist vs paths, URL bracket keys, empty-rule short-circuit, etc.) are covered here and here