feat: add complete V server example and improve runner
- Create new `server.vsh` example with custom tools - Update `example.sh` to use the new V server script - Improve README with new, clearer running instructions - Fix server to not send responses for notifications - Remove debug logging statements from server and handler
This commit is contained in:
@@ -24,15 +24,33 @@ server.start()!
|
|||||||
|
|
||||||
## Running Example
|
## Running Example
|
||||||
|
|
||||||
|
### Option 1: Using the example script (Recommended)
|
||||||
|
|
||||||
1. `bash example.sh`
|
1. `bash example.sh`
|
||||||
2. `open localhost:5173`
|
2. The MCP inspector will open automatically in your browser
|
||||||
|
3. The inspector should connect automatically to the V server
|
||||||
|
|
||||||
|
### Option 2: Manual configuration
|
||||||
|
|
||||||
|
1. Start the MCP inspector: `npx @modelcontextprotocol/inspector`
|
||||||
|
2. Open `localhost:5173` in your browser
|
||||||
|
3. Configure inspector:
|
||||||
|
- Transport type: `STDIO`
|
||||||
|
- Command: `<path-to-server.vsh>`
|
||||||
|
- Arguments: (leave empty)
|
||||||
|
4. Click "Connect"
|
||||||
|
|
||||||
|
### Option 3: Using V directly
|
||||||
|
|
||||||
|
1. Start the MCP inspector: `npx @modelcontextprotocol/inspector`
|
||||||
|
2. Open `localhost:5173` in your browser
|
||||||
3. Configure inspector:
|
3. Configure inspector:
|
||||||
- Transport type: `STDIO`
|
- Transport type: `STDIO`
|
||||||
- Command: `v`
|
- Command: `v`
|
||||||
- Arguments: `-w -n run /Users/timurgordon/code/github/freeflowuniverse/herolib/lib/mcp/v_do/vdo.v`
|
- Arguments: `run <path-to-server.vsh>`
|
||||||
4. Connect
|
4. Click "Connect"
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
|
|
||||||
Expected output:
|
Expected output:
|
||||||

|

