Skip to main content
Glama
mpp-mcp-gateway

mpp-mcp-gateway

mpp-mcp-gateway

MCPサーバーを、Tempoブロックチェーン上のMachine Payments Protocol(MPP)によるステーブルコインのマイクロペイメントで収益化します。

AIエージェントに対して、呼び出しごと、セッションごと、またはアクセスキーごとに課金するMCPツールサーバーを構築できます。決済はpathUSDおよびその他のTIP-20ステーブルコインで行われます。また、設定可能な支出上限に基づいて、これらのツールに自動的に支払うAIエージェントクライアントを構築できます。

License: MIT

目次

Related MCP server: easy-mcp

概要

mpp-mcp-gateway は、MCP(Model Context Protocol)サーバーにステーブルコインのマイクロペイメントゲーティングを追加するTypeScriptライブラリです。AIエージェントが有料ツールを呼び出すと、サーバーは402 Payment Requiredチャレンジを発行します。エージェントのクライアントはTempoブロックチェーン上で支払いトランザクションに署名し、資格情報を付けて呼び出しを再試行します。サーバーはハンドラーを実行してレシート付きで結果を返す前に、決済を検証します。

主な機能:

  • 4つの価格モデル — 従量課金(per-call)、段階的(tiered)、セッション(支払いチャネル)、アクセスキー(サブスクリプション)

  • マルチカレンシー対応 — ツールごとに複数のTIP-20ステーブルコインを受け付け

  • 正確な収益追跡 — BigInt演算により、数百万件のサブセント支払いでも浮動小数点のずれを防止

  • プラグ可能なストレージ — インメモリ、Upstash Redis(アトミックCAS)、Cloudflare KV、または独自実装

  • レート制限 — トークンバケット(インメモリまたはRedisバックエンド)とツールごとの上書き設定

  • 認証ミドルウェア — ベアラートークン、APIキー、HTTP Basic、署名付きURL、CORS — すべてタイミングセーフ

  • Prometheusメトリクス — /metrics エンドポイント、依存関係ゼロ

  • OpenTelemetryトレーシング — 有料呼び出しごとのオプトインのスパンツリー、無効時はコストゼロ

  • ウェブフック — HMAC署名付きイベントプッシュ、リトライ、バックオフ、デッドレターフック付き

  • サービスディスカバリー — x-payment-info 拡張機能を備えたOpenAPI 3.1(mpp.landがクロール)

  • ダッシュボード — ライブ収益と呼び出し監視のためのReact UI + JSON API

  • グレースフルシャットダウン — 実行中の呼び出しをドレインし、フックを発火し、ウェブフックを決済

  • ランタイムポータブル — Node.js 20+、Cloudflare Workers、Vercel Edge、Deno、Bunで動作

仕組み

┌─────────────┐         402 Challenge         ┌──────────────────┐
│  AI Agent   │ ────────────────────────────── │  Paid MCP Server │
│  (Client)   │                                │  (Gateway)       │
│             │ ◄── Payment Required (-32042)  │                  │
│             │                                │                  │
│  Signs tx   │ ── Credential (signed payment) │  Verifies on     │
│  via mppx   │                            ──► │  Tempo chain     │
│             │                                │                  │
│             │ ◄── Tool Result + Receipt      │  Runs handler    │
└─────────────┘                                └──────────────────┘
  1. エージェントがMCP経由で有料ツールを呼び出す

  2. サーバーがMPPチャレンジを含むMCPエラーコード -32042 で応答する

  3. クライアントが支出上限を適用し、支払いに署名し、資格情報を付けて再試行する

  4. サーバーが mppx 経由でオンチェーン決済を検証する

  5. ハンドラーが実行され、支払いレシート(txハッシュ、タイムスタンプ)とともに結果が返される

インストール

npm install mpp-mcp-gateway

ピア依存関係(使用するものだけをインストール):

# For HTTP/Express transports and dashboard
npm install express

# For Upstash Redis stores / rate limiting
npm install @upstash/redis

