Skip to content

Quickstart: Python server

Synapse has two halves. The React quickstart covers the client. This one covers the server: nimblebrain-synapse, the PyPI package that attaches a self-contained ui:// component to a FastMCP server. From one declaration the component renders across ChatGPT (OpenAI Apps SDK), Claude (MCP Apps), and the NimbleBrain runtime, with the client SDK inlined so the component ships as one asset.

This walks you from an empty server to a tool whose result renders in a UI. It assumes you already have a FastMCP server, or can start one.

  1. Install the package.

    Terminal window
    pip install nimblebrain-synapse # or: uv add nimblebrain-synapse

    It pairs with FastMCP (the mcp Python package), which you use to define the server and its tools.

  2. Write a data-free template.

    The component’s HTML carries no data. It carries two markers the package fills in: one for the inlined client SDK, one for the data slot.

    TEMPLATE = """
    <div id="root"></div>
    <!--__SYNAPSE_SDK__-->
    <script type="application/json" id="synapse-ui-data">/*__SYNAPSE_DATA__*/</script>
    """

    <!--__SYNAPSE_SDK__--> is replaced with the inlined client SDK <script>, so the component is self-contained (no CDN, CSP-safe). The synapse-ui-data script is the data slot: the served copy leaves the marker in place, so the client reads null and falls back to the host’s push, and a per-result copy substitutes the escaped payload there.

  3. Declare the component and register it.

    from mcp.server.fastmcp import FastMCP
    from nimblebrain_synapse import SynapseUI
    mcp = FastMCP("weather")
    forecast_ui = SynapseUI(
    uri="ui://weather/forecast",
    template=TEMPLATE,
    preferred_size=("100%", "auto"),
    )
    forecast_ui.register(mcp)

    register serves the ui:// resource with the client SDK inlined. uri is the resource id the host loads; preferred_size is the size hint the component asks for.

  4. Add a tool and bind the UI to it.

    @mcp.tool(
    meta=forecast_ui.tool_meta(invoking="Checking the forecast", invoked="Forecast ready"),
    )
    async def get_forecast(city: str) -> dict:
    return {"city": city, "high": 78, "low": 61, "summary": "Sunny"}
    forecast_ui.bind(mcp, tool="get_forecast", should_render=lambda d: "city" in d)

    tool_meta attaches the status strings the host shows while the tool runs and after it finishes, plus the metadata that points the tool at this component. bind post-processes the tool’s CallToolResult: it appends the embedded ui:// resource (the data baked into a <script>) and mirrors the ChatGPT _meta. should_render receives the tool’s output and decides whether to render the UI for that call.

  5. Run the server. Start it however you normally run FastMCP (stdio or HTTP). In a host that supports embedded UIs, calling get_forecast now renders your component. A plain MCP client that renders no UI ignores the extra resource and still reads the tool’s structuredContent, so the same server works everywhere.

  • SynapseUI described one component once: a URI, a data-free template, and a size hint.
  • register published it as a ui:// resource with the client SDK inlined, so the component is fully self-contained.
  • tool_meta and bind wired the component to a tool, so its result is delivered both as structuredContent (for any MCP client) and as a rendered UI (for hosts that support it).

Because the component is self-contained and the wiring is declarative, the same server renders across ChatGPT, Claude, and NimbleBrain without per-host code.