Class ::nsmcp::ToolRegistry

::nsmcp::ToolRegistry[i] create ... \
           [ -providerContext (default "") ] \
           [ -providers (default "") ] \
           [ -stateStore (default "") ] \
           [ -tools (default "") ]

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

Class Relations

  • class: ::nx::Class[i]
  • superclass: ::nsmcp::Object[i]
::nx::Class create ::nsmcp::ToolRegistry \
     -superclass ::nsmcp::Object

Methods (to be applied on the object)

  • providerConfig (scripted, public)

     nsmcp::ToolRegistry[i] providerConfig context parameter default

    Read a provider setting from the configured sections. Try the primary and fallback sections in order, using the supplied default when neither supplies the parameter.

    Parameters:
    context (required)
    Provider configuration dictionary with primary and fallback section names.
    parameter (required)
    NaviServer configuration parameter name.
    default (required)
    Value used when the requested configuration entry is absent.
    Returns:
    The configured setting, or the default.

    Testcases:
    No testcase defined.
    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

Methods (to be applied on instances)

  • call (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> call context authorizer name \
        arguments

    Authorize, validate and invoke a registered tool. Check enable state, permissions and question pins before handing arguments to the tool.

    Parameters:
    context (required)
    Authenticated request context carrying the principal, grants and selected server.
    authorizer (required)
    Authorizer used to enforce the request context's permissions and targets.
    name (required)
    Name of the configured or registered item to resolve.
    arguments (required)
    Rooted ns_json typed triples for the tool's JSON argument object.
    Returns:
    The tool's rooted JSON result; policy and validation failures raise RPC errors.

    Testcases:
    No testcase defined.
    :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"
    }
    :verifyQuestionPin $context $name
    $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]]
  • controlsAvailable (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> controlsAvailable

    Report whether runtime tool enable controls are configured. The state store determines availability of administrative overrides.

    Returns:
    True when a runtime state store is present, otherwise false.

    Testcases:
    No testcase defined.
    # An explicitly configured backend owns per-server runtime overrides.
    return [expr {${:stateStore} ne ""}]
  • describe (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> describe

    List administrative metadata for registered tools. Include permission and enable-state information without executing tools or applying a caller's permission filter.

    Returns:
    A list of tool metadata dictionaries.

    Testcases:
    No testcase defined.
    :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
  • enabled (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> enabled name

    Report whether a named tool is administratively enabled. Refresh provider registrations first; caller permissions are checked separately during listing and invocation.

    Parameters:
    name (required)
    Registered MCP tool name.
    Returns:
    The effective enabled flag, or false for an unknown tool.

    Testcases:
    No testcase defined.
    :refreshProviders
    # Administrative availability, independent of caller permissions.
    if {![dict exists ${:tools} $name]} {
        error "unknown tool: $name"
    }
    return [:readEnabled $name]
  • fingerprint (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> fingerprint name

    Hash a registered tool's permission and descriptor. Question conversations use this bounded descriptor fingerprint to detect a changed tool definition.

    Parameters:
    name (required)
    Registered MCP tool name whose descriptor is pinned.
    Returns:
    The hexadecimal SHA-256 fingerprint; unknown tools raise an error.

    Testcases:
    No testcase defined.
    :refreshProviders
    if {![dict exists ${:tools} $name]} {
        error "Unknown question tool"
    }
    set tool [dict get ${:tools} $name]
    set data [list [$tool permission] [ns_json value -- [$tool describe]]]
    if {[string bytelength $data]>1048576} {
        error "Question tool descriptor exceeds limit"
    }
    return [ns_crypto::md string -digest sha256 -- $data]
  • list (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> list context authorizer

    List the tools available to an authenticated caller. Refresh providers and apply administrative enable flags and the supplied authorizer.

    Parameters:
    context (required)
    Authenticated request context carrying the principal, grants and selected server.
    authorizer (required)
    Authorizer used to enforce the request context's permissions and targets.
    Returns:
    A rooted JSON object containing the tools array.

    Testcases:
    No testcase defined.
    :refreshProviders
    set result {}
    foreach name [lsort [dict keys ${:tools}]] {
        set tool [dict get ${:tools} $name]
        if {[:readEnabled $name] && [$authorizer allowed $context [$tool permission]]} {
            if {[$context cget -questionRestricted]} {
                if {![dict exists [$context cget -questionPins] $name]} {
                    continue
                }
                :verifyQuestionPin $context $name
            }
            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]]
  • questionSnapshot (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> questionSnapshot context \
        authorizer

    Pin the permitted tool inventory for a new question. Apply request authorization and question allowlists; outbound upstream comparison additionally requires its configured provider and switches.

    Parameters:
    context (required)
    Authenticated request context carrying the principal, grants and selected server.
    authorizer (required)
    Authorizer used to enforce the request context's permissions and targets.
    Returns:
    A dictionary mapping allowed tool names to descriptor fingerprints.

    Testcases:
    No testcase defined.
    set pins {}
    set listed [:list $context $authorizer]
    foreach {index type members} [ns_json triples getvalue -output triples -path tools -- $listed] {
        set metadata [ns_json parse [ns_json value -- [list {} object $members]]]
        if {![dict exists $metadata annotations readOnlyHint] || ![dict get $metadata annotations readOnlyHint]} {
            continue
        }
        set name [dict get $metadata name]
        if {$name ni [::nsmcp::questionAllowedTools]} {
            continue
        }
        if {![dict exists $metadata annotations destructiveHint] || [dict get $metadata annotations destructiveHint]
            || ![dict exists $metadata annotations openWorldHint]} {
            continue
        }
        if {[dict get $metadata annotations openWorldHint]} {
            # This one outbound tool has a fixed GitHub transport and
            # administrator-owned repository mappings. All other external
            # tools remain excluded from question execution.
            if {$name ne "code_upstream_compare"
                || ![nsf::is object,type=::nsmcp::CodeUpstreamTool [dict get ${:tools} $name]]
                || ![ns_config -bool ns/server/[ns_info server]/module/nsmcp enableCodeTools false]
                || ![ns_config -bool ns/server/[ns_info server]/module/nsmcp enableCodeUpstream false]} {
                continue
            }
        }
        if {[dict size $pins]>=256 || [string bytelength $name]>128} {
            error "Question tool inventory exceeds limit"
        }
        dict set pins $name [:fingerprint $name]
    }
    if {$pins eq {}} {
        error "No permitted read-only question tools are available"
    }
    return $pins
  • refreshProviders (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> refreshProviders

    Refresh registrations supplied by configured tool providers. Reconcile provider descriptors with registry state; this is registration work, not a tool invocation.

    Returns:
    The provider dictionary when a provider context is configured, otherwise an empty result.

    Testcases:
    No testcase defined.
    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}
  • register (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> register tool

    Register a tool object under its configured name. Reject duplicate names so dispatch cannot silently replace an existing registration.

    Parameters:
    tool (required)
    Tool object to register.
    Returns:
    The updated internal registration dictionary.

    Testcases:
    No testcase defined.
    set name [$tool name]
    if {[dict exists ${:tools} $name]} {
        error "duplicate tool: $name"
    }
    dict set :tools $name $tool
  • setEnabled (scripted, public)

     <instance of nsmcp::ToolRegistry[i]> setEnabled name enabled

    Change the administrative enable flag for a registered tool. This trusted interface affects new calls, not jobs already running or background collectors.

    Parameters:
    name (required)
    Registered MCP tool name.
    enabled (required, boolean)
    Boolean administrative enable flag.
    Returns:
    The enabled flag written to the state store.

    Testcases:
    No testcase defined.
    # 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