Skip to main content

Overview

Built-in MCP servers live in the packages/ directory and run as stdio processes alongside the platform. Each server exposes tools that agents can call during a run.
Just want to connect an existing MCP server without modifying the platform? See Add a Custom MCP Server to add one through the portal UI.
muxAI ships with several built-in servers you can reference as examples:
PackagePatternWhat it does
mcp-chart-analystDirect toolFetches a chart image URL and returns base64 for Claude to analyze
mcp-news-analystAggregatorPulls RSS feeds and CoinMarketCap data, filters by currency
mcp-docsProxyForwards requests to a remote Mintlify MCP endpoint
mcp-orchestratorInternal APICalls the muxAI API to invoke reporter agents
mcp-contractorInternal APICalls the muxAI API to consult external LLM contractors
mcp-walletInternal APIProvides wallet access and x402 payment for agents

Quick start

1. Create the package

mkdir packages/mcp-my-server
Create packages/mcp-my-server/index.js:
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";

const server = new Server(
  { name: "my-server", version: "1.0.0" },
  { capabilities: { tools: {} } }
);

// List available tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: "my_tool",
      description: "Describe what this tool does so the agent knows when to use it.",
      inputSchema: {
        type: "object",
        properties: {
          query: {
            type: "string",
            description: "The input parameter",
          },
        },
        required: ["query"],
      },
    },
  ],
}));

// Handle tool calls
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  if (name === "my_tool") {
    // Your logic here
    const result = `You said: ${args.query}`;
    return {
      content: [{ type: "text", text: result }],
    };
  }

  throw new Error(`Unknown tool: ${name}`);
});

const transport = new StdioServerTransport();
await server.connect(transport);
All MCP server dependencies live in the root package.json, not in individual packages. This keeps installs fast and avoids duplication.

2. Register in the MCP registry

Add an entry to config/mcp-registry.json:
{
  "id": "my-server",
  "label": "My Server",
  "description": "A short description shown on the MCP Servers page.",
  "command": "node",
  "args": ["packages/mcp-my-server/index.js"],
  "tools": [
    {
      "name": "my_tool",
      "fullName": "mcp__my-server__my_tool",
      "description": "Same description as in the tool definition."
    }
  ]
}
The tools array is metadata for the UI. The actual tool definitions come from your server’s ListToolsRequestSchema handler.

3. Test it

Restart the platform (pnpm dev) and check the MCP Servers page. Your server should appear in the Built-in section. Create an agent in Built-in MCP mode and it will have access to your tool.

Common patterns

Direct tool

The simplest pattern. Your server does the work directly — call an API, process data, return results. See mcp-chart-analyst for a clean example: it fetches an image URL, converts to base64, and returns it for Claude to analyze.
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { image_url } = request.params.arguments;
  const res = await fetch(image_url);
  const buffer = await res.arrayBuffer();
  const base64 = Buffer.from(buffer).toString("base64");

  return {
    content: [
      { type: "text", text: "Analyze this chart:" },
      { type: "image", data: base64, mimeType: "image/png" },
    ],
  };
});

Proxy to a remote MCP

When an MCP server already exists as a remote HTTP endpoint, you can wrap it as a local stdio server. This keeps the registry format consistent and lets the platform manage it like any other built-in. See mcp-docs for this pattern: it proxies to a Mintlify MCP endpoint.
const REMOTE_MCP_URL = "https://example.com/mcp";

async function proxyRequest(method, params = {}) {
  const res = await fetch(REMOTE_MCP_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ jsonrpc: "2.0", id: 1, method, params }),
  });
  const json = await res.json();
  if (json.error) throw new Error(json.error.message);
  return json.result;
}

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const result = await proxyRequest("tools/call", {
    name: request.params.name,
    arguments: request.params.arguments,
  });
  return { content: [{ type: "text", text: result?.content?.[0]?.text }] };
});

Internal API access

Servers that need to call the muxAI API (like the orchestrator or contractor servers) can use environment variables injected at runtime:
  • MUXAI_API_URL — the API base URL (e.g. http://localhost:3001)
  • MUXAI_INTERNAL_SECRET — secret for authenticated internal requests
  • MUXAI_AGENT_ID, MUXAI_AGENT_NAME, MUXAI_AGENT_ROLE — the calling agent’s identity
const API_URL = process.env.MUXAI_API_URL || "http://localhost:3001";
const SECRET = process.env.MUXAI_INTERNAL_SECRET;

async function apiCall(path) {
  const res = await fetch(`${API_URL}${path}`, {
    headers: { "x-muxai-internal": SECRET },
  });
  return res.json();
}

Return types

MCP tools can return different content types:
// Text
{ content: [{ type: "text", text: "Hello" }] }

// Image (base64)
{ content: [{ type: "image", data: base64String, mimeType: "image/png" }] }

// Error
{ content: [{ type: "text", text: "Something went wrong" }], isError: true }

// Mixed (text + image)
{ content: [
  { type: "text", text: "Here is the chart analysis:" },
  { type: "image", data: base64String, mimeType: "image/jpeg" },
] }

Settings toggle (optional)

If you want users to be able to enable or disable your server from the Settings page, add an entry to the BUILTIN_SERVERS array in apps/web/src/app/settings/page.tsx:
const BUILTIN_SERVERS = [
  // ... existing servers
  { id: "my-server", label: "My Server", description: "Short description shown in settings" },
];
The platform already reads the mcp_disabled_servers setting at runtime and filters out any disabled server IDs when building the MCP config. No backend changes needed — just the UI entry.
Servers listed in CORE_SERVERS (like wallet and orchestrator) cannot be toggled off. Only add your server there if it is essential for platform operation.

Tips

  • Logging: Use process.stderr.write() for debug output. MCP uses stdout for protocol messages, so console.log will break things.
  • Timeouts: Always set AbortSignal.timeout() on fetch calls to avoid hanging the agent’s run.
  • Tool descriptions: Write descriptions as if briefing an agent. Be specific about what the tool does and when to use it. The agent decides which tools to call based on these descriptions.
  • Naming: Use snake_case for tool names. The full tool name the agent sees is mcp__<server-id>__<tool-name>.
  • No package.json needed: Dependencies go in the root package.json. Just import what you need.