On Windows, use 127.0.0.1 instead of localhost to avoid IPv6 connection issues:
- Server:
HttpTransport(host="127.0.0.1", port=3000) - Client:
http://127.0.0.1:3000/ - MCP Remote:
npx mcp-remote http://127.0.0.1:3000 --allow-http
-
Direct curl test:
curl -X POST http://127.0.0.1:3000/ -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
-
MCP Inspector with mcp-remote bridge:
npx @modelcontextprotocol/inspector stdio -- npx mcp-remote http://127.0.0.1:3000 --allow-http
-
Claude Desktop configuration:
{ "mcpServers": { "julia-http": { "command": "npx", "args": ["mcp-remote", "http://127.0.0.1:3000", "--allow-http"] } } }
The HTTP transport implements the Streamable HTTP specification:
- POST requests: JSON-RPC messages with immediate JSON responses
- GET requests: SSE streams for server-to-client notifications (requires
Accept: text/event-streamheader)
Test stdio servers using pipe communication:
# Single request
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}},"id":1}' | julia --project examples/time_server.jl 2>/dev/null | jq .
# Multiple requests (initialize, then list tools)
echo -e '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}},"id":1}\n{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}' | julia --project examples/time_server.jl 2>/dev/null | tail -1 | jq .
# Call a tool
echo -e '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}},"id":1}\n{"jsonrpc":"2.0","method":"tools/call","params":{"name":"get_time","arguments":{"format":"HH:MM:SS"}},"id":2}' | julia --project examples/time_server.jl 2>/dev/null | tail -1 | jq .-
Start the server (in one terminal):
julia --project examples/simple_http_server.jl
-
Test with curl (in another terminal):
# Initialize curl -X POST http://localhost:3000/ \ -H 'Content-Type: application/json' \ -H 'MCP-Protocol-Version: 2025-06-18' \ -H 'Accept: application/json' \ -d '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"test-client","version":"1.0.0"}},"id":1}' | jq . # List tools (include session ID if provided) curl -X POST http://localhost:3000/ \ -H 'Content-Type: application/json' \ -H 'Mcp-Session-Id: <session-id-from-init>' \ -d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":2}' | jq . # Call a tool curl -X POST http://localhost:3000/ \ -H 'Content-Type: application/json' \ -H 'Mcp-Session-Id: <session-id-from-init>' \ -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"echo","arguments":{"message":"Hello MCP!"}},"id":3}' | jq .
-
Test SSE streaming:
# Connect SSE client curl -N -H 'Accept: text/event-stream' http://localhost:3000/
-
Protocol negotiation:
- Test with different protocol versions
- Verify server capabilities response
-
Tool testing:
- List available tools
- Call tools with valid arguments
- Test error handling with invalid arguments
- Test missing required parameters
-
Session management (HTTP only):
- Verify session ID generation on init
- Test requests with/without session ID
- Verify 400 Bad Request for missing session when required
-
Error scenarios:
- Call non-existent methods
- Send malformed JSON
- Test with invalid tool names
- Exceed parameter limits
Create a test script for comprehensive testing:
# test_client.jl
using HTTP, JSON3
function test_mcp_server(url)
# Initialize
init_response = HTTP.post(url,
["Content-Type" => "application/json"],
JSON3.write(Dict(
"jsonrpc" => "2.0",
"method" => "initialize",
"params" => Dict(
"protocolVersion" => "2025-06-18",
"clientInfo" => Dict("name" => "test", "version" => "1.0")
),
"id" => 1
))
)
println("Init: ", String(init_response.body))
# Extract session ID if present
session_id = HTTP.header(init_response, "Mcp-Session-Id", "")
# List tools
headers = ["Content-Type" => "application/json"]
if !isempty(session_id)
push!(headers, "Mcp-Session-Id" => session_id)
end
tools_response = HTTP.post(url, headers,
JSON3.write(Dict(
"jsonrpc" => "2.0",
"method" => "tools/list",
"params" => Dict(),
"id" => 2
))
)
println("Tools: ", String(tools_response.body))
end
test_mcp_server("http://localhost:3000/")Problem: Session validation happens before checking if request is initialization, causing init requests to fail.
Solution: Read and parse request body BEFORE validating session:
# ✅ CORRECT: Parse body first, then check session
body = String(read(stream))
msg = JSON3.read(body)
is_initialize = get(msg, "method", "") == "initialize"
if !is_initialize && transport.session_required
# Check session only for non-initialization requests
end
# ❌ WRONG: Don't use HTTP.payload(request) - causes streaming issuesUse less common ports to avoid conflicts:
- 8765: Good default test port (less common than 8080)
- 3000-3999: Often used by web dev servers
- 8080: Commonly used by proxies/web servers
- 5000-5999: Often used by Flask/Python servers
Check for running servers:
ps aux | grep -E "julia.*(server|mcp)" | grep -v grepNotifications (requests without id field) must return 202 Accepted with no body:
if is_notification
HTTP.setstatus(stream, 202)
HTTP.setheader(stream, "Content-Length" => "0")
HTTP.startwrite(stream) # Send headers
# No body for 202 per spec
endUse Base.flush() explicitly for HTTP streams to avoid naming conflicts:
write(stream, event)
Base.flush(stream) # Not just flush(stream)- Build:
using Pkg; Pkg.build("ModelContextProtocol") - Test all:
using Pkg; Pkg.test("ModelContextProtocol") - Test single:
julia --project -e 'using Pkg; Pkg.test("ModelContextProtocol", test_args=["specific_test.jl"])' - Documentation:
julia --project=docs docs/make.jl - Documentation deployment: Automatic via GitHub Actions on push to main
- REPL:
using ModelContextProtocolafter activating project - Example server:
julia --project examples/multi_content_tool.jl
Integration tests with external MCP clients (Python SDK) are located in dev/integration_tests/. These tests are separate from the main test suite and require additional setup:
-
Setup the integration test environment:
cd dev/integration_tests julia --project -e 'using Pkg; Pkg.instantiate()' pip install -r requirements.txt
-
Run individual integration tests:
# Basic STDIO communication test julia --project test_basic_stdio.jl # Full integration test with Python MCP client julia --project test_integration.jl # Python client compatibility test julia --project test_python_client.jl
-
Run all integration tests:
julia --project runtests.jl
- STDIO Protocol: Tests bidirectional JSON-RPC communication over stdio
- Python Client Compatibility: Validates that Julia MCP servers work with the official Python MCP SDK
- Real Protocol Compliance: End-to-end testing with actual MCP clients
- Cross-Language Interoperability: Ensures the Julia implementation follows the MCP specification correctly
- Before releasing new versions
- When making protocol-level changes
- When adding new MCP features
- For debugging client compatibility issues
Note: Integration tests are not run automatically in CI and require manual execution due to their external Python dependencies.
src/
├── ModelContextProtocol.jl # Main module entry point
├── core/ # Core server functionality
│ ├── capabilities.jl # Protocol capability management
│ ├── init.jl # Initialization logic
│ ├── server.jl # Server implementation
│ ├── server_types.jl # Server-specific types
│ └── types.jl # Core type definitions
├── features/ # MCP feature implementations
│ ├── prompts.jl # Prompt handling
│ ├── resources.jl # Resource management
│ └── tools.jl # Tool implementation
├── protocol/ # JSON-RPC protocol layer
│ ├── handlers.jl # Request handlers
│ ├── jsonrpc.jl # JSON-RPC implementation
│ └── messages.jl # Protocol message types
├── types.jl # Public type exports
└── utils/ # Utility functions
├── errors.jl # Error handling
├── logging.jl # MCP-compliant logging
└── serialization.jl # Message serialization
- Imports: Group related imports (e.g.,
using JSON3, URIs, DataStructures) - Types: Use abstract type hierarchy, concrete types with
Base.@kwdef - Naming:
- PascalCase for types (e.g.,
MCPTool,TextContent) - snake_case for functions and variables (e.g.,
mcp_server,request_id) - Use descriptive names that reflect purpose
- PascalCase for types (e.g.,
- Utility Functions:
content2dict(content::Content): Convert Content objects to Dict for JSON serialization- Uses multiple dispatch for different content types (TextContent, ImageContent, EmbeddedResource)
- Automatically handles base64 encoding for binary data
- Documentation:
- Add full docstrings for all types and methods
- Use imprative phrasing for the one line description in docstrings "Scan a directory" not "Scans a directory"
- Use triple quotes with function signature at top including all parameters and return type:
""" function_name(param1::Type1, param2::Type2) -> ReturnType Brief, one line imperative phrase of the function's action. # Arguments - `param1::Type1`: Description of the first parameter - `param2::Type2`: Description of the second parameter # Returns - `ReturnType`: Description of the return value """
- For structs and types, include the constructor pattern and all fields:
""" StructName(; field1::Type1=default1, field2::Type2=default2) Description of the struct's purpose. # Fields - `field1::Type1`: Description of the first field - `field2::Type2`: Description of the second field """
- Include a concise description after the signature
- Always separate sections with blank lines
- No examples block required
- Error handling: Use
ErrorCodesenum for structured error reporting - Organization: Follow modular structure with core, features, protocol, utils
- Type annotations: Use for function parameters and struct fields
- Constants: Use UPPER_CASE for true constants
- Multi-Content Tool Returns: Tools can return either a single
Contentobject or aVector{<:Content}for multiple items- Single:
return TextContent(text = "result") - Multiple:
return [TextContent(text = "item1"), ImageContent(data = ..., mime_type = "image/png")] - Mixed content types in same response supported
- Default
return_typeisVector{Content}- single items are auto-wrapped - Set
return_type = TextContentto validate single content returns
- Single:
- MCP Protocol Compliance: Tools are only returned via
tools/listrequest, not in initialization response- Initialization response only indicates tool support with
{"tools": {"listChanged": true/false}} - Clients must call
tools/listafter initialization to discover available tools
- Initialization response only indicates tool support with
- Tool Parameter Defaults: Tool parameters can have default values specified in ToolParameter struct
- Define using
defaultfield:ToolParameter(name="timeout", type="number", default=30.0) - Handler automatically applies defaults when parameters are not provided
- Defaults are included in the tool schema returned by
tools/list
- Define using
- Direct CallToolResult Returns: Tool handlers can return CallToolResult objects directly
- Provides full control over response structure including error handling
- Example:
return CallToolResult(content=[...], is_error=true) - When returning CallToolResult, the tool's return_type field is ignored
- Useful for tools that need to indicate errors or complex response patterns
The ModelContextProtocol.jl package includes infrastructure for progress monitoring, but with significant limitations:
-
Types and Structures:
ProgressTokentype alias:Union{String,Int}for tracking operationsProgressstruct: Containstoken,current,total, and optionalmessagefieldsProgressParamsstruct: Used for progress notifications with token, progress value, and optional totalRequestMetastruct: Contains optionalprogress_tokenfield for request tracking
-
Server Infrastructure:
- Server maintains
progress_trackers::Dict{Union{String,Int}, Progress}for tracking ongoing operations - Request handlers receive
RequestContextwith optionalprogress_tokenfrom the request metadata
- Server maintains
-
Protocol Support:
- JSON-RPC notification handler recognizes
"notifications/progress"method - Progress notification messages are defined in the protocol layer
- JSON-RPC notification handler recognizes
-
No Outbound Notification Mechanism:
- The server can receive and process notifications but cannot send them to clients
- The
process_messagefunction only handles stdin→stdout request/response flow - No
send_notificationor similar function exists for pushing updates to clients
-
Tool Handler Constraints:
- Tool handlers execute synchronously and return a single result
- No access to server context or communication channels within handlers
- Cannot emit progress updates during long-running operations
-
Missing Implementation:
- The
handle_notificationfunction for"notifications/progress"is empty (no-op) - No examples or documentation showing progress monitoring usage
- Progress trackers are maintained in server state but never utilized
- The
-
Server Context Enhancement:
# Add notification capability to RequestContext mutable struct RequestContext server::Server request_id::Union{RequestId,Nothing} progress_token::Union{ProgressToken,Nothing} notification_channel::Union{Channel,Nothing} # New field end
-
Asynchronous Tool Execution:
# Modified tool handler pattern handler = function(params, ctx::RequestContext) # Can send progress notifications via ctx.notification_channel if !isnothing(ctx.progress_token) put!(ctx.notification_channel, ProgressNotification(...)) end end
-
Bidirectional Communication:
- Implement a notification queue alongside the request/response flow
- Add a
send_notificationfunction that writes to stdout - Ensure thread-safe access to stdout for concurrent notifications
-
Alternative Workarounds:
- Return partial results as streaming content
- Use resource subscriptions for status updates
- Implement polling-based progress checking via separate tool calls
- Short-term: Document the current limitations clearly in tool implementations
- Medium-term: Add server context access to tool handlers for future extensibility
- Long-term: Implement full bidirectional communication with proper notification support
The infrastructure exists but requires additional implementation to enable progress monitoring from within tool handlers.
- Use 127.0.0.1 instead of localhost on Windows for HTTP transport
- Julia JIT compilation takes 5-10 seconds on first server start
- Port 8765 is good alternative to avoid common conflicts (3000, 8080, etc.)
When testing MCP servers, check for running processes:
# Check for running Julia MCP servers
ps aux | grep "julia.*examples.*" | grep -v grep
# Check specific ports (HTTP servers)
netstat -tlnp | grep ":300[0-9]" # Common MCP HTTP ports 3000-3009
netstat -tlnp | grep ":8765" # Alternative test port# Kill specific processes by PID
kill PID1 PID2 PID3
# Kill all Julia MCP processes (use carefully)
pkill -f "julia.*examples.*"
# Force kill if needed
pkill -9 -f "julia.*examples.*"- Always check for running servers before starting new ones
- Use different ports for concurrent testing (3000, 3001, 3002, etc.)
- Kill servers after testing to free ports and resources
- Allow 5-10 seconds for Julia JIT compilation before making requests
- Use proper session management for HTTP servers (Mcp-Session-Id header)
- Port conflicts: Use
netstatto check occupied ports - Hanging processes: Use
kill -9for force termination - JIT compilation timeouts: Allow adequate time for server startup
- Session validation: HTTP servers require proper session headers after initialization
-
Core Transport Protocols
- ✅ stdio transport (standard input/output)
- ✅ Streamable HTTP transport with SSE support
- ✅ Session management (Mcp-Session-Id headers)
- ✅ Protocol version negotiation (only 2025-06-18 supported)
-
Version Validation
- ✅ Strict protocol version validation (only accepts 2025-06-18)
- ✅ Proper error responses for unsupported versions
- ✅ MCP-Protocol-Version header validation in HTTP transport
-
JSON-RPC Compliance (2025-06-18)
- ✅ Removed JSON-RPC batching support (returns proper error)
- ✅ Single message per request enforcement
- ✅ Proper JSON-RPC 2.0 validation
-
Content Types
- ✅ TextContent - Text-based responses
- ✅ ImageContent - Binary image content with base64 encoding
- ✅ EmbeddedResource - Embedded resource content
- ✅ ResourceLink - Resource references (NEW in 2025-06-18)
-
Multi-Content Tool Returns
- ✅ Single content return:
return TextContent(...) - ✅ Multiple content return:
return [TextContent(...), ImageContent(...)] - ✅ Mixed content types in single response
- ✅ Single content return:
-
Security Features
- ✅ Origin header validation for HTTP transport
- ✅ Localhost binding by default
- ✅ Cryptographically secure session IDs (UUID format)
- ✅ Session ID ASCII validation (0x21-0x7E)
-
Auto-Registration System
- ✅ Directory-based component organization
- ✅ Automatic tool/prompt/resource discovery
- ✅ Isolated module loading for each component file
-
OAuth Authorization (Optional)
- ❌ OAuth Resource Server classification
- ❌ Authorization server discovery
- ❌ Protected resource metadata
- ❌ Resource Indicators (RFC 8707) support
-
Elicitation (Optional)
- ❌ Server-to-client user interaction requests
- ❌ elicitation/create method handling
- ❌ Structured user input with JSON schemas
- ❌ Nested interaction workflows
-
Client Features (Client-Side)
- ❌ Roots - filesystem access boundaries
- ❌ Sampling - LLM completion requests
- ❌ Completion/autocompletion suggestions
-
Advanced Features (Optional)
- ❌ _meta fields on message types (metadata support)
- ❌ Audio content type (AudioContent)
- ❌ Progress notifications with bidirectional updates
- ❌ Stream resumption with Last-Event-ID
- ❌ title field support for human-friendly display names
-
Enterprise Features (Optional)
- ❌ Advanced authentication beyond basic session management
- ❌ Fine-grained authorization per tool/resource
- ❌ Enterprise SSO integration
High Priority (Core 2025-06-18 compliance):
- _meta field support on core types
- title field support for tools/resources/prompts
- Audio content type (AudioContent)
Medium Priority (Enhanced functionality):
- Progress notification improvements
- Stream resumption support
- Elicitation basic support
Low Priority (Enterprise/Optional):
- OAuth authorization support
- Advanced authentication
- Client-side features (roots, sampling)
- ✅ Protocol version validation working
- ✅ JSON-RPC batch rejection working
- ✅ ResourceLink content type working
- ✅ All existing functionality preserved
- ✅ HTTP transport fully compliant with 2025-06-18