# For OpenTelemetry tracing
npm install @opentelemetry/api

# For Cloudflare Workers KV store
npm install @cloudflare/workers-types

クイックスタート

サーバー(ツールプロバイダー)

import { createPaidMcpServer } from 'mpp-mcp-gateway/server'
import { z } from 'zod'

const server = createPaidMcpServer({
  name: 'my-api',
  version: '1.0.0',
  recipient: '0xYourWalletAddress',
  secretKey: process.env.PAYMENT_SECRET_KEY!,
  network: 'testnet',
  tools: [
    {
      name: 'get_weather',
      description: 'Get weather for a city. $0.001 per call.',
      inputSchema: { city: z.string() },
      pricing: { type: 'per-call', amount: '0.001' },
      handler: async ({ city }) => ({
        content: [{ type: 'text', text: `Weather in ${city}: 72°F, sunny` }],
      }),
    },
    {
      name: 'ping',
      description: 'Free liveness check.',
      inputSchema: {},
      // No pricing = free tool
      handler: async () => ({
        content: [{ type: 'text', text: 'pong' }],
      }),
    },
  ],
})

await server.startStdio()

クライアント(AIエージェント)

import { createPaidMcpClient } from 'mpp-mcp-gateway/client'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'

const client = createPaidMcpClient({
  name: 'my-agent',
  version: '1.0.0',
  privateKey: process.env.AGENT_PRIVATE_KEY! as `0x${string}`,
  maxPerCall: '0.10',    // safety cap: max $0.10 per single call
  maxTotal: '10.00',     // safety cap: max $10.00 total spend
  network: 'testnet',
})

const transport = new StdioClientTransport({
  command: 'node',
  args: ['server.js'],
})
await client.connect(transport)

// Free call — no payment required
const ping = await client.callTool('ping')
console.log(ping.content[0].text) // "pong"
console.log(ping.paid)            // false

// Paid call — automatic 402 → sign → retry
const weather = await client.callTool('get_weather', { city: 'Tokyo' })
console.log(weather.content[0].text) // "Weather in Tokyo: 72°F, sunny"
console.log(weather.paid)            // true
console.log(weather.receipt?.reference) // "0xabc...def" (tx hash)

await client.close()

価格モデル

従量課金(Per-Call)

呼び出しごとの固定価格です。1回の呼び出しにつき1つのオンチェーントランザクションが必要です。

pricing: { type: 'per-call', amount: '0.001' }

段階的(Tiered)

累計呼び出し回数に応じて価格が下がる(または上がる)方式です。

pricing: {
  type: 'tiered',
  tiers: [
    { upTo: 100, amount: '0.01' },
    { upTo: 1000, amount: '0.005' },
    { upTo: 'unlimited', amount: '0.001' },
  ],
}

セッション(支払いチャネル)

エージェントはオンチェーンのエスクローチャネルを一度だけ開きます。以降の呼び出しでは、署名済みバウチャーをオフチェーンで送信します。サーバーはチャネルが閉じられたときに最も高いバウチャーを決済します。ストリーミングや高頻度のツールに最適です。

pricing: {
  type: 'session',
  amount: '0.0005',          // per-unit price
  unitType: 'request',       // informational label
  suggestedDeposit: '0.50',  // hint for initial channel funding
}

クライアント側のセッション管理:

// Make multiple calls against the same channel
await client.callTool('think', { topic: 'AI alignment' })
await client.callTool('think', { topic: 'quantum computing' })

// Cooperatively close and settle on-chain
const result = await client.closeSession('think')
console.log(result.receipt.reference) // settlement tx hash

アクセスキー(サブスクリプション)

エージェントは前払いで一度だけ支払い、不透明なトークンを受け取ります。以降の呼び出しではそのトークンを提示します。キーが期限切れになるか使い切られるまで追加の支払いは不要です。「デイパスを購入」や「N回分の呼び出しを購入」といったUXに最適です。

