
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:
powershellInvoke-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

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.clsglobal 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:
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:

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.