implement tools resources and prompts for mcp
This commit is contained in:
@@ -94,6 +94,11 @@ pub fn path_fix_absolute(path string) string {
|
||||
return "/${path_fix(path)}"
|
||||
}
|
||||
|
||||
// normalize a file path while preserving path structure
|
||||
pub fn path_fix(path string) string {
|
||||
return path.trim('/')
|
||||
}
|
||||
|
||||
|
||||
// remove underscores and extension
|
||||
pub fn name_fix_no_ext(name_ string) string {
|
||||
|
||||
28
lib/mcp/backend_interface.v
Normal file
28
lib/mcp/backend_interface.v
Normal file
@@ -0,0 +1,28 @@
|
||||
module mcp
|
||||
|
||||
import x.json2
|
||||
|
||||
interface Backend {
|
||||
// Resource methods
|
||||
resource_exists(uri string) !bool
|
||||
resource_get(uri string) !Resource
|
||||
resource_list() ![]Resource
|
||||
resource_subscribed(uri string) !bool
|
||||
resource_contents_get(uri string) ![]ResourceContent
|
||||
resource_templates_list() ![]ResourceTemplate
|
||||
|
||||
// Prompt methods
|
||||
prompt_exists(name string) !bool
|
||||
prompt_get(name string) !Prompt
|
||||
prompt_list() ![]Prompt
|
||||
prompt_messages_get(name string, arguments map[string]string) ![]PromptMessage
|
||||
|
||||
// Tool methods
|
||||
tool_exists(name string) !bool
|
||||
tool_get(name string) !Tool
|
||||
tool_list() ![]Tool
|
||||
tool_call(name string, arguments map[string]string) !ToolCallResult
|
||||
mut:
|
||||
resource_subscribe(uri string) !
|
||||
resource_unsubscribe(uri string) !
|
||||
}
|
||||
138
lib/mcp/backend_memory.v
Normal file
138
lib/mcp/backend_memory.v
Normal file
@@ -0,0 +1,138 @@
|
||||
module mcp
|
||||
|
||||
import x.json2
|
||||
|
||||
pub struct MemoryBackend {
|
||||
pub mut:
|
||||
// Resource related fields
|
||||
resources map[string]Resource
|
||||
subscriptions []string // list of subscribed resource uri's
|
||||
resource_contents map[string][]ResourceContent
|
||||
resource_templates map[string]ResourceTemplate
|
||||
|
||||
// Prompt related fields
|
||||
prompts map[string]Prompt
|
||||
prompt_messages map[string][]PromptMessage
|
||||
|
||||
// Tool related fields
|
||||
tools map[string]Tool
|
||||
tool_handlers map[string]ToolHandler
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) resource_exists(uri string) !bool {
|
||||
return uri in b.resources
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) resource_get(uri string) !Resource {
|
||||
return b.resources[uri] or { return error("resource not found") }
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) resource_list() ![]Resource {
|
||||
return b.resources.values()
|
||||
}
|
||||
|
||||
fn (mut b MemoryBackend) resource_subscribe(uri string) ! {
|
||||
if uri !in b.subscriptions {
|
||||
b.subscriptions << uri
|
||||
}
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) resource_subscribed(uri string) !bool {
|
||||
return uri in b.subscriptions
|
||||
}
|
||||
|
||||
fn (mut b MemoryBackend) resource_unsubscribe(uri string) ! {
|
||||
b.subscriptions = b.subscriptions.filter(it != uri)
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) resource_contents_get(uri string) ![]ResourceContent {
|
||||
return b.resource_contents[uri] or { return error("resource contents not found") }
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) resource_templates_list() ![]ResourceTemplate {
|
||||
return b.resource_templates.values()
|
||||
}
|
||||
|
||||
// Prompt related methods
|
||||
|
||||
fn (b &MemoryBackend) prompt_exists(name string) !bool {
|
||||
return name in b.prompts
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) prompt_get(name string) !Prompt {
|
||||
return b.prompts[name] or { return error("prompt not found") }
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) prompt_list() ![]Prompt {
|
||||
return b.prompts.values()
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) prompt_messages_get(name string, arguments map[string]string) ![]PromptMessage {
|
||||
// Get the base messages for this prompt
|
||||
base_messages := b.prompt_messages[name] or { return error("prompt messages not found") }
|
||||
|
||||
// Apply arguments to the messages
|
||||
mut messages := []PromptMessage{}
|
||||
|
||||
for msg in base_messages {
|
||||
mut content := msg.content
|
||||
|
||||
// If the content is text, replace argument placeholders
|
||||
if content.typ == 'text' {
|
||||
mut text := content.text
|
||||
|
||||
// Replace each argument in the text
|
||||
for arg_name, arg_value in arguments {
|
||||
text = text.replace('{{${arg_name}}}', arg_value)
|
||||
}
|
||||
|
||||
content = PromptContent{
|
||||
typ: content.typ
|
||||
text: text
|
||||
data: content.data
|
||||
mimetype: content.mimetype
|
||||
resource: content.resource
|
||||
}
|
||||
}
|
||||
|
||||
messages << PromptMessage{
|
||||
role: msg.role
|
||||
content: content
|
||||
}
|
||||
}
|
||||
|
||||
return messages
|
||||
}
|
||||
|
||||
// Tool related methods
|
||||
|
||||
fn (b &MemoryBackend) tool_exists(name string) !bool {
|
||||
return name in b.tools
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) tool_get(name string) !Tool {
|
||||
return b.tools[name] or { return error("tool not found") }
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) tool_list() ![]Tool {
|
||||
return b.tools.values()
|
||||
}
|
||||
|
||||
fn (b &MemoryBackend) tool_call(name string, arguments map[string]string) !ToolCallResult {
|
||||
// Get the tool handler
|
||||
handler := b.tool_handlers[name] or { return error("tool handler not found") }
|
||||
|
||||
// Call the handler with the provided arguments
|
||||
return handler(arguments) or {
|
||||
// If the handler throws an error, return it as a tool error
|
||||
return ToolCallResult{
|
||||
is_error: true
|
||||
content: [
|
||||
ToolContent{
|
||||
typ: 'text'
|
||||
text: 'Error: ${err.msg}'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,18 +7,41 @@ import x.json2
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.mcp.logger
|
||||
|
||||
@[params]
|
||||
pub struct ServerParams {
|
||||
pub:
|
||||
handlers map[string]jsonrpc.ProcedureHandler
|
||||
config ServerConfiguration
|
||||
}
|
||||
|
||||
// new_server creates a new MCP server
|
||||
pub fn new_server(handlers map[string]jsonrpc.ProcedureHandler, config ServerConfiguration) !&Server {
|
||||
pub fn new_server(backend Backend, params ServerParams) !&Server {
|
||||
mut server := &Server{
|
||||
ServerConfiguration: config
|
||||
ServerConfiguration: params.config,
|
||||
backend: backend,
|
||||
}
|
||||
|
||||
// Create a handler with the core MCP procedures registered
|
||||
handler := jsonrpc.new_handler(jsonrpc.Handler{
|
||||
procedures: {
|
||||
...handlers,
|
||||
...params.handlers,
|
||||
// Core handlers
|
||||
'initialize': server.initialize_handler,
|
||||
'notifications/initialized': initialized_notification_handler
|
||||
'notifications/initialized': initialized_notification_handler,
|
||||
|
||||
// Resource handlers
|
||||
'resources/list': server.resources_list_handler,
|
||||
'resources/read': server.resources_read_handler,
|
||||
'resources/templates/list': server.resources_templates_list_handler,
|
||||
'resources/subscribe': server.resources_subscribe_handler,
|
||||
|
||||
// Prompt handlers
|
||||
'prompts/list': server.prompts_list_handler,
|
||||
'prompts/get': server.prompts_get_handler,
|
||||
|
||||
// Tool handlers
|
||||
'tools/list': server.tools_list_handler,
|
||||
'tools/call': server.tools_call_handler
|
||||
}
|
||||
})!
|
||||
|
||||
|
||||
132
lib/mcp/handler_prompts.v
Normal file
132
lib/mcp/handler_prompts.v
Normal file
@@ -0,0 +1,132 @@
|
||||
module mcp
|
||||
|
||||
import time
|
||||
import os
|
||||
import log
|
||||
import x.json2
|
||||
import json
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.mcp.logger
|
||||
|
||||
// Prompt related structs
|
||||
|
||||
pub struct Prompt {
|
||||
pub:
|
||||
name string
|
||||
description string
|
||||
arguments []PromptArgument
|
||||
}
|
||||
|
||||
pub struct PromptArgument {
|
||||
pub:
|
||||
name string
|
||||
description string
|
||||
required bool
|
||||
}
|
||||
|
||||
pub struct PromptMessage {
|
||||
pub:
|
||||
role string
|
||||
content PromptContent
|
||||
}
|
||||
|
||||
pub struct PromptContent {
|
||||
pub:
|
||||
typ string @[json: 'type']
|
||||
text string
|
||||
data string
|
||||
mimetype string @[json: 'mimeType']
|
||||
resource ResourceContent
|
||||
}
|
||||
|
||||
// Prompt List Handler
|
||||
|
||||
pub struct PromptListParams {
|
||||
pub:
|
||||
cursor string
|
||||
}
|
||||
|
||||
pub struct PromptListResult {
|
||||
pub:
|
||||
prompts []Prompt
|
||||
next_cursor string @[json: 'nextCursor']
|
||||
}
|
||||
|
||||
// prompts_list_handler handles the prompts/list request
|
||||
// This request is used to retrieve a list of available prompts
|
||||
fn (mut s Server) prompts_list_handler(data string) !string {
|
||||
// Decode the request with cursor parameter
|
||||
request := jsonrpc.decode_request_generic[PromptListParams](data)!
|
||||
cursor := request.params.cursor
|
||||
|
||||
// TODO: Implement pagination logic using the cursor
|
||||
// For now, return all prompts
|
||||
|
||||
// Create a success response with the result
|
||||
response := jsonrpc.new_response_generic[PromptListResult](request.id, PromptListResult{
|
||||
prompts: s.backend.prompt_list()!,
|
||||
next_cursor: '' // Empty if no more pages
|
||||
})
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Prompt Get Handler
|
||||
|
||||
pub struct PromptGetParams {
|
||||
pub:
|
||||
name string
|
||||
arguments map[string]string
|
||||
}
|
||||
|
||||
pub struct PromptGetResult {
|
||||
pub:
|
||||
description string
|
||||
messages []PromptMessage
|
||||
}
|
||||
|
||||
// prompts_get_handler handles the prompts/get request
|
||||
// This request is used to retrieve a specific prompt with arguments
|
||||
fn (mut s Server) prompts_get_handler(data string) !string {
|
||||
// Decode the request with name and arguments parameters
|
||||
request := jsonrpc.decode_request_generic[PromptGetParams](data)!
|
||||
|
||||
if !s.backend.prompt_exists(request.params.name)! {
|
||||
return jsonrpc.new_error_response(request.id, prompt_not_found(request.params.name)).encode()
|
||||
}
|
||||
|
||||
// Get the prompt by name
|
||||
prompt := s.backend.prompt_get(request.params.name)!
|
||||
|
||||
// Validate required arguments
|
||||
for arg in prompt.arguments {
|
||||
if arg.required && (request.params.arguments[arg.name] == '') {
|
||||
return jsonrpc.new_error_response(request.id, missing_required_argument(arg.name)).encode()
|
||||
}
|
||||
}
|
||||
|
||||
// Get the prompt messages with arguments applied
|
||||
messages := s.backend.prompt_messages_get(request.params.name, request.params.arguments)!
|
||||
|
||||
// Create a success response with the result
|
||||
response := jsonrpc.new_response_generic[PromptGetResult](request.id, PromptGetResult{
|
||||
description: prompt.description,
|
||||
messages: messages
|
||||
})
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Prompt Notification Handlers
|
||||
|
||||
// send_prompts_list_changed_notification sends a notification when the list of prompts changes
|
||||
pub fn (mut s Server) send_prompts_list_changed_notification() ! {
|
||||
// Check if the client supports this notification
|
||||
if !s.client_config.capabilities.roots.list_changed {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a notification
|
||||
notification := jsonrpc.new_blank_notification('notifications/prompts/list_changed')
|
||||
s.send(json.encode(notification))
|
||||
// Send the notification to all connected clients
|
||||
log.info('Sending prompts list changed notification: ${json.encode(notification)}')
|
||||
}
|
||||
183
lib/mcp/handler_resources.v
Normal file
183
lib/mcp/handler_resources.v
Normal file
@@ -0,0 +1,183 @@
|
||||
module mcp
|
||||
|
||||
import time
|
||||
import os
|
||||
import log
|
||||
import x.json2
|
||||
import json
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.mcp.logger
|
||||
|
||||
pub struct Resource {
|
||||
pub:
|
||||
uri string
|
||||
name string
|
||||
description string
|
||||
mimetype string @[json: 'mimeType']
|
||||
}
|
||||
|
||||
// Resource List Handler
|
||||
|
||||
pub struct ResourceListParams {
|
||||
pub:
|
||||
cursor string
|
||||
}
|
||||
|
||||
pub struct ResourceListResult {
|
||||
pub:
|
||||
resources []Resource
|
||||
next_cursor string @[json: 'nextCursor']
|
||||
}
|
||||
|
||||
// resources_list_handler handles the resources/list request
|
||||
// This request is used to retrieve a list of available resources
|
||||
fn (mut s Server) resources_list_handler(data string) !string {
|
||||
// Decode the request with cursor parameter
|
||||
request := jsonrpc.decode_request_generic[ResourceListParams](data)!
|
||||
cursor := request.params.cursor
|
||||
|
||||
// TODO: Implement pagination logic using the cursor
|
||||
// For now, return all resources
|
||||
|
||||
// Create a success response with the result
|
||||
response := jsonrpc.new_response_generic[ResourceListResult](request.id, ResourceListResult{
|
||||
resources: s.backend.resource_list()!,
|
||||
next_cursor: '' // Empty if no more pages
|
||||
})
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Resource Read Handler
|
||||
|
||||
pub struct ResourceReadParams {
|
||||
pub:
|
||||
uri string
|
||||
}
|
||||
|
||||
pub struct ResourceReadResult {
|
||||
pub:
|
||||
contents []ResourceContent
|
||||
}
|
||||
|
||||
pub struct ResourceContent {
|
||||
pub:
|
||||
uri string
|
||||
mimetype string @[json: 'mimeType']
|
||||
text string
|
||||
blob string // Base64-encoded binary data
|
||||
}
|
||||
|
||||
// resources_read_handler handles the resources/read request
|
||||
// This request is used to retrieve the contents of a resource
|
||||
fn (mut s Server) resources_read_handler(data string) !string {
|
||||
// Decode the request with uri parameter
|
||||
request := jsonrpc.decode_request_generic[ResourceReadParams](data)!
|
||||
|
||||
if !s.backend.resource_exists(request.params.uri)! {
|
||||
return jsonrpc.new_error_response(request.id, resource_not_found(request.params.uri)).encode()
|
||||
}
|
||||
|
||||
// Get the resource contents by URI
|
||||
resource_contents := s.backend.resource_contents_get(request.params.uri)!
|
||||
|
||||
// Create a success response with the result
|
||||
response := jsonrpc.new_response_generic[ResourceReadResult](request.id, ResourceReadResult{
|
||||
contents: resource_contents
|
||||
})
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Resource Templates Handler
|
||||
|
||||
pub struct ResourceTemplatesListResult {
|
||||
pub:
|
||||
resource_templates []ResourceTemplate @[json: 'resourceTemplates']
|
||||
}
|
||||
|
||||
pub struct ResourceTemplate {
|
||||
pub:
|
||||
uri_template string @[json: 'uriTemplate']
|
||||
name string
|
||||
description string
|
||||
mimetype string @[json: 'mimeType']
|
||||
}
|
||||
|
||||
// resources_templates_list_handler handles the resources/templates/list request
|
||||
// This request is used to retrieve a list of available resource templates
|
||||
fn (mut s Server) resources_templates_list_handler(data string) !string {
|
||||
// Decode the request
|
||||
request := jsonrpc.decode_request(data)!
|
||||
|
||||
// Create a success response with the result
|
||||
response := jsonrpc.new_response_generic[ResourceTemplatesListResult](request.id, ResourceTemplatesListResult{
|
||||
resource_templates: s.backend.resource_templates_list()!
|
||||
})
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Resource Subscription Handler
|
||||
|
||||
pub struct ResourceSubscribeParams {
|
||||
pub:
|
||||
uri string
|
||||
}
|
||||
|
||||
pub struct ResourceSubscribeResult {
|
||||
pub:
|
||||
subscribed bool
|
||||
}
|
||||
|
||||
// resources_subscribe_handler handles the resources/subscribe request
|
||||
// This request is used to subscribe to changes for a specific resource
|
||||
fn (mut s Server) resources_subscribe_handler(data string) !string {
|
||||
request := jsonrpc.decode_request_generic[ResourceSubscribeParams](data)!
|
||||
|
||||
if !s.backend.resource_exists(request.params.uri)! {
|
||||
return jsonrpc.new_error_response(request.id, resource_not_found(request.params.uri)).encode()
|
||||
}
|
||||
|
||||
s.backend.resource_subscribe(request.params.uri)!
|
||||
|
||||
response := jsonrpc.new_response_generic[ResourceSubscribeResult](request.id, ResourceSubscribeResult{
|
||||
subscribed: true
|
||||
})
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Resource Notification Handlers
|
||||
|
||||
// send_resources_list_changed_notification sends a notification when the list of resources changes
|
||||
pub fn (mut s Server) send_resources_list_changed_notification() ! {
|
||||
// Check if the client supports this notification
|
||||
if !s.client_config.capabilities.roots.list_changed {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a notification
|
||||
notification := jsonrpc.new_blank_notification('notifications/resources/list_changed')
|
||||
s.send(json.encode(notification))
|
||||
// Send the notification to all connected clients
|
||||
// In a real implementation, this would use a WebSocket or other transport
|
||||
log.info('Sending resources list changed notification: ${json.encode(notification)}')
|
||||
}
|
||||
|
||||
pub struct ResourceUpdatedParams {
|
||||
pub:
|
||||
uri string
|
||||
}
|
||||
|
||||
// send_resource_updated_notification sends a notification when a subscribed resource is updated
|
||||
pub fn (mut s Server) send_resource_updated_notification(uri string) ! {
|
||||
// Check if the client is subscribed to this resource
|
||||
if !s.backend.resource_subscribed(uri)! {return}
|
||||
|
||||
// Create a notification
|
||||
notification := jsonrpc.new_notification[ResourceUpdatedParams]('notifications/resources/updated', ResourceUpdatedParams{
|
||||
uri: uri
|
||||
})
|
||||
|
||||
s.send(json.encode(notification))
|
||||
// Send the notification to all connected clients
|
||||
// In a real implementation, this would use a WebSocket or other transport
|
||||
log.info('Sending resource updated notification: ${json.encode(notification)}')
|
||||
}
|
||||
134
lib/mcp/handler_tools.v
Normal file
134
lib/mcp/handler_tools.v
Normal file
@@ -0,0 +1,134 @@
|
||||
module mcp
|
||||
|
||||
import time
|
||||
import os
|
||||
import log
|
||||
import x.json2
|
||||
import json
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
import freeflowuniverse.herolib.mcp.logger
|
||||
|
||||
// Tool related structs
|
||||
|
||||
pub struct Tool {
|
||||
pub:
|
||||
name string
|
||||
description string
|
||||
input_schema ToolInputSchema @[json: 'inputSchema']
|
||||
}
|
||||
|
||||
pub struct ToolInputSchema {
|
||||
pub:
|
||||
typ string @[json: 'type']
|
||||
properties map[string]ToolProperty
|
||||
required []string
|
||||
}
|
||||
|
||||
pub struct ToolProperty {
|
||||
pub:
|
||||
typ string @[json: 'type']
|
||||
items ToolItems
|
||||
enum []string
|
||||
}
|
||||
|
||||
pub struct ToolItems {
|
||||
pub:
|
||||
typ string @[json: 'type']
|
||||
enum []string
|
||||
}
|
||||
|
||||
pub struct ToolContent {
|
||||
pub:
|
||||
typ string @[json: 'type']
|
||||
text string
|
||||
}
|
||||
|
||||
// Tool List Handler
|
||||
|
||||
pub struct ToolListParams {
|
||||
pub:
|
||||
cursor string
|
||||
}
|
||||
|
||||
pub struct ToolListResult {
|
||||
pub:
|
||||
tools []Tool
|
||||
next_cursor string @[json: 'nextCursor']
|
||||
}
|
||||
|
||||
// tools_list_handler handles the tools/list request
|
||||
// This request is used to retrieve a list of available tools
|
||||
fn (mut s Server) tools_list_handler(data string) !string {
|
||||
// Decode the request with cursor parameter
|
||||
request := jsonrpc.decode_request_generic[ToolListParams](data)!
|
||||
cursor := request.params.cursor
|
||||
|
||||
// TODO: Implement pagination logic using the cursor
|
||||
// For now, return all tools
|
||||
|
||||
// Create a success response with the result
|
||||
response := jsonrpc.new_response_generic[ToolListResult](request.id, ToolListResult{
|
||||
tools: s.backend.tool_list()!,
|
||||
next_cursor: '' // Empty if no more pages
|
||||
})
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Tool Call Handler
|
||||
|
||||
pub struct ToolCallParams {
|
||||
pub:
|
||||
name string
|
||||
arguments map[string]string
|
||||
}
|
||||
|
||||
pub struct ToolCallResult {
|
||||
pub:
|
||||
is_error bool @[json: 'isError']
|
||||
content []ToolContent
|
||||
}
|
||||
|
||||
// tools_call_handler handles the tools/call request
|
||||
// This request is used to call a specific tool with arguments
|
||||
fn (mut s Server) tools_call_handler(data string) !string {
|
||||
// Decode the request with name and arguments parameters
|
||||
request := jsonrpc.decode_request_generic[ToolCallParams](data)!
|
||||
|
||||
if !s.backend.tool_exists(request.params.name)! {
|
||||
return jsonrpc.new_error_response(request.id, tool_not_found(request.params.name)).encode()
|
||||
}
|
||||
|
||||
// Get the tool by name
|
||||
tool := s.backend.tool_get(request.params.name)!
|
||||
|
||||
// Validate arguments against the input schema
|
||||
// TODO: Implement proper JSON Schema validation
|
||||
for req in tool.input_schema.required {
|
||||
if req !in request.params.arguments {
|
||||
return jsonrpc.new_error_response(request.id, missing_required_argument(req)).encode()
|
||||
}
|
||||
}
|
||||
|
||||
// Call the tool with the provided arguments
|
||||
result := s.backend.tool_call(request.params.name, request.params.arguments)!
|
||||
|
||||
// Create a success response with the result
|
||||
response := jsonrpc.new_response_generic[ToolCallResult](request.id, result)
|
||||
return response.encode()
|
||||
}
|
||||
|
||||
// Tool Notification Handlers
|
||||
|
||||
// send_tools_list_changed_notification sends a notification when the list of tools changes
|
||||
pub fn (mut s Server) send_tools_list_changed_notification() ! {
|
||||
// Check if the client supports this notification
|
||||
if !s.client_config.capabilities.roots.list_changed {
|
||||
return
|
||||
}
|
||||
|
||||
// Create a notification
|
||||
notification := jsonrpc.new_blank_notification('notifications/tools/list_changed')
|
||||
s.send(json.encode(notification))
|
||||
// Send the notification to all connected clients
|
||||
log.info('Sending tools list changed notification: ${json.encode(notification)}')
|
||||
}
|
||||
@@ -49,7 +49,7 @@ pub:
|
||||
// ServerConfiguration represents the server configuration
|
||||
pub struct ServerConfiguration {
|
||||
pub:
|
||||
protocol_version string = protocol_version @[json: 'protocolVersion']
|
||||
protocol_version string = '2024-11-05' @[json: 'protocolVersion']
|
||||
capabilities ServerCapabilities
|
||||
server_info ServerInfo @[json: 'serverInfo']
|
||||
}
|
||||
|
||||
35
lib/mcp/model_error.v
Normal file
35
lib/mcp/model_error.v
Normal file
@@ -0,0 +1,35 @@
|
||||
module mcp
|
||||
|
||||
import freeflowuniverse.herolib.schemas.jsonrpc
|
||||
|
||||
// resource_not_found indicates that the requested resource doesn't exist.
|
||||
// This error is returned when the resource specified in the request is not found.
|
||||
// Error code: -32002
|
||||
pub fn resource_not_found(uri string) jsonrpc.RPCError {
|
||||
return jsonrpc.RPCError{
|
||||
code: -32002
|
||||
message: 'Resource not found'
|
||||
data: 'The requested resource ${uri} was not found.'
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_not_found(name string) jsonrpc.RPCError {
|
||||
return jsonrpc.RPCError{
|
||||
code: -32602, // Invalid params
|
||||
message: 'Prompt not found: $name'
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_required_argument(arg_name string) jsonrpc.RPCError {
|
||||
return jsonrpc.RPCError{
|
||||
code: -32602, // Invalid params
|
||||
message: 'Missing required argument: $arg_name'
|
||||
}
|
||||
}
|
||||
|
||||
fn tool_not_found(name string) jsonrpc.RPCError {
|
||||
return jsonrpc.RPCError{
|
||||
code: -32602, // Invalid params
|
||||
message: 'Tool not found: $name'
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ pub struct Server {
|
||||
pub mut:
|
||||
client_config ClientConfiguration
|
||||
handler jsonrpc.Handler
|
||||
backend Backend
|
||||
}
|
||||
|
||||
// start starts the MCP server
|
||||
@@ -41,7 +42,13 @@ pub fn (mut s Server) start() ! {
|
||||
}
|
||||
|
||||
// Send the response
|
||||
println(response)
|
||||
flush_stdout()
|
||||
s.send(response)
|
||||
}
|
||||
}
|
||||
|
||||
// send sends a response to the client
|
||||
pub fn (mut s Server) send(response string) {
|
||||
// Send the response
|
||||
println(response)
|
||||
flush_stdout()
|
||||
}
|
||||
6
lib/mcp/tool_handler.v
Normal file
6
lib/mcp/tool_handler.v
Normal file
@@ -0,0 +1,6 @@
|
||||
module mcp
|
||||
|
||||
import x.json2
|
||||
|
||||
// ToolHandler is a function type that handles tool calls
|
||||
pub type ToolHandler = fn (arguments map[string]string) !ToolCallResult
|
||||
50
lib/schemas/jsonrpc/model_notification.v
Normal file
50
lib/schemas/jsonrpc/model_notification.v
Normal file
@@ -0,0 +1,50 @@
|
||||
module jsonrpc
|
||||
|
||||
// Notification represents a JSON-RPC 2.0 notification object.
|
||||
// It contains all the required fields according to the JSON-RPC 2.0 specification.
|
||||
// See: https://www.jsonrpc.org/specification#notification
|
||||
pub struct Notification {
|
||||
pub mut:
|
||||
// The JSON-RPC protocol version, must be exactly "2.0"
|
||||
jsonrpc string = "2.0" @[required]
|
||||
|
||||
// The name of the method to be invoked on the server
|
||||
method string @[required]
|
||||
}
|
||||
|
||||
// Notification represents a JSON-RPC 2.0 notification object.
|
||||
// It contains all the required fields according to the JSON-RPC 2.0 specification.
|
||||
// See: https://www.jsonrpc.org/specification#notification
|
||||
pub struct NotificationGeneric[T] {
|
||||
pub mut:
|
||||
// The JSON-RPC protocol version, must be exactly "2.0"
|
||||
jsonrpc string = "2.0" @[required]
|
||||
|
||||
// The name of the method to be invoked on the server
|
||||
method string @[required]
|
||||
params ?T
|
||||
}
|
||||
|
||||
// new_notification creates a new JSON-RPC notification with the specified method and parameters.
|
||||
// It automatically sets the JSON-RPC version to the current version.
|
||||
//
|
||||
// Parameters:
|
||||
// - method: The name of the method to invoke on the server
|
||||
// - params: The parameters to the method, encoded as a JSON string
|
||||
//
|
||||
// Returns:
|
||||
// - A fully initialized Notification object
|
||||
pub fn new_notification[T](method string, params T) NotificationGeneric[T] {
|
||||
return NotificationGeneric[T]{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
method: method
|
||||
params: params
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_blank_notification(method string) Notification {
|
||||
return Notification{
|
||||
jsonrpc: jsonrpc.jsonrpc_version
|
||||
method: method
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user