pricing: {
  type: 'access-key',
  amount: '0.01',       // upfront cost
  validFor: '1d',       // time limit (supports: 60s, 30m, 4h, 7d)
  maxCalls: 100,        // call limit (at least one of validFor/maxCalls required)
}

クライアントはキャッシュを自動的に処理します:

// First call: pays $0.01, receives access key
const r1 = await client.callTool('premium_data', { query: 'foo' })
console.log(r1.paid)                    // true
console.log(r1.accessKey?.justIssued)   // true
console.log(r1.accessKey?.remainingCalls) // 99

// Subsequent calls: free (key presented in _meta)
const r2 = await client.callTool('premium_data', { query: 'bar' })
console.log(r2.paid)  // false

マルチカレンシー

どの価格モデルでも複数のTIP-20ステーブルコインを受け付けることができます:

pricing: {
  type: 'per-call',
  amount: '0.001',
  accept: [
    { currency: '0x20c0...0000', amount: '0.001' }, // pathUSD
    { currency: '0x20c0...0001', amount: '0.001' }, // alphaUSD
  ],
}

サーバーAPI

import { createPaidMcpServer, PaidMcpServer } from 'mpp-mcp-gateway/server'

const server = createPaidMcpServer(config)

// Start on stdio (for CLI / subprocess use)
await server.startStdio()

// Or access the underlying McpServer for custom transports
const mcpServer = server.server
await mcpServer.connect(someTransport)

// Runtime inspection
server.getStats()           // GatewayStats (calls, revenue, sessions, keys)
server.listTools()          // tool names, descriptions, current prices
server.getRecentCalls(100)  // last N calls from the ring buffer
server.getInFlightCount()   // currently active handlers
server.isShuttingDown()     // true after close() begins
server.describe()           // full descriptor for discovery/OpenAPI

// Access-key management
await server.listAccessKeys()          // live keys issued by this instance
await server.revokeAccessKey(token)    // { revoked: boolean }

// Graceful shutdown
await server.close({ timeoutMs: 25_000 })

クライアントAPI

import { createPaidMcpClient, PaidMcpClient } from 'mpp-mcp-gateway/client'

const client = createPaidMcpClient(config)

await client.connect(transport)
await client.listTools()
const result = await client.callTool('tool_name', { arg: 'value' })

// Spending state
client.getSpending()    // { totalSpent, remaining, maxTotal, maxPerCall, ... }
client.resetSpending()  // reset cumulative counter (for tests)

// Access key management
client.getAccessKeys()          // cached keys by tool name
client.clearAccessKey('tool')   // force re-payment on next call
client.clearAccessKeys()        // drop all cached keys

// Session management
client.getOpenSessions()        // open channels by tool name
await client.closeSession('tool') // settle channel on-chain

await client.close()

トランスポート

ゲートウェイは任意のMCPトランスポートで動作します。同梱の例:

トランスポート

ユースケース

例

stdio

CLIツール、サブプロセス起動

examples/paid-weather-mcp/

Streamable HTTP

ネットワークサーバー(モダン)

examples/paid-weather-http/

SSE(レガシー)

旧来のMCPクライアント

examples/paid-weather-sse/

In-Memory

テスト、同一プロセス

examples/in-memory-demo/

ストアアダプター

ゲートウェイは、アクセスキーレコードとセッションチャネル状態を永続化するためのプラグ可能な MppMcpStore インターフェースを使用します。

import { Store } from 'mpp-mcp-gateway/stores'

アダプター

原子性

ユースケース

Store.memory()

アトミック(プロミスチェーン)

テスト、ローカル開発、単一インスタンス

Store.upstash(redis)

アトミック(Lua CAS)

本番、複数インスタンス

Store.cloudflareKv(ns)

ベストエフォート

エッジアクセスキー(セッション用ではない)

Store.bridge(legacy)

ベストエフォート

mppxストアとの後方互換

Upstashの例

import { Redis } from '@upstash/redis'
import { createUpstashStore } from 'mpp-mcp-gateway/stores'

