CtrlK
CommunityDocumentationLog inGet started
Tessl Logo

tessl/maven-org-springframework-ai--spring-ai-starter-mcp-client-webflux

Spring Boot starter providing auto-configuration for Model Context Protocol (MCP) client with Spring WebFlux, enabling reactive AI applications to connect to MCP servers via SSE and Streamable HTTP transports

Overview
Eval results
Files

stdio-transport.mddocs/reference/

Stdio Transport Auto-Configuration

Auto-configuration for standard input/output (stdio) based MCP client transport in the Model Context Protocol (MCP).

Note: Stdio transport is part of the common MCP client autoconfiguration, not WebFlux-specific. This functionality is available in all MCP client starters (WebFlux, WebMVC, etc.) through the shared common autoconfiguration dependency.

Overview

The stdio transport auto-configuration provides communication with local MCP servers through standard input/output streams. This is ideal for running MCP servers as local processes that communicate via stdin/stdout, such as command-line tools, scripts, or local executables.

Key Features

  • Local Process Communication: Spawn and communicate with child processes
  • Cross-Platform: Works on Windows, Linux, macOS
  • Language Agnostic: Server can be written in any language (Node.js, Python, Go, etc.)
  • Simple Protocol: Standard input/output streams
  • Automatic Lifecycle: Process management handled automatically
  • Not WebFlux-Specific: Available in all MCP client starters

Auto-Configuration Class

package org.springframework.ai.mcp.client.common.autoconfigure;

/**
 * Auto-configuration for stdio-based client transport.
 * Part of common MCP client autoconfiguration - available in all starters.
 * Creates transports that communicate with local processes via stdin/stdout.
 * Thread-safe - process spawning is synchronized.
 */
@org.springframework.boot.autoconfigure.AutoConfiguration
@org.springframework.boot.autoconfigure.condition.ConditionalOnClass(io.modelcontextprotocol.spec.ServerParameters.class)
@org.springframework.boot.context.properties.EnableConfigurationProperties({
    org.springframework.ai.mcp.client.common.autoconfigure.properties.McpStdioClientProperties.class,
    org.springframework.ai.mcp.client.common.autoconfigure.properties.McpClientCommonProperties.class
})
@org.springframework.boot.autoconfigure.condition.ConditionalOnProperty(
    prefix = "spring.ai.mcp.client",
    name = "enabled",
    havingValue = "true",
    matchIfMissing = true
)
public class StdioTransportAutoConfiguration {
}

Conditional Activation

This auto-configuration is conditionally enabled when:

  1. Class Presence: io.modelcontextprotocol.spec.ServerParameters is on the classpath
  2. Property Check: spring.ai.mcp.client.enabled is true (default) or not specified
  3. Stdio Configuration: At least one stdio connection is configured

If these conditions are not met, the auto-configuration is skipped.

Required Dependencies

Maven:

<dependency>
    <groupId>io.modelcontextprotocol</groupId>
    <artifactId>mcp-core</artifactId>
</dependency>

Gradle:

implementation 'io.modelcontextprotocol:mcp-core'

Beans Created

Stdio Client Transports

package org.springframework.ai.mcp.client.common.autoconfigure;

/**
 * Creates a list of stdio-based transports for MCP communication.
 *
 * Each transport is configured with:
 * - Command to execute (e.g., "node", "python", "/path/to/executable")
 * - Command arguments (list of strings)
 * - Environment variables (optional)
 *
 * Process lifecycle:
 * 1. Process spawned when transport is created
 * 2. Communication via process stdin/stdout
 * 3. Process terminated when Spring context closes
 *
 * Thread-safe - process spawning is synchronized.
 * Blocking - process I/O is blocking.
 *
 * @param stdioProperties Stdio client properties containing server configurations
 * @return List of named MCP transports, one for each configured stdio connection (never null, may be empty)
 * @throws IllegalArgumentException if command configuration is invalid
 * @throws IOException if process fails to start
 */
