惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IT之家
IT之家
Y
Y Combinator Blog
月光博客
月光博客
Blog — PlanetScale
Blog — PlanetScale
GbyAI
GbyAI
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园 - 三生石上(FineUI控件)
S
SegmentFault 最新的问题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
博客园 - 司徒正美
V
Visual Studio Blog
小众软件
小众软件
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
T
Tailwind CSS Blog
Apple Machine Learning Research
Apple Machine Learning Research
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
A
About on SuperTechFans
The Cloudflare Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Build a tiny MCP server in JavaScript -Claude, Codex frie...
Gaurav Kumar Singh · 2026-06-12 · via DEV Community
Cover image for Build a tiny MCP server in JavaScript -Claude, Codex friendly

Gaurav Kumar Singh

TL;DR — Ship a tiny server that exposes tools to LLMs using the Model Context Protocol (MCP). This guide shows a minimal, runnable example, explains each part, and covers common pitfalls (ESM imports, transports, adapters).

Why this matters

  • MCP lets models call real tools (fetching data, running commands) instead of hallucinating.
  • It’s a great way to extend models with live data: notifications, GitHub lookups, database queries, etc.

What you'll get

  • A working server (index.js) exposing a single tool: get-repo-stats
  • Quick instructions to run locally (stdio) or remotely (HTTP)
  • Short adapter patterns for OpenAI (Codex/GPT), Google Gemini, and Anthropic Claude

Quick checklist

  • Node 18+
  • npm install (project already depends on @modelcontextprotocol/sdk and zod)
  • Add "type": "module" to package.json for ESM

Quick start (copy-paste)

1) Install deps or verify existing:

npm install

2) Run locally:

node index.js

If you see a MODULE_TYPELESS_PACKAGE_JSON warning, add "type": "module" to package.json (example below).

Minimal index.js (copy to your repo)

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const BASE_URL = "https://api.github.com";

const server = new McpServer({ name: 'github-stats-server', version: '1.0.0' });

