How to Run your MCP Servers from the Command Line Interface to Call Multiple Tools

Nand Kishore

banner

This walkthrough shows how to run your MCP servers from the command line, call tools and fix the extensionCall error we hit. Everything is written in simple steps so you can repeat it later.

1) What we already have

Your repo already has two MCP servers configured in:

d:\mcp.vscode\mcp.json

They are:

  • Salesforce MCP (runs npx @advanced-communities/salesforce-mcp-server)
  • Custom MCP (runs node server.js in my-mcp-server/)

Your Custom MCP server exposes tools like:

  • getObjectFields
  • extensionCall (calls Apex Extension.call)

There is also a local HTTP server that lets us call MCP tools from the CLI:

my-mcp-server/ui-server.js

That server reads .vscode/mcp.json and exposes endpoints like:

  • GET /api/mcp/servers
  • GET /api/mcp/tools
  • `POST /api/mcp/call**
  • POST /api/agent (natural language prompts via GPT)

2) Start the local MCP runner

From the repo root:

npm --prefix my-mcp-server run ui

This starts the local server at:

http://localhost:3000


3) List MCP servers

Invoke-RestMethod http://localhost:3000/api/mcp/servers

You should see something like:

{ "servers": ["Salesforce MCP", "Custom MCP"] }

4) List tools on a server

Example for the Custom MCP server:

Invoke-RestMethod "http://localhost:3000/api/mcp/tools?server=Custom%20MCP" | ConvertTo-Json -Depth 20

You will see tool names and their input schemas.


5) Call a tool directly from the CLI

Example: get fields for Account:

powershell
Invoke-RestMethod http://localhost:3000/api/mcp/call ` -Method Post ` -ContentType "application/json" ` -Body '{"server":"Custom MCP","tool":"getObjectFields","args":{"objectName":"Account"}}'

You will see the output like this:

result------ @{content=System.Object[]; structuredContent=}

That output is normal, PowerShell is showing the result object, but it isn’t expanding the content array.

To see the detailed output execute this command:

((Invoke-RestMethod http://localhost:3000/api/mcp/call -Method Post -ContentType "application/json" ` -Body '{"server":"Custom MCP","tool":"getObjectFields","args":{"objectName":"Account"}}').result.content | Where-Object { $_.type -eq "text" }).text

img1

6) Use natural language prompts (optional)

If you want to type prompts like a chat (GPT chooses tools automatically), you need an OpenAI key in .env:

OPENAI_API_KEY=your_key_here

Then run:

Invoke-RestMethod http://localhost:3000/api/agent -Method Post -ContentType "application/json" ` -Body '{"prompt":"show the 10 most recent Account records"}' | ConvertTo-Json -Depth 20

To show only the final response:

(Invoke-RestMethod http://localhost:3000/api/agent -Method Post -ContentType "application/json" ` -Body '{"prompt":"show the 10 most recent Account records"}').text

7) Call your Apex Extension via extensionCall

You created this Apex class:

Extension.cls
global class Extension implements Callable { String concatStrings(String stringValue) { return stringValue + stringValue; } Decimal squareNumbers(Decimal decimalValue) { return decimalValue * decimalValue; } global Object call(String action, Map<String, Object> args) { switch on action { when 'concatStrings' { return this.concatStrings((String)args.get('stringValue')); } when 'squareNumbers' { return this.squareNumbers((Decimal)args.get('decimalValue')); } when else { throw new ExtensionMalformedCallException('Method not implemented'); } } } global class ExtensionMalformedCallException extends Exception {} }

extensionCall needs:

Free newsletter

Get Salesforce insights delivered weekly

Join 500+ Salesforce professionals and stay ahead of the curve.

No spam. Unsubscribe any time.

action (required): the Apex method name
args (optional): object of parameters

Example call:

Invoke-RestMethod http://localhost:3000/api/mcp/call -Method Post -ContentType "application/json" -Body '{"server":"Custom MCP","tool":"extensionCall","args":{"action":"YourMethodName","args":{"param1":"value1"}}}' | ConvertTo-Json -Depth 20

Use it from the CLI like this:

Invoke-RestMethod http://localhost:3000/api/mcp/call -Method Post -ContentType "application/json" ` -Body '{"server":"Custom MCP","tool":"extensionCall","args":{"action":"concatStrings","args":{"stringValue":"abc"}}}' | ConvertTo-Json -Depth 10

For Square:

Invoke-RestMethod http://localhost:3000/api/mcp/call -Method Post -ContentType "application/json" ` -Body '{"server":"Custom MCP","tool":"extensionCall","args":{"action":"squareNumbers","args":{"decimalValue":3.5}}}' | ConvertTo-Json -Depth 10

You will see output like this:

img2

8) Fixing the "Invalid input: expected record" error

If you hit this error when calling extensionCall:

Invalid tools/call result: expected record, received string

Why it happened:

  • The MCP tool response included structuredContent: result
  • But the Apex result was just a string
  • MCP requires structuredContent to be an object/record

Fix:

In my-mcp-server/server.js, remove structuredContent for extensionCall so it only returns content text.

return { content: [{ type: "text", text: JSON.stringify(result) }], structuredContent: result //Remove this line };

This change makes string results valid.

9) Summary (quick checklist)

  • Start the UI server: npm --prefix my-mcp-server run ui
  • List servers: GET /api/mcp/servers
  • List tools: GET /api/mcp/tools?server=Custom%20MCP
  • Call tools: POST /api/mcp/call
  • Natural language prompts: POST /api/agent (needs OpenAI key)
  • If tool returns a string, do not set structuredContent

Here’s the end‑to‑end flow for a CLI tool call, mapped to the files/classes you have now:

1) CLI client starts the MCP server (stdio)

  • Client spawns node server.js (your MCP server).

  • Transport is stdio, so messages are JSON‑RPC over stdin/stdout.

2) MCP server initializes

  • server.js creates McpServer.

  • Tools are registered, including extensionCall.

3) Client calls the tool

  • CLI sends tools/call with:
{ "name": "extensionCall", "arguments": { "action": "...", "args": {...} } }

4) MCP server handles the tool

  • The extensionCall handler runs in server.js.

  • It calls getSfConnection() to build a jsforce.Connection using env vars.

5) MCP server calls Salesforce

  • Handler POSTs to Apex REST:
/ExtensionCall/

with { action, args }.

6) Apex REST executes the callable class

  • ExtensionCallApi (Apex REST class) receives the POST.

  • It instantiates Extension and calls:

extension.call(action, args)
  • Extension.call dispatches to the correct method and returns a result.

7) Result flows back

  • Apex REST returns JSON (e.g., { "result": 16 }).

  • MCP tool returns it as:

    • content text (stringified JSON)

    • structuredContent (raw JSON)

  • CLI prints the tool result.



Related Articles

Ready to implement? Learn more about our Agentforce Implementation Services.

Get a Free Consultation