@org.springframework.context.annotation.Bean
public java.util.List<NamedClientMcpTransport> stdioTransports(
    org.springframework.ai.mcp.client.common.autoconfigure.properties.McpStdioClientProperties stdioProperties
);

Return Type: List<NamedClientMcpTransport>

Configuration

Basic Configuration

Configure stdio connections in application.yml:

spring.ai.mcp.client.stdio:
  connections:
    local-server:
      command: node
      args:
        - /path/to/mcp-server.js
    python-server:
      command: python
      args:
        - /path/to/mcp_server.py
        - --debug
      env:
        PYTHONPATH: /custom/path
        DEBUG: "true"

Properties format:

spring.ai.mcp.client.stdio.connections.local-server.command=node
spring.ai.mcp.client.stdio.connections.local-server.args[0]=/path/to/mcp-server.js
spring.ai.mcp.client.stdio.connections.python-server.command=python
spring.ai.mcp.client.stdio.connections.python-server.args[0]=/path/to/mcp_server.py
spring.ai.mcp.client.stdio.connections.python-server.args[1]=--debug
spring.ai.mcp.client.stdio.connections.python-server.env.PYTHONPATH=/custom/path
spring.ai.mcp.client.stdio.connections.python-server.env.DEBUG=true

Configuration from External JSON File

You can also configure stdio servers from an external JSON file:

spring.ai.mcp.client.stdio:
  servers-configuration: classpath:mcp-servers.json

The JSON file format (Claude Desktop format compatible):

{
  "mcpServers": {
    "local-server": {
      "command": "node",
      "args": ["/path/to/mcp-server.js"]
    },
    "python-server": {
      "command": "python",
      "args": ["/path/to/mcp_server.py", "--debug"],
      "env": {
        "PYTHONPATH": "/custom/path",
        "DEBUG": "true"
      }
    }
  }
}

Configuration Properties Reference

See Configuration Properties for complete stdio configuration options.

Usage Examples

Injecting Stdio Transports

import org.springframework.ai.mcp.client.common.autoconfigure.NamedClientMcpTransport;
import org.springframework.stereotype.Service;
import java.util.List;

/**
 * Service demonstrating stdio transport usage.
 * Typically you don't use transports directly - inject McpSyncClient or McpAsyncClient instead.
 * This example shows how to access transports for advanced scenarios.
 */
@Service
public class McpStdioService {

    private final List<NamedClientMcpTransport> stdioTransports;

    /**
     * Constructor injection of stdio transports.
     * If no stdio connections configured, list will be empty.
     * Processes are already spawned and ready for communication.
     *
     * @param stdioTransports Auto-configured stdio transports
     */
    public McpStdioService(List<NamedClientMcpTransport> stdioTransports) {
        this.stdioTransports = stdioTransports;
    }

    /**
     * Find specific stdio transport by name.
     * Throws if not found - check existence first in production.
     *
     * @throws IllegalStateException if transport not found
     */
    public void connectToLocalServer() {
        // Find the local-server transport
        NamedClientMcpTransport localTransport = stdioTransports.stream()
            .filter(t -> "local-server".equals(t.name()))
            .findFirst()
            .orElseThrow(() -> new IllegalStateException("local-server transport not found"));

        // Use the transport (typically via an MCP client)
        io.modelcontextprotocol.spec.McpClientTransport transport = localTransport.transport();
        // ... connect and use transport
    }

    /**
     * List all configured stdio connections.
     * Useful for debugging and monitoring.
     */
    public void listAllStdioConnections() {
        stdioTransports.forEach(t ->
            System.out.println("Stdio Transport: " + t.name())
        );
    }
}

Filtering by Transport Type

If your application uses multiple transport types, you can filter for stdio transports:

import io.modelcontextprotocol.client.transport.StdioClientTransport;

/**
 * Filter transports by type.
 * Useful when multiple transport types are configured.
 *
 * @param allTransports All configured transports (stdio, SSE, streamable HTTP)
 */
