Skip to content

Installation

Install MCPulse in two lines

Add the package, paste your key, wrap your server. It runs inside your own process, not in front of it — so your URL, your OAuth and your uptime are untouched.

Your server's language

All ten official MCP SDKs have an MCPulse package, each checked against the same wire-format fixture so they cannot drift apart. And none of them is a wall: ingest is a plain HTTP POST, so a language without a package can still report today.

  1. 01

    Add the package

    No runtime dependencies at all, so it will not drag anything into your tree.

    $ npm install @mcpulse/sdk
    $ pnpm add @mcpulse/sdk
    $ yarn add @mcpulse/sdk
    $ bun add @mcpulse/sdk
  2. 02

    Put the key in your environment

    An ingest key is a secret — anyone holding it can write data into this MCP. Keep it out of the repository and out of your client bundle. You are shown the full key once, at creation, because only a hash of it is stored.

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  3. 03

    Wrap your server

    One import, one call, after your tools are registered. watch returns the same server, so nothing downstream changes.

    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { watch } from "@mcpulse/sdk";
    
    const server = new McpServer({ name: "my-server", version: "1.0.0" });
    
    // … your registerTool calls …
    
    watch(server, { key: process.env.MCPULSE_KEY });

    Serving over HTTP instead? Those servers build a new McpServer per request, so there is no long-lived instance to wrap — put it inside the factory, beside the tool registration. Calling watch once per request is expected and cheap: the session and its buffer are shared for the life of the process, which is what keeps your calls grouped into one session rather than one per request.

    function createServer() {
      const server = new McpServer({ name: "my-server", version: "1.0.0" });
    
      // … your registerTool calls …
    
      return watch(server, { key: process.env.MCPULSE_KEY });
    }
  4. 04

    Restart and check back

    On start the SDK sends one payload listing your tools and their schema sizes — that is what fills the tools table, so a server that has booted but never been called still shows what it costs per session. Tool calls follow as they happen, batched every 30 calls or 5 seconds.

    Nothing arriving? Set debug: true to log every batch to stderr, and confirm the key has not been revoked.

Options

Two, and there will not be more than there need to be. Every option is another way for something to go wrong inside a process we do not control.

Option Type Default What it does
key string — Required. Without one the SDK does nothing.
debug boolean false Log every batch to stderr — never stdout, which is the transport.

Requires Node 20.12 or newer and @modelcontextprotocol/sdk as a peer — whatever version your server already uses. The package is on npm.

Install for Python

Wrap the server you already built. It is handed straight back, so nothing downstream changes.

  1. 1 · Add the package

    pip install mcpulse-sdk
  2. 2 · Wrap your server

    from mcp.server.mcpserver import MCPServer
    from mcpulse import watch
    
    mcp = MCPServer("my-server")
    watch(mcp, key="mp_live_…")
  3. 3 · Add your key

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Shown once, when you create the MCP. An ingest key is a secret — anyone holding it can write data into this MCP.

Pairs with modelcontextprotocol/python-sdk

Or skip the package entirely

Ingest is one authenticated POST with a bearer key and two payload shapes — one when your server boots, one per tool call. Any language that can make an HTTP request can report today; the package only exists to save you writing it.

Install for C#

Wrap your handler body. There is no server object to attach to here, so recording goes around the call instead of in front of it.

  1. 1 · Add the package

    dotnet add package MCPulse
  2. 2 · Wrap your handler

    using MCPulse;
    
    McPulse.Configure(new McPulseOptions { Key = "mp_live_…" });
    
    // Around your tool handler:
    return await McPulse.RecordAsync(
        toolName: request.Params.Name,
        arguments: request.Params.Arguments,
        clientName: server.ClientInfo?.Name,
        invoke: () => next(request, cancellationToken));
  3. 3 · Add your key

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Shown once, when you create the MCP. An ingest key is a secret — anyone holding it can write data into this MCP.

Pairs with modelcontextprotocol/csharp-sdk

Or skip the package entirely

Ingest is one authenticated POST with a bearer key and two payload shapes — one when your server boots, one per tool call. Any language that can make an HTTP request can report today; the package only exists to save you writing it.

Install for Go

Wrap the server you already built. It is handed straight back, so nothing downstream changes.

  1. 1 · Add the package

    go get github.com/getmcpulse/mcpulse-go
  2. 2 · Wrap your server

    import (
        "github.com/modelcontextprotocol/go-sdk/mcp"
        mcpulse "github.com/getmcpulse/mcpulse-go"
    )
    
    server := mcp.NewServer(&mcp.Implementation{Name: "my-server"}, nil)
    mcpulse.Watch(server, mcpulse.Options{Key: "mp_live_…"})
  3. 3 · Add your key

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Shown once, when you create the MCP. An ingest key is a secret — anyone holding it can write data into this MCP.

Pairs with modelcontextprotocol/go-sdk

Or skip the package entirely

Ingest is one authenticated POST with a bearer key and two payload shapes — one when your server boots, one per tool call. Any language that can make an HTTP request can report today; the package only exists to save you writing it.

Install for Rust

