Sample MCP Server + AI Hook Web Service

A sample MCP (Model Context Protocol) server and an AI Hook Web Service built with Node.js and TypeScript.

  • MCP Server — exposes Tools, Resources, and Prompts to AI clients (Claude Desktop, Claude Code, etc.)
  • AI Hook Web Service — HTTP service for logging AI usage events from Claude, Claude Code, OpenAI/Codex, OpenCode, and any other provider. Logs are kept in memory and persisted to a JSON file (no database required), with automatic cost estimation and a live web dashboard.

Table of Contents

  • Project Structure
  • Setup
  • MCP Server
    • Running the MCP Server
    • MCP Core Primitives
    • Connect to Claude Desktop
  • AI Hook Web Service
    • Running the Web Service
    • Web Dashboard
    • Logging Claude Code Usage
    • Endpoints
    • Sending Hooks
    • Cost Estimation
    • Query Logs
    • View Stats
  • Dependencies

Project Structure

sample-mcp-and-hook/
├── src/
│   ├── main.ts          # MCP server entry point
│   ├── tools.ts         # MCP Tools (greet, add)
│   ├── resources.ts     # MCP Resources (config, user-profile)
│   ├── prompts.ts       # MCP Prompts (explain-code, review-code)
│   └── web-server.ts    # AI Hook Web Service entry point
├── public/
│   └── index.html       # Static web dashboard (AI Hook Monitor)
├── scripts/
│   └── ai-hook.sh       # Enriches Claude Code Stop hooks with token usage
├── .claude/
│   └── settings.json    # Claude Code hooks + AI_HOOK_URL
├── .mcp.json            # MCP server registration for Claude Code
├── logs/                # Persisted hook logs (ai-hooks.json, git-ignored)
├── dist/                # Compiled JavaScript (after build)
├── package.json
├── tsconfig.json
└── README.md

Setup

Prerequisites: Node.js 18+ (and jq + curl for the Claude Code hooks)

# Install dependencies
pnpm install

# Build TypeScript
pnpm build

MCP Server

Running the MCP Server

# Production
pnpm start:mcp

# Development (auto-rebuild on change)
pnpm dev

MCP Core Primitives

Primitive Purpose Direction Example Use Case
Tools Execute actions AI → Server Calculations, API calls
Resources Provide data Server → AI Config, documents, user data
Prompts Template messages Server → AI Standardized instructions
Tools (src/tools.ts)
  • greet — greets a user by name
  • add — adds two numbers together
Resources (src/resources.ts)
  • config://app — static application configuration
  • users://{userId}/profile — dynamic user profile by ID
Prompts (src/prompts.ts)
  • explain-code — prompt template for explaining code
  • review-code — parameterized code review (language, focus area)

Connect to Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "sample-mcp": {
      "command": "node",
      "args": ["/absolute/path/to/sample-mcp-and-hook/dist/main.js"]
    }
  }
}

For Claude Code, the server is already registered in .mcp.json (relative path) and enabled in .claude/settings.local.json — no manual setup needed once you've run pnpm build.

AI Hook Web Service

A lightweight HTTP service that receives AI usage events, logs them to the console, and persists them to logs/ai-hooks.json. Logs survive server restarts — the file is loaded automatically on startup. Cost is estimated automatically from token counts, and a live dashboard is served at the root URL. No database needed.

Running the Web Service

# Production (build first)
pnpm build
pnpm start:hook

# Development (watches compiled output)
pnpm dev          # terminal 1 — recompile on change
pnpm dev:hook     # terminal 2 — restart server on change

Default port: 3000. Override with the PORT environment variable:

PORT=4000 pnpm start:hook

Log file: logs/ai-hooks.json (auto-created, already in .gitignore). Logs persist across restarts — the file is loaded on startup and rewritten after every hook received. Up to 1000 most-recent entries are retained in memory.

Web Dashboard

Once the service is running, open the dashboard in your browser:

http://localhost:3000/

