Self-hosted Commerce MCP

Ask about this Page
Copy for LLM
View as Markdown

You can run the self-hosted variant of Commerce MCP locally, extend it, or use it as part of agent frameworks.

For more information about the tools, see Commerce MCP server tools. You decide which API Client Commerce MCP uses and configure which tools the MCP server can use.
For a hosted, centrally managed alternative with guaranteed enforcement of your configuration, see Managed MCP Servers.
This page describes how to set up your self-hosted Commerce MCP server in Visual Studio Code and configure its transport protocol. You can also control data transformation and enable logging for debugging.
Find best practices and options for integrating Commerce MCP in agent frameworks and for extending it with custom tools.

Placeholder values

This page uses the following placeholders in examples. Replace these placeholders with your own values.

If you do not have an API Client, follow the Get your API Client guide.
PlaceholderReplace withFrom
{PROJECT_KEY}project_keyYour API Client
{CLIENT_ID}client_idYour API Client
{CLIENT_SECRET}secretYour API Client
{ACCESS_TOKEN}access_tokenAn existing access token (when using auth_token authentication type)
{AUTH_URL}Your Authorization URLRequest an access token using the internal OAuth 2.0 service
{API_URL}Your API URLHosts

Get started

This section describes how to set up and use self-hosted Commerce MCP with Visual Studio Code in stdio mode.

You need Node.js 18 or later installed on your machine.

You also need an API Client with the appropriate scopes for the tools you want to use.

For this getting started guide, use an Admin Client with manage and view scopes enabled to see all available tools.

Have the API Client credentials ready before you proceed. Replace the placeholder values with your own values to access your Project.

Add self-hosted Commerce MCP server to Visual Studio Code

Add the following entry to the mcp.json file in your VS Code configuration:
Add self-hosted Commerce MCP server to VS Codejson
{
  "servers": {
    "commerce-mcp": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "@commercetools/commerce-mcp@latest",
        "--tools=all",
        "--isAdmin=true",
        "--dynamicToolLoadingThreshold=450",
        "--authType=client_credentials",
        "--clientId={CLIENT_ID}",
        "--clientSecret={CLIENT_SECRET}",
        "--authUrl={AUTH_URL}",
        "--projectKey={PROJECT_KEY}",
        "--apiUrl={API_URL}"
      ]
    }
  }
}
The --dynamicToolLoadingThreshold=450 value is intentionally set above the total number of available tools, so all tools are loaded and dynamic tool loading is effectively disabled. A higher value injects more tools upfront, which increases context and token usage.

Replace the placeholders with your own values.

You can start the server directly from the configuration file.

If the status does not change to Running, open the Output panel in VS Code to see any error messages.
If the server is running, you should see that self-hosted Commerce MCP discovered several tools.

Use self-hosted Commerce MCP's tools with VS Code Chat

You can use the Commerce MCP tools in your VS Code chat using Agent mode.

Ask questions like:

  • What is the project key?
  • Which countries are configured in my project?

You can also create resources in your Project, for example:

  • Create a new inventory supply channel for Italy.

Verify in the Merchant Center that the channel has been created.

Next steps

If you have completed this guide to this point, you have successfully set up self-hosted Commerce MCP running on your machine.

You can explore more advanced configurations in the following sections. For example, you can set up self-hosted Commerce MCP to run as a Streamable HTTP server for remote access.
In the Best practices section, you can find recommendations on how to configure only the tools you want to enable for an agent.
The Arguments and environment variables section explains the parameters you provided in the MCP server configuration.

Reference

Executable package

You can run self-hosted Commerce MCP directly using npx without installing it globally:
Run self-hosted Commerce MCP with npxbash
npx -y @commercetools/commerce-mcp {list of arguments}

Find a list of the available arguments and environment variables that control the MCP server in the following section.

Arguments and environment variables

You can provide the following arguments as command-line parameters or as environment variables when invoking self-hosted Commerce MCP. The two examples below show each form, and every row in the argument table applies to both forms.

Use command line argumentbash
npx -y @commercetools/commerce-mcp
  --tools=all
  --dynamicToolLoadingThreshold=50
  --clientId={CLIENT_ID}
  --clientSecret={CLIENT_SECRET}
  --projectKey={PROJECT_KEY}
  --authUrl={AUTH_URL}
  --apiUrl={API_URL}