Wrap your handler body. There is no server object to attach to here, so recording goes around the call instead of in front of it.

  1. 1 · Add the package

    cargo add mcpulse
  2. 2 · Wrap your handler

    use mcpulse::{McPulse, Options};
    
    let mcpulse = McPulse::new(Options::new("mp_live_…"));
    
    // Around your tool handler:
    let result = mcpulse.record("search", Some(&arguments), || {
        my_handler(&arguments)
    });
  3. 3 · Add your key

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Shown once, when you create the MCP. An ingest key is a secret — anyone holding it can write data into this MCP.

Pairs with modelcontextprotocol/rust-sdk

Or skip the package entirely

Ingest is one authenticated POST with a bearer key and two payload shapes — one when your server boots, one per tool call. Any language that can make an HTTP request can report today; the package only exists to save you writing it.

Install for Java

Wrap the server you already built. It is handed straight back, so nothing downstream changes.

  1. 1 · Add the package

    <dependency>
      <groupId>com.mcpulse</groupId>
      <artifactId>mcpulse</artifactId>
      <version>0.1.0</version>
    </dependency>
  2. 2 · Wrap your server

    import com.mcpulse.MCPulse;
    import com.mcpulse.Options;
    
    var specs = MCPulse.instrument(Options.key("mp_live_…"), searchSpec, fetchSpec);
    
    McpSyncServer server = McpServer.sync(transport)
        .serverInfo("my-server", "1.0.0")
        .tools(specs)
        .build();
  3. 3 · Add your key

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Shown once, when you create the MCP. An ingest key is a secret — anyone holding it can write data into this MCP.

Pairs with modelcontextprotocol/java-sdk

Or skip the package entirely

Ingest is one authenticated POST with a bearer key and two payload shapes — one when your server boots, one per tool call. Any language that can make an HTTP request can report today; the package only exists to save you writing it.

Install for Ruby

Wrap your handler body. There is no server object to attach to here, so recording goes around the call instead of in front of it.

  1. 1 · Add the package

    bundle add mcpulse
  2. 2 · Wrap your handler

    require "mcpulse"
    
    MCPulse.configure(key: "mp_live_…")
    
    # Around your tool handler:
    MCPulse.record("search", arguments, client_name: client) do
      my_handler.call(arguments)
    end
  3. 3 · Add your key

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Shown once, when you create the MCP. An ingest key is a secret — anyone holding it can write data into this MCP.

Pairs with modelcontextprotocol/ruby-sdk

Or skip the package entirely

Ingest is one authenticated POST with a bearer key and two payload shapes — one when your server boots, one per tool call. Any language that can make an HTTP request can report today; the package only exists to save you writing it.

Install for Swift

Wrap your handler body. There is no server object to attach to here, so recording goes around the call instead of in front of it.

  1. 1 · Add the package

    .package(url: "https://github.com/getmcpulse/mcpulse-swift.git", from: "0.1.0")
  2. 2 · Wrap your handler

    import MCPulse
    
    let mcpulse = MCPulse(options: Options(key: "mp_live_…"))
    
    // Around your tool handler:
    let result = try await mcpulse.record("search", arguments: arguments, result: { $0 }) {
        try await handler(arguments)
    }
  3. 3 · Add your key

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Shown once, when you create the MCP. An ingest key is a secret — anyone holding it can write data into this MCP.

Pairs with modelcontextprotocol/swift-sdk

Or skip the package entirely

Ingest is one authenticated POST with a bearer key and two payload shapes — one when your server boots, one per tool call. Any language that can make an HTTP request can report today; the package only exists to save you writing it.

Install for PHP

Wrap your handler body. There is no server object to attach to here, so recording goes around the call instead of in front of it.

  1. 1 · Add the package

    composer require mcpulse/mcpulse
  2. 2 · Wrap your handler

    use MCPulse\MCPulse;
    use MCPulse\Options;
    
    MCPulse::configure(new Options('mp_live_…'));
    
    // Around your tool handler:
    $result = MCPulse::record('search', $arguments, fn () => $handler($arguments), $clientName);
  3. 3 · Add your key

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Shown once, when you create the MCP. An ingest key is a secret — anyone holding it can write data into this MCP.

Pairs with modelcontextprotocol/php-sdk

Or skip the package entirely

Ingest is one authenticated POST with a bearer key and two payload shapes — one when your server boots, one per tool call. Any language that can make an HTTP request can report today; the package only exists to save you writing it.

Install for Kotlin

Wrap your handler body. There is no server object to attach to here, so recording goes around the call instead of in front of it.

  1. 1 · Add the package

    implementation("com.mcpulse:mcpulse:0.1.0")
  2. 2 · Wrap your handler

    import com.mcpulse.MCPulse
    import com.mcpulse.Options
    
    MCPulse.configure(Options(key = "mp_live_…"))
    
    // Around your tool handler:
    val result = MCPulse.record("search", arguments, clientName) {
        handler(arguments)
    }
  3. 3 · Add your key

    MCPULSE_KEY=mp_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Shown once, when you create the MCP. An ingest key is a secret — anyone holding it can write data into this MCP.

Pairs with modelcontextprotocol/kotlin-sdk

Or skip the package entirely

Ingest is one authenticated POST with a bearer key and two payload shapes — one when your server boots, one per tool call. Any language that can make an HTTP request can report today; the package only exists to save you writing it.

Get your key, then run these two lines

Create a server in the dashboard and copy the key into the .env above. It takes about a minute, and your dead tools and schema cost show up straight away.

Free forever. No card, no trial to expire. All ten MCP SDKs supported.