const store = createUpstashStore(
  new Redis({ url: process.env.UPSTASH_URL!, token: process.env.UPSTASH_TOKEN! }),
  { keyPrefix: 'mppmcp:', ttlSeconds: 30 * 24 * 3600 }
)

const server = createPaidMcpServer({
  // ...
  accessKeyStore: store,
  sessionStore: store,
})

カスタムストア

4つのメソッドからなるインターフェースを実装します:

interface MppMcpStore {
  get<T>(key: string): Promise<T | null>
  put(key: string, value: unknown): Promise<void>
  delete(key: string): Promise<void>
  update<T>(key: string, transform: (current: T | null) => T | null): Promise<T | null>
}

update メソッドはアトミックな読み取り-変更-書き込みを保証する必要があります。競合が発生した場合(CAS方式のバックエンド)、transform コールバックが複数回呼び出される可能性があります。

レート制限

レート制限は支払いおよびハンドラーロジックの前に実行されます。拒否された呼び出しは402を発行せず、ハンドラーも実行されません。

const server = createPaidMcpServer({
  // ...
  rateLimit: {
    refillPerMinute: 60,   // sustained rate
    capacity: 10,          // burst capacity
    perTool: {
      expensive_ai: { refillPerMinute: 5, capacity: 2 },
      cheap_lookup:  { refillPerMinute: 600, capacity: 100 },
    },
    // Custom bucketing (e.g. per-session on HTTP transports)
    keyExtractor: (toolName, extra) => `${toolName}:${extra.sessionId ?? 'default'}`,
  },
})

複数インスタンスのデプロイでは、Upstashバックエンドのリミッターを使用します:

import { upstashTokenBucketLimiter } from 'mpp-mcp-gateway/rate-limit'

const limiter = upstashTokenBucketLimiter(redis, {
  keyPrefix: 'mppmcp:rl:',
  refillPerMinute: 120,
  capacity: 20,
})

const server = createPaidMcpServer({
  // ...
  rateLimit: { limiter },
})

認証ミドルウェア

ダッシュボード、メトリクス、ディスカバリーの各エンドポイントを保護するための5つのExpressミドルウェアファクトリー:

import { auth } from 'mpp-mcp-gateway'

// Bearer token (constant-time comparison)
mountDashboard(server, app, {
  middleware: auth.bearerToken(process.env.DASHBOARD_TOKEN!, { realm: 'admin' }),
})

// API key in custom header
mountMetrics(server, app, {
  middleware: auth.apiKey({ header: 'x-api-key', value: process.env.METRICS_KEY! }),
})

// HTTP Basic Auth (multi-user)
mountDashboard(server, app, {
  middleware: auth.basicAuth({ users: { admin: 'secret' }, realm: 'gateway' }),
})

// HMAC-signed URLs with TTL
mountDashboard(server, app, {
  middleware: auth.signedQuery({ secret: process.env.URL_SECRET!, ttlSeconds: 300 }),
})

// Public CORS for registry crawlers
mountDiscovery(server, app, {
  middleware: auth.publicCors(),
})

ダッシュボードとモニタリング

JSON API

import { mountDashboard } from 'mpp-mcp-gateway'

mountDashboard(server, app, { prefix: '/api' })

公開エンドポイント:

エンドポイント

レスポンス

GET /api/stats

{ stats: GatewayStats } — 呼び出し数、収益、セッション、キー、稼働時間

GET /api/tools

{ tools: [{ name, description, price }] }

GET /api/calls?limit=N

{ calls: CallLogEntry[] } — 新しい順

GET /api/keys

{ keys: AccessKeyListEntry[] } — 有効なアクセスキー

DELETE /api/keys/:token

{ revoked: boolean } — キーの失効(変更を伴うためミドルウェアで保護)

Prometheusメトリクス

import { mountMetrics } from 'mpp-mcp-gateway'

mountMetrics(server, app, {
  middleware: auth.bearerToken(process.env.METRICS_TOKEN!),
})