Use environment variablebash
export DYNAMIC_TOOL_LOADING_THRESHOLD=50
npx -y @commercetools/commerce-mcp
  --tools=all
  --clientId={CLIENT_ID}
  --clientSecret={CLIENT_SECRET}
  --projectKey={PROJECT_KEY}
  --authUrl={AUTH_URL}
  --apiUrl={API_URL}
Command-line argumentEnvironment variableRequiredDescription or set value
toolsTOOLSRequiredThe tools to use. To include multiple tools, use a comma-separated list without spaces.
authTypeAUTH_TYPERequiredThe authentication type to use.
projectKeyPROJECT_KEYRequiredThe {PROJECT_KEY} of the Project you want to use.
authUrlAUTH_URLRequiredThe {AUTH_URL} of your Project's authorization service.
apiUrlAPI_URLRequiredThe {API_URL} of your Project's API service.
accessTokenACCESS_TOKENRequired, if authType is auth_token.The access token to use for token authentication.
clientIdCLIENT_IDRequired, if authType is client_credentials.The {CLIENT_ID} to use for client credentials authentication.
clientSecretCLIENT_SECRETRequired, if authType is client_credentials.The {CLIENT_SECRET} to use for client credentials authentication.
remoteREMOTEOptionalSpecifies the transport protocol between MCP server and client. If true, Streamable HTTP is used, stdio if false.
statelessSTATELESSRequired, if remote is true.If true, the server is stateless.
portPORTOptionalThe port number of the MCP server in remote mode. Defaults to port 8080.
isAdminIS_ADMINOptionalIf the server should be run with administrator privileges. Must be true if tools is set to all.
dynamicToolLoadingThresholdDYNAMIC_TOOL_LOADING_THRESHOLDOptionalThe number of tools to inject. If the value exceeds the number of available tools, all available tools are injected. For more information, see dynamic tool loading.
toolOutputFormatTOOL_OUTPUT_FORMATOptionalThe output format of the MCP server. Can be json or tabular. If not defined, tabular is used. See data transformation.
loggingLOGGINGOptionalIf true, the server runs in logging mode.
customerIdCUSTOMER_IDOptionalThe customerId to use in Customer-specific queries.
cartIdCART_IDOptionalThe cartId to use in Cart-specific queries.
storeKeySTORE_KEYOptionalThe storeKey to use in Store-specific queries.
businessUnitKeyBUSINESS_UNIT_KEYOptionalThe businessUnitKey to use in Business Unit-specific queries.

Dynamic tool loading

To optimize performance, the self-hosted Commerce MCP uses an efficient loading strategy for the enabled tools called dynamic tool loading. It limits the number of tools loaded at once and only loads additional tools as needed. This strategy reduces context usage when working with large numbers of tools.

The self-hosted Commerce MCP applies dynamic tool loading automatically when the number of enabled tools exceeds a configurable threshold. The default threshold is 30. To change it, use one of the following options:

  • the command-line argument --dynamicToolLoadingThreshold=50, or
  • the environment variable DYNAMIC_TOOL_LOADING_THRESHOLD=50.

When dynamic tool loading is active, the server does not expose every enabled tool at once. Instead, it provides the following meta-tools that let the agent discover and load the business tools it needs:

ToolDescription
list_available_toolsLists the available tools for a given resource type.
inject_toolsLoads a list of tools into the active context so the agent can call them.
execute_toolExecutes a tool by its method name and arguments.
The agent calls list_available_tools to discover the operations for a resource, then uses inject_tools or execute_tool to load or run them.

Authentication types

The self-hosted Commerce MCP supports two types of authentication:

Choose the authentication type that best fits your use case and provide the corresponding credentials.

Client credentials authentication

Use the --authType=client_credentials argument (or type: 'client_credentials' in TypeScript) and provide the API Client ID and secret using --clientId={CLIENT_ID} and --clientSecret={CLIENT_SECRET}.
Self-hosted Commerce MCP server configuration using client_credentials authenticationjson
{
  "servers": {
    "commerce-mcp": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "@commercetools/commerce-mcp@latest",
        "--authType=client_credentials",
        "--clientId={CLIENT_ID}",
        "--clientSecret={CLIENT_SECRET}",
        "--authUrl={AUTH_URL}",
        "--projectKey={PROJECT_KEY}",
        "--apiUrl={API_URL}",
        "--tools=read_all"
      ]
    }
  }
}

Auth token authentication

