skills/emillindfors/mcp-transport-guide/SKILL.md
Understand MCP transport mechanisms - stdio, SSE, HTTP streaming, and custom transports
npx skillsauth add aiskillstore/marketplace mcp-transport-guideInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in MCP transport layers, with knowledge of stdio, SSE, HTTP streaming, and how to choose and implement the right transport for different deployment scenarios.
You guide developers on:
Transport is the communication layer that carries MCP messages between clients and servers. It defines how JSON-RPC messages are sent and received.
Use for: Local execution, subprocess communication, desktop tools
use rmcp::transport::stdio::stdio_transport;
#[tokio::main]
async fn main() -> Result<()> {
let service = MyService::new();
let transport = stdio_transport();
service.serve(transport).await?;
Ok(())
}
Characteristics:
When to use:
Use for: Cloud hosting, web applications, remote access
use rmcp::transport::sse::{SseServer, SseTransport};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> Result<()> {
let service = MyService::new();
// Bind to address
let listener = TcpListener::bind("0.0.0.0:3000").await?;
println!("SSE server listening on http://localhost:3000");
loop {
let (stream, addr) = listener.accept().await?;
println!("Connection from: {}", addr);
let transport = SseTransport::new(stream);
let service = service.clone();
tokio::spawn(async move {
if let Err(e) = service.serve(transport).await {
eprintln!("Error serving connection: {}", e);
}
});
}
}
Characteristics:
When to use:
Use for: Modern web services, API gateways, load balancers
use rmcp::transport::http::{HttpServer, HttpTransport};
use axum::{routing::post, Router};
#[tokio::main]
async fn main() -> Result<()> {
let service = Arc::new(MyService::new());
let app = Router::new()
.route("/mcp", post(handle_mcp_request))
.with_state(service);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
println!("HTTP server listening on http://localhost:3000");
axum::serve(listener, app).await?;
Ok(())
}
async fn handle_mcp_request(
State(service): State<Arc<MyService>>,
body: String,
) -> impl IntoResponse {
let transport = HttpTransport::from_request(body);
match service.serve(transport).await {
Ok(response) => Json(response),
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
}
}
Characteristics:
When to use:
// Full stdio server with logging
use rmcp::prelude::*;
use tracing::{info, error};
#[tokio::main]
async fn main() -> Result<()> {
// Initialize logging (stderr doesn't interfere with stdio transport)
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.init();
info!("Starting MCP server");
let service = MyService::new();
let transport = stdio_transport();
info!("Serving via stdio");
match service.serve(transport).await {
Ok(_) => info!("Server terminated normally"),
Err(e) => error!("Server error: {}", e),
}
Ok(())
}
Important: Always log to stderr, never stdout, as stdout is used for JSON-RPC messages.
use axum::{
extract::State,
response::sse::{Event, Sse},
routing::get,
Router,
};
use tokio::sync::mpsc;
use tokio_stream::wrappers::ReceiverStream;
use std::convert::Infallible;
#[derive(Clone)]
struct SseServer {
service: Arc<MyService>,
}
async fn sse_handler(
State(server): State<SseServer>,
) -> Sse<ReceiverStream<Result<Event, Infallible>>> {
let (tx, rx) = mpsc::channel(100);
tokio::spawn(async move {
// Handle SSE connection
// Send MCP messages as SSE events
});
Sse::new(ReceiverStream::new(rx))
}
#[tokio::main]
async fn main() -> Result<()> {
let service = Arc::new(MyService::new());
let server = SseServer { service };
let app = Router::new()
.route("/sse", get(sse_handler))
.with_state(server);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app).await?;
Ok(())
}
use axum::{
extract::{Request, State},
http::{HeaderMap, StatusCode},
middleware::{self, Next},
response::Response,
Json, Router,
};
async fn auth_middleware(
headers: HeaderMap,
request: Request,
next: Next,
) -> Result<Response, StatusCode> {
// Check authorization header
let auth_header = headers
.get("authorization")
.and_then(|v| v.to_str().ok())
.ok_or(StatusCode::UNAUTHORIZED)?;
if !auth_header.starts_with("Bearer ") {
return Err(StatusCode::UNAUTHORIZED);
}
let token = &auth_header[7..];
// Validate token
if !validate_token(token).await {
return Err(StatusCode::UNAUTHORIZED);
}
Ok(next.run(request).await)
}
#[tokio::main]
async fn main() -> Result<()> {
let service = Arc::new(MyService::new());
let app = Router::new()
.route("/mcp", post(handle_mcp_request))
.layer(middleware::from_fn(auth_middleware))
.with_state(service);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app).await?;
Ok(())
}
use rmcp::transport::Transport;
use tokio::io::{AsyncRead, AsyncWrite};
struct CustomTransport<R, W> {
reader: R,
writer: W,
}
impl<R, W> Transport for CustomTransport<R, W>
where
R: AsyncRead + Unpin + Send,
W: AsyncWrite + Unpin + Send,
{
// Implement transport trait methods
}
// Example: WebSocket transport
use tokio_tungstenite::{accept_async, WebSocketStream};
struct WebSocketTransport {
ws: WebSocketStream<TcpStream>,
}
impl WebSocketTransport {
async fn new(stream: TcpStream) -> Result<Self> {
let ws = accept_async(stream).await?;
Ok(Self { ws })
}
}
// Implement Transport trait for WebSocketTransport
| Scenario | Best Transport | Reason | |----------|---------------|--------| | Claude Desktop | stdio | Native integration | | Local CLI tool | stdio | Simple, standard | | Cloud service | SSE or HTTP | Remote access, scalable | | Web application | HTTP | Standard web tech | | Real-time updates | SSE | Server push capability | | Behind load balancer | HTTP | Stateless, proxy-friendly | | Microservices | HTTP | Service mesh compatible | | IoT/Embedded | Custom | Resource constrained |
| Transport | Latency | Throughput | Scalability | Complexity | |-----------|---------|------------|-------------|------------| | stdio | Very Low | High | Single user | Very Low | | SSE | Low | Medium | Good | Medium | | HTTP | Low | High | Excellent | Low | | Custom | Varies | Varies | Varies | High |
// stdio is inherently secure - only parent process can communicate
// No additional security needed
let transport = stdio_transport();
// 1. Use TLS
use axum_server::tls_rustls::RustlsConfig;
let config = RustlsConfig::from_pem_file("cert.pem", "key.pem").await?;
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
axum_server::bind_rustls(addr, config)
.serve(app.into_make_service())
.await?;
// 2. Implement authentication
async fn verify_token(token: &str) -> Result<UserId, AuthError> {
// JWT validation, API key check, etc.
}
// 3. Rate limiting
use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer};
let governor_conf = Box::new(
GovernorConfigBuilder::default()
.per_second(10)
.burst_size(50)
.finish()
.unwrap()
);
let app = Router::new()
.route("/mcp", post(handle_mcp_request))
.layer(GovernorLayer { config: Box::leak(governor_conf) });
use tower_http::cors::{CorsLayer, Any};
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET, Method::POST])
.allow_headers([AUTHORIZATION, CONTENT_TYPE]);
let app = Router::new()
.route("/mcp", post(handle_mcp_request))
.layer(cors);
#[tokio::test]
async fn test_stdio_transport() {
let service = MyService::new();
// Create mock stdin/stdout
let (stdin_reader, mut stdin_writer) = tokio::io::duplex(1024);
let (stdout_reader, mut stdout_writer) = tokio::io::duplex(1024);
// Send test request
let request = r#"{"jsonrpc":"2.0","method":"test","id":1}"#;
stdin_writer.write_all(request.as_bytes()).await.unwrap();
// Read response
let mut response = String::new();
stdout_reader.read_to_string(&mut response).await.unwrap();
assert!(response.contains("result"));
}
#[tokio::test]
async fn test_http_transport() {
let service = Arc::new(MyService::new());
let app = create_app(service);
let client = reqwest::Client::new();
let response = client
.post("http://localhost:3000/mcp")
.json(&json!({
"jsonrpc": "2.0",
"method": "test",
"id": 1
}))
.send()
.await
.unwrap();
assert_eq!(response.status(), 200);
let body: serde_json::Value = response.json().await.unwrap();
assert!(body.get("result").is_some());
}
# Package as binary
cargo build --release
# Run as subprocess
./target/release/my-mcp-server
FROM rust:1.85-slim as builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
COPY --from=builder /app/target/release/my-mcp-server /usr/local/bin/
EXPOSE 3000
CMD ["my-mcp-server"]
docker build -t my-mcp-server .
docker run -p 3000:3000 my-mcp-server
apiVersion: apps/v1
kind: Deployment
metadata:
name: mcp-server
spec:
replicas: 3
selector:
matchLabels:
app: mcp-server
template:
metadata:
labels:
app: mcp-server
spec:
containers:
- name: mcp-server
image: my-mcp-server:latest
ports:
- containerPort: 3000
env:
- name: RUST_LOG
value: "info"
---
apiVersion: v1
kind: Service
metadata:
name: mcp-server
spec:
selector:
app: mcp-server
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
use tracing::{info, error, instrument};
#[instrument]
async fn handle_request(request: Request) -> Result<Response> {
info!("Received request: {:?}", request);
match process_request(request).await {
Ok(response) => {
info!("Sending response");
Ok(response)
}
Err(e) => {
error!("Request failed: {}", e);
Err(e)
}
}
}
use prometheus::{Counter, Histogram, Registry};
lazy_static! {
static ref REQUEST_COUNTER: Counter =
Counter::new("mcp_requests_total", "Total requests").unwrap();
static ref REQUEST_DURATION: Histogram =
Histogram::new("mcp_request_duration_seconds", "Request duration").unwrap();
}
async fn handle_request_with_metrics(request: Request) -> Result<Response> {
REQUEST_COUNTER.inc();
let timer = REQUEST_DURATION.start_timer();
let result = handle_request(request).await;
timer.observe_duration();
result
}
When helping with transport selection and implementation:
Understand Deployment
Recommend Transport
Implement Securely
Add Monitoring
Test Thoroughly
Your goal is to help developers choose and implement the right transport for their MCP server, ensuring security, performance, and reliability.
development
Apple Human Interface Guidelines for content display components. Use this skill when the user asks about charts component, collection view, image view, web view, color well, image well, activity view, lockup, data visualization, content display, displaying images, rendering web content, color pickers, or presenting collections of items in Apple apps. Also use when the user says how should I display charts, what's the best way to show images, should I use a web view, how do I build a grid of items, what component shows media, or how do I present a share sheet. Cross-references: hig-foundations for color/typography/accessibility, hig-patterns for data visualization patterns, hig-components-layout for structural containers, hig-platforms for platform-specific component behavior.
tools
Automate HelpDesk tasks via Rube MCP (Composio): list tickets, manage views, use canned responses, and configure custom fields. Always search tools first for current schemas.
testing
Expert Haskell engineer specializing in advanced type systems, pure functional design, and high-reliability software. Use PROACTIVELY for type-level programming, concurrency, and architecture guidance.
tools
GraphQL gives clients exactly the data they need - no more, no less. One endpoint, typed schema, introspection. But the flexibility that makes it powerful also makes it dangerous. Without proper controls, clients can craft queries that bring down your server. This skill covers schema design, resolvers, DataLoader for N+1 prevention, federation for microservices, and client integration with Apollo/urql. Key insight: GraphQL is a contract. The schema is the API documentation. Design it carefully.