|
||||||
|
|||||||
10
examples/mpc/inspector/example.sh
Normal file → Executable file
10
examples/mpc/inspector/example.sh
Normal file → Executable file
@@ -1,3 +1,11 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
npx @modelcontextprotocol/inspector node build/index.js
|
# Get the absolute path to the server.vsh file
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
SERVER_PATH="$SCRIPT_DIR/server.vsh"
|
||||||
|
|
||||||
|
# Make sure the server script is executable
|
||||||
|
chmod +x "$SERVER_PATH"
|
||||||
|
|
||||||
|
# Start the MCP inspector with the V server
|
||||||
|
npx @modelcontextprotocol/inspector "$SERVER_PATH"
|
||||||
BIN
examples/mpc/inspector/server
Executable file
BIN
examples/mpc/inspector/server
Executable file
Binary file not shown.
120
examples/mpc/inspector/server.vsh
Executable file
120
examples/mpc/inspector/server.vsh
Executable file
@@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/env -S v -n -w -cg -d use_openssl -enable-globals run
|
||||||
|
|
||||||
|
import freeflowuniverse.herolib.mcp
|
||||||
|
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||||
|
import freeflowuniverse.herolib.schemas.jsonschema
|
||||||
|
import x.json2
|
||||||
|
|
||||||
|
// Example custom tool handler function
|
||||||
|
fn my_custom_handler(arguments map[string]json2.Any) !mcp.ToolCallResult {
|
||||||
|
return mcp.ToolCallResult{
|
||||||
|
is_error: false
|
||||||
|
content: [
|
||||||
|
mcp.ToolContent{
|
||||||
|
typ: 'text'
|
||||||
|
text: 'Hello from custom handler! Arguments: ${arguments}'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example of calculating 2 numbers
|
||||||
|
fn calculate(arguments map[string]json2.Any) !mcp.ToolCallResult {
|
||||||
|
// Check if num1 exists and can be converted to a number
|
||||||
|
if 'num1' !in arguments {
|
||||||
|
return mcp.ToolCallResult{
|
||||||
|
is_error: true
|
||||||
|
content: [
|
||||||
|
mcp.ToolContent{
|
||||||
|
typ: 'text'
|
||||||
|
text: 'Missing num1 parameter'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get num1 as a number (JSON numbers can be int, i64, or f64)
|
||||||
|
num1 := arguments['num1'].f64()
|
||||||
|
|
||||||
|
// Check if num2 exists and can be converted to a number
|
||||||
|
if 'num2' !in arguments {
|
||||||
|
return mcp.ToolCallResult{
|
||||||
|
is_error: true
|
||||||
|
content: [
|
||||||
|
mcp.ToolContent{
|
||||||
|
typ: 'text'
|
||||||
|
text: 'Missing num2 parameter'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to get num2 as a number
|
||||||
|
num2 := arguments['num2'].f64()
|
||||||
|
|
||||||
|
// Calculate the result
|
||||||
|
result := num1 + num2
|
||||||
|
// Return the result
|
||||||
|
return mcp.ToolCallResult{
|
||||||
|
is_error: false
|
||||||
|
content: [
|
||||||
|
mcp.ToolContent{
|
||||||
|
typ: 'text'
|
||||||
|
text: 'Result: ${result} (${num1} + ${num2})'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a backend with custom tools and handlers
|
||||||
|
backend := mcp.MemoryBackend{
|
||||||
|
tools: {
|
||||||
|
'custom_method': mcp.Tool{
|
||||||
|
name: 'custom_method'
|
||||||
|
description: 'A custom example tool'
|
||||||
|
input_schema: jsonschema.Schema{
|
||||||
|
typ: 'object'
|
||||||
|
properties: {
|
||||||
|
'message': jsonschema.SchemaRef(jsonschema.Schema{
|
||||||
|
typ: 'string'
|
||||||
|
description: 'A message to process'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
required: ['message']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'calculate': mcp.Tool{
|
||||||
|
name: 'calculate'
|
||||||
|
description: 'Calculates the sum of two numbers'
|
||||||
|
input_schema: jsonschema.Schema{
|
||||||
|
typ: 'object'
|
||||||
|
properties: {
|
||||||
|
'num1': jsonschema.SchemaRef(jsonschema.Schema{
|
||||||
|
typ: 'number'
|
||||||
|
description: 'The first number'
|
||||||
|
})
|
||||||
|
'num2': jsonschema.SchemaRef(jsonschema.Schema{
|
||||||
|
typ: 'number'
|
||||||
|
description: 'The second number'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
required: ['num1', 'num2']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tool_handlers: {
|
||||||
|
'custom_method': my_custom_handler
|
||||||
|
'calculate': calculate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and start the server
|
||||||
|
mut server := mcp.new_server(backend, mcp.ServerParams{
|
||||||
|
config: mcp.ServerConfiguration{
|
||||||
|
server_info: mcp.ServerInfo{
|
||||||
|
name: 'inspector_example'
|
||||||
|
version: '1.0.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})!
|
||||||
|
server.start()!
|
||||||
@@ -41,15 +41,16 @@ pub fn (mut s Server) start() ! {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the response
|
// Send the response only if it's not empty (notifications return empty responses)
|
||||||
s.send(response)
|
if response.len > 0 {
|
||||||
|
s.send(response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send sends a response to the client
|
// send sends a response to the client
|
||||||
pub fn (mut s Server) send(response string) {
|
pub fn (mut s Server) send(response string) {
|
||||||
// Send the response
|
// Send the response
|
||||||
log.error('Sending response: ${response}')
|
|
||||||
println(response)
|
println(response)
|
||||||
flush_stdout()
|
flush_stdout()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,15 +41,16 @@ pub fn (mut s Server) start() ! {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the response
|
// Send the response only if it's not empty (notifications return empty responses)
|
||||||
s.send(response)
|
if response.len > 0 {
|
||||||
|
s.send(response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// send sends a response to the client
|
// send sends a response to the client
|
||||||
pub fn (mut s Server) send(response string) {
|
pub fn (mut s Server) send(response string) {
|
||||||
// Send the response
|
// Send the response
|
||||||
log.error('Sending response: ${response}')
|
|
||||||
println(response)
|
println(response)
|
||||||
flush_stdout()
|
flush_stdout()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ pub fn (handler Handler) handler(client &websocket.Client, message string) strin
|
|||||||
// - The JSON-RPC response as a string, or an error if processing fails
|
// - The JSON-RPC response as a string, or an error if processing fails
|
||||||
pub fn (handler Handler) handle(message string) !string {
|
pub fn (handler Handler) handle(message string) !string {
|
||||||
// Extract the method name from the request
|
// Extract the method name from the request
|
||||||
log.error('debugzo1')
|
|
||||||
method := decode_request_method(message)!
|
method := decode_request_method(message)!
|
||||||
// log.info('Handling remote procedure call to method: ${method}')
|
// log.info('Handling remote procedure call to method: ${method}')
|
||||||
// Look up the procedure handler for the requested method
|
// Look up the procedure handler for the requested method
|
||||||
@@ -77,8 +76,6 @@ pub fn (handler Handler) handle(message string) !string {
|
|||||||
return method_not_found
|
return method_not_found
|
||||||
}
|
}
|
||||||
|
|
||||||
log.error('debugzo3')
|
|
||||||
|
|
||||||
// Execute the procedure handler with the request payload
|
// Execute the procedure handler with the request payload
|
||||||
response := procedure_func(message) or { panic(err) }
|
response := procedure_func(message) or { panic(err) }
|
||||||
return response
|
return response
|
||||||
|
|||||||
Reference in New Issue
Block a user