Use the --authType=auth_token argument (or type: 'auth_token' in TypeScript) and provide the existing access token using --accessToken={ACCESS_TOKEN}.
Self-hosted Commerce MCP server configuration using auth_token authenticationjson
{
  "servers": {
    "commerce-mcp": {
      "type": "stdio",
      "command": "npx",
      "args": [
        "-y",
        "@commercetools/commerce-mcp@latest",
        "--authType=auth_token",
        "--accessToken={ACCESS_TOKEN}",
        "--authUrl={AUTH_URL}",
        "--projectKey={PROJECT_KEY}",
        "--apiUrl={API_URL}",
        "--tools=read_all"
      ]
    }
  }
}

Transport protocol

The self-hosted Commerce MCP supports two transport protocols for communication between the MCP server and the client.

  • stdio: use this for development and testing locally on your machine.
  • Streamable HTTP: use this for remote deployments where your MCP server is hosted on a remote machine or in the cloud.

stdio

With stdio transport, you run the self-hosted Commerce MCP server locally. The server interacts with your client using standard input and output streams.
This is the transport used in Get started, where "type": "stdio" selects it in the server entry. Your exact configuration may differ depending on the software you use, but it follows the same shape shown there.

Streamable HTTP

The self-hosted Commerce MCP supports Streamable HTTP transport, allowing you to run the MCP server in a remote mode accessible over HTTP.

This setup is useful for deploying the MCP server in cloud environments or on remote machines.

In Streamable HTTP mode, every /mcp request must include a valid Authorization header that uses the Bearer scheme. If the header is missing or malformed, the server returns 401 Unauthorized immediately.
The Bearer token must be a valid commercetools OAuth access token and is passed through to the commercetools API as auth_token.

To run the self-hosted Commerce MCP server in remote mode, add the following arguments to the command:

Set up the self-hosted Commerce MCP server with all available tools and Streamable HTTP transportbash
npx -y @commercetools/commerce-mcp \
  --remote=true \
  --stateless=true \
  --port=8888 \
  --tools=all \
  --isAdmin=true \
  --dynamicToolLoadingThreshold=450 \
  --authType=client_credentials \
  --clientId={CLIENT_ID} \
  --clientSecret={CLIENT_SECRET} \
  --projectKey={PROJECT_KEY} \
  --authUrl={AUTH_URL} \
  --apiUrl={API_URL}
The clientId and clientSecret parameters are only needed to start the server and are not used to authenticate /mcp requests. Each request must supply its own Bearer token.

Use with Claude Desktop

To use a self-hosted Commerce MCP server running on port 8888, configure Claude Desktop with the following:
Claude Desktop configuration to use a self-hosted Commerce MCP server running on port 8888json
{
  "mcpServers": {
    "commercetools": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "--header",
        "Authorization: Bearer {ACCESS_TOKEN}",
        "http://localhost:8888/mcp"
      ]
    }
  }
}

Data transformation

By default, the self-hosted Commerce MCP transforms data fetched from tools and resources before sending it back to the LLM. This transformation converts JSON data into tabular format, with minor modifications to booleans and property names.

The transformed data format improves the quality of the data passed to the LLM, and in most cases reduces the token count. For deeply nested data structures, the token count may increase slightly instead.

The following example shows the original JSON object together with its transformed tabular format:

Original JSON output from a tooljson
{
  "customer": {
    "customerID": "12",
    "firstName": "John",
    "lastName": "Doe",
    "isActive": true,
    "isGoldCustomer": false
  }
}
Transformed tabular outputtext
Customer
  Customer ID: 12
  First Name: John
  Last Name: Doe
  Is Active: Yes
  Is Gold Customer: No
You can override this behavior by passing the --toolOutputFormat=json argument or setting the TOOL_OUTPUT_FORMAT environment variable. This setting makes self-hosted Commerce MCP bypass the data transformation and return the original JSON string instead.

Field filtering and data redaction

The self-hosted Commerce MCP supports resource-level field filtering and redaction. You can apply it in two ways:

  • On the built-in MCP server, by passing rules to the --fieldFiltering argument.
  • In a custom MCP, by setting the fieldFiltering option when creating a Commerce Agent.
Both ways accept the same rule configuration, a FieldFilteringManagerConfig. A custom MCP can additionally replace the filtering logic entirely.

Rule configuration