public void findStdioTransports(List<NamedClientMcpTransport> allTransports) {
    List<NamedClientMcpTransport> stdioTransports = allTransports.stream()
        .filter(t -> t.transport() instanceof StdioClientTransport)
        .toList();

    // Use only stdio transports
    stdioTransports.forEach(t -> {
        System.out.println("Stdio: " + t.name());
    });
}

Combining Multiple Transport Types

You can configure stdio, SSE, and Streamable HTTP transports in the same application:

spring.ai.mcp.client:
  # Stdio connections for local servers
  stdio:
    connections:
      local-node-server:
        command: node
        args:
          - ./servers/mcp-server.js
      local-python-server:
        command: python
        args:
          - ./servers/mcp_server.py

  # SSE connections for remote servers
  sse:
    connections:
      remote-notifications:
        url: http://localhost:8080

  # Streamable HTTP connections
  streamable-http:
    connections:
      remote-api:
        url: http://localhost:9000

Common Use Cases

Running Local MCP Tool Servers

Stdio transport is ideal for running local tool servers that provide specialized capabilities:

spring.ai.mcp.client.stdio:
  connections:
    filesystem-tools:
      command: npx
      args:
        - -y
        - "@modelcontextprotocol/server-filesystem"
        - /home/user/documents
      env:
        NODE_ENV: production
    database-tools:
      command: python
      args:
        - ./tools/database-mcp-server.py
      env:
        DATABASE_URL: postgresql://localhost:5432/mydb
        LOG_LEVEL: INFO

Development and Testing

Use stdio for local development servers:

spring.ai.mcp.client.stdio:
  connections:
    dev-server:
      command: npm
      args:
        - run
        - mcp-server:dev
      env:
        NODE_ENV: development
        DEBUG: "mcp:*"
        PORT: "3000"

Platform-Specific Commands

Configure different commands based on the operating system:

# application-linux.yml
spring.ai.mcp.client.stdio:
  connections:
    shell-tools:
      command: /bin/bash
      args:
        - ./scripts/mcp-server.sh

# application-windows.yml
spring.ai.mcp.client.stdio:
  connections:
    shell-tools:
      command: cmd.exe
      args:
        - /c
        - .\\scripts\\mcp-server.bat

# application-mac.yml
spring.ai.mcp.client.stdio:
  connections:
    shell-tools:
      command: /bin/zsh
      args:
        - ./scripts/mcp-server.sh

Transport Implementation Details

Internally, the auto-configuration creates stdio transports using the MCP SDK:

// Conceptual example (not for direct use) - shows internal process
package org.springframework.ai.mcp.client.common.autoconfigure;

// For each configured stdio connection:
for (Map.Entry<String, Parameters> entry : connections.entrySet()) {
    String connectionName = entry.getKey();
    Parameters params = entry.getValue();
    
    // Convert to ServerParameters
    io.modelcontextprotocol.spec.ServerParameters serverParams =
        new io.modelcontextprotocol.spec.ServerParameters(
            params.command(),        // Command to execute
            params.args(),           // Command arguments (may be null)
            params.env()             // Environment variables (may be null)
        );
    
    // Create stdio transport
    var transport = io.modelcontextprotocol.client.transport.StdioClientTransport.builder()
        .serverParameters(serverParams)
        .build();
    
    // Wrap in named transport
    transports.add(new NamedClientMcpTransport(connectionName, transport));
}

Each configured connection gets its own transport instance with an independent process.

Process Management

  • Process Spawning: Processes are spawned when transport is created (during Spring context initialization)
  • Working Directory: Inherited from parent process (Spring Boot application)
  • Standard Streams:
    • stdin: Used for sending MCP messages to server
    • stdout: Used for receiving MCP messages from server
    • stderr: Captured for logging (not used for MCP protocol)
  • Process Termination: Processes are terminated when Spring context closes (graceful shutdown)
  • Error Handling: Process failures are logged and may cause transport initialization to fail

Advanced Configuration

Custom Working Directory

While not directly supported in properties, you can customize the working directory through environment variables or shell wrappers:

