Class ::nsmcp::ToolRegistry (public)

 ::nx::Class ::nsmcp::ToolRegistry[i]

Defined in /usr/local/ns/tcl/nsmcp/01-tools.tcl

Testcases:
No testcase defined.
Source code:
    :variable tools {}
    :property {stateStore {}}
    # Nonempty context opts this registry into trusted module providers.
    :property {providerContext {}}
    :variable providers {}

    # Look up provider-owned settings without inserting defaults into either
    # section. Explicit false/empty values must not fall through to legacy data.
    :public object method providerConfig {context parameter default} {
        foreach key {section fallbackSection} {
            if {![dict exists $context $key]} {continue}
            set section [dict get $context $key]
            set name $parameter
            if {$key eq "fallbackSection" && [dict exists $context fallbackParameters $parameter]} {
                set name [dict get $context fallbackParameters $parameter]
            }
            set configuration [ns_configsection $section]
            if {$configuration ne "" && [ns_set ifind $configuration $name] >= 0} {
                return [ns_config $section $name]
            }
        }
        return $default
    }

    :public method refreshProviders {} {
        if {${:providerContext} eq ""} {return}
        set descriptors {}
        catch {set descriptors [nsv_array get nsmcp:providers]}
        # Registration is trusted startup Tcl, never an MCP/client operation.
        dict for {provider descriptor} $descriptors {
            if {[dict exists ${:providers} $provider]} {continue}
            foreach field {file factory} {
                if {![dict exists $descriptor $field]} {error "provider $provider lacks $field"}
            }
            if {[dict exists $descriptor requires]
                && [info commands [dict get $descriptor requires]] eq ""} {continue}
            set context ${:providerContext}
            if {[dict exists $descriptor section]} {
                dict set context fallbackSection [dict get $context section]
                dict set context section [dict get $descriptor section]
            }
            if {[dict exists $descriptor fallbackParameters]} {
                dict set context fallbackParameters [dict get $descriptor fallbackParameters]
            }
            if {[dict exists $descriptor enabledParameter]} {
                set parameter [dict get $descriptor enabledParameter]
                set enabled [::nsmcp::ToolRegistry providerConfig $context $parameter false]
                if {![string is boolean -strict $enabled]} {
                    error "provider $provider requires a boolean $parameter"
                }
                if {!$enabled} {continue}
            }
            set file [dict get $descriptor file]
            if {[file pathtype $file] ne "absolute" || ![file isfile $file]} {
                error "provider $provider must provide an absolute readable Tcl file"
            }
            uplevel #0 [list source $file]
            set factory [dict get $descriptor factory]
            if {[llength $factory] != 1 || [info commands $factory] eq ""} {
                error "provider $provider must provide a factory command"
            }
            # Validate the entire batch before adding any tool. Factory failures
            # must not leave a partially registered provider or replace a tool.
            set candidates [$factory $context]
            set pending {}
            foreach tool $candidates {
                if {![nsf::object::exists $tool] || ![$tool info has type ::nsmcp::Tool]} {
                    error "provider $provider returned a non-Tool object"
                }
                set name [$tool name]
                if {[dict exists ${:tools} $name] || [dict exists $pending $name]} {
                    error "duplicate tool: $name"
                }
                dict set pending $name $tool
            }
            dict for {name tool} $pending {dict set :tools $name $tool}
            dict set :providers $provider [dict keys $pending]
        }
        return ${:providers}
    }


    :public method controlsAvailable {} {
        # An explicitly configured backend owns per-server runtime overrides.
        return [expr {${:stateStore} ne ""}]
    }

    :method readEnabled {name} {
        # Isolate storage access from discovery and invocation. An absent
        # override means enabled; NSV state is shared by this server's threads.
        if {${:stateStore} ne "" && [nsv_exists ${:stateStore} $name]} {
            return [nsv_get ${:stateStore} $name]
        }
        return true
    }

    :method writeEnabled {name enabled} {
        nsv_set ${:stateStore} $name $enabled
    }

    :public method enabled {name} {
        :refreshProviders
        # Administrative availability, independent of caller permissions.
        if {![dict exists ${:tools} $name]} {
            error "unknown tool: $name"
        }
        return [:readEnabled $name]
    }

    :public method setEnabled {name enabled:boolean} {
        # Trusted administrative interface, not an MCP tool. Disabling prevents
        # new invocations; it does not cancel existing jobs or stop collectors.
        # Overrides survive interpreter recreation/reload, not process restart.
        if {![:controlsAvailable]} {
            error "runtime tool controls are not configured"
        }
        :enabled $name
        set enabled [expr {!!$enabled}]
        :writeEnabled $name $enabled
        return $enabled
    }

    :public method register {tool} {
        set name [$tool name]
        if {[dict exists ${:tools} $name]} {error "duplicate tool: $name"}
        dict set :tools $name $tool
    }
    :public method describe {} {
        :refreshProviders
        # Administrative metadata for the registered tools. This does not
        # execute tools or change permission-filtered MCP tools/list results.
        set result {}
        foreach name [lsort [dict keys ${:tools}]] {
            set tool [dict get ${:tools} $name]
            set description [ns_json parse [ns_json value -- [$tool describe]]]
            foreach field {adminSummary adminExample} {
                set value [$tool cget -$field]
                if {$value ne ""} {dict set description $field $value}
            }
            dict set description permission [$tool permission]
            dict set description enabled [:readEnabled $name]
            lappend result $description
        }
        return $result
    }
    :public method list {context authorizer} {
        :refreshProviders
        set result {}
        foreach name [lsort [dict keys ${:tools}]] {
            set tool [dict get ${:tools} $name]
            if {[:readEnabled $name] && [$authorizer allowed $context [$tool permission]]} {
                lappend result [$tool describe]
            }
        }
        set members {}; set index 0
        foreach description $result {
            lappend members $index object [lindex $description 2]
            incr index
        }
        return [list {} object [list tools array $members]]
    }
    :public method call {context authorizer name arguments} {
        :refreshProviders
        if {![dict exists ${:tools} $name]} {
            return -code error -errorcode {NSMCP RPC -32602} "Unknown tool"
        }
        set tool [dict get ${:tools} $name]
        $authorizer require $context [$tool permission]
        if {![:readEnabled $name]} {
            return -code error -errorcode {NSMCP RPC -32602} "Tool is disabled on this server"
        }
        $tool validate $arguments
        if {[catch {$tool execute $context $arguments} result options]} {
            if {[lrange [dict get $options -errorcode] 0 1] eq {NSMCP RPC}} {
                return -options $options $result
            }
            ns_log error "nsmcp: tool execution failed: $name"
            return [list {} object {content array {0 object {type string text text string {Tool execution failed; consult the server log.}}} isError boolean true}]
        }
        # Both structured output and text for clients without structured support.
        return [list {} object [list structuredContent object [lindex $result 2]  content array [list 0 object [list type string text text string [ns_json value -- $result]]]  isError boolean false]]
    }
XQL Not present:
Generic, PostgreSQL, Oracle
[ hide source ] | [ make this the default ]
Show another procedure: