Skip to main content
Glama
abduznik

mcp-scaffold

by abduznik
README.md
# mcp-scaffold

A generic, plug-and-play MCP (Model Context Protocol) server. Clone it, add your own
tools to `tools.js`, run it, and connect it to Claude, Cursor, or Windsurf. No MCP SDK,
no framework, no build step — just Node's built-in `http` module.

## Quick start

```bash
git clone https://github.com/abduznik/mcp-scaffold.git
cd mcp-scaffold
npm install   # no dependencies, but keeps npm happy
npm start
```

```
mcp-scaffold running on http://localhost:3939/mcp
Registered tools: (none yet — add some in tools.js)
```

That's expected — `tools.js` ships with its example tool commented out. See below to
turn it on.

## Try the example tool

Open `tools.js`. Near the top of the `tools` array there's a `roll_dice` example, fully
commented out:

```javascript
export const tools = [
  // ── EXAMPLE TOOL — commented out by default ─────────────────────────────────
  // Uncomment the block below to try a working example (rolls dice). Comment it
  // back out (or delete it) once you're ready to add your own tools instead.
  //
  // {
  //   name: "roll_dice",
  //   ...
  // },
];
```

Uncomment that block (just the `{ ... }` object — remove the leading `//` from each
line), restart the server, and `roll_dice` is live:

```
Registered tools: roll_dice
```

Comment it back out any time to disable it again — nothing else in the project needs to
change either way.

## Add your own tool

`mcp-server.js` is generic plumbing — you shouldn't need to edit it. Everything you touch
lives in `tools.js`:

```javascript
export const tools = [
  // ...existing tools...

  {
    name: "my_tool",
    description: "One clear sentence describing what it does and when to use it.",
    inputSchema: {
      type: "object",
      properties: {
        arg_one: { type: "string", description: "What this argument is for" },
      },
      required: ["arg_one"],
    },
    handler({ arg_one }) {
      return `You said: ${arg_one}`;
    },
  },
];
```

Restart the server (`npm start`) and your tool is live.

**The `description` field matters more than it looks.** The model reads it to decide
*when* to call your tool, not just what it does. Be specific about the trigger ("use this
when the user asks to...") rather than just naming the function.

## Test it before connecting any AI client

Always verify with `curl` first — if something's broken, this tells you whether the bug
is in your server or in the client config.

```bash
# initialize
curl -s -X POST localhost:3939/mcp \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize"}'

# tools/list — confirm your tool shows up
curl -s -X POST localhost:3939/mcp \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# tools/call — confirm it actually runs (after uncommenting roll_dice)
curl -s -X POST localhost:3939/mcp \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"roll_dice","arguments":{"sides":20,"count":2}}}'
```

## Connect it to Claude Desktop

Add this to `claude_desktop_config.json`
(macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "mcp-scaffold": {
      "type": "url",
      "url": "http://localhost:3939/mcp"
    }
  }
}
```

Restart Claude Desktop. Ask it to do whatever your tool does, in plain language — the
model calls your tool on its own when it decides it's relevant.

## Deploying it for free (optional)

This server has zero dependencies, so it also runs unmodified on Cloudflare Workers —
just wrap the same logic in a `fetch(request)` export instead of `http.createServer`.
Free tier, no credit card, public URL anyone can connect to.

## How it works

- `mcp-server.js` — generic MCP protocol handler. Answers the three methods every MCP
  client needs: `initialize`, `tools/list`, `tools/call`. Reads whatever tools are
  exported from `tools.js` and wires them up automatically.
- `tools.js` — where your tools live. Each tool is `{ name, description, inputSchema,
  handler }`. `handler` can be sync or async and can return anything that stringifies
  sensibly — that string is what the model sees back.

## License

MIT — do whatever you want with it.