公開メトリクス:

  • mppmcp_calls_total{tool} — ツール別カウンター

  • mppmcp_calls_by_mode_total{mode} — paid、free、session、access_key、total

  • mppmcp_revenue_micro_usd_total{tool} — マイクロUSD単位の累計収益

  • mppmcp_in_flight_calls — 実行中ハンドラーのゲージ

  • mppmcp_access_keys_issued_total / expired_total

  • mppmcp_sessions_opened_total / closed_total

  • mppmcp_rate_limited_total — レートリミッターによって拒否された呼び出し

  • mppmcp_rejected_shutting_down_total — シャットダウン中に拒否された呼び出し

  • mppmcp_uptime_seconds

  • mppmcp_shutting_down

Reactダッシュボード

ビルド済みのReact + Viteダッシュボードは dashboard/ にあります。JSON APIを2秒ごとにポーリングし、以下を表示します:

  • 収益カウンターと収益順にソートされたツールテーブル

  • 支払いモードごとに色分けされたライブ呼び出しログ

  • アクセスキーとセッションの統計

cd dashboard
npm install
npm run build

dashboard/dist/ をExpressアプリから静的ファイルとして配信します。

サービスディスカバリー

MPPサービスディスカバリーIETFドラフトに従い、x-payment-info 拡張機能を含むOpenAPI 3.1ドキュメントを生成して配信します。mpp.landなどの公開レジストリがこれを自動的にクロールします。

import { mountDiscovery } from 'mpp-mcp-gateway'

mountDiscovery(server, app, {
  baseUrl: 'https://api.example.com',
  categories: ['data', 'search'],
  docs: { homepage: 'https://example.com/docs' },
})
// GET /openapi.json → OpenAPI 3.1 with x-payment-info per tool

ウェブフック

HMAC-SHA-256署名付きでイベントをURLにプッシュします。配信はファイアアンドフォーゲット(非ブロッキング)で、リトライと指数バックオフ付きです。

const server = createPaidMcpServer({
  // ...
  webhooks: {
    url: 'https://example.com/webhook',
    secret: process.env.WEBHOOK_SECRET!,
    events: ['payment.received', 'session.closed'], // or omit for all
    maxAttempts: 3,
    onDrop: async (event, lastError) => {
      // Dead-letter: persist to DB for replay
      await db.insert('webhook_dlq', { event, error: lastError })
    },
  },
})

イベントタイプ: payment.received、access-key.issued、access-key.expired、session.opened、session.closed、call.failed

受信側の検証:

import { createHmac } from 'node:crypto'

function verify(req) {
  const expected = 'sha256=' + createHmac('sha256', WEBHOOK_SECRET)
    .update(`${req.headers['x-mppmcp-timestamp']}.${req.body}`)
    .digest('hex')
  return timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers['x-mppmcp-signature']))
}

OpenTelemetryトレーシング

オプトイン方式です。トレーサーを渡すと、有料呼び出しごとにスパンツリーが取得できます。無効時はオーバーヘッドゼロです。

import { trace } from '@opentelemetry/api'

const server = createPaidMcpServer({
  // ...
  tracer: trace.getTracer('mpp-mcp-gateway', '1.0.0'),
})

スパンツリー:

mppmcp.tool.call (root)
├── mppmcp.payment.charge   (or mppmcp.session.advance, mppmcp.access-key.redeem)
└── mppmcp.handler.run

属性: mppmcp.tool.name、mppmcp.pricing.type、mppmcp.amount、mppmcp.payment.mode、mppmcp.payment.tx-hash、mppmcp.session.action、mppmcp.error.code

オペレーターCLI

デプロイ済みゲートウェイをコマンドラインから検査および管理します:

npx mpp-mcp inspect https://my-gateway.fly.dev --token=secret123
npx mpp-mcp stats https://api.example.com
npx mpp-mcp tools https://api.example.com
npx mpp-mcp calls https://api.example.com --limit=50
npx mpp-mcp keys list https://api.example.com --token=admin
npx mpp-mcp keys revoke mppmcp_abc123... https://api.example.com --token=admin