A FieldFilteringManagerConfig defines how fields are filtered or redacted, and applies to both the --fieldFiltering argument and the rules-based custom MCP approach.
PropertyDescription
typeString of value "filter" or "redact". Specify "filter" to remove the whole key and value, or "redact" to replace the value with a redaction text, leaving the key for context.
valueString representing the property value in which to apply the rule.
caseSensitiveBoolean dictating whether to apply the rule value with case sensitivity.
PropertyDescription
pathsOptional array of FieldFilteringRule objects for explicit object paths (for example, account.users.token).
propertiesOptional array of FieldFilteringRule objects for property names at any object depth (for example, token).
whitelistPathsOptional array of FieldFilteringRule objects for explicit object paths to retain, overriding all other rules (for example, account.users.passwordHint).
includesOptional array of FieldFilteringRule objects; all properties containing the given substring are filtered or redacted (for example, password).
jsonRedactionTextOptional string in which to replace redacted properties in JSON objects, default of "[REDACTED]".
urlRedactionTextOptional string in which to replace redacted properties in URL queries, default of "REDACTED".

Filter fields on the built-in MCP server

To apply rules to the built-in server, pass a stringified FieldFilteringManagerConfig JSON to the --fieldFiltering argument.

For macOS or Linux, use the following:

Set up the self-hosted Commerce MCP server with the fieldFiltering argument on macOS or Linuxbash
npx -y @commercetools/commerce-mcp \
  ... \
  --fieldFiltering="{\"jsonRedactionText\":\"CUSTOM REDACTION TEXT\",\"includes\":[{\"value\": \"token\",\"type\": \"redact\",\"caseSensitive\":true}]}"

For Windows, use the following:

Set up the self-hosted Commerce MCP server with the fieldFiltering argument on Windowsbash
npx -y @commercetools/commerce-mcp \
  ... \
  --fieldFiltering='{"jsonRedactionText":"CUSTOM REDACTION TEXT","includes":[{"value":"token","type":"redact","caseSensitive":true}]}'

Filter fields in a custom MCP

When building a custom MCP, pass the fieldFiltering option when creating a Commerce Agent. Choose one of the following approaches:
To apply rules, provide a FieldFilteringManagerConfig object:
Apply field filtering and redaction rulestypescript
import { Configuration } from '@commercetools/commerce-agent/modelcontextprotocol';

 const configuration: Configuration = {
    // ...
    context: {
      // ...
      fieldFiltering: {
        paths: [{caseSensitive: true, value: 'user.context.token', type: 'redact'}],
        properties: [{caseSensitive: false, value: 'META_DATA', type: 'filter'}],
        includes: [{caseSensitive: false, value: 'password', type: 'redact'}],
        whitelistPaths: [{caseSensitive: false, value: 'user.context.passwordHint'}],
        jsonRedactionText: "REDACTED_JSON",
        urlRedactionText: "REDACTED_QUERY"
      },
    },
  };
To override the filtering logic, provide a FieldFilteringManager implementation:
Override field filtering and redaction logictypescript
import { Configuration } from '@commercetools/commerce-agent/modelcontextprotocol';

 const configuration: Configuration = {
    // ...
    context: {
      // ...
      fieldFiltering: {
        filterFields: <T>(data: T): T extends number | boolean ? string | T : T => {
          // custom JSON filtering logic
          return data as any;
        },
        filterUrlFields: (url: string): string => {
          // custom URL filtering logic
          return url;
        },
      },
    },
  };

Logging

The self-hosted Commerce MCP supports logging (as of @commercetools/commerce-mcp version 2.3.0) to stdout. The server logs the mode, the correlationId, and the sessionId when running a remote server in stateful mode.
You can enable logging by passing the --logging=true argument or setting the LOGGING environment variable to true.
Configure the self-hosted Commerce MCP server with loggingbash
npx -y @commercetools/commerce-mcp \
  --tools=all \
  --authType=client_credentials \
  --clientId={CLIENT_ID} \
  --clientSecret={CLIENT_SECRET} \
  --projectKey={PROJECT_KEY} \
  --authUrl={AUTH_URL} \
  --apiUrl={API_URL} \
  --remote=true \
  --stateless=true \
  --port=8888 \
  --logging=true # log to stdout

Best practices

Select specific tools

