Files
dingtalk-workspace-cli/internal/cli/schema_interface_registry.go
玉澜andCursor ba72358f78 retire pinned schema_mcp_metadata.json from Schema assembly
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>
2026-08-03 17:24:32 +08:00

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
}