146 lines
6.1 KiB
Go
146 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"
|
||
"strings"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
|
||
apperrors "github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/errors"
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
|
||
"github.com/spf13/cobra"
|
||
)
|
||
|
||
// schemaCommandCatalogError / payloads use deliverySchemaCatalog
|
||
// (RegisterSchemaSourceRoot → ResolveSchemaBuild). There is no committed
|
||
// Schema Catalog embed fallback.
|
||
var schemaCommandCatalogError = deliverySchemaCatalogError
|
||
|
||
// NewMCPCommand registers the mcp product declaration and returns its root
|
||
// command. The app layer attaches reviewed static MCP helpers as
|
||
// subcommands; live discovery surfaces are retired.
|
||
func NewMCPCommand() *cobra.Command {
|
||
// Product-level Agent routing Decl (migrated from selection/mcp.json
|
||
// products.mcp). Schema assembly stamps provenance contract_final.
|
||
contract.RegisterProductDecl(contract.ProductDecl{
|
||
ID: "mcp",
|
||
Selection: contract.ProductSelectionDecl{
|
||
AgentSummary: "解析和管理当前身份可用的 MCP 服务连接信息",
|
||
UseWhen: []string{
|
||
"需要把钉钉 MCP 市场中的服务连接到支持 Streamable HTTP 的 Agent 或客户端",
|
||
},
|
||
AvoidWhen: []string{
|
||
"查询普通钉钉业务数据时使用对应产品命令,不要使用 mcp",
|
||
},
|
||
},
|
||
})
|
||
// The legacy dynamic MCP surface has been removed; the app layer adds
|
||
// reviewed static MCP helpers as subcommands of this command.
|
||
cmd := &cobra.Command{
|
||
Use: "mcp",
|
||
Short: "管理 MCP 服务连接信息",
|
||
Long: "管理经过审核并纳入 Schema 的 MCP 服务连接辅助能力。",
|
||
Args: cobra.NoArgs,
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
return cmd.Help()
|
||
},
|
||
}
|
||
return cmd
|
||
}
|
||
|
||
// NewSchemaCommand serves the typed Schema contract. Production assembles from
|
||
// declarations via ResolveSchemaBuild (factory registered by internal/app).
|
||
// A malformed assembly fails closed; the command takes no discovery loader
|
||
// because queries never run service discovery.
|
||
func NewSchemaCommand() *cobra.Command {
|
||
cmd := &cobra.Command{
|
||
Use: "schema [path]",
|
||
Short: "渐进查看命令 Schema (产品 / 分组 / 工具参数)",
|
||
Long: `查看当前可运行命令的 Schema 元数据。
|
||
|
||
不带参数时列出产品和工具数量;传产品或分组路径逐层展开;传具体工具路径输出扁平参数 Schema(对齐 GWS:parameters 内联 required,键为 CLI flag)。普通 Agent 查询应使用 --compact:它按稳定字段白名单输出选参、约束、安全语义和已评审的返回契约。省略 --compact 的 full leaf 保留参数映射、接口绑定和 provenance,仅用于定向审计;--all 输出全部工具的完整 leaf Schema,用于审计/CI。helper、MCP 与本地 Cobra 命令均须通过 ContractFinal.Identity 声明进入收集的身份集,并从同一声明装配的 ToolSpec 投影;查询不执行服务发现或临时合成第二份 Schema。`,
|
||
Args: cobra.MaximumNArgs(1),
|
||
DisableAutoGenTag: true,
|
||
RunE: func(cmd *cobra.Command, args []string) error {
|
||
all, _ := cmd.Flags().GetBool("all")
|
||
compact, _ := cmd.Flags().GetBool("compact")
|
||
cliPath, _ := cmd.Flags().GetString("cli-path")
|
||
cliPath = strings.TrimSpace(cliPath)
|
||
if cliPath != "" && len(args) > 0 {
|
||
return apperrors.NewValidation("--cli-path and positional argument are mutually exclusive")
|
||
}
|
||
if len(args) == 1 && strings.EqualFold(strings.TrimSpace(args[0]), "list") {
|
||
args = nil
|
||
}
|
||
if all && (cliPath != "" || len(args) > 0) {
|
||
return apperrors.NewValidation("--all cannot be combined with a schema path")
|
||
}
|
||
if cliPath != "" {
|
||
args = []string{cliPath}
|
||
}
|
||
if err := schemaCommandCatalogError(); err != nil {
|
||
return fmt.Errorf("load typed Schema registry: %w", err)
|
||
}
|
||
var payload map[string]any
|
||
var err error
|
||
if all {
|
||
payload, err = deliverySchemaAllPayload()
|
||
} else if len(args) == 0 {
|
||
payload, err = deliverySchemaOverviewPayload()
|
||
} else {
|
||
payload, err = queryDeliverySchemaPayload(args)
|
||
}
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if compact {
|
||
payload = stripSchemaPayloadCompact(payload)
|
||
}
|
||
return output.WriteFiltered(cmd.OutOrStdout(), output.ResolveFormat(cmd, output.FormatJSON), payload, output.ResolveFields(cmd), output.ResolveJQ(cmd))
|
||
},
|
||
}
|
||
cmd.Flags().Bool("all", false, "输出全部工具的完整 leaf Schema(包括参数和约束,用于审计/CI)")
|
||
cmd.Flags().Bool("compact", false, "按稳定字段白名单输出 Agent 选参、约束、安全语义和返回契约")
|
||
cmd.Flags().String("cli-path", "", "按 CLI 命令路径查询")
|
||
return cmd
|
||
}
|
||
|
||
// splitSchemaPathTokens splits a CLI path on dots, slashes, and
|
||
// whitespace, returning only non-empty tokens.
|
||
func splitSchemaPathTokens(raw string) []string {
|
||
fields := strings.FieldsFunc(raw, func(r rune) bool {
|
||
return r == '.' || r == '/' || r == ' ' || r == '\t'
|
||
})
|
||
out := fields[:0]
|
||
for _, f := range fields {
|
||
if s := strings.TrimSpace(f); s != "" {
|
||
out = append(out, s)
|
||
}
|
||
}
|
||
return out
|
||
}
|
||
|
||
// normalizeSchemaQueryCLIPath accepts the historical query spellings while
|
||
// keeping authored Registry CLI paths strict and space-separated. Canonical
|
||
// identity lookup still runs before this compatibility normalization.
|
||
func normalizeSchemaQueryCLIPath(path string) string {
|
||
parts := splitSchemaPathTokens(strings.TrimSpace(path))
|
||
if len(parts) > 0 && parts[0] == "dws" {
|
||
parts = parts[1:]
|
||
}
|
||
return strings.Join(parts, " ")
|
||
}
|