Switches the discovery version code bamboo -> cedar and aligns the open edition CLI with dws-wukong across communication (calendar book/acl/attendee, minutes tag, mail folder/template/contact, chat file upload, todo add-attachment, attendance transforms) and structured-office (aitable advperm/view/section/workflow/record, sheet/drive/wiki/doc) domains. Includes output-envelope parity, parse_bool/attendance_class_check_time transforms, --calendar-id support, CHANGELOG 1.0.43 and README command-index refresh. cedar config validated on pre and prod endpoints.
Extract the server-list endpoint path into a single discoveryAPIPath constant and move it from /cli/discovery/apis to /cli/discovery/apis/bamboo so future version bumps touch one place.
Only the path changes: the MCP base host stays on production https://mcp.dingtalk.com and the auth / skill / doctor endpoints are untouched. The edition DiscoveryURL hook (full-URL FetchServersFromURL) is unaffected. All mock/test fixtures are updated to the new path and a CHANGELOG 1.0.34 entry is added.
* feat(transform): add file_read transform for UTF-8 file / stdin content (#277)
Introduces a new ApplyTransform case "file_read":
- Input: a string flag value treated as a UTF-8 file path
- Special case: the path "-" reads from stdin
- Output: the file contents as a string
- Errors surface as validation errors (exit 2) — non-UTF-8, missing
file, empty path, and non-string input all reject cleanly
This is a foundational primitive intended to compose with envelope
schema features so a CLI flag like --content-file can carry a path
that ultimately feeds a string-typed MCP parameter with the file's
contents.
## Scope note — MapsTo intentionally NOT included
The original proposal in #277 paired this transform with a new
CLIFlagOverride.MapsTo field that retargets a flag's value to a
different MCP parameter (e.g. --content / --content-file both feed
the upstream `markdown` param). Pre-production end-to-end validation
(see #282) showed that the MCP registry server-side schema does not
currently recognise `mapsTo` and strips it on serialisation. Every
other new envelope field (hidden / required / transform /
mutuallyExclusive / requireOneOf) survives — only mapsTo is dropped.
Shipping MapsTo without server-side support would land dead client
code. The MapsTo field + sourceFlag transform guard were therefore
removed from this PR; they will return in a follow-up PR once #282
(server-side schema acknowledgement of mapsTo) is resolved. The
file_read transform stays here because it composes with multiple
mechanisms beyond MapsTo and is independently testable.
## Tests
Six new cases in internal/compat/transform_test.go cover the
contract: literal file, stdin via "-", missing file, non-UTF-8
input, empty path, non-string input. All pass under both `go test`
and `go test -coverprofile`.
Refs #277, blocked-by #282
* feat(schema): add CLIFlagOverride.MapsTo for sibling-flag routing (#277#282)
Adds the `MapsTo` field on `CLIFlagOverride` and wires the dispatch loop
in `compat.buildOverrideBindings` so a flag's final value (post-transform
or literal) is routed into a different MCP parameter slot than its own
property name. This lets two sibling CLI flags feed a single upstream
parameter — the canonical case being `--content` (literal) + `--content-file`
(transform: file_read) both mapping to `markdown`.
Tool-level `CLIToolOverride.MutuallyExclusive` (cobra MarkFlagsMutuallyExclusive)
is the right partner for guarding "set one, not both" at parse time; no
new exclusion machinery is added.
Closes the client-side gap previously misattributed to a server-side
mapsTo strip in #282. Once a doc envelope with mapsTo lands in pre-prod,
end-to-end `--content-file` becomes shippable, finishing #277 Step 1b.
Test coverage (internal/compat/dynamic_commands_test.go):
- MapsTo without transform: literal --content → params[markdown]
- MapsTo with file_read transform: --content-file path → params[markdown]
- Sibling flags both mapsTo same target, only one set: clean routing
- Sibling flags both set: rejected by tool-level MutuallyExclusive (regression)
Backward-compat: empty MapsTo preserves existing params[propertyName] write
semantics for every existing envelope.
Two generic CLI envelope schema enhancements that close gaps surfaced by
the cli_to_mcp test suite without resorting to product-specific helpers:
1. CLIToolOverride.CLIAliases (registry.go + dynamic_commands.go)
- Lets a single MCP tool register additional cobra command aliases via
envelope JSON (e.g. `range read` accepts `range get`, `member list`
accepts `member ls`). Plumbs through the existing Route.Aliases ->
cobra.Command.Aliases path; conflicts with siblings are silently
skipped by cobra.
2. json_parse_strict transform (transform.go)
- Strict JSON variant of json_parse that does NOT fall back to YAML.
Use when the upstream tool requires a structured array/object value
and silently coercing malformed input to a scalar string would mask
a real user error (observed: filter-view --criteria 'NOT_VALID_JSON'
was being accepted and quietly creating an empty-criteria view).
A generic envelope schema feature that lets a single CLI command orchestrate
an ordered sequence of MCP tool calls plus optional HTTP-download sinks,
declared entirely in the envelope JSON. The motivating use case is the
"submit-job + poll-status + download-result" pattern (e.g. sheet export),
which previously required hardcoded helper commands per product.
## Schema additions
- `CLIToolOverride.Pipeline []PipelineStep` — when non-empty, dispatch
ignores the parent map key (no single "primary tool") and walks the
steps in order. CLI surface (CLIName / Group / Flags) still applies.
- `PipelineStep` struct — supports two step types:
- `type:"call"` (default) invokes Tool with templated Args. Optional
PollUntilField/Value turn it into a polling loop with configurable
PollIntervalSec / PollTimeoutSec.
- `type:"download"` resolves DownloadURLField, fetches via HTTP GET,
and writes the body to the path supplied by OutputFlag's value
(with directory-path filename inference).
- `CLIFlagOverride.PipelineLocal bool` — marks a flag as CLI-side only;
CollectBindings skips it so the value never reaches MCP params, but
the pipeline executor reads it via extractFlagValuesByAlias.
## Template language
Two prefixes supported in PipelineStep.Args / DownloadURLField:
$flag.<name> — value of the user's CLI flag whose alias is <name>
$step.<idx>.<dotPath> — field from a prior step's response (idx 0-based)
literal — passed through
dotPath walks nested map[string]any so "$step.1.content.downloadUrl"
resolves through wrapped MCP envelopes correctly.
## Stdout contract
The download step always emits machine-parseable plain-text lines
("jobId: <id>\\n", "downloadUrl: <url>\\n") in addition to the standard
JSON output, so shell pipelines and regex-based test suites can extract
key values without parsing JSON. Structured callers continue to consume
the output.WriteCommandPayload JSON envelope.
## Why this is schema enhancement, not product hardcode
- The executor is product-agnostic: any envelope can declare a Pipeline
and benefit (sheet export today; doc/drive/aitable async patterns
tomorrow).
- The template language is the only product-coupling, and it lives in
the envelope JSON — not in Go code.
- No sheet/wiki/aitable-specific code in dingtalk-workspace-cli.
Files: 1 new + 3 modified (~250 LOC of executor + ~30 LOC of schema
plumbing). Existing tests pass; new pipeline tests TBA in a follow-up
once the schema fields are in upstream.
- Support file path reference in plugin.json cli field (e.g. "cli": "overlay.json")
in addition to inline JSON objects, resolving path relative to plugin root
- Add description field to CLIToolOverride for static command descriptions
as fallback when MCP tools/list is unavailable (e.g. upstream server offline)
- Fix plugin install on Windows: use cmd /C instead of sh -c for build commands
- Skip copying identical files during plugin install to avoid overwriting
locked executables (running stdio plugin processes on Windows)
- Add symlink skip and path traversal guard in copyDir for security
- Clean up stale files in destination during plugin upgrade via removeStaleFiles
🤖 Generated with [Qoder][https://qoder.com]
Add Auth Token Registry pattern (inspired by stdio_registry.go) to allow
plugins to declare per-server HTTP headers in plugin.json. This enables
third-party streamable-http MCP servers (e.g. Alibaba Cloud Bailian) to
use their own API keys independently from the default DingTalk OAuth token.
Changes:
- New: internal/app/auth_registry.go — per-productID auth credential store
- manifest.go: MCPServer gains Headers field for custom HTTP headers
- registry.go: ServerDescriptor gains AuthHeaders field
- converter.go: resolve and pass headers through ToServerDescriptors
- root.go: inject plugin auth at discovery time + auto-generate ToolOverrides
- runner.go: route plugin-auth servers to their own Bearer token at runtime
Multi-token isolation: each server is keyed by its CLI.ID (productID),
so different servers can use independent tokens without interfering with
each other or with the default DingTalk OAuth token.
- Build tool → endpoint mapping from CLI tools and overrides
- directRuntimeEndpoint resolves by tool name before product fallback
- Support dynamic command routing with compat layer updates