feat: add HTTP/REST transport for MCP servers
- Refactor server to use a generic transport interface - Add HttpTransport for JSON-RPC and REST over HTTP - Move existing STDIO logic into a StdioTransport - Enable dual-mode (STDIO/HTTP) via command-line flags - Add new examples and docs for HTTP server usage
This commit is contained in:
289
examples/mcp/http_demo/server.vsh
Executable file
289
examples/mcp/http_demo/server.vsh
Executable file
@@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env -S v -n -w -cg -d use_openssl -d json_no_inline_sumtypes -enable-globals run
|
||||
|
||||
import freeflowuniverse.herolib.mcp
|
||||
import freeflowuniverse.herolib.mcp.transport
|
||||
import freeflowuniverse.herolib.schemas.jsonschema
|
||||
import x.json2
|
||||
import os
|
||||
import time
|
||||
|
||||
// File operations tool
|
||||
fn read_file_handler(arguments map[string]json2.Any) !mcp.ToolCallResult {
|
||||
path := arguments['path'].str()
|
||||
|
||||
content := os.read_file(path) or {
|
||||
return mcp.ToolCallResult{
|
||||
is_error: true
|
||||
content: [
|
||||
mcp.ToolContent{
|
||||
typ: 'text'
|
||||
text: 'Error reading file: ${err}'
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: [mcp.ToolContent{
|
||||
typ: 'text'
|
||||
text: content
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
// Calculator tool
|
||||
fn calculator_handler(arguments map[string]json2.Any) !mcp.ToolCallResult {
|
||||
operation := arguments['operation'].str()
|
||||
num1 := arguments['num1'].f64()
|
||||
num2 := arguments['num2'].f64()
|
||||
|
||||
result := match operation {
|
||||
'add' {
|
||||
num1 + num2
|
||||
}
|
||||
'subtract' {
|
||||
num1 - num2
|
||||
}
|
||||
'multiply' {
|
||||
num1 * num2
|
||||
}
|
||||
'divide' {
|
||||
if num2 == 0 {
|
||||
return mcp.ToolCallResult{
|
||||
is_error: true
|
||||
content: [
|
||||
mcp.ToolContent{
|
||||
typ: 'text'
|
||||
text: 'Division by zero'
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
num1 / num2
|
||||
}
|
||||
else {
|
||||
return mcp.ToolCallResult{
|
||||
is_error: true
|
||||
content: [
|
||||
mcp.ToolContent{
|
||||
typ: 'text'
|
||||
text: 'Unknown operation: ${operation}'
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: [
|
||||
mcp.ToolContent{
|
||||
typ: 'text'
|
||||
text: '${num1} ${operation} ${num2} = ${result}'
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// System info tool
|
||||
fn system_info_handler(arguments map[string]json2.Any) !mcp.ToolCallResult {
|
||||
info_type := arguments['type'].str()
|
||||
|
||||
result := match info_type {
|
||||
'os' {
|
||||
$if windows {
|
||||
'Windows'
|
||||
} $else $if macos {
|
||||
'macOS'
|
||||
} $else {
|
||||
'Linux'
|
||||
}
|
||||
}
|
||||
'time' {
|
||||
time.now().str()
|
||||
}
|
||||
'user' {
|
||||
os.getenv('USER')
|
||||
}
|
||||
'home' {
|
||||
os.home_dir()
|
||||
}
|
||||
else {
|
||||
'Unknown info type: ${info_type}'
|
||||
}
|
||||
}
|
||||
|
||||
return mcp.ToolCallResult{
|
||||
is_error: false
|
||||
content: [
|
||||
mcp.ToolContent{
|
||||
typ: 'text'
|
||||
text: '${info_type}: ${result}'
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
fn parse_args() (transport.TransportMode, int) {
|
||||
args := os.args[1..]
|
||||
mut mode := transport.TransportMode.stdio
|
||||
mut port := 8080
|
||||
|
||||
for i, arg in args {
|
||||
match arg {
|
||||
'--http' {
|
||||
mode = .http
|
||||
}
|
||||
'--port' {
|
||||
if i + 1 < args.len {
|
||||
port = args[i + 1].int()
|
||||
}
|
||||
}
|
||||
else {}
|
||||
}
|
||||
}
|
||||
|
||||
return mode, port
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// Parse command line arguments
|
||||
mode, port := parse_args()
|
||||
|
||||
// Create backend with multiple useful tools
|
||||
backend := mcp.MemoryBackend{
|
||||
tools: {
|
||||
'read_file': mcp.Tool{
|
||||
name: 'read_file'
|
||||
description: 'Read the contents of a file'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'path': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Path to the file to read'
|
||||
})
|
||||
}
|
||||
required: ['path']
|
||||
}
|
||||
}
|
||||
'calculator': mcp.Tool{
|
||||
name: 'calculator'
|
||||
description: 'Perform basic mathematical operations'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'operation': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Operation to perform: add, subtract, multiply, divide'
|
||||
})
|
||||
'num1': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'number'
|
||||
description: 'First number'
|
||||
})
|
||||
'num2': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'number'
|
||||
description: 'Second number'
|
||||
})
|
||||
}
|
||||
required: ['operation', 'num1', 'num2']
|
||||
}
|
||||
}
|
||||
'system_info': mcp.Tool{
|
||||
name: 'system_info'
|
||||
description: 'Get system information'
|
||||
input_schema: jsonschema.Schema{
|
||||
typ: 'object'
|
||||
properties: {
|
||||
'type': jsonschema.SchemaRef(jsonschema.Schema{
|
||||
typ: 'string'
|
||||
description: 'Type of info: os, time, user, home'
|
||||
})
|
||||
}
|
||||
required: ['type']
|
||||
}
|
||||
}
|
||||
}
|
||||
tool_handlers: {
|
||||
'read_file': read_file_handler
|
||||
'calculator': calculator_handler
|
||||
'system_info': system_info_handler
|
||||
}
|
||||
}
|
||||
|
||||
// Create transport configuration
|
||||
transport_config := transport.TransportConfig{
|
||||
mode: mode
|
||||
http: transport.HttpConfig{
|
||||
port: port
|
||||
protocol: .both // Support both JSON-RPC and REST
|
||||
}
|
||||
}
|
||||
|
||||
// Create and start the server
|
||||
mut server := mcp.new_server(backend, mcp.ServerParams{
|
||||
config: mcp.ServerConfiguration{
|
||||
server_info: mcp.ServerInfo{
|
||||
name: 'http_demo'
|
||||
version: '1.0.0'
|
||||
}
|
||||
}
|
||||
transport: transport_config
|
||||
})!
|
||||
|
||||
if mode == .http {
|
||||
println('🚀 HTTP MCP Server Demo')
|
||||
println('=======================')
|
||||
println('')
|
||||
println('📡 Server running on: http://localhost:${port}')
|
||||
println('')
|
||||
println('🔗 Endpoints:')
|
||||
println(' • Health: http://localhost:${port}/health')
|
||||
println(' • JSON-RPC: http://localhost:${port}/jsonrpc')
|
||||
println(' • Tools: http://localhost:${port}/api/tools')
|
||||
println('')
|
||||
println('🧪 Test Commands:')
|
||||
println(' # Health check')
|
||||
println(' curl http://localhost:${port}/health')
|
||||
println('')
|
||||
println(' # List available tools')
|
||||
println(' curl http://localhost:${port}/api/tools')
|
||||
println('')
|
||||
println(' # Call calculator tool (REST)')
|
||||
println(' curl -X POST http://localhost:${port}/api/tools/calculator/call \\')
|
||||
println(' -H "Content-Type: application/json" \\')
|
||||
println(' -d \'{"operation":"add","num1":10,"num2":5}\'')
|
||||
println('')
|
||||
println(' # Get system info (REST)')
|
||||
println(' curl -X POST http://localhost:${port}/api/tools/system_info/call \\')
|
||||
println(' -H "Content-Type: application/json" \\')
|
||||
println(' -d \'{"type":"os"}\'')
|
||||
println('')
|
||||
println(' # Call via JSON-RPC')
|
||||
println(' curl -X POST http://localhost:${port}/jsonrpc \\')
|
||||
println(' -H "Content-Type: application/json" \\')
|
||||
println(' -d \'{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"calculator","arguments":{"operation":"multiply","num1":7,"num2":8}}}\'')
|
||||
println('')
|
||||
println('🔌 VS Code Integration:')
|
||||
println(' Add to your VS Code MCP settings:')
|
||||
println(' {')
|
||||
println(' "mcpServers": {')
|
||||
println(' "http_demo": {')
|
||||
println(' "transport": "http",')
|
||||
println(' "url": "http://localhost:${port}/jsonrpc"')
|
||||
println(' }')
|
||||
println(' }')
|
||||
println(' }')
|
||||
println('')
|
||||
} else {
|
||||
println('📟 STDIO MCP Server Demo')
|
||||
println('========================')
|
||||
println('Ready for JSON-RPC messages on stdin...')
|
||||
println('')
|
||||
println('💡 Tip: Run with --http --port 8080 for HTTP mode')
|
||||
}
|
||||
|
||||
server.start()!
|
||||
}
|
||||
Reference in New Issue
Block a user