For optimal security and performance, enable only the specific tools required for your use case. To do so, modify the tools argument or TOOLS environment variable to include only the tools you need.
For example, to set up only read-only tools, use --tools=read_all. To specify individual tools, provide a comma-separated list, such as --tools=read_carts,read_orders,create_products. For a complete list of all available tools, see Available tools.
Set up the self-hosted Commerce MCP server with product-related tools onlyjson
{
  "commerce-mcp": {
    "type": "stdio",
    "command": "npx",
    "args": [
      "-y",
      "@commercetools/commerce-mcp@latest",
      "--tools=create_products,read_products,update_products",
      "--authType=client_credentials",
      "--clientId={CLIENT_ID}",
      "--clientSecret={CLIENT_SECRET}",
      "--authUrl={AUTH_URL}",
      "--projectKey={PROJECT_KEY}",
      "--apiUrl={API_URL}"
    ]
  }
}

Use administrator privileges only when necessary

Many examples on this page use the --isAdmin=true argument, which provides full access to all resources, settings, and operations in your Project.
This is useful during the exploration and experimentation phase, but should be used with caution in real-world scenarios.
When administrator privileges are not necessary, omit --isAdmin=true so that access is restricted to only the tools specified in the --tools argument.

Commerce Agent

Commerce Agent allows agent frameworks to interact with your Project through function calling. It supports a subset of the commercetools API using the TypeScript SDK.
Commerce Agent requires Node.js 18 (or later). To get started, install @commercetools/commerce-agent:
Install Commerce Agentbash
npm install @commercetools/commerce-agent

Usage

Configure Commerce Agent with your API Client credentials. Your API Client must include scopes for each enabled action. For example, to enable read_products, the API Client must have the view_products scope.
The following examples use environment variables to load the credentials, which you can save after creating your API Client.
The available actions are defined in configuration.actions.
The following example configures Commerce Agent for LangChain. AI SDK by Vercel and Mastra use the same authConfig and configuration shape.
Configure Commerce Agent with LangChaintypescript
import { CommercetoolsCommerceAgent } from '@commercetools/commerce-agent/langchain';

const commercetoolsCommerceAgent = new CommercetoolsCommerceAgent({
  authConfig: {
    type: 'client_credentials',
    clientId: process.env.CLIENT_ID!,
    clientSecret: process.env.CLIENT_SECRET!,
    projectKey: process.env.PROJECT_KEY!,
    authUrl: process.env.AUTH_URL!,
    apiUrl: process.env.API_URL!,
  },
  configuration: {
    actions: {
      products: {
        read: true,
        create: true,
        update: true,
      },
      project: {
        read: true,
      },
    },
  },
});

Tools

The toolkit integrates with LangChain and AI SDK by Vercel. Pass the tools to your agent executor:

Pass tools to an agent executortypescript
import { AgentExecutor, createStructuredChatAgent } from 'langchain/agents';

const tools = commercetoolsCommerceAgent.getTools();

const agent = await createStructuredChatAgent({
  llm,
  tools,
  prompt,
});

const agentExecutor = new AgentExecutor({
  agent,
  tools,
});

Expose your own MCP server over stdio

You can also expose your own MCP server using CommercetoolsCommerceAgent:
Expose your own MCP server with stdio transporttypescript
import { CommercetoolsCommerceAgent } from '@commercetools/commerce-agent/modelcontextprotocol';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new CommercetoolsCommerceAgent({
  authConfig: {
    type: 'client_credentials',
    clientId: process.env.CLIENT_ID!,
    clientSecret: process.env.CLIENT_SECRET!,
    projectKey: process.env.PROJECT_KEY!,
    authUrl: process.env.AUTH_URL!,
    apiUrl: process.env.API_URL!,
  },
  configuration: {
    actions: {
      products: {
        read: true,
      },
      cart: {
        read: true,
        create: true,
        update: true,
      },
    },
  },
});

async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('My custom commercetools MCP Server running on stdio');
}

main().catch((error) => {
  console.error('Fatal error in main():', error);
  process.exit(1);
});

Expose your own MCP server over Streamable HTTP (Express)

You can run the Streamable HTTP server with Commerce Agent like an SDK and build on it. Choose one of the following two approaches.

Option A: wrap an agent factory

Provide a server factory function that creates a CommercetoolsCommerceAgent for each session, and pass it to CommercetoolsCommerceAgentStreamable. Use this approach when you want the agent configured per session, for example to run stateful sessions.
Set up a Streamable HTTP server with CommercetoolsCommerceAgenttypescript
import {
  CommercetoolsCommerceAgent,
  CommercetoolsCommerceAgentStreamable,
} from '@commercetools/commerce-agent/modelcontextprotocol';
import express from 'express';