設定リファレンス

サーバー(PaidMcpServerConfig)

フィールド

型

デフォルト

説明

name

string

必須

クライアントに通知されるサーバー名

version

string

必須

サーバーバージョン

recipient

0x${string}

必須

支払いを受け取るウォレットアドレス

secretKey

string

必須

支払いチャレンジをバインドするためのHMACキー

tools

PaidToolDefinition[]

必須

ハンドラー付きのツール定義

currency

0x${string}

pathUSD

TIP-20ステーブルコインのコントラクトアドレス

network

'mainnet' | 'testnet'

'testnet'

Tempoネットワーク

feePayerKey

0x${string}

—

サーバー負担のガス(フィーペイヤーの秘密鍵)

sessionAccountKey

0x${string}

—

セッション決済に必要なオペレーターキー

escrowContract

0x${string}

ネットワークごとのデフォルト

セッションのエスクローコントラクト

accessKeyStore

MppMcpStore

インメモリ

アクセスキーの永続化

sessionStore

MppMcpStore

インメモリ

セッションチャネルの永続化

accessKeyBinding

'none' | 'wallet'

'none'

キーを支払いウォレットにバインド

callLogSize

number

1000

リングバッファ容量(0 = 無効)

logger

Logger

console+redaction

構造化ロガー

drainTimeoutMs

number

30000

グレースフルシャットダウンのタイムアウト

onShutdown

() => void

—

ドレイン開始時に発火するフック

rateLimit

object

60/min per tool

レート制限の設定

tracer

Tracer

—

OpenTelemetryトレーサー(オプトイン)

webhooks

WebhookConfig

—

イベントプッシュの設定

クライアント(PaidMcpClientConfig)

フィールド

型

デフォルト

説明

name

string

required

クライアント名

version

string

required

クライアントバージョン

privateKey

0x${string}

required

エージェントウォレットの秘密鍵

maxPerCall

string

'1.00'

1回の呼び出しあたりの最大支出(USD)

maxTotal

string

'100.00'

累計最大支出(USD)

maxSessionDeposit

string

'1.00'

チャネル最大預入額(USD)

network

'mainnet' | 'testnet'

'testnet'

Tempoネットワーク

logger

Logger

console+redaction

構造化ロガー

verifySettlement

boolean

false

セッション決済トランザクションをオンチェーンで検証

例

例

料金体系

トランスポート

デモ内容

in-memory-demo

呼び出しごと

InMemory

単一プロセスでの完全な402ラウンドトリップ

paid-weather-mcp

呼び出しごと

stdio

エージェントがサーバーをサブプロセスとして起動

paid-weather-http

呼び出しごと

Streamable HTTP

Express上のネットワークサーバー

paid-weather-sse

呼び出しごと

SSE (legacy)

後方互換のSSEトランスポート

paid-weather-dashboard

呼び出しごと + アクセスキー

Streamable HTTP

MCP + ダッシュボード + ディスカバリの組み合わせ

paid-streaming-mcp

セッション

stdio

ペイメントチャネル、バウチャー、クローズ

paid-subscription-mcp

アクセスキー

stdio

デイパス、時間限定、コールパック

paid-peer-cash-mcp

呼び出しごと

stdio

Peer Cashツールをゲートし、MPP収益を現金化

任意の例を実行:

# In-memory demo (no wallet needed)
npm run example:demo

# Server + client pairs
npm run example:server      # then in another terminal:
npm run example:client

npm run example:http:server
npm run example:http:client

npm run example:streaming:server
npm run example:streaming:client

npm run example:subscription:server
npm run example:subscription:client

# Node.js 22+, Tempo mainnet
npm run example:peer-cash:server

# Dashboard (with all endpoints)
npm run example:dashboard:server

テストウォレットへの資金投入

