Initial project import
This commit is contained in:
commit
23f2d29e32
11957 files changed
+1754577
No files matched your search
@@ -0,0 +1,210 @@
|
||||
// Comprehensive MCP server example covering every feature exposed by `vlib/mcp`:
|
||||
// tools (with annotations), concrete resources, resource templates, prompts,
|
||||
// per-argument completions, RFC 5424 logging, cooperative cancellation,
|
||||
// progress notifications, subscriptions, and server-initiated requests.
|
||||
//
|
||||
// Usage:
|
||||
// v run examples/mcp/server.v # stdio transport (default)
|
||||
// v run examples/mcp/server.v -- --http # HTTP transport on 127.0.0.1:8080
|
||||
// v run examples/mcp/server.v -- --http 127.0.0.1:9000 # HTTP transport on 127.0.0.1:9000
|
||||
//
|
||||
// Connect a client (e.g. Claude Desktop / Cursor / a custom MCP client) to the
|
||||
// command above for stdio, or POST to `http://127.0.0.1:8080/mcp` for HTTP.
|
||||
module main
|
||||
|
||||
import json
|
||||
import mcp
|
||||
import os
|
||||
import time
|
||||
|
||||
struct CountArgs {
|
||||
n int
|
||||
}
|
||||
|
||||
const supported_languages = ['rust', 'python', 'go', 'v', 'typescript', 'zig']!
|
||||
const welcome_text = 'Welcome to the V MCP showcase server.'
|
||||
|
||||
fn main() {
|
||||
mut server := mcp.new_server(
|
||||
name: 'v.mcp.showcase'
|
||||
version: '1.0.0'
|
||||
title: 'V MCP Showcase'
|
||||
description: 'Reference server for vlib/mcp covering every capability of the 2025-11-25 spec.'
|
||||
website_url: 'https://vlang.io'
|
||||
icons: [
|
||||
mcp.Icon{
|
||||
src: 'https://vlang.io/img/v-logo.png'
|
||||
mime_type: 'image/png'
|
||||
sizes: ['256x256']
|
||||
},
|
||||
]
|
||||
instructions: 'Demo server exercising every MCP capability shipped by vlib/mcp.'
|
||||
enable_logging: true
|
||||
// `*` only for the demo; tighten this for real deployments.
|
||||
allowed_origins: ['*']
|
||||
)
|
||||
register_tools(mut server)!
|
||||
register_resources(mut server)!
|
||||
register_prompts(mut server)!
|
||||
register_completions(mut server)!
|
||||
|
||||
// Strip a leading `--` so the same binary works whether launched as
|
||||
// `./v run server.v -- --http :8080` (V's run forwards `--`) or as the
|
||||
// pre-compiled binary `./server --http :8080`.
|
||||
args := os.args[1..].filter(it != '--')
|
||||
if args.len > 0 && args[0] == '--http' {
|
||||
addr := if args.len > 1 { args[1] } else { '127.0.0.1:8080' }
|
||||
eprintln('mcp showcase listening on http://${addr}/mcp')
|
||||
server.serve_http(addr)!
|
||||
} else {
|
||||
server.serve_stdio()!
|
||||
}
|
||||
}
|
||||
|
||||
fn register_tools(mut server mcp.Server) ! {
|
||||
// `echo` — pure, idempotent, read-only. Demonstrates annotations + icons.
|
||||
server.add_tool(mcp.Tool{
|
||||
name: 'echo'
|
||||
title: 'Echo'
|
||||
description: 'Return the provided text unchanged.'
|
||||
input_schema: '{"type":"object","required":["text"],"properties":{"text":{"type":"string"}}}'
|
||||
icons: [
|
||||
mcp.Icon{
|
||||
src: 'https://vlang.io/img/echo.svg'
|
||||
mime_type: 'image/svg+xml'
|
||||
},
|
||||
]
|
||||
annotations: mcp.ToolAnnotations{
|
||||
title: 'Echo input'
|
||||
read_only_hint: true
|
||||
idempotent_hint: true
|
||||
open_world_hint: false
|
||||
}
|
||||
}, fn (_ mcp.Context, arguments string) !mcp.ToolResult {
|
||||
// `arguments` is a JSON string the caller may decode. Keep it simple here.
|
||||
return mcp.tool_text_result(arguments)
|
||||
})!
|
||||
|
||||
// `count_to` — long-running tool that reports progress and is cancellable.
|
||||
server.add_tool(mcp.Tool{
|
||||
name: 'count_to'
|
||||
title: 'Counter'
|
||||
description: 'Count up to N with progress notifications. Cooperatively cancellable.'
|
||||
input_schema: '{"type":"object","required":["n"],"properties":{"n":{"type":"integer","minimum":1,"maximum":50}}}'
|
||||
}, fn (ctx mcp.Context, arguments string) !mcp.ToolResult {
|
||||
args := json.decode(CountArgs, arguments) or {
|
||||
return mcp.tool_text_result('invalid arguments: ${err.msg()}')
|
||||
}
|
||||
if args.n < 1 || args.n > 50 {
|
||||
return mcp.tool_text_result('n must be in [1, 50]')
|
||||
}
|
||||
for i in 1 .. args.n + 1 {
|
||||
if ctx.is_cancelled() {
|
||||
return mcp.tool_text_result('cancelled at ${i - 1}')
|
||||
}
|
||||
ctx.notify_progress(f64(i), f64(args.n), 'tick ${i}')
|
||||
time.sleep(50 * time.millisecond)
|
||||
}
|
||||
return mcp.tool_text_result('counted to ${args.n}')
|
||||
})!
|
||||
|
||||
// `delete_record` — destructive; a host can warn the user before invoking.
|
||||
server.add_tool(mcp.Tool{
|
||||
name: 'delete_record'
|
||||
title: 'Delete record (demo)'
|
||||
description: 'Demonstration only — does not delete anything.'
|
||||
annotations: mcp.ToolAnnotations{
|
||||
destructive_hint: true
|
||||
idempotent_hint: false
|
||||
}
|
||||
}, fn (_ mcp.Context, _ string) !mcp.ToolResult {
|
||||
return mcp.tool_text_result('record removed (no-op)')
|
||||
})!
|
||||
}
|
||||
|
||||
fn register_resources(mut server mcp.Server) ! {
|
||||
server.add_resource(mcp.Resource{
|
||||
uri: 'demo://welcome.txt'
|
||||
name: 'welcome'
|
||||
title: 'Welcome message'
|
||||
description: 'A static welcome string.'
|
||||
mime_type: 'text/plain'
|
||||
size: welcome_text.len
|
||||
annotations: mcp.Annotations{
|
||||
audience: ['user']
|
||||
priority: 0.5
|
||||
}
|
||||
}, fn (_ mcp.Context, uri string) !mcp.ReadResourceResult {
|
||||
return mcp.ReadResourceResult{
|
||||
contents: [
|
||||
mcp.ResourceContents{
|
||||
uri: uri
|
||||
mime_type: 'text/plain'
|
||||
text: welcome_text
|
||||
},
|
||||
]
|
||||
}
|
||||
})!
|
||||
|
||||
server.add_resource_template(mcp.ResourceTemplate{
|
||||
uri_template: 'demo://greet/{language}'
|
||||
name: 'greet'
|
||||
title: 'Localised greeting'
|
||||
description: 'Returns a greeting in {language}.'
|
||||
mime_type: 'text/plain'
|
||||
})!
|
||||
}
|
||||
|
||||
fn register_prompts(mut server mcp.Server) ! {
|
||||
server.add_prompt(mcp.Prompt{
|
||||
name: 'review'
|
||||
title: 'Code review'
|
||||
description: 'Ask the assistant to review a snippet in the requested language.'
|
||||
arguments: [
|
||||
mcp.PromptArgument{
|
||||
name: 'language'
|
||||
description: 'Source language of the snippet.'
|
||||
required: true
|
||||
},
|
||||
mcp.PromptArgument{
|
||||
name: 'snippet'
|
||||
description: 'The code to review.'
|
||||
required: true
|
||||
},
|
||||
]
|
||||
}, fn (_ mcp.Context, arguments string) !mcp.GetPromptResult {
|
||||
return mcp.GetPromptResult{
|
||||
description: 'Code review prompt'
|
||||
messages: [
|
||||
mcp.prompt_text_message('user',
|
||||
'Please review the following code. Arguments: ${arguments}'),
|
||||
]
|
||||
}
|
||||
})!
|
||||
}
|
||||
|
||||
fn register_completions(mut server mcp.Server) ! {
|
||||
// Auto-complete the prompt argument `language` against a known list.
|
||||
server.add_completion(mcp.CompletionRef{
|
||||
ref_type: 'ref/prompt'
|
||||
name: 'review'
|
||||
}, 'language', fn (_ mcp.Context, current_value string, _ string) !mcp.CompletionResult {
|
||||
matches := supported_languages.filter(it.starts_with(current_value.to_lower()))
|
||||
return mcp.CompletionResult{
|
||||
values: matches
|
||||
total: matches.len
|
||||
has_more: false
|
||||
}
|
||||
})!
|
||||
|
||||
// Auto-complete the resource template variable `language` similarly.
|
||||
server.add_completion(mcp.CompletionRef{
|
||||
ref_type: 'ref/resource'
|
||||
uri: 'demo://greet/{language}'
|
||||
}, 'language', fn (_ mcp.Context, current_value string, _ string) !mcp.CompletionResult {
|
||||
matches := supported_languages.filter(it.starts_with(current_value.to_lower()))
|
||||
return mcp.CompletionResult{
|
||||
values: matches
|
||||
}
|
||||
})!
|
||||
}
|
||||
Reference in new issue
Block a user