The static dashboard (public/index.html) visualizes logged events and aggregate stats per provider and model — color-coded by provider (Claude, Claude Code, OpenAI, OpenCode) and event type.

Logging Claude Code Usage

This repo is preconfigured to log its own Claude Code usage to the web service.

Step 1 — Start the web service (keep it running in a separate terminal):

pnpm build
pnpm start:hook
# AI Hook Web Service running on http://localhost:3000

Step 2 — No model config needed. The hooks auto-detect your model at runtime:

  • On Stop events, scripts/ai-hook.sh reads the session transcript and extracts the exact model plus real token usage (input, output, cache read/write).
  • On SessionStart / UserPromptSubmit events (no token data available), an inline hook resolves the model from .claude/settings.json~/.claude/settings.json → falls back to "claude-code".

Whatever model you have configured in Claude Code is tracked automatically — no manual configuration required.

The hooks and endpoint are already wired up in .claude/settings.json:

{
  "env": {
    "AI_HOOK_URL": "http://localhost:3000/api/hook/claudecode"
  },
  "hooks": {
    "SessionStart":     [ /* inline curl — logs session_start */ ],
    "UserPromptSubmit": [ /* inline curl — logs prompt_submit */ ],
    "Stop":             [ { "command": "bash scripts/ai-hook.sh" } ]
  }
}

Step 3 — Open Claude Code in this project directory as usual:

claude

Every session start, user prompt, and Claude response is logged automatically (asynchronously, so Claude Code is never blocked).

Step 4 — View the logs in the dashboard or via the API:

# All events from Claude Code
curl "http://localhost:3000/api/logs?provider=claudecode"

# Aggregated stats
curl http://localhost:3000/api/stats

Note: The web service must be running on the configured port before Claude Code starts. If the service is down, hooks fail silently (Claude Code is not affected).

Endpoints

Method Path Description
GET / Static web dashboard
POST /api/hook Receive a hook (provider auto-detected from body)
POST /api/hook/:provider Receive a hook with explicit provider name
GET /api/logs List logs (newest first)
GET /api/stats Aggregate stats grouped by provider + model
DELETE /api/logs Clear all logs
GET /health Health check (log count, log file path, uptime)

Sending Hooks

The service auto-detects the provider from the request body. You can also send hooks to /api/hook/:provider to set the provider explicitly.

Claude
curl -X POST http://localhost:3000/api/hook \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "claude",
    "model": "claude-sonnet-4-6",
    "type": "message",
    "id": "msg_01abc",
    "session_id": "sess_xyz",
    "usage": {
      "input_tokens": 1200,
      "output_tokens": 350,
      "cache_read_input_tokens": 800
    }
  }'

Console output (cost is auto-estimated from tokens):

[HOOK] 2026-07-02T06:00:00.000Z | claude     | claude-sonnet-4-6              | in=1200 out=350 total=1550 cost=$0.008850 session=sess_xyz
OpenAI / Codex
curl -X POST http://localhost:3000/api/hook \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "openai",
    "model": "gpt-4o",
    "object": "chat.completion",
    "id": "chatcmpl-abc",
    "cost_usd": 0.00014,
    "usage": {
      "prompt_tokens": 500,
      "completion_tokens": 200,
      "total_tokens": 700
    }
  }'
OpenCode
# Using explicit provider path
curl -X POST http://localhost:3000/api/hook/opencode \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-3-5-sonnet",
    "event": "completion",
    "input_tokens": 900,
    "output_tokens": 420,
    "total_tokens": 1320,
    "cost": 0.00031,
    "duration_ms": 1850,
    "session_id": "oc_sess_999"
  }'
Generic / Custom Provider

Any JSON body with a provider field works:

curl -X POST http://localhost:3000/api/hook \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "my-custom-llm",
    "model": "my-model-v1",
    "event": "inference",
    "input_tokens": 300,
    "output_tokens": 150,
    "cost_usd": 0.00005,
    "user_id": "user_42",
    "metadata": { "region": "ap-southeast-1" }
  }'