有料の例では、Tempoテストネット上で資金が入ったウォレットが必要です。Peer Cashの例は例外で、収益ルートがライブのみのためTempoメインネットを使用します。

cast rpc tempo_fundAddress 0xYourAddress --rpc-url https://rpc.moderato.tempo.xyz

ランタイム互換性

コアライブラリ(サーバー、クライアント、ストア、レート制限、金額、アクセスキー)は、Web Cryptoを介してランタイム間で移植可能です:

ランタイム

サポート

Node.js 20+

完全

Cloudflare Workers

完全

Vercel Edge

完全

Deno

完全

Bun

完全

auth.tsミドルウェアモジュールはnode:cryptoを使用し、Node.jsが必要です。エッジデプロイメントでは、代わりにプラットフォームのネイティブルーターと認証プリミティブを使用します。

アーキテクチャ

src/
├── server.ts          PaidMcpServer — payment gating, stats, shutdown, webhooks
├── client.ts          PaidMcpClient — auto-payment, caps, key caching, sessions
├── types.ts           Core interfaces (PricingModel, configs, stats, results)
├── index.ts           Barrel exports (11 subpath entry points)
├── access-keys.ts     Issue, redeem (atomic), validate, duration parsing
├── amounts.ts         BigInt <-> USD string conversion (exact arithmetic)
├── auth.ts            5 Express middleware factories (timing-safe)
├── cli.ts             Operator CLI (inspect, stats, tools, calls, keys)
├── constants.ts       Tempo networks, token addresses, escrow contracts
├── dashboard.ts       JSON API: /api/stats, /api/tools, /api/calls
├── discovery.ts       OpenAPI 3.1 generation with x-payment-info
├── errors.ts          9 typed error classes with stable codes
├── logger.ts          Logger interface + 4 implementations + redaction
├── metrics.ts         Prometheus /metrics (hand-formatted, zero deps)
├── rate-limit.ts      RateLimiter interface + 3 implementations
├── runtime.ts         Cross-runtime: randomHex, writeLogLine, hmacSha256Hex
├── tracing.ts         OTel span helpers (no-op when disabled)
├── webhooks.ts        HMAC-signed event push with retry + dead-letter
└── stores/
    ├── types.ts       MppMcpStore interface
    ├── index.ts       Store namespace + re-exports
    ├── memory.ts      In-memory (atomic via promise chains)
    ├── upstash.ts     Upstash Redis (atomic via Lua CAS)
    ├── cloudflare-kv.ts  Cloudflare KV (best-effort)
    └── bridge.ts      Legacy 3-method store adapter

パッケージエクスポート

{
  ".": "Main barrel (everything)",
  "./server": "PaidMcpServer",
  "./client": "PaidMcpClient",
  "./dashboard": "mountDashboard",
  "./discovery": "mountDiscovery, buildOpenApi",
  "./stores": "Store adapters",
  "./rate-limit": "Rate limiter implementations",
  "./auth": "Auth middleware factories",
  "./metrics": "mountMetrics, formatMetrics",
  "./tracing": "startSpan, withSpan, TRACE_ATTRS",
  "./webhooks": "WebhookDispatcher, event types"
}

設計原則

  • 収益の正確性 — すべての金額計算はbigint基本単位(小数6桁)を使用します。数百万回の操作後も浮動小数点のずれは発生しません。

  • ゼロコストのオプトイン — トレーシング、ウェブフック、レート制限は、設定しない限りノーオペレーションです。トレースしないデプロイメントではスパンは割り当てられません。

  • すべてプラグ可能 — ストア、ロガー、レートリミッター、認証はインターフェースベースです。ゲートウェイコードに触れることなく実装を交換できます。

  • フェイルファスト — 設定エラーはリクエスト時ではなく構築時にスローされます。

  • エラーは値 — 安定したコードを持つ型付きエラークラス。プログラムによる処理にはinstanceofまたはerr.codeを使用します。

  • リングバッファコールログ — O(1)で事前割り当てされ、決して増大しません。高スループット下でもGCプレッシャーがありません。

  • グレースフルライフサイクル — シャットダウンゲートは新しい呼び出しを拒否し、ドレインは進行中の呼び出しを待機し、ウェブフックをフラッシュしてから切断します。

