# Purpose of the `notifications/initialized` Message During the MCP Handshake

> Understand the purpose of the notifications/initialized message in the MCP handshake. Learn how this signal unblocks the server for asynchronous notifications and handshake finalization.

- Repository: [Junjie.M/dify-plugin-tools-mcp_sse](https://github.com/junjiem/dify-plugin-tools-mcp_sse)
- Tags: how-to-guide
- Published: 2026-03-05

---

**The `notifications/initialized` message signals that the MCP client has completed its initialization, unblocking the server to start sending asynchronous notifications and finalizing the bidirectional handshake.**

The `notifications/initialized` message plays a critical role in the Message Communication Protocol (MCP) handshake implemented in the junjiem/dify-plugin-tools-mcp_sse repository. This JSON-RPC notification marks the transition from the setup phase to active communication, following a Language Server Protocol (LSP)-style flow where the client must explicitly announce its readiness to receive server-side events.

## The MCP Handshake Flow

The MCP client implements a two-phase initialization sequence mirrored from the LSP specification. First, the client sends an `initialize` request to negotiate capabilities and protocol versions. However, the handshake remains incomplete until the client dispatches a second message: the **`notifications/initialized`** notification.

In [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py), the `initialize()` method orchestrates this sequence. Lines 22–29 construct and transmit the notification immediately after the initial request succeeds:

```python

# utils/mcp_client.py – client-side handshake completion

notify_data = {
    "jsonrpc": "2.0",
    "method": "notifications/initialized",
    "params": {}
}
response = self.send_message(notify_data)

```

This notification differs from the initial request because it uses the JSON-RPC notification pattern (no `id` field), indicating the client does not expect a response payload, only acknowledgment that the server has processed the state change.

## Why the notifications/initialized Message Matters

### Signals Client Readiness

The primary purpose is to inform the server that the client has finished its own setup logic and is now capable of handling incoming asynchronous messages. Without this signal, the server remains in a "pre-initialized" state and queues or suppresses push-type notifications such as tool list updates, resource changes, or custom events.

### Completes the LSP-Style Handshake

The protocol strictly requires the `initialize` request **followed by** the `initialized` notification. This two-step verification ensures both sides agree on protocol versions and capabilities before the server begins streaming data. The server uses this boundary to separate initialization-time configuration from runtime operation.

### Enables Server-Sent Events (SSE)

Once the server receives `notifications/initialized`, it may begin streaming events over Server-Sent Events (SSE) or HTTP long-polling connections. In the dify-plugin-tools-mcp_sse implementation, this transition allows the client in [`provider/mcp_tool.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/provider/mcp_tool.py) to receive real-time tool execution updates without continuous polling, establishing full-duplex communication over the SSE transport.

## Automatic Handshake in McpClients

The `McpClients` wrapper class automates this handshake for all configured servers. In lines 37–39 of [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py), the constructor iterates through server configurations and immediately calls `initialize()` for each client:

```python
from utils.mcp_client import McpClients

# Configuration defines transport and endpoint

servers_cfg = {
    "exampleServer": {
        "url": "https://example.com/mcp",
        "transport": "sse"
    }
}

# Constructor performs handshake including notifications/initialized

clients = McpClients(servers_cfg)

# Server can now push notifications; client is ready

# result = clients.call_tool("my_tool", {"param": "value"})

```

This abstraction ensures application code in [`main.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/main.py) never manually manages the handshake state, reducing the risk of protocol violations where the `notifications/initialized` step might be skipped.

## Error Handling During Initialization

The implementation treats failures during the `notifications/initialized` step as fatal handshake errors. Lines 27–28 of the `initialize()` method validate the server's response and raise an exception if the notification is rejected:

```python

# Error handling immediately after sending notification

if "error" in response:
    raise Exception(f"Failed to send initialized notification: {response['error']}")

```

This prevents silent partial initializations where the client believes it is ready but the server has not acknowledged the state transition, which would otherwise cause subsequent tool calls or event subscriptions to fail unpredictably.

## Server-Side Expectations

The [`provider/mcp_tool.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/provider/mcp_tool.py) module expects this handshake to complete before dispatching tool-related notifications. The server logic checks initialization state to determine whether it can safely stream progress updates or resource deltas back to the client. Without the `notifications/initialized` message, the server defers these transmissions, potentially causing timeouts or stale data in the Dify plugin interface.

## Summary

- The `notifications/initialized` message finalizes the client-side portion of the MCP handshake, mirroring the LSP initialization pattern.
- It signals the server to transition from setup mode to active notification streaming, enabling bidirectional SSE communication.
- Implemented in [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py) lines 22–29, the notification is sent automatically by the `McpClients` wrapper during construction.
- Error handling at lines 27–28 ensures handshake failures are detected immediately rather than causing silent communication breakdowns.
- The server-side logic in [`provider/mcp_tool.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/provider/mcp_tool.py) relies on this notification to begin pushing tool execution updates and resource changes.

## Frequently Asked Questions

### What happens if the client never sends the `notifications/initialized` message?

The server remains in a pre-initialized state and will not dispatch asynchronous notifications or Server-Sent Events. While simple request-response calls might still function depending on server implementation, any real-time updates, tool progress streams, or resource subscriptions will be withheld or queued indefinitely, leading to stale data or timeouts in the Dify interface.

### Is `notifications/initialized` a JSON-RPC request or a notification?

It is a **notification**, meaning it lacks the `id` field required in JSON-RPC requests. As shown in the [`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py) implementation, the payload contains only `jsonrpc`, `method`, and `params` fields. The client does not expect a response payload, though the implementation checks for error responses to detect server-side rejection of the handshake state change.

### How does this message relate to Server-Sent Events (SSE) transport?

The `notifications/initialized` notification serves as the gateway to SSE streaming. According to the source code, after receiving this message, the server knows the client is ready to accept push communications and may begin streaming events over the SSE connection. This decouples the initialization handshake from the long-lived event stream, ensuring the client is fully configured before receiving high-frequency updates.

### Where is the handshake logic located in the codebase?

The core handshake implementation resides in **[`utils/mcp_client.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/utils/mcp_client.py)**, specifically within the `initialize()` method (lines 22–29) and the `McpClients.__init__` wrapper (lines 37–39). The server-side handling that respects this initialization state is found in **[`provider/mcp_tool.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/provider/mcp_tool.py)**, while **[`main.py`](https://github.com/junjiem/dify-plugin-tools-mcp_sse/blob/main/main.py)** demonstrates practical usage of the automated handshake via the `McpClients` constructor.