Schema Catalog now assembles from Contract/ParamDecl/Interface and Cobra only. Keep fetch-mcp-metadata as an optional diagnostic dump and ban the retired pin path. Co-authored-by: Cursor <cursoragent@cursor.com>
177 lines
6.1 KiB
Go
177 lines
6.1 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
|
|
)
|
|
|
|
// InterfaceRefKey is the exact, typed identity of an embedded MCP operation.
|
|
// It is deliberately independent from a CLI canonical path: multiple commands
|
|
// may explicitly project the same operation.
|
|
type InterfaceRefKey struct {
|
|
ProductID string
|
|
RPCName string
|
|
}
|
|
|
|
// InterfaceRegistryEntry is one operation projection from an optional
|
|
// diagnostic MCP-shaped map (tests / fetch dumps). CanonicalPath is a lookup
|
|
// key, not command identity. Production assembly does not build this from a
|
|
// committed pin.
|
|
type InterfaceRegistryEntry struct {
|
|
CanonicalPath string
|
|
Ref contract.InterfaceRefSpec
|
|
Metadata embeddedMCPToolMetadata
|
|
}
|
|
|
|
// InterfaceRegistry is the typed interface fact model for optional MCP-shaped
|
|
// fixtures. ByCanonical supports exact navigation; ByInterfaceRef answers
|
|
// whether an explicitly selected (product_id, rpc_name) exists.
|
|
type InterfaceRegistry struct {
|
|
ByCanonical map[string]InterfaceRegistryEntry
|
|
ByInterfaceRef map[InterfaceRefKey][]string
|
|
}
|
|
|
|
// buildInterfaceRegistry creates deterministic exact indexes over an optional
|
|
// MCP-shaped tool map. It rejects malformed or conflicting canonical
|
|
// projections and never manufactures an interface from a command identity.
|
|
func buildInterfaceRegistry(tools map[string]embeddedMCPToolMetadata) (InterfaceRegistry, error) {
|
|
registry := InterfaceRegistry{
|
|
ByCanonical: make(map[string]InterfaceRegistryEntry, len(tools)),
|
|
ByInterfaceRef: make(map[InterfaceRefKey][]string),
|
|
}
|
|
|
|
rawCanonicals := make([]string, 0, len(tools))
|
|
for canonical := range tools {
|
|
rawCanonicals = append(rawCanonicals, canonical)
|
|
}
|
|
sort.Strings(rawCanonicals)
|
|
|
|
for _, rawCanonical := range rawCanonicals {
|
|
canonical := strings.TrimSpace(rawCanonical)
|
|
if canonical == "" {
|
|
return InterfaceRegistry{}, fmt.Errorf("embedded MCP interface has empty canonical path")
|
|
}
|
|
if existing, exists := registry.ByCanonical[canonical]; exists {
|
|
return InterfaceRegistry{}, fmt.Errorf(
|
|
"embedded MCP canonical path %q conflicts with %q",
|
|
rawCanonical,
|
|
existing.CanonicalPath,
|
|
)
|
|
}
|
|
|
|
metadata := tools[rawCanonical]
|
|
if metadata.InterfaceRef == nil {
|
|
return InterfaceRegistry{}, fmt.Errorf("embedded MCP interface %s has no interface_ref", canonical)
|
|
}
|
|
key := InterfaceRefKey{
|
|
ProductID: strings.TrimSpace(metadata.InterfaceRef.ProductID),
|
|
RPCName: strings.TrimSpace(metadata.InterfaceRef.RPCName),
|
|
}
|
|
if key.ProductID == "" || key.RPCName == "" {
|
|
return InterfaceRegistry{}, fmt.Errorf("embedded MCP interface %s has incomplete interface_ref", canonical)
|
|
}
|
|
|
|
entry := InterfaceRegistryEntry{
|
|
CanonicalPath: canonical,
|
|
Ref: contract.InterfaceRefSpec(key),
|
|
Metadata: metadata,
|
|
}
|
|
registry.ByCanonical[canonical] = entry
|
|
registry.ByInterfaceRef[key] = append(registry.ByInterfaceRef[key], canonical)
|
|
}
|
|
|
|
for key := range registry.ByInterfaceRef {
|
|
sort.Strings(registry.ByInterfaceRef[key])
|
|
}
|
|
return registry, nil
|
|
}
|
|
|
|
// validateSchemaRegistryInterfaces proves every ToolSpec's declared Interface
|
|
// self-consistency (mode / ref / reason). ContractFinal Interface is the
|
|
// authority; there is no committed MCP pin to cross-check against.
|
|
func validateSchemaRegistryInterfaces(schema SchemaRegistry) error {
|
|
return validateSchemaRegistryInterfacesWithMetadata(schema, emptyPinnedMCPMetadata())
|
|
}
|
|
|
|
func validateSchemaRegistryInterfacesWithMetadata(schema SchemaRegistry, metadata embeddedMCPMetadata) error {
|
|
// When a non-empty diagnostic/fixture map is supplied, MCP-mode refs must
|
|
// resolve into it. Production metadata is empty, so only Validate() runs.
|
|
var interfaces InterfaceRegistry
|
|
if len(metadata.Tools) > 0 {
|
|
built, err := buildInterfaceRegistry(metadata.Tools)
|
|
if err != nil {
|
|
return fmt.Errorf("build typed Interface registry: %w", err)
|
|
}
|
|
interfaces = built
|
|
}
|
|
|
|
var problems []string
|
|
for _, product := range schema.Products {
|
|
for _, tool := range product.Tools {
|
|
canonical := strings.TrimSpace(tool.Identity.CanonicalPath)
|
|
if canonical == "" {
|
|
canonical = strings.Trim(strings.TrimSpace(tool.Identity.ProductID)+"."+strings.TrimSpace(tool.Identity.Name), ".")
|
|
}
|
|
if canonical == "" {
|
|
canonical = "<unknown>"
|
|
}
|
|
|
|
if err := tool.Interface.Validate(canonical); err != nil {
|
|
problems = append(problems, err.Error())
|
|
continue
|
|
}
|
|
if len(interfaces.ByInterfaceRef) == 0 {
|
|
continue
|
|
}
|
|
if mode := strings.TrimSpace(tool.Interface.Mode); mode == contract.InterfaceModeMCP && tool.Interface.AgentExecutable() {
|
|
if err := validateToolInterfaceRef(canonical, mode, tool.Interface.Ref, interfaces); err != nil {
|
|
problems = append(problems, err.Error())
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if len(problems) > 0 {
|
|
sort.Strings(problems)
|
|
return fmt.Errorf("%s", strings.Join(problems, "; "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateToolInterfaceRef(canonical, mode string, ref *contract.InterfaceRefSpec, interfaces InterfaceRegistry) error {
|
|
if ref == nil {
|
|
return fmt.Errorf("schema tool %s with interface mode %s has no interface_ref", canonical, mode)
|
|
}
|
|
key := InterfaceRefKey{
|
|
ProductID: strings.TrimSpace(ref.ProductID),
|
|
RPCName: strings.TrimSpace(ref.RPCName),
|
|
}
|
|
if key.ProductID == "" || key.RPCName == "" {
|
|
return fmt.Errorf("schema tool %s with interface mode %s has incomplete interface_ref", canonical, mode)
|
|
}
|
|
if _, exists := interfaces.ByInterfaceRef[key]; !exists {
|
|
return fmt.Errorf(
|
|
"schema tool %s references unknown MCP interface %s.%s",
|
|
canonical,
|
|
key.ProductID,
|
|
key.RPCName,
|
|
)
|
|
}
|
|
return nil
|
|
}
|