---
url: /mcp.zig/guide/transport.md
description: >-
  MCP transport layer — STDIO, HTTP, and custom transports for client-server
  communication.
---

# Transport

Transports handle the communication layer between MCP clients and servers.

## Available Transports

| Transport | Use Case        | Protocol     |
| --------- | --------------- | ------------ |
| **STDIO** | Local processes | stdin/stdout |
| **HTTP**  | Remote servers  | HTTP/HTTPS   |

## STDIO Transport

The most common transport for local MCP servers.

### Server Side

```zig
try server.run(io, allocator, .stdio);
```

### Client Side

```zig
try client.connectStdio("./my-server", &.{});
```

### How It Works

1. Client spawns the server as a child process
2. Communication happens via stdin/stdout
3. JSON-RPC messages are exchanged line by line

### Message Format

Each message is a single line of JSON followed by a newline:

```
{"jsonrpc":"2.0","method":"server/discover","id":1,"params":{...}}\n
```

## HTTP Transport

For remote MCP servers or web-based integration.

### Server Side

```zig
try server.run(io, allocator, .{ .http = .{ .port = 8080 } });
```

Custom host/domain and port:

```zig
try server.run(io, allocator, .{ .http = .{ .host = "api.example.com", .port = 8443 } });
```

### Client Side

```zig
try client.connectHttp("http://127.0.0.1:8080/mcp");
```

### Endpoints

| Endpoint | Method | Description       |
| -------- | ------ | ----------------- |
| `/mcp`   | POST   | JSON-RPC endpoint |

HTTP serving is implemented with httpx.zig; the HTTP client uses the httpx client.

### HTTP Request Format

Send JSON-RPC payloads as `application/json` with HTTP `POST` to `/mcp`:

```bash
curl -X POST http://127.0.0.1:8080/mcp \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"test","version":"1.0.0"}}}}'
```

The response body contains the JSON-RPC response.

### Example Pattern

Most examples default to `stdio` and include optional HTTP run lines.
Switch to HTTP by changing the run call in the example source.

## Streamable HTTP (SSE)

If the client sends `Accept: text/event-stream`, the server responds with a
single Server-Sent Events payload containing the JSON-RPC response.

```bash
curl -X POST http://127.0.0.1:8080/mcp \
    -H "Content-Type: application/json" \
    -H "Accept: text/event-stream" \
    -d '{"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28","io.modelcontextprotocol/clientInfo":{"name":"test","version":"1.0.0"}}}}'
```

## Custom Transports

Implement the `Transport` interface for custom transports:

```zig
const MyTransport = struct {
    pub fn send(self: *MyTransport, io: std.Io, allocator: std.mem.Allocator, message: []const u8) !void {
        // Send the message
    }

    pub fn receive(self: *MyTransport, io: std.Io, allocator: std.mem.Allocator) !?[]const u8 {
        // Receive a message
    }

    pub fn close(self: *MyTransport) void {
        // Close the transport
    }

    pub fn destroy(self: *MyTransport, allocator: std.mem.Allocator) void {
        // Release transport resources
    }

    pub fn transport(self: *MyTransport) mcp.transport.Transport {
        return .{
            .ptr = self,
            .vtable = &.{
                .send = send_wrapper,
                .receive = receive_wrapper,
                .close = close_wrapper,
                .destroy = destroy_wrapper,
            },
        };
    }
};
```

## Transport Options

### STDIO Options

```zig
// STDIO transport is a plain struct value (no init function):
var stdio_transport: mcp.transport.StdioTransport = .{};
defer stdio_transport.deinit(allocator);
```

### HTTP Options

```zig
const http_transport = mcp.transport.HttpTransport.init(
    allocator,
    "http://127.0.0.1:8080/mcp",
);
```

## Best Practices

### STDIO

::: tip Recommended for

* Command-line tools
* Local development
* IDE integrations
* Desktop applications
  :::

### HTTP

::: tip Recommended for

* Remote servers
* Microservices
* Cloud deployments
* Multi-client scenarios
  :::

## Error Handling

```zig
const message = transport.receive(io, allocator) catch |err| {
    switch (err) {
        error.ConnectionClosed => {
            // Handle disconnect
        },
        error.EndOfStream => {
            // Handle graceful end of input
        },
        else => return err,
    }
};
```

## Complete Example

```zig
const std = @import("std");
const mcp = @import("mcp");

pub fn main(init: std.process.Init) void {
    run(init.io, init.gpa, init.minimal.args) catch |err| {
        mcp.reportError(err);
    };
}

fn run(io: std.Io, allocator: std.mem.Allocator, process_args: std.process.Args) !void {
    // Create server
    var server: mcp.Server = .init(allocator, .{
        .name = "multi-transport-server",
        .version = "1.0.0",
    });
    defer server.deinit();

    // Get transport mode from args
    var args = try std.process.Args.Iterator.initAllocator(process_args, allocator);
    defer args.deinit();
    _ = args.next();
    const mode = args.next() orelse "stdio";

    if (std.mem.eql(u8, mode, "http")) {
        std.debug.print("Starting HTTP server on port 8080...\n", .{});
        try server.run(io, allocator, .{ .http = .{ .port = 8080 } });
    } else {
        std.debug.print("Starting STDIO server...\n", .{});
        try server.run(io, allocator, .stdio);
    }
}
```

## Next Steps

* [JSON-RPC Protocol](/guide/jsonrpc) - Understand the protocol
* [Error Handling](/guide/error-handling) - Handle transport errors
* [API Reference](/api/protocol#transport) - Transport API details
