Skip to main content

Transport Configuration

MCP servers can be accessed via different transport mechanisms. This guide covers all supported transport types and their configuration.

Table of Contents​

Transport Types​

@gleanwork/mcp-server-tester supports two transport types:

  1. stdio - Local server processes (via stdin/stdout)
  2. HTTP - Remote servers (via HTTP/SSE)

Choose based on your server deployment:

  • Use stdio for local development and testing
  • Use HTTP for remote servers or production environments

Evaluation client support​

Plain stdio is supported by these built-in evaluation client families:

  • Claude CLI (claude-code) and Vercel AI SDK (api).
  • The ChatGPT desktop app's ChatGPT Work and Codex surfaces: the chatgpt and codex-app clients.
  • Cowork (cowork) on macOS and Linux. The package exports COWORK_STDIO_PLATFORMS as ['darwin', 'linux'].

Plain stdio uses command, optional args, cwd, and declared env; it does not require a URL or plugin. Cowork also supports private file-backed native proxies and mixed HTTP/stdio sets. Its client-resolved fields (url, auth, files, minTools, and placeholders) are not portable to ChatGPT or direct MCP clients. ChatGPT plugin-specific overrides use plugins[].mcp instead.

Over HTTP, chatgpt and codex-app take a bearer token only: an HTTP server with headers or OAuth fails before the app starts (ChatGPT guide). Cowork takes headers and a bearer token.

Cowork rejects inheritEnv: true; declare the server environment explicitly. It implements cwd with a fixed argv wrapper, not shell-interpolated values. Mac setup owns the private files and runtime paths; Linux setup checks caller-staged paths. On Mac, only ${pluginRoot:...} resolution is unavailable because marketplace installation paths are unknown before startup; native proxies with known local paths remain supported. See the Cowork contract and ChatGPT guide.

Transport support does not enforce read-only tools. The caller's proxy policy must intercept writes where required; MST stdio transport alone does not.

Stdio (Local Server)​

The stdio transport starts a local MCP server as a child process and communicates via standard input/output.

Basic Configuration​

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
projects: [
{
name: 'mcp-local',
use: {
mcpConfig: {
transport: 'stdio',
command: 'node',
args: ['path/to/server.js'],
},
},
},
],
});

Configuration Options​

{
transport: 'stdio',
command: string, // Command to run (e.g., 'node', 'python', 'deno')
args?: string[], // Command arguments
env?: Record<string, string>, // Environment variables
capabilities?: { // MCP capabilities
roots?: { listChanged?: boolean },
sampling?: { ... },
},
protocol?: 'legacy' | 'auto' | string, // Protocol to speak (default: 'legacy'); see below
protocolProbe?: { timeoutMs?: number }, // Bounds the 'auto' probe
}

Debug Logging: Enable with DEBUG=mcp-server-tester:* npm test

Examples​

Node.js Server:

mcpConfig: {
transport: 'stdio',
command: 'node',
args: ['dist/server.js'],
}

TypeScript with ts-node:

mcpConfig: {
transport: 'stdio',
command: 'npx',
args: ['ts-node', 'src/server.ts'],
}

Python Server:

mcpConfig: {
transport: 'stdio',
command: 'python',
args: ['server.py'],
env: {
PYTHONPATH: '/path/to/modules',
},
}

Deno Server:

mcpConfig: {
transport: 'stdio',
command: 'deno',
args: ['run', '--allow-all', 'server.ts'],
}

With Environment Variables:

mcpConfig: {
transport: 'stdio',
command: 'node',
args: ['server.js'],
env: {
NODE_ENV: 'test',
API_KEY: 'test-key',
LOG_LEVEL: 'debug',
},
}

With Debug Logging:

# Enable debug logging via environment variable
DEBUG=mcp-server-tester:* npm test

Process Management​

The stdio transport automatically:

  • Starts the server process when tests begin
  • Manages stdin/stdout communication
  • Terminates the process when tests complete
  • Handles process errors and crashes

HTTP (Remote Server)​

The HTTP transport connects to a remote MCP server via HTTP and Server-Sent Events (SSE).

Basic Configuration​

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
projects: [
{
name: 'mcp-remote',
use: {
mcpConfig: {
transport: 'http',
serverUrl: 'http://localhost:3000/mcp',
},
},
},
],
});

Configuration Options​

{
transport: 'http',
serverUrl: string, // MCP server URL
requestTimeoutMs?: number, // Request timeout (default: 30000)
headers?: Record<string, string>, // Custom HTTP headers
protocol?: 'legacy' | 'auto' | string, // Protocol to speak (default: 'legacy')
protocolProbe?: { timeoutMs?: number }, // Bounds the 'auto' probe
}

Protocol Version​

Both transports accept protocol. The default, 'legacy', uses the initialize handshake exactly as MST 1.x did. Pin '2026-07-28' to test the stateless protocol, pin a legacy revision such as '2025-06-18', or use 'auto' to probe and fall back. The legacy HTTP+SSE fallback only speaks 2024-11-05, so a connection pinned to any other revision never falls back to it. See Protocol Versions for running an eval against several protocols.

Security note: When serverUrl uses http:// with a non-localhost address, the framework emits a console warning: tokens and credentials are transmitted unencrypted over plain HTTP. Always use https:// for remote servers. The warning is suppressed for localhost / 127.0.0.1 since local development traffic stays on the machine.

