Getting started6-minute read7 languages

Build Your First MCP Code Tool: ERP Orders and CRM Account in One Call

A step-by-step tutorial for AnythingMCP code mode: start the code runner, turn code mode on, and save a code tool that reads the last orders from the ERP (weclapp) and the account and open deals from the CRM (HubSpot) in one tool call. Read-only, with the caller's permissions, audited.

HCBy HelpCode teamUpdated 6 min read Open-source on GitHub

No credit card · 7-day trial · Self-host alternative available

  • 7-day free trial
    No credit card required
  • GDPR & SOC 2 ready
    EU data residency, audit logs
  • Open-source on GitHub
    Open source · AGPL-3.0
  • Works with ChatGPT, Claude, Gemini
    Any MCP-compatible client

Summary

A step-by-step tutorial for AnythingMCP code mode: start the code runner, turn code mode on, and save a code tool that reads the last orders from the ERP (weclapp) and the account and open deals from the CRM (HubSpot) in one tool call. Read-only, with the caller's permissions, audited.

TL;DR: Before a customer call, a sales rep asks the assistant for an overview: the last orders from the ERP and the account owner and open deals from the CRM. Without code mode that is four or five tool calls, each read in full by the model. With a saved code tool it is one: customer_overview(customer) runs a short JavaScript program that makes the calls, keeps what matters and returns one compact summary. This tutorial builds that tool on a self-hosted AnythingMCP (v0.52.1 or later) with weclapp as the ERP and HubSpot as the CRM; the same pattern works with any two connectors.

What you need

  • Self-hosted AnythingMCP v0.52.1 or later. Code mode is coming soon to AnythingMCP Cloud.
  • Two connectors with read tools, here weclapp and HubSpot, installed and working.
  • Administrator rights in the workspace.

New to the idea? What is MCP code mode? explains why one program beats many tool calls.

Step 1 — Start the code runner

Code never runs inside AnythingMCP itself. It runs in a separate code-runner container, and without it every code run is refused. Add to your .env:

COMPOSE_PROFILES=code-runner
CODE_RUNNER_URL=ws://code-runner:8777

If you already use profiles, list them together, for example COMPOSE_PROFILES=files,code-runner. Then:

docker compose up -d

The runner holds no secrets, publishes no ports and has no route to the internet; only the backend talks to it, on the internal Docker network. How it isolates each run: Secure code execution for AI agents.

Step 2 — Turn on code mode for the MCP server

Code mode is off per MCP server by default, and saved code tools are refused on a server whose code mode is off. Set the code mode of the server your sales team uses to tools (saved code tools only). inline additionally offers the agent code_execute and code_propose_tool; you do not need it for this tutorial.

In the dashboard, open MCP Servers and then the server. In the Server settings card at the top, set Code mode to Saved code tools only and click Save. Reconnect the AI client afterwards.

Prefer the API? PUT /api/mcp-servers/<id> with {"codeMode": "tools"} does the same. The setting arrived in v0.52.1; on v0.52.0, upgrade first.

Step 3 — Note the two connector ids

A code tool declares which connectors its program may call. Open each connector in the dashboard: its id is the last part of the address, /connectors/<id>. Copy the ids of the weclapp and the HubSpot connector.

Step 4 — Create the code tool

Open the weclapp connector, go to MCP Tools and click Add Tool. Set Tool Type to Code Tool, then fill in:

  • Tool Name: customer_overview
  • Description: "Overview of one customer before a call: the last 10 orders from the ERP, the CRM account and its open deals. Pass the company name or part of it." The description is what the AI reads to decide when to call the tool, so say what it returns.
  • Input Parameters: one parameter, customer, type string, required.