spring.ai.mcp.client.stdio:
  connections:
    custom-dir-server:
      command: sh
      args:
        - -c
        - cd /custom/dir && node mcp-server.js

Process Lifecycle Management

Stdio transports automatically manage the lifecycle of spawned processes:

  • Startup: Processes started when Spring context initializes
  • Health Check: First MCP initialize request serves as health check
  • Crash Detection: Process crashes detected via stream closure
  • Termination: Processes terminated gracefully on Spring context close
    1. Send SIGTERM signal
    2. Wait for graceful shutdown (configurable timeout)
    3. Send SIGKILL if process doesn't terminate
  • Resource Cleanup: Streams and process handles cleaned up automatically

Resource Limits

Set resource limits through environment variables or command arguments:

spring.ai.mcp.client.stdio:
  connections:
    limited-server:
      command: node
      args:
        - --max-old-space-size=512  # Limit Node.js heap to 512MB
        - mcp-server.js
      env:
        NODE_OPTIONS: --max-old-space-size=512

For Python:

spring.ai.mcp.client.stdio:
  connections:
    python-limited:
      command: python
      args:
        - -X
        - dev                        # Enable Python development mode
        - mcp_server.py
      env:
        PYTHONMALLOC: debug          # Enable memory debugging

Troubleshooting

No Stdio Transports Created

If no stdio transports are created, check:

  1. Dependencies: Ensure the MCP SDK dependencies are on the classpath
  2. Configuration: Verify spring.ai.mcp.client.stdio.connections is properly configured
  3. Enabled: Check spring.ai.mcp.client.enabled=true (or not set, as true is default)
  4. Command Availability: Ensure the command specified is available on the system PATH
  5. Logs: Enable debug logging: logging.level.org.springframework.ai.mcp=DEBUG

Process Fails to Start

If the stdio process fails to start:

  1. Command Path: Verify the command is accessible
    • Use full path: /usr/bin/node instead of node
    • Check PATH environment variable
  2. Arguments: Check that arguments are correctly specified as a list
  3. Permissions: Ensure the command has execute permissions
    • Linux/Mac: chmod +x /path/to/script
  4. Environment: Verify environment variables are correctly set
  5. Logs: Check application logs for process startup errors
  6. Manual Test: Try running the command manually:
    node /path/to/mcp-server.js

Communication Failures

If communication with the stdio server fails:

  1. Server Implementation: Verify the server correctly implements the MCP protocol
  2. Stdin/Stdout: Ensure the server uses stdin for input and stdout for output
  3. Stderr: Redirect stderr to logs for debugging
    • Server should not write MCP messages to stderr
    • Use stderr for diagnostic logging only
  4. Buffering: Ensure the server flushes stdout after each message
    • Node.js: process.stdout.write() auto-flushes
    • Python: sys.stdout.flush() after each message
  5. JSON Format: Verify messages are valid JSON-RPC 2.0
  6. Line Endings: Use \n (LF) for line endings, not \r\n (CRLF)

Process Termination

If processes don't terminate cleanly:

  1. Graceful Shutdown: Ensure your application calls ApplicationContext.close()
  2. Signal Handling: The server should handle termination signals properly
    • Linux/Mac: Handle SIGTERM signal
    • Windows: Handle SIGBREAK signal
  3. Resource Cleanup: Check for resource leaks in the server implementation
  4. Timeout: Adjust shutdown timeout if needed
  5. Force Kill: As last resort, processes are force-killed (SIGKILL)

Permission Errors

If you see permission errors:

  1. Execute Permission: Ensure script files have execute permission
  2. File Path: Use absolute paths to avoid ambiguity
  3. Parent Directory: Ensure parent process has access to command directory
  4. Security Manager: Check Java security manager settings (if enabled)

Comparison: Stdio vs HTTP Transports

