feat: add schema command enhancements and todo helper improvements
- Support schema output in table format for human-readable display - Add product-level endpoint loading in CLI loader - Wire schema-text rendering into runner output pipeline - Add priority field support for todo task helper
This commit is contained in:
@@ -189,6 +189,9 @@ func NewRootCommandWithEngine(rootCtx context.Context, engine *pipeline.Engine)
|
||||
loader := cli.EnvironmentLoader{
|
||||
LookupEnv: os.LookupEnv,
|
||||
CatalogBaseURLOverride: DiscoveryBaseURL(),
|
||||
AuthTokenFunc: func(ctx context.Context) string {
|
||||
return resolveRuntimeAuthToken(ctx, "")
|
||||
},
|
||||
}
|
||||
runner := newCommandRunnerWithFlags(loader, flags)
|
||||
|
||||
|
||||
@@ -171,6 +171,15 @@ func (r *runtimeRunner) executeInvocation(ctx context.Context, endpoint string,
|
||||
return executor.Result{}, err
|
||||
}
|
||||
|
||||
if bizErr := detectBusinessError(callResult.Content); bizErr != "" {
|
||||
return executor.Result{}, apperrors.NewAPI(bizErr,
|
||||
apperrors.WithOperation("tools/call"),
|
||||
apperrors.WithReason("business_error"),
|
||||
apperrors.WithServerKey(invocation.CanonicalProduct),
|
||||
apperrors.WithHint("The API returned a business-level error. Check required parameters and values."),
|
||||
)
|
||||
}
|
||||
|
||||
invocation.Implemented = true
|
||||
response := map[string]any{
|
||||
"endpoint": transport.RedactURL(endpoint),
|
||||
@@ -279,6 +288,27 @@ func resolveIdentityHeaders() map[string]string {
|
||||
return headers
|
||||
}
|
||||
|
||||
// detectBusinessError checks the MCP response content for DingTalk business
|
||||
// errors (success=false + errorCode/errorMsg) that are not flagged at the MCP
|
||||
// protocol level. Returns the error message, or "" if the response is OK.
|
||||
func detectBusinessError(content map[string]any) string {
|
||||
success, ok := content["success"]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
b, ok := success.(bool)
|
||||
if !ok || b {
|
||||
return ""
|
||||
}
|
||||
if msg, ok := content["errorMsg"].(string); ok && strings.TrimSpace(msg) != "" {
|
||||
return strings.TrimSpace(msg)
|
||||
}
|
||||
if code, ok := content["errorCode"].(string); ok && strings.TrimSpace(code) != "" {
|
||||
return "business error: code " + strings.TrimSpace(code)
|
||||
}
|
||||
return "business error: success=false"
|
||||
}
|
||||
|
||||
// extractMCPErrorMessage builds an error message from a ToolCallResult with
|
||||
// isError=true. It extracts text from content blocks when available.
|
||||
func extractMCPErrorMessage(result transport.ToolCallResult) string {
|
||||
|
||||
@@ -585,10 +585,23 @@ func collectOverrides(cmd *cobra.Command, specs []FlagSpec, guard *StdinGuard) (
|
||||
|
||||
func schemaPayload(catalog ir.Catalog, args []string) (map[string]any, error) {
|
||||
if len(args) == 0 {
|
||||
products := make([]map[string]any, 0, len(catalog.Products))
|
||||
for _, p := range catalog.Products {
|
||||
tools := make([]map[string]any, 0, len(p.Tools))
|
||||
for _, t := range p.Tools {
|
||||
tools = append(tools, compactTool(t))
|
||||
}
|
||||
products = append(products, map[string]any{
|
||||
"id": p.ID,
|
||||
"name": p.DisplayName,
|
||||
"description": p.Description,
|
||||
"tools": tools,
|
||||
})
|
||||
}
|
||||
return map[string]any{
|
||||
"kind": "schema",
|
||||
"products": catalog.Products,
|
||||
"count": len(catalog.Products),
|
||||
"count": len(products),
|
||||
"products": products,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -597,14 +610,34 @@ func schemaPayload(catalog ir.Catalog, args []string) (map[string]any, error) {
|
||||
return nil, apperrors.NewValidation(fmt.Sprintf("unknown canonical schema path %q", args[0]))
|
||||
}
|
||||
return map[string]any{
|
||||
"kind": "schema",
|
||||
"path": args[0],
|
||||
"product": product,
|
||||
"tool": tool,
|
||||
"required": requiredFields(tool.InputSchema),
|
||||
"kind": "schema",
|
||||
"path": args[0],
|
||||
"product": map[string]any{"id": product.ID, "name": product.DisplayName},
|
||||
"tool": compactTool(tool),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// compactTool returns a lean representation of a tool for schema
|
||||
// output, keeping only the fields that AI agents and developers
|
||||
// need: name, description, parameters, and sensitivity flag.
|
||||
func compactTool(t ir.ToolDescriptor) map[string]any {
|
||||
tool := map[string]any{
|
||||
"name": t.RPCName,
|
||||
"title": t.Title,
|
||||
"description": t.Description,
|
||||
"sensitive": t.Sensitive,
|
||||
}
|
||||
|
||||
if props, ok := t.InputSchema["properties"]; ok {
|
||||
tool["parameters"] = props
|
||||
}
|
||||
if req := requiredFields(t.InputSchema); len(req) > 0 {
|
||||
tool["required"] = req
|
||||
}
|
||||
|
||||
return tool
|
||||
}
|
||||
|
||||
func confirmSensitiveTool(cmd *cobra.Command, tool ir.ToolDescriptor, guard *StdinGuard) error {
|
||||
if !tool.Sensitive {
|
||||
return nil
|
||||
|
||||
@@ -89,6 +89,9 @@ type EnvironmentLoader struct {
|
||||
// DiscoveryTimeout overrides the default timeout for live registry discovery.
|
||||
// Zero means use defaultDiscoveryTimeout.
|
||||
DiscoveryTimeout time.Duration
|
||||
// AuthTokenFunc returns an access token for MCP discovery requests
|
||||
// (initialize, tools/list). When nil, discovery runs without auth.
|
||||
AuthTokenFunc func(context.Context) string
|
||||
}
|
||||
|
||||
type cachedCatalogState struct {
|
||||
@@ -125,6 +128,11 @@ func (l EnvironmentLoader) Load(ctx context.Context) (ir.Catalog, error) {
|
||||
}
|
||||
|
||||
transportClient := transport.NewClient(nil)
|
||||
if l.AuthTokenFunc != nil {
|
||||
if token := l.AuthTokenFunc(ctx); token != "" {
|
||||
transportClient = transportClient.WithAuth(token, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// Use a bounded context so discovery doesn't hang in test or CI environments.
|
||||
timeout := defaultDiscoveryTimeout
|
||||
|
||||
@@ -88,6 +88,7 @@ func newTodoTaskListCommand(runner executor.Runner) *cobra.Command {
|
||||
)
|
||||
|
||||
if size <= todoListPageSizeMax {
|
||||
invocation.DryRun = commandDryRun(cmd)
|
||||
result, err := runner.Run(cmd.Context(), invocation)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user