開発

# Install dependencies
npm install

# Build
npm run build

# Type check
npm run typecheck

# Run tests
npm run test

# Run tests in watch mode
npm run test:watch

# Type tests (tsd)
npm run test:types

# Benchmarks
npm run bench

# Generate docs
npm run docs

テストスイート

27以上のテストファイルが以下をカバー:

  • アクセスキーの原子性(Nコールキーの並行リデーム)

  • アクセスキーフロー(発行 → リデーム → 消費 → 再支払い)

  • 金額計算(BigInt変換、エッジケース)

  • 認証ミドルウェア(全5ファクトリー)

  • コールログリングバッファ(ラップアラウンド、容量制限)

  • グレースフルシャットダウンとドレイン

  • ダッシュボードAPIレスポンス

  • ディスカバリ / OpenAPI生成

  • エラータクソノミー

  • 無料ツール(支払い不要パス)

  • ロガー(構造化出力、リダクション、子ロガー)

  • Prometheusメトリクスフォーマット

  • マルチカレンシーディスカバリ

  • 有料フロー(402 → クレデンシャル → レシート)

  • 料金計算(段階的、呼び出しごと)

  • レート制限(トークンバケット、拒否、retry-after)

  • 収益の正確性(多数の呼び出しにわたるBigInt累積)

  • ランタイムヘルパー(randomHex、hmacSha256Hex)

  • セッションライフサイクル(オープン → バウチャー → クローズ → 決済)

  • 支出上限(1回あたり、合計、セッション預入)

  • OpenTelemetryトレーシング(スパン属性、エラー記録)

  • ウェブフック(配信、リトライ、HMAC署名、デッドレター)

  • 型テスト(tsd経由)

  • スループットベンチマーク(vitest bench経由)

グレースフルシャットダウン

close()をコンテナのシャットダウンシグナルに接続:

process.on('SIGTERM', async () => {
  try {
    await server.close({ timeoutMs: 25_000 })
    process.exit(0)
  } catch {
    process.exit(1) // drain timed out
  }
})

構造化ロギング

ライブラリにはプラグ可能なLoggerインターフェースが同梱されています。デフォルト:秘密情報(秘密鍵、クレデンシャル、署名済みトランザクション)を自動的にリダクションしてstderrにJSONで出力します。

import { consoleLogger, silentLogger, withRedaction } from 'mpp-mcp-gateway'

// Custom logger
const server = createPaidMcpServer({
  // ...
  logger: withRedaction(consoleLogger({ level: 'debug', pretty: true })),
})

// Silence for tests
const server = createPaidMcpServer({
  // ...
  logger: silentLogger(),
})

pino、winston、または任意のロギングライブラリに適合:

const adapter: Logger = {
  debug: (m, c) => pino.debug(c, m),
  info: (m, c) => pino.info(c, m),
  warn: (m, c) => pino.warn(c, m),
  error: (m, c) => pino.error(c, m),
  child: (bindings) => /* wrap pino.child(bindings) */,
}

ライセンス

MIT — Gaurav Pant

Related MCP Connectors

Related MCP Servers

  • -
    license
    Not graded
    quality
    Not graded
    maintenance
    A production-ready TypeScript MCP server providing basic tools (add, echo, timestamp), resources (server info, greetings, data access), and prompt templates (analyze, code-review, summarize). Serves as a foundation for building custom MCP servers with extensible architecture.
    452 npm
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    Simplifies creating MCP servers in TypeScript with an Express-like API and experimental decorators, enabling quick definition of tools, resources, and prompts.
    18 npm
    197
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    Turn your typed TypeScript functions into an MCP server — tool, resource, and prompt schemas inferred from your types and JSDoc. No schema library, no decorators, no boilerplate.
    7 npm
    MIT