> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcpfy.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Python

> Install and configure mcpfy-pulse for MCP servers built with FastMCP or the official mcp SDK

## Prerequisites

* Python with the asyncio backend under anyio - the default for `anyio.run()` and for both FastMCP flavors' own runners. trio is not currently supported.
* An MCPFY API key - see [Get your API key](/docs/pulse-sdk/overview#get-your-api-key) if you don't have one yet

There are three ways to use `mcpfy-pulse`. None of them edit your files for you - pick the one that matches your situation.

<Tabs>
  <Tab title="FastMCP">
    Works for both `mcp.server.fastmcp.FastMCP` (bundled in the official `mcp` package) and the standalone [`fastmcp`](https://pypi.org/project/fastmcp/) package - same call either way, since both expose the same underlying server object.

    <Steps>
      <Step title="Install the package">
        ```bash theme={null}
        pip install mcpfy-pulse
        ```
      </Step>

      <Step title="Set your API key">
        In your `.env` file:

        ```bash theme={null}
        MCPFY_API_KEY=mk_live_xxx
        ```
      </Step>

      <Step title="Instrument your app">
        Right before you call `.run()`:

        ```python theme={null}
        import os
        from mcp.server.fastmcp import FastMCP  # or: from fastmcp import FastMCP
        from mcpfy_pulse import instrument_fastmcp, TelemetryOptions

        mcp = FastMCP("my-server")

        instrument_fastmcp(mcp, TelemetryOptions(api_key=os.environ.get("MCPFY_API_KEY")))

        mcp.run()  # telemetry now flows for stdio, SSE, and streamable-HTTP alike
        ```

        `instrument_fastmcp` monkey-patches the one call every FastMCP transport funnels through internally (`self._mcp_server.run(...)`), so it doesn't matter which transport you pick. If `api_key` is unset, it's a complete no-op - `mcp` is returned untouched, so it's safe to leave this in place across environments.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Raw mcp SDK">
    For anyone who wrote their own server directly on `mcp.server.Server` (or anything else that hands a `Server.run()` call a pair of read/write streams) and has the source in front of them.

    <Steps>
      <Step title="Install the package">
        ```bash theme={null}
        pip install mcpfy-pulse
        ```
      </Step>

      <Step title="Set your API key">
        In your `.env` file:

        ```bash theme={null}
        MCPFY_API_KEY=mk_live_xxx
        ```
      </Step>

      <Step title="Wrap your streams">
        Right before you run them. This works for any transport - stdio, SSE, StreamableHTTP - not just the stdio example below:

        ```python theme={null}
        import os
        from mcp.server.stdio import stdio_server
        from mcpfy_pulse import with_mcpfy_telemetry, TelemetryOptions

        async def main():
            async with stdio_server() as (read_stream, write_stream):
                read_stream, write_stream = with_mcpfy_telemetry(
                    read_stream, write_stream,
                    TelemetryOptions(api_key=os.environ.get("MCPFY_API_KEY")),
                )
                await server.run(read_stream, write_stream, server.create_initialization_options())
        ```

        `with_mcpfy_telemetry` wraps the read/write stream pair - the two points every JSON-RPC message passes through regardless of which transport is underneath. If `api_key` is unset, it returns the original streams unchanged, so it's safe to leave this in place across environments.
      </Step>
    </Steps>
  </Tab>

  <Tab title="Someone else's server (no source)">
    No install step needed if your client can run `uvx` - it fetches `mcpfy-proxy` automatically the first time it runs. Edit your MCP client's config (`claude_desktop_config.json`, Cursor's `mcp.json`, etc.) to route the command through the proxy:

    ```jsonc theme={null}
    // before:
    "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] }

    // after:
    "github": {
      "command": "uvx",
      "args": ["--from", "mcpfy-pulse", "mcpfy-proxy", "--", "npx", "-y", "@modelcontextprotocol/server-github"],
      "env": { "MCPFY_API_KEY": "mk_live_xxx" }
    }
    ```

    <Tip>
      Prefer not to rely on `uvx`? Run `pip install mcpfy-pulse` yourself and use the plain `mcpfy-proxy` command instead.
    </Tip>

    `mcpfy-proxy` becomes the process your client spawns. It spawns the real command as its own child, sits in that child's stdin/stdout, and forwards every byte unchanged while classifying JSON-RPC messages on the side. Works for any language - Python, Node, Go, Rust, anything - since it only ever reads newline-delimited JSON off a pipe.
  </Tab>
</Tabs>

## Advanced options

`TelemetryOptions` fields all fall back to an environment variable if omitted:

| Option              | Env var                    | Default                                    |
| ------------------- | -------------------------- | ------------------------------------------ |
| `api_key`           | `MCPFY_API_KEY`            | *(required to send data)*                  |
| `endpoint`          | `MCPFY_TELEMETRY_ENDPOINT` | `https://api.mcpfy.ai/v1/telemetry/ingest` |
| `flush_interval_ms` | -                          | `5000`                                     |
| `max_batch_size`    | -                          | `500`                                      |

## What's next

Once your server sends its first event, head to the dashboard to see it.

<Card title="View telemetry in the dashboard" icon="chart-line" href="/docs/pulse-sdk/dashboard">
  See tool-level usage, health scores, and quality signals once data starts flowing.
</Card>