server.tool(
  'get-repo-stats',
  'Get star count, fork count, and open issues for a GitHub repo',
  {
    owner: z.string().describe("GitHub username or org"),
    repo: z.string().describe("Repository name"),
  },
  async ({ owner, repo }) => {
    const res = await fetch(`${BASE_URL}/repos/${owner}/${repo}`);
    if (!res.ok) {
      return { content: [{ type: 'text', text: `Could not find repo ${owner}/${repo}` }] };
    }
    const data = await res.json();
    return {
      content: [{ type: 'text', text: `${owner}/${repo} — ⭐ ${data.stargazers_count} · 🍴 ${data.forks_count} · 🐛 ${data.open_issues_count}` }]
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

Code walkthrough

  • McpServer: high-level API to register tools and handle protocol
  • server.tool(name, description, schema, handler): register callable tools. Use zod for input validation.

  • Handler returns a result shaped like

{ content: [ { type: 'text', text: '...' } ] }

  • StdioServerTransport: easiest local transport — reads/writes over stdin/stdout

  • Call server.connect(transport) after registering tools so the initialization advertises available tools

ESM / import gotchas

  • Add "type": "module" to package.json so Node treats your files as ESM and avoids MODULE_TYPELESS_PACKAGE_JSON warning.

  • Use package export entrypoints and include .js extension:

    • Good: import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    • Bad: deep imports to dist/esm/… — Node may fail to resolve or duplicate path segments.

Example package.json snippet

{
  "name": "mcp",
  "version": "1.0.0",
  "type": "module",
  "main": "index.js",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.29.0",
    "zod": "^3.25"
  }
}

Transports at a glance

  • stdio: local adapters (Claude Desktop, local testing). Simpler to run.
  • StreamableHTTP: model host calls an HTTP endpoint (preferred for cloud deployments).
  • WebSocket: long-lived bidirectional connections for advanced flows.

how to connect an LLM

OpenAI / Codex (function-calling flow)

Add a Local MCP Server to Codex

Codex can use local MCP servers through STDIO. MCP configuration is stored in
~/.codex/config.toml by default, or in a project-level .codex/config.toml
for trusted projects.

Option 1: Add with the Codex CLI
codex mcp add my-local-git-stat-server -- node /path/to/your/project/index.js

If the server needs environment variables:

codex mcp add my-local-server --env -- node /path/to/your/project/index.js

Option 2: Add Manually in config.toml

Open ~/.codex/config.toml and add:

[mcp_servers.my-local-server]
command = "node"
args = ["/path/to/your/project/index.js"]

Verify the Server

Restart Codex, then check active MCP servers from the Codex TUI:

/mcp

You can also inspect available MCP commands with:

codex mcp --help

Google Gemini

  • If the hosting environment supports HTTP callbacks, use StreamableHTTPClientTransport. Otherwise, run a bridge process that translates Gemini function calls into MCP client calls.

Anthropic Claude

  • For local Claude Desktop: the desktop app can spawn your server process and use stdio. Configure the desktop app to run node /path/index.js.
  • For hosted Claude integrations, prefer HTTP transport.

Small adapter sketch (pseudo)

// spawn server then connect client-facing logic
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

const transport = new StdioClientTransport({ command: 'node', args: ['index.js'] });
await transport.start();
const client = new Client({ name: 'bridge', version: '1.0.0' });
await client.connect(transport);

// When model asks for a tool: client.callTool({ method: 'get-repo-stats', params: { owner, repo } })

Using with OpenAI (Codex / GPT function-calling)

Recommended patterns

  • Local/testing: use stdio transport + StdioClientTransport (spawn the server as a child process from your adapter).
  • Hosted: expose your MCP server with StreamableHTTPTransport and let your adapter call it via StreamableHTTPClientTransport.

Minimal adapter sketch (pseudo)

// adapter-openai.js — high level sketch
import OpenAI from 'openai'; // or your HTTP wrapper
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'; // or StreamableHTTPClientTransport


// spawn local server and connect client
const transport = new StdioClientTransport({ command: 'node', args: ['index.js'] });
await transport.start();
const client = new Client({ name: 'openai-bridge', version: '1.0.0' });
await client.connect(transport);

// When a model function call arrives (pseudo):
async function handleModelFunctionCall(name, params) {
  // Map function name to your tool method (e.g., 'get-repo-stats')
  const toolResult = await client.callTool({ method: `tools/${name}`, params });
  // Convert the MCP result into the expected function-response string or JSON and return it
  return toolResult;
}

// Use your OpenAI SDK to call model and route function-calls through handleModelFunctionCall

Notes

  • Keep the mapping from model function names to MCP tool names explicit and documented.
  • Don't commit API keys — use env vars, and sanitize when spawning processes.

Using with Anthropic Claude (Claude Desktop)

Local desktop (stdio) — easiest

1. Add your server to Claude Desktop configuration (absolute path to your index.js):

{
  "mcpServers": {
    "github-stats": {
      "command": "node",
      "args": ["/absolute/path/to/index.js"]
    }
  }
}

2. Ensure your project has "type": "module" in package.json and your imports use .js extensions.

3. Start / restart Claude Desktop. The desktop app will spawn your server and talk over stdin/stdout. Ask Claude: "What are the stats for facebook/react?" and it should call the get-repo-stats tool.

Hosted Claude / remote

  • If your Claude hosting supports HTTP callbacks, host the MCP server behind an HTTP wrapper and use StreamableHTTPClientTransport on the model side.

Testing tips

  • Run the server with node --trace-warnings index.js when debugging import/ESM issues.
  • Use a small script that calls client.listTools() after connecting to verify the advertised tools.

Debugging checklist

  • ERR_MODULE_NOT_FOUND for deep path? Switch to @modelcontextprotocol/sdk/server/mcp.js imports.
  • MODULE_TYPELESS_PACKAGE_JSON? Add "type": "module" to package.json.
  • Run node --trace-warnings index.js for resolution traces.

Security & deployment (brief)

  • If exposing HTTP, require auth (API key, OAuth) and use TLS.
  • When spawning child processes, sanitize environment and avoid leaking secrets.
  • Limit resource usage (timeouts, concurrency) on tools that call external services.

Make it yours (next steps)

  • Add more tools (issues, releases, search)
  • Add authentication for private GitHub repos
  • Wrap as an npm CLI and add a config file for easy local installs
References & where to look