const expressApp = express();

async function agentServer(id: string) {
  // `id` is the session-id for`stateful` instance
  // that is the `stateless` option is set to false
  // console.log(id) -> f81d4fae-7dec-11d0-a765-00a0c91e6bf6
  return CommercetoolsCommerceAgent.create({
    authConfig: {
      type: 'client_credentials',
      clientId: process.env.CLIENT_ID!,
      clientSecret: process.env.CLIENT_SECRET!,
      projectKey: process.env.PROJECT_KEY!,
      authUrl: process.env.AUTH_URL!,
      apiUrl: process.env.API_URL!,
    },
    configuration: {
      actions: {
        products: {
          read: true,
        },
        cart: {
          read: true,
          create: true,
          update: true,
        },
        ...
      },
    },
  });
}

const serverStreamable = new CommercetoolsCommerceAgentStreamable({
  stateless: false, // make the MCP server stateless/stateful
  streamableHttpOptions: {
    sessionIdGenerator: undefined,
  },
  server: agentServer,
  app: expressApp, // optional express app instance
});

serverStreamable.listen(8888, function () {
  console.log('listening on 8888');
});

Option B: use the streamable class directly

Instantiate CommercetoolsCommerceAgentStreamable on its own, passing authConfig and configuration inline, without a separate CommercetoolsCommerceAgent.
Set up a Streamable HTTP server with CommercetoolsCommerceAgentStreamabletypescript
import { CommercetoolsCommerceAgentStreamable } from '@commercetools/commerce-agent/modelcontextprotocol';
import express from 'express';

const expressApp = express();

const server = new CommercetoolsCommerceAgentStreamable({
  authConfig: {
    type: 'client_credentials',
    clientId: process.env.CLIENT_ID!,
    clientSecret: process.env.CLIENT_SECRET!,
    projectKey: process.env.PROJECT_KEY!,
    authUrl: process.env.AUTH_URL!,
    apiUrl: process.env.API_URL!,
  },
  configuration: {
    actions: {
      project: {
        read: true,
      },
      // other tools can go here
    },
  },

  stateless: false,
  streamableHttpOptions: {
    sessionIdGenerator: undefined,
  },
  app: expressApp,
});

server.listen(8888, function () {
  console.log('listening on 8888');
});

Mastra

Like the @commercetools/commerce-agent/langchain library, the @commercetools/commerce-agent/mastra essentials library can execute multi-step commands or prompts.

For example:

Run a multi-step task sequence with Mastratypescript
require('dotenv').config();
import { Agent } from '@mastra/core/agent';
import { CommercetoolsCommerceAgent } from '@commercetools/commerce-agent/mastra';

const commercetoolsCommerceAgent = new CommercetoolsCommerceAgent({
  authConfig: {
    type: 'client_credentials',
    clientId: process.env.CLIENT_ID!,
    clientSecret: process.env.CLIENT_SECRET!,
    authUrl: process.env.AUTH_URL!,
    projectKey: process.env.PROJECT_KEY!,
    apiUrl: process.env.API_URL!,
  },
  configuration: {
    actions: {
      products: {
        read: true,
        create: true,
        update: true,
      },
      'product-type': {
        read: true,
        create: true,
      },
    },
  },
});

const agent = new Agent({
  name: 'CommercetoolsAgent',
  instructions:
    'You are a helpful agent that can manage products and product types in commercetools. Use the available tools to help users with their commerce operations.',
  tools: commercetoolsCommerceAgent.getTools(),
});