Examples​

Local HTTP Server:

mcpConfig: {
transport: 'http',
serverUrl: 'http://localhost:3000/mcp',
}

Remote Server with Authentication:

mcpConfig: {
transport: 'http',
serverUrl: 'https://api.example.com/mcp',
headers: {
'Authorization': 'Bearer your-token-here',
'X-API-Key': 'your-api-key',
},
}

For OAuth 2.1 authentication and more advanced auth patterns, see the Authentication Guide.

With Custom Timeout:

mcpConfig: {
transport: 'http',
serverUrl: 'http://localhost:3000/mcp',
requestTimeoutMs: 60000, // 60 seconds
}

Production Server:

mcpConfig: {
transport: 'http',
serverUrl: process.env.MCP_SERVER_URL || 'https://mcp.production.com',
headers: {
'Authorization': `Bearer ${process.env.MCP_API_TOKEN}`,
},
requestTimeoutMs: 45000,
}

Connection Management​

The HTTP transport:

  • Establishes HTTP connection on first use
  • Maintains SSE stream for server events
  • Closes connection when tests complete

Note: Automatic reconnection is not currently implemented. If the remote server drops the connection mid-test-suite, subsequent tests will fail with connection errors. For production use against unreliable servers, implement reconnect logic at the Playwright globalSetup level or use Playwright's retry mechanism.

Multiple Transports​

Test against multiple server configurations using Playwright projects:

import { defineConfig } from '@playwright/test';

export default defineConfig({
projects: [
// Local development server
{
name: 'local-dev',
use: {
mcpConfig: {
transport: 'stdio',
command: 'node',
args: ['dist/server.js'],
env: { NODE_ENV: 'development' },
},
},
},

// Local production build
{
name: 'local-prod',
use: {
mcpConfig: {
transport: 'stdio',
command: 'node',
args: ['dist/server.js'],
env: { NODE_ENV: 'production' },
},
},
},

// Staging server
{
name: 'staging',
use: {
mcpConfig: {
transport: 'http',
serverUrl: 'https://staging.example.com/mcp',
headers: {
Authorization: `Bearer ${process.env.STAGING_TOKEN}`,
},
},
},
},

// Production server
{
name: 'production',
use: {
mcpConfig: {
transport: 'http',
serverUrl: 'https://api.example.com/mcp',
headers: {
Authorization: `Bearer ${process.env.PROD_TOKEN}`,
},
},
},
},
],
});

Running Specific Projects​

# Run all projects
npx playwright test

# Run only local tests
npx playwright test --project=local-dev

# Run only remote tests
npx playwright test --project=staging --project=production

Environment Variables​

Use environment variables for sensitive data and environment-specific config:

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
projects: [
{
name: 'mcp',
use: {
mcpConfig: {
transport: process.env.MCP_TRANSPORT || 'stdio',
// Stdio config
...(process.env.MCP_TRANSPORT === 'stdio' && {
command: process.env.MCP_COMMAND || 'node',
args: [process.env.MCP_SERVER_PATH || 'server.js'],
}),
// HTTP config
...(process.env.MCP_TRANSPORT === 'http' && {
serverUrl: process.env.MCP_SERVER_URL,
headers: {
Authorization: `Bearer ${process.env.MCP_API_TOKEN}`,
},
}),
},
},
},
],
});

Example .env File​

# Transport type
MCP_TRANSPORT=stdio

# Stdio config
MCP_COMMAND=node
MCP_SERVER_PATH=dist/server.js
NODE_ENV=test

# HTTP config (when MCP_TRANSPORT=http)
# MCP_SERVER_URL=https://api.example.com/mcp
# MCP_API_TOKEN=your-token-here

Load with dotenv:

npm install --save-dev dotenv
// playwright.config.ts
import { defineConfig } from '@playwright/test';
import * as dotenv from 'dotenv';

dotenv.config();

export default defineConfig({
// ... config using process.env
});

Troubleshooting​

Stdio Transport Issues​

Issue: Server process fails to start

Error: Command not found: node server.js

Solutions:

  • Verify command exists: which node
  • Use absolute paths: /usr/local/bin/node
  • Check file exists: ls -la server.js
  • Ensure executable permissions: chmod +x server.js

Issue: Server starts but doesn't respond

Solutions:

  • Enable debug logging: DEBUG=mcp-server-tester:* npm test
  • Check server logs for errors
  • Verify server writes to stdout (not stderr)
  • Ensure server follows MCP protocol

Issue: Environment variables not set

Solutions:

  • Use env option in config
  • Check variable names and values
  • Verify dotenv is loaded before config

HTTP Transport Issues​

Issue: Connection refused

Error: connect ECONNREFUSED localhost:3000

Solutions:

  • Verify server is running: curl http://localhost:3000/mcp
  • Check correct port and host
  • Ensure server accepts HTTP connections
  • Check firewall rules

Issue: Request timeout

Error: Request timeout after 30000ms

Solutions:

  • Increase timeout: requestTimeoutMs: 60000
  • Check server response time
  • Verify server isn't blocking
  • Look for network issues

Issue: Authentication failure

Error: 401 Unauthorized

Solutions:

  • Verify API token is correct
  • Check header format: Authorization: Bearer <token>
  • Ensure token hasn't expired
  • Test authentication separately: curl -H "Authorization: Bearer <token>" <url>

Next Steps​