From pinky-promise
Generates a Node.js MCP server from a pinky-promise API spec, exposing each operation as an MCP tool for AI agent invocation.
How this skill is triggered — by the user, by Claude, or both
Slash command
/pinky-promise:api-mcp-server [<service-name>][<service-name>]The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Generate a Node.js MCP server from a pinky-promise spec. Each operation in the spec becomes an MCP tool. The server proxies tool calls to the real HTTP service.
Generate a Node.js MCP server from a pinky-promise spec. Each operation in the spec becomes an MCP tool. The server proxies tool calls to the real HTTP service.
Specs come exclusively from .pinky-promise/draft-spec.json or the registry. Never read service implementation files.
Announce: "Running api-mcp-server."
Check for a draft spec first:
cat .pinky-promise/draft-spec.json 2>/dev/null
If not found, resolve API_REGISTRY_REPO (Read tool: .claude/settings.json then project CLAUDE.md) and fetch from the registry:
rm -rf .pinky-promise/registry
git clone --depth 1 --filter=blob:none --sparse "$API_REGISTRY_REPO" .pinky-promise/registry
git -C .pinky-promise/registry sparse-checkout set "services/<service-name>"
cat .pinky-promise/registry/services/<service-name>/<latest-version>.json
rm -rf .pinky-promise/registry
If neither is found, stop:
"No spec found. Run the brainstorming skill first to define the API surface."
cat .pinky-promise/bindings.json 2>/dev/null
Extract connection.url and the per-operation method and path from the HTTP bindings entry. If no bindings file exists, use http://localhost:8080 as the base URL and derive paths as /<operation-name> (one path per operation).
Derive from the service name: uppercase, hyphens to underscores, append _URL.
Examples:
repo-stats → REPO_STATS_URLuser-service → USER_SERVICE_URLpayment-api → PAYMENT_API_URLmcp-server/mcp-server.jsWrite a Node.js ESM file. Use this exact structure — one server.tool(...) block per operation:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "<service-name>",
version: "<version from spec>",
});
// --- <operation-name> ---
server.tool(
"<operation-name>",
"<operation description from spec>",
{
// one entry per input field
<field-name>: <zod-type>,
},
async (params) => {
const baseUrl = process.env.<SERVICE_URL_ENV_VAR> ?? "<base-url from bindings or http://localhost:8080>";
// Build the URL — replace path params, add query params for GET
let path = "<path from bindings, e.g. /repos/{owner}/{repo}/stats>";
<path-param-replacements>
const url = new URL(path, baseUrl);
<query-param-additions>
const response = await fetch(url.toString(), {
method: "<METHOD>",
<body-if-post>
});
if (!response.ok) {
throw new Error(`<service-name> returned ${response.status}: ${await response.text()}`);
}
const data = await response.json();
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
}
);
// repeat server.tool(...) for each additional operation
const transport = new StdioServerTransport();
await server.connect(transport);
Field name-to-zod type mapping (use for every input field):
| Spec type | Zod expression |
|---|---|
string | z.string() |
integer | z.number().int() |
number | z.number() |
boolean | z.boolean() |
array of strings | z.array(z.string()) |
array of integers | z.array(z.number().int()) |
| object or unknown | z.record(z.unknown()) |
Path parameter handling (for each path template {paramName}):
path = path.replace("{<paramName>}", encodeURIComponent(params.<paramName>));
Query parameter handling (GET only — input fields not consumed as path params):
url.searchParams.set("<fieldName>", String(params.<fieldName>));
Request body (POST/PUT/PATCH — collect all non-path input fields):
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ <fieldName>: params.<fieldName>, ... }),
mcp-server/package.json{
"name": "<service-name>-mcp-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "node mcp-server.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0",
"zod": "^3.0.0"
}
}
Print exactly this block (substituting service name and env var):
Generated mcp-server/mcp-server.js and mcp-server/package.json.
Install dependencies:
cd mcp-server && npm install
Add to .claude/settings.json to use in Claude Code:
"mcpServers": {
"<service-name>": {
"command": "node",
"args": ["./mcp-server/mcp-server.js"],
"env": {
"<SERVICE_URL_ENV_VAR>": "http://localhost:8080"
}
}
}
Then restart your Claude Code session. Claude will have a '<operation-name>' tool for each operation in the spec.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub superluminar-io/pinky-promise --plugin pinky-promise