feat: ascend-tools SDK, CLI, and MCP server#1
Merged
lostmygithubaccount merged 41 commits intomainfrom Feb 27, 2026
Merged
Conversation
Rust SDK + CLI with Python wrapper for the Ascend REST API. - Rust SDK crate (ascend-ops): Ed25519 JWT auth, Cloud API token exchange, typed HTTP client for Instance API /api/v1 endpoints - Rust CLI crate (ascend-ops-cli): clap-derived commands for runtime/flow/build operations with table + JSON output - PyO3 binding crate (ascend-ops-py): exposes Client class + CLI run() - Python wrapper: `from ascend_ops import Client` SDK, `ascend-ops` CLI - Build scripts mirroring md-cli pattern (bin/build, check, test, etc.) Refs ascend-io/product#733 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Depends on https://github.com/ascend-io/ascend-backend/pull/1240 for the |
- Drop ASCEND_ORG_ID — use POST /auth/token with instance_api_host instead - Rename ASCEND_PRIVATE_KEY → ASCEND_SERVICE_ACCOUNT_KEY (old name still works) - Python Client() reads env vars automatically when no args provided - Merge flow-run into flow (list-runs, get-run subcommands) - Add flow list command (lists available flow names) - Remove build command (not needed for public API) - Add -r shorthand for --runtime - Hide secret env var values in --help - Fix token cache race condition (hold lock during refresh) - Parse token expiry from response instead of hardcoding 1h - Table output by default, -o json for machine output - No subcommand prints help instead of error - Extract client.py, rename _cli.py → cli.py - Add CLAUDE.md - Remove unused clap dep from SDK crate Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Updated with all polish: dropped org_id, env var auth, flow list, CLI restructure, token cache fix, CLAUDE.md. Related PRs: https://github.com/ascend-io/ascend-backend/pull/1240, https://github.com/ascend-io/ascend-ui/pull/2190 |
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enables IDE autocomplete and go-to-definition for the ascend_ops package. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ci.yml: runs checks on push/PR to main - check.yml: reusable workflow (fmt, clippy, test, ruff) - release.yml: multi-platform Rust binaries on version tags - release-python.yml: PyPI wheels + sdist on version tags - bin/setup, bin/build-wheels, bin/build-sdist scripts Mirrors the md-cli CI/CD pattern. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Token exchange now goes through Instance API's /api/v1/auth/token instead of calling Cloud API directly. Users only need 3 env vars: - ASCEND_SERVICE_ACCOUNT_ID - ASCEND_SERVICE_ACCOUNT_KEY - ASCEND_INSTANCE_API_URL Removed ASCEND_CLOUD_API_URL from config, CLI, PyO3, and Python SDK. ASCEND_CLOUD_API_DOMAIN remains as a local dev override for JWT audience. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SDK now fetches the JWT audience domain from GET /api/v1/auth/config at first auth, eliminating ASCEND_CLOUD_API_DOMAIN entirely. Users only need 3 env vars: SA ID, SA key, Instance API URL. Config is simpler: removed cloud_api_domain and cloud_api_url from Config struct, CLI flags, and PyO3 binding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove backfill_flow from SDK, CLI, PyO3, Python wrapper, type stubs - Rename --flow/-f to --flow-name/-f on flow list-runs - Rename flow= to flow_name= in Python SDK list_flow_runs() Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove backfill references, fix flow_name param name, add auth/config endpoint, remove CLOUD_API_DOMAIN from docstring. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename the core Rust SDK crate directory and package from ascend-ops to ascend-ops-core for clarity alongside ascend-ops-cli and ascend-ops-py. Updated: Cargo.toml dependencies, build/check/test/format scripts, CI workflow cache paths, pyproject.toml cache keys, CLAUDE.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dling
- Rename private_key_bytes/private_key_b64 to key_bytes/key_b64 in auth.rs
- Add custom Debug impl for Config to redact service_account_key
- Remove legacy ASCEND_PRIVATE_KEY env var alias
- Fix run_flow spec: use `is not None` so empty dict {} is forwarded
- Update CLAUDE.md conventions for accuracy
- Stop gitignoring Cargo.lock (should be tracked for binaries)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Local-only integration tests that exercise the full API surface against a running ASE workspace: runtimes (list, get, filter by id/kind), flows (list), flow runs (list, get, trigger, count verification, pagination, status filter), and run_flow with empty spec. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rename the project wholesale: package names, crate names, CLI binary, Python module (import ascend_tools), directory structure, CI workflows, bin scripts, tests, and documentation. GitHub repo already renamed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ascend-tools-mcp crate with 8 MCP tools (list/get runtimes, resume/pause runtime, list flows, run flow, list/get flow runs) over stdio and HTTP. Add runtime lifecycle management: resume_runtime() and pause_runtime() across SDK, CLI, MCP, and Python bindings. run_flow() now checks runtime health before submitting and errors if not running; pass --resume (CLI) or resume=True (SDK) to auto-resume a paused runtime first. CLI improvements: subcommands show help when called without required args, HEALTH column shows "paused" for paused runtimes, env var resolution moved out of clap to fix arg_required_else_help with env vars. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The MCP crate referenced rmcp via a local path (../../rust-sdk/) which doesn't exist in CI. Switch to the published rmcp 0.16 from crates.io. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add coverage for flow run spec fields (full_refresh, parameters, runner_overrides, multi-field) in both Python and shell tests. Add pause/resume/--resume cycle to shell tests. Fix timing issues with health clearing after pause and error capture in shell. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Install a SKILL.md file that teaches AI agents how to use the ascend-tools CLI. The skill template is embedded in the binary via include_str!() and written to <target>/ascend-tools/SKILL.md. Usage: ascend-tools skill install --target ./.claude/skills No authentication required. Runs before config/client initialization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename `wakeup` to `resume` in core SDK for consistency with CLI/Python/MCP - Share single ureq Agent between Auth and AscendClient (one connection pool) - Replace glob imports with explicit imports in client, CLI, and MCP server - Extract `display_health()` helper in CLI to deduplicate health formatting - Remove duplicated paragraph in CLAUDE.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move ascend-tools-core, ascend-tools-mcp, and ascend-tools-cli into a single Cargo workspace at src/ascend_tools/. This gives us one shared Cargo.lock (down from 3), a single target/ directory, and one-command workspace operations (cargo test --workspace, cargo clippy --workspace). The py crate stays outside the workspace (maturin/cdylib constraint) with its own Cargo.lock. - Add workspace Cargo.toml with resolver = "3" - Remove [workspace] stubs from each crate's Cargo.toml - Delete per-crate Cargo.lock files, replaced by workspace lock - Simplify bin/ scripts to use workspace commands - Update CI cache paths to single workspace target - Update release binary path Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- config.rs: Replace single-element env var arrays with plain &str constants; simplify resolve_required signature - server.rs: Extract `blocking()` helper to deduplicate spawn_blocking + error mapping + JSON serialization across all 8 tool handlers - py lib.rs: Release the GIL during all SDK HTTP calls via py.detach() so other Python threads aren't blocked during network I/O Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Release workflows: checkout the correct ref on workflow_dispatch instead of defaulting to branch HEAD, preventing mismatched binaries for tags. SDK (run_flow): check runtime state before resuming — only call resume when the runtime is actually paused, avoiding failures on already-running runtimes. Omit spec key from request body when None instead of sending "spec": null. Add skip_serializing_if to FlowRunSpec fields so unset options aren't serialized as null. Auth: remove spurious Content-Type: application/json from token exchange (empty body). Use .expect() instead of .unwrap() on SystemTime. CLI: add proper clap env bindings with hide_env_values on the SA key. Expose --since, --until, --offset, --limit on flow list-runs. Require --http when --bind is passed on the mcp subcommand. OSS readiness: add MIT LICENSE (Ascend Labs, Inc), remove private PyPI index from pyproject.toml, add .env to .gitignore, add repository/ homepage URLs to all Cargo.toml and pyproject.toml, add readme to pyproject.toml, note Windows unsupported in README. API surface: add #[non_exhaustive] to RuntimeFilters and FlowRunFilters for semver safety. Add SAFETY comment on unsafe block in reset_sigint. Update skill.md with missing spec fields. Apply same fixes (conditional resume, spec omission, Content-Type) to tests/rest.py reference client. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bump requires-python to >=3.13, update PyO3 ABI target to abi3-py313 - Add forbid(unsafe_code) to core, cli, py crates; deny(unsafe_code) to mcp crate with scoped allow on the one necessary libc::signal call - Add docstrings to all Python type stub methods (core.pyi) - Document base64 format acceptance on Auth::new (URL-safe and standard) - Gate reset_sigint() with #[cfg(unix)], add no-op for non-unix - Add unit tests for auth: PKCS#8 DER encoding (output size, prefix, seed embedding, wrong-length rejection, jsonwebtoken roundtrip), base64 decoding (URL-safe, standard, invalid, whitespace), JWT signing (structure, all 6 claims), Debug redaction - Add unit tests for config: CLI-overrides-env priority, env fallback, empty value handling, error message format; refactor resolve() to be a pure function for testability without unsafe env mutation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update requires-python in tests/integration.py and tests/rest.py to match the package requirement (>=3.13) - Remove pytest from dev dependencies (test scripts are standalone uv scripts, not pytest modules) - Remove bin/test-py, simplify bin/test to Rust-only Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prepares for adding skill-py.md and skill-mcp.md by giving the CLI skill template a more specific name. Updates include_str! reference in cli.rs and paths in CLAUDE.md. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Auth::new now rejects keys that aren't exactly 32 bytes immediately after base64 decoding, instead of deferring to JWT signing time. Also replaces x.com/example.com with ascend.io in test URLs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Contributor
Author
|
merging at this point to get the core scaffolding + MVP functionality in place; follow ups for new features & such |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
closes https://github.com/ascend-io/product/issues/733
Summary
ascend-tools-core): Rust HTTP client for Instance API/api/v1— Ed25519 JWT auth with auto-discovery, token caching, typed methods for runtimes, flows, flow runs, pause/resumeascend-tools-cli): clap commands with table + JSON output,--resumeflag onflow runto auto-resume paused runtimes, subcommands show help when called without argsascend-tools-mcp): 8 tools over stdio/HTTP for AI assistants (Claude Code, Cursor, etc.) — list/get runtimes, resume/pause, list flows, run flow, list/get flow runsascend-tools-py): PyO3Clientclass exposing all SDK methods,ascend-toolsCLI entry point via maturinskill install --target <PATH>): installs a SKILL.md teaching AI agents the CLI commands — an alternative to MCP for agent integration. Template is embedded in the binary and written to<target>/ascend-tools/SKILL.mdrun_flowchecks runtime health before submitting; errors on paused/unhealthy runtimes unless--resume/resume=Trueis passedTested end-to-end locally against a booted ASE workspace.