(async () => {
  console.log('--- Starting Commercetools Mastra Agent Task Sequence ---');

  // Task 1: List all products
  const task1Prompt =
    'List all products in the commercetools project. This is the first step.';
  console.log(`\nExecuting: ${task1Prompt}`);
  const result1 = await agent.step({
    messages: [
      {
        role: 'user',
        content: task1Prompt,
      },
    ],
  });
  console.log('--- Response from "List all products" ---');
  console.log(result1.text);
  console.log(
    '--------------------------------------------------------------------------------'
  );

  // Task 2: List product types
  const task2Prompt = `Based on the products listed above, list all product types available in the project. I need this information to select a product type for creating a product in the next step.`;
  console.log(`\nExecuting: ${task2Prompt}`);
  const result2 = await agent.step({
    messages: [
      ...result1.messages,
      {
        role: 'user',
        content: task2Prompt,
      },
    ],
  });
  console.log('--- Response from "List product types" (Task 2) ---');
  console.log(result2.text);
  console.log(
    '--------------------------------------------------------------------------------'
  );

  // Task 3: Create a test product
  const productName = `Mastra Test Product ${Math.floor(Date.now() / 1000)}`;
  const productKey = `MASTRA-${Math.random().toString(36).substring(2, 9).toUpperCase()}`;
  const productSku = `MTP-${Math.random().toString(36).substring(2, 9).toUpperCase()}`;
  const productSlug = productName.toLowerCase().replace(/\s+/g, '-');

  const task3Prompt = `Based on the product types listed above, please create a new product with the following details:
- Name: "${productName}"
- Key: "${productKey}"
- Slug (en): "${productSlug}"
- SKU: "${productSku}"
- Description: "This product was created automatically by a Mastra AI agent."
- Ensure the product is published if possible during creation.

Please provide the ID of this newly created product in your response.`;
  console.log(`\nExecuting: ${task3Prompt}`);
  const result3 = await agent.step({
    messages: [
      ...result2.messages,
      {
        role: 'user',
        content: task3Prompt,
      },
    ],
  });
  console.log('--- Response from "Create test product" (Task 3) ---');
  console.log(result3.text);
  console.log(
    '--------------------------------------------------------------------------------'
  );

  // Task 4: Update the product
  const updateDescription =
    'This product was created and then updated automatically by a Mastra AI agent.';
  const task4Prompt = `Using the ID of the product that was just created (from the previous step), please update it.
Change its description to "${updateDescription}"`;
  console.log(`\nExecuting: ${task4Prompt}`);
  const result4 = await agent.step({
    messages: [
      ...result3.messages,
      {
        role: 'user',
        content: task4Prompt,
      },
    ],
  });
  console.log('--- Response from "Update the product" (Task 4) ---');
  console.log(result4.text);
  console.log(
    '--------------------------------------------------------------------------------'
  );

  console.log('\n--- Commercetools Mastra Agent Task Sequence Finished ---');
})().catch((error) => {
  console.error('An error occurred during the async execution:', error);
});

Get tools

The getTools() method returns the list of tools available to your agent.
Get the available toolstypescript
const tools = commercetoolsCommerceAgent.getTools();

Custom tools

The self-managed @commercetools/commerce-agent library supports custom tools. The bootstrapping MCP server can pass and register a list of custom tool implementations at runtime. This is useful when the tools you need are not implemented, and gives you full control over how tools interact with the underlying LLM.

Usage

The following example registers a Get Products in Category tool. It returns the published Products in a Category and lets the agent optionally sort them by creation date.
Register custom tools at runtimetypescript
import { CommercetoolsCommerceAgent } from "@commercetools/commerce-agent/modelcontextprotocol";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";

const server = await CommercetoolsCommerceAgent.create({
  authConfig: {...},
  configuration: {
    customTools: [
      {
        name: "Get Products in Category",
        method: "get_products_in_category",
        description: `Returns the published Products in a Category, sorted by creation date.\n\nProvide "categoryId" to select the Category. Optionally set "sort" to "ASC" or "DESC" to order results by "createdAt". If "sort" is omitted, the newest Products are returned first.`, // Ensure the description clearly states what the tool does and which parameters it accepts.
        parameters: z.object({
          categoryId: z
            .string()
            .describe("The ID of the Category to return Products for."),
          sort: z
            .enum(["ASC", "DESC"])
            .optional()
            .default("DESC")
            .describe(
              'Order the results by "createdAt". "ASC" returns the oldest Products first, "DESC" the newest.'
            ),
        }),
        actions: {},
        execute: async (
          args: { categoryId: string; sort?: "ASC" | "DESC" },
          api: ApiRoot
        ) => {
          // The Query ProductProjections endpoint returns only published
          // Products by default, so no `staged` argument is required.
          const response = await api
            .withProjectKey({ projectKey: process.env.PROJECT_KEY! })
            .productProjections()
            .get({
              queryArgs: {
                where: `categories(id="${args.categoryId}")`,
                sort: `createdAt ${args.sort === "ASC" ? "asc" : "desc"}`,
              },
            })
            .execute();
          return JSON.stringify(response.body);
        },
      },
      ...
    ],
    actions: {...},
  },
});
...