Cost Estimation

When a hook does not include an explicit cost_usd (or cost), the service estimates it from token counts using built-in per-model pricing (input, output, cache-read, and cache-write rates per million tokens). Pricing is matched by substring against the model name, covering the Claude Opus/Sonnet/Haiku families (v3, v3.5, and v4).

  • If an explicit cost is provided in the payload, it is used as-is.
  • If no matching model pricing is found, cost is left unset.
  • Estimated cost appears in the console output, the cost_usd log field, and the aggregated stats.

Adjust the MODEL_PRICING table in src/web-server.ts to add models or update rates.

Query Logs

# All logs (newest first, default limit 50, max 500)
curl http://localhost:3000/api/logs

# Filter by provider (exact match)
curl "http://localhost:3000/api/logs?provider=claude"

# Filter by model name (partial match)
curl "http://localhost:3000/api/logs?model=sonnet"

# Combine filters and set limit
curl "http://localhost:3000/api/logs?provider=openai&limit=10"

# Clear all logs
curl -X DELETE http://localhost:3000/api/logs

Response shape:

{
  "total": 42,
  "returned": 10,
  "logs": [
    {
      "id": "log_1782972333250_96p2e",
      "received_at": "2026-07-02T06:05:33.250Z",
      "provider": "claude",
      "model": "claude-sonnet-4-6",
      "event": "message",
      "session_id": "sess_xyz",
      "tokens": {
        "input": 1200,
        "output": 350,
        "cache_read": 800,
        "total": 1550
      },
      "cost_usd": 0.00885,
      "request_id": "msg_01abc",
      "raw": { "...": "original payload" }
    }
  ]
}

View Stats

curl http://localhost:3000/api/stats

Stats are grouped by provider + model and sorted by call count (descending).

Response shape:

{
  "providers": ["claude", "openai", "opencode"],
  "stats": [
    {
      "provider": "claude",
      "model": "claude-sonnet-4-6",
      "calls": 15,
      "total_input_tokens": 18000,
      "total_output_tokens": 5250,
      "total_tokens": 23250,
      "total_cache_read_tokens": 9600,
      "total_cache_write_tokens": 0,
      "total_cost_usd": 0.1328,
      "total_duration_ms": 0
    },
    {
      "provider": "openai",
      "model": "gpt-4o",
      "calls": 8,
      "total_input_tokens": 4000,
      "total_output_tokens": 1600,
      "total_tokens": 5600,
      "total_cache_read_tokens": 0,
      "total_cache_write_tokens": 0,
      "total_cost_usd": 0.00112,
      "total_duration_ms": 0
    }
  ]
}

Dependencies

Package Purpose
@modelcontextprotocol/sdk Official MCP SDK
express HTTP server for the AI Hook Web Service
zod Schema validation for MCP tool inputs

Learn More

License

ISC

MCP Server · Populars

MCP Server · New

    tsouth89

    Toolport

    Local-first MCP gateway. One port for every tool and every AI client: lazy discovery (~90% token savings), tool integrity + quarantine, secrets in the OS keychain.

    Community tsouth89
    Sendmux

    Email Inbox API + Sending by Sendmux

    Official monorepo of SDKs, CLI, and MCP servers for Sendmux email APIs across TypeScript, Python, Go, PHP, Rust, and Ruby.

    Community Sendmux
    ATH-MaaS

    🎨 Pixelle MCP - Omnimodal Agent Framework

    An Open-Source Multimodal AIGC Solution based on ComfyUI + MCP + LLM https://pixelle.ai

    Community ATH-MaaS
    cauldr0nx

    EspoCRM MCP Server

    Opensource MCP Server for EspoCRM

    Community cauldr0nx
    cisco-open

    Network Sketcher

    Network Sketcher is an AI-ready network design tool with Local MCP, Online, and Offline editions for creating network designs and exporting PowerPoint diagrams and Excel-based configuration data.

    Community cisco-open