FeatureStdio TransportHTTP Transports (SSE/Streamable)
LocationLocal processRemote server
Communicationstdin/stdoutHTTP
SetupNo server setup neededRequires running server
SecurityProcess isolationNetwork security (TLS, auth)
DeploymentCommand executionURL configuration
Use CaseLocal tools, dev environmentsProduction, distributed systems
ScalabilityOne process per connectionShared server, multiple clients
FirewallNo network concernsMay require firewall rules
LatencyVery low (~1-5ms)Higher (network latency)
Resource UsageOne process per connectionShared server resources
DebuggingProcess logs to stderrNetwork traffic inspection

Choose stdio for:

  • Local MCP servers (tools, utilities)
  • Development and testing
  • Single-machine deployments
  • Language-agnostic server implementations
  • No network infrastructure needed

Choose HTTP transports for:

  • Remote MCP servers
  • Production deployments
  • Distributed systems
  • Load balancing and scaling
  • Network-based security

Example Server Implementations

Node.js MCP Server

#!/usr/bin/env node

// Simple Node.js MCP server for stdio transport
const readline = require('readline');

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

// Read JSON-RPC messages from stdin
rl.on('line', (line) => {
  try {
    const message = JSON.parse(line);
    
    // Handle MCP initialize request
    if (message.method === 'initialize') {
      const response = {
        jsonrpc: '2.0',
        id: message.id,
        result: {
          protocolVersion: '2024-11-05',
          capabilities: {
            tools: {},
            resources: {},
            prompts: {}
          },
          serverInfo: {
            name: 'example-server',
            version: '1.0.0'
          }
        }
      };
      
      // Write response to stdout (auto-flushes)
      console.log(JSON.stringify(response));
    }
    
    // Handle other MCP methods...
    
  } catch (error) {
    // Log errors to stderr (not stdout!)
    console.error('Error:', error);
  }
});

// Log startup to stderr
console.error('MCP Server started');

Python MCP Server

#!/usr/bin/env python3

import sys
import json

def handle_message(message):
    """Handle incoming MCP message."""
    if message.get('method') == 'initialize':
        response = {
            'jsonrpc': '2.0',
            'id': message['id'],
            'result': {
                'protocolVersion': '2024-11-05',
                'capabilities': {
                    'tools': {},
                    'resources': {},
                    'prompts': {}
                },
                'serverInfo': {
                    'name': 'example-python-server',
                    'version': '1.0.0'
                }
            }
        }
        
        # Write response to stdout
        print(json.dumps(response), flush=True)  # IMPORTANT: flush=True

def main():
    # Log startup to stderr (not stdout!)
    print('MCP Server started', file=sys.stderr)
    
    # Read JSON-RPC messages from stdin
    for line in sys.stdin:
        try:
            message = json.loads(line)
            handle_message(message)
        except Exception as error:
            # Log errors to stderr
            print(f'Error: {error}', file=sys.stderr)

if __name__ == '__main__':
    main()

Best Practices

  1. Use Absolute Paths: Specify full paths to executables and scripts
  2. Flush Output: Always flush stdout after writing MCP messages
  3. Separate Streams: Use stdout for MCP protocol, stderr for logging
  4. Handle Signals: Implement graceful shutdown on SIGTERM
  5. Validate JSON: Ensure all MCP messages are valid JSON-RPC 2.0
  6. Error Handling: Catch and log errors without crashing
  7. Resource Limits: Set appropriate memory and CPU limits
  8. Testing: Test servers manually before configuring in Spring
  9. Documentation: Document required environment variables and dependencies
  10. Versioning: Include version information in server info

Security Considerations

  1. Command Injection: Never use user input directly in command or args
  2. Path Traversal: Validate file paths if server accesses filesystem
  3. Environment Variables: Don't expose sensitive data in env vars (use secrets management)
  4. Process Isolation: Stdio processes run with same privileges as Spring app
  5. Resource Limits: Set limits to prevent resource exhaustion
  6. Input Validation: Validate all inputs in server implementation
  7. Least Privilege: Run processes with minimal required permissions

Related Documentation

tessl i tessl/maven-org-springframework-ai--spring-ai-starter-mcp-client-webflux@1.1.0

docs

index.md

tile.json