anythingmcp.internal/connectors
Edit Tool form of a connector named Accounting: tool name revenue_by_month, Tool Type Code Tool, a Program field with JavaScript that calls amcp.run, the Uses (connectors) JSON with a connectorId, Run as Invoker (the caller's own permissions), the Allow write tools checkbox unticked, and Input Parameters
The tool editor with Tool Type Code Tool, here for a different example tool (revenue_by_month) in a demo workspace. Program, Uses, Run as and Allow write tools are the fields this tutorial fills in.

Step 5 — Write the program

The Program is the body of an async JavaScript function. The tool's inputs are in args; amcp.run(name, args) calls another tool and returns its result as parsed JSON (and throws if the tool fails). Whatever you return is what the AI receives.

// Input: args.customer, a company name or part of it.
const name = String(args.customer ?? '').trim();
if (!name) return { error: 'Pass a customer name.' };

// ERP (weclapp): the customer, then its latest sales orders.
const parties = await amcp.run('weclapp_list_customers', {
  filter: `company-ilike=%${name}%`,
  pageSize: 5,
});
const customer = (parties.result ?? [])[0];
if (!customer) return { error: `No customer matching "${name}" in the ERP.` };

const orders = await amcp.run('weclapp_list_sales_orders', {
  filter: `customerId-eq=${customer.id}`,
  sort: '-orderDate',
  pageSize: 10,
});

// CRM (HubSpot): the company record and its open deals.
const companies = await amcp.run('hubspot_search_objects', {
  object_type: 'companies',
  query: customer.company,
  properties: ['name', 'domain', 'hubspot_owner_id', 'lifecyclestage'],
  limit: 1,
});
const company = companies.results?.[0];

let deals = [];
if (company) {
  const found = await amcp.run('hubspot_search_objects', {
    object_type: 'deals',
    filter_groups: [{ filters: [
      { propertyName: 'associations.company', operator: 'EQ', value: company.id },
      { propertyName: 'hs_is_closed', operator: 'EQ', value: 'false' },
    ] }],
    properties: ['dealname', 'amount', 'dealstage', 'closedate'],
    limit: 20,
  });
  deals = found.results ?? [];
}

// Only this goes back to the model.
return {
  customer: { number: customer.customerNumber, name: customer.company },
  lastOrders: (orders.result ?? []).map((o) => ({
    number: o.orderNumber, date: o.orderDate, status: o.status, net: o.netAmount,
  })),
  crm: company
    ? { owner: company.properties.hubspot_owner_id, stage: company.properties.lifecyclestage }
    : null,
  openDeals: deals.map((d) => ({
    name: d.properties.dealname, amount: d.properties.amount,
    stage: d.properties.dealstage, close: d.properties.closedate,
  })),
};

Field names are the ones the weclapp and HubSpot APIs return. Before you rely on a field, run the underlying tool once in its Test panel and look at the result; inside the program, await amcp.describe('weclapp_list_sales_orders') returns a tool's input schema.

A note on the join. This program matches the CRM company by name, which is good enough for a briefing. If you store the ERP customer number in a HubSpot property, filter on that instead and the match is exact.

Step 6 — Declare what the program may use

Uses (connectors) lists the connectors, and optionally the exact tools, the program may call. Paste the two ids from step 3:

[
  { "connectorId": "<weclapp connector id>", "tools": ["weclapp_list_customers", "weclapp_list_sales_orders"] },
  { "connectorId": "<hubspot connector id>", "tools": ["hubspot_search_objects"] }
]

Naming the tools is optional (without "tools" the whole connector is allowed), but a short list makes the tool easy to review. Only connectors of the same workspace are accepted. A tool outside this list fails with an error, even if the user could call it directly.

Step 7 — Run as invoker, read-only

  • Run as: Invoker (the caller's own permissions). Every inner call is checked against the role of the person using the tool. A rep without HubSpot access gets an error on the HubSpot part, exactly as if they had called HubSpot directly. (Definer runs with the author's curation instead, like a stored procedure; it is for administrators and Enterprise.)
  • Allow write tools: off. The program may then call only read-only tools; weclapp_list_* and hubspot_search_objects are reads. Write-enabled code tools need Enterprise.

Click Create Tool.

Step 8 — Try it from Claude or ChatGPT

Check the program first with the tool's Test button in the dashboard: it runs the code through the code runner, with the same rules as a real call. Then try it from an AI client connected to the MCP server. The tool is available on every server that has the weclapp connector and code mode turned on. Reconnect or refresh the tools in your AI client and ask:

"I'm calling Lindenhof Bau in ten minutes. What did they order recently, who owns the account, and are there open deals?"

The assistant calls customer_overview once and writes the briefing from the compact result, instead of reading full order lists and CRM records. If something is wrong, the tool result says what: a call outside Uses ('hubspot_search_objects' is not a tool this code tool can use), an error returned by the API, or a timeout. Fix the program, save, and ask again.

What happens on every call

  • One run, one record. Each call of the tool is recorded as a code run, and every tool call the program makes is in the audit log like any other call, with the user.
  • Limits. A run may take up to 10 seconds, make up to 500 tool calls (16 at a time) and return up to 1 MB. A program that exceeds a limit stops with a clear error and affects nothing else.
  • No nesting. A code tool cannot call another code tool.
  • Only on your servers. Code tools are served on the workspace's own MCP servers, never on the shared /mcp endpoint.

Plans

Saved code tools per edition, as announced with v0.52: Community 3, Starter 5, Team 20, Enterprise unlimited. Write-enabled code tools and "run as definer" are Enterprise. Compare plans.

FAQ

Do I need to know JavaScript? Basic JavaScript is enough: await, objects, map and filter. You can also let the agent draft the program and propose it as a tool for you to review.

Can the program call an external API directly? No. It has no network access. It can only call the MCP tools listed in Uses; to reach another API, add it as a connector first.

Which connector should the code tool live on? Any connector of the ones it uses; the tool shows up wherever that connector is assigned. Pick the one whose audience should get the tool.

Was this guide helpful?

Ship MCP to your stack in 60 seconds.

Spin up AnythingMCP on managed Cloud or self-host it on your infrastructure. Free for 7 days, no credit card.

Related guides

GS
Getting started

Code Mode: Cut AI Agent Token Usage by Running Tools in One Step

Code mode lets an AI agent run a short program that orchestrates many MCP tools in a single call, instead of one round trip per tool. Filtering and joining happen in a locked-down sandbox, so only the final answer returns — cutting token usage on data-heavy workflows by up to 90–99%.

GS
Getting started

MCP Code Mode Recipes: Monthly Revenue, Invoice Reconciliation and 100 Price Lookups in One Call

Three practical code mode programs for AnythingMCP: monthly revenue across the online shop (Shopify) and accounting (Lexware Office), orders reconciled against invoices, and 100 customer price lookups in one tool call instead of 100. With the code, the limits that matter, and when to save a program as a tool.

GS
Getting started

What Is MCP Code Mode? Fewer Tokens and Round Trips for AI Agents

Code mode lets an AI agent run one short program that calls several MCP tools, instead of calling them one at a time through the model. Why that saves tokens and round trips, when it helps and when it does not, and how AnythingMCP runs it: saved code tools, inline code and agent proposals, in an isolated runner.

GS
Getting started

Connect AnythingMCP to Claude: One URL for All Your Business APIs

Add AnythingMCP as a Claude connector in five minutes. Sign in, pick the MCP servers Claude may reach, and query weclapp, DATEV, Etsy, Deutsche Bahn or your own API in plain language.

GS
Getting started

The MCP Knowledge Graph: a connector that learns how your systems connect

AnythingMCP isn't just an MCP proxy — it builds a knowledge graph of your tools so AI agents know how to chain a customer in your CRM to their orders in your ERP. Here's how the smart connector works.

GS
Getting started

What is the Model Context Protocol (MCP)?

A comprehensive guide to understanding the Model Context Protocol — how it works, why it matters, and how to get started.