sodcalculator

10 Tech How-To

How to Build an MCP Server for a Mac App

Guide 10-8122 · Updated · Step by step

This guide walks you through creating a Model Context Protocol (MCP) server that integrates with a Mac app, giving AI assistants like Claude or custom tools secure, controlled access to your app’s data and actions. You’ll build a local server that exposes your app’s features as tools, then connect it to an MCP client. No prior MCP experience is needed, but you should be comfortable with the command line and have Node.js installed.

What You’ll Need

  • A Mac running macOS 12 or later
  • Node.js 18 or newer (check with node -v)
  • An MCP client (e.g., Claude Desktop, VS Code with MCP extension, or a custom client)
  • Basic knowledge of JavaScript/TypeScript and HTTP or stdio concepts
  • Your Mac app’s data or functionality that you want to expose (e.g., a local database, file system, or API)

Step-by-Step

  1. Set up a new Node.js project. Open Terminal, create a folder for your server (e.g., mcp-macapp-server), and run npm init -y.
  2. Install the MCP SDK. Run npm install @modelcontextprotocol/sdk. This provides the server classes and protocol handling.
  3. Create the server entry file. Make a file named server.js (or server.ts if you prefer TypeScript). This will hold your server logic.
  4. Import and instantiate the server. In server.js, add:
    const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
    const server = new McpServer({ name: 'MacAppServer', version: '1.0.0' });
  5. Define a tool. Use server.tool() to register a function the AI can call. For example, a tool that returns the current date:
    server.tool('getCurrentDate', {}, () => ({ content: [{ type: 'text', text: new Date().toISOString() }] }));
  6. Add a tool that accesses your Mac app’s data. Replace the placeholder with real logic. For instance, if your app stores notes in a JSON file, read that file inside the tool function. Use fs.promises.readFile and return the parsed data as text.
  7. Connect to a transport. For local use, stdio is simplest. Add:
    const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
    const transport = new StdioServerTransport();
    await server.connect(transport);
  8. Make the server executable. Add #!/usr/bin/env node at the top of server.js and run chmod +x server.js in Terminal.
  9. Test the server manually. Run node server.js in Terminal. It should start without errors and wait for input. Press Ctrl+C to stop.
  10. Configure your MCP client. In Claude Desktop, go to Settings → Developer → Edit Config, and add your server to the mcpServers object. For stdio, the command is the absolute path to your server file, e.g., /Users/you/mcp-macapp-server/server.js.
  11. Restart the client and test. Open a new chat in Claude Desktop and ask it to use your tool (e.g., “What’s the current date?�). The client should invoke your server and display the result.
  12. Expand with more tools. Add more server.tool() calls for other app features. Each tool should have a clear name, input schema (JSON Schema), and a handler that returns an array of content blocks.
  13. Handle errors gracefully. Wrap your tool logic in try/catch and return an error message as text, so the AI can explain it to the user.

Common Mistakes

  • Forgetting to install the SDK – the require will fail if @modelcontextprotocol/sdk isn’t in your node_modules.
  • Using the wrong transport – if you use HTTP but your client expects stdio (or vice versa), the connection fails silently.
  • Not making the file executable – the client may try to run it directly; without the shebang and exec bit, it errors.
  • Returning non-text content – MCP expects an array of content blocks; returning a plain string breaks the protocol.
  • Hardcoding absolute paths – if you move the server, paths break. Use relative paths or environment variables.
  • Blocking the event loop – long synchronous operations (like reading a huge file) freeze the server. Use async functions.

Tips

  • Start with one simple tool – get the connection working before adding complexity.
  • Use TypeScript – it catches schema errors and makes tool definitions self-documenting.
  • Log to stderr – console.log in a stdio server corrupts the protocol; use console.error for debugging.
  • Validate inputs – the SDK does basic validation, but add your own checks for security.
  • Keep tools stateless – if you need state, store it in a file or database, not in memory.
  • Document each tool’s purpose – the AI uses your descriptions to decide when to call them.

Troubleshooting

Server doesn’t start: Check for syntax errors by running node --check server.js. Ensure all dependencies are installed (npm install).

Client says “No tools found�: Verify the server is running and the client config points to the correct command. Try running the server manually and sending a JSON-RPC initialize request to see if it responds.

Connection drops immediately: Look for unhandled promise rejections or crashes. Add a process.on('uncaughtException') handler to log errors.

Tools return empty results: Test your tool function standalone with sample input. Check that you’re returning the content array correctly.

Permission errors accessing app data: On macOS, your app may have sandbox restrictions. Grant Full Disk Access to the terminal or the client app in System Settings → Privacy & Security.

Client can’t find the server executable: Use the full path in the client config. If using a symlink, ensure it’s not broken.

Frequently Asked Questions

What exactly is MCP?

Model Context Protocol is an open standard that lets AI models call external tools and access data through a uniform interface. Your server exposes tools, and the client (like Claude) decides when to call them.

Can I build an MCP server in Swift instead of Node.js?

Yes, the official SDK has a Swift version. The steps are similar: define tools, connect to a transport, and register with the client. This guide uses Node for simplicity, but the concepts transfer.

How do I secure my server?

Since it runs locally, you control access. Validate all inputs, avoid exposing sensitive data unless necessary, and consider adding an API key if you use HTTP transport. For stdio, only local processes can connect.

Can I use HTTP instead of stdio?

Yes, the SDK supports Streamable HTTP transport. This is useful if your client runs on a different machine or you want to serve multiple clients. You’ll need to set up an HTTP server (e.g., Express) and handle POST requests.

How do I add a tool that requires user input?

Define the tool’s input schema with required properties. The AI will ask the user for the values and pass them to your handler. Your function then uses those parameters.

What if my Mac app is sandboxed?

Sandboxed apps have limited file access. You can either disable sandbox for development, or have your app expose data via a local API that your MCP server calls. This keeps the sandbox intact.

Related

General steps for current mainstream versions; menus can differ by device and version. See the disclaimer or report an error.

More in this bed

The whole Tech How-To bed →