Skip to content

Commit cbf939e

Browse files
feat: add environment, project, and profile support (#26)
* feat: add environment, project, and profile support Add Environment and Project models to core SDK with list/get API methods. Add list_profiles endpoint supporting workspace, deployment, or project+branch targeting. New CLI subcommands: environment (list/get), project (list/get), profile (list). New MCP tools: list_environments, list_projects, list_profiles. New Python SDK methods: list_environments, get_environment, list_projects, get_project, list_profiles. Update type stubs, docs, and skill files for all three interfaces. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add get_environment and get_project MCP tools, update AGENTS.md Add missing get_environment and get_project MCP tools with GetEnvironmentParams and GetProjectParams, matching the CLI and Python SDK which already had these. Update AGENTS.md tool count (18→23), tool table, CLI reference, Python SDK reference, and backend API table to reflect all environment/project/profile additions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: format * fix: validate list_profiles params, rename test, add missing docs - MCP: require branch when project is provided in list_profiles - Python: require branch with project and at least one selector in list_profiles - Rename misleading test_cli_parses_environment_default_is_list - Add list_profiles examples to docs/python.md and skill-py.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 5fdc7f3 commit cbf939e

File tree

18 files changed

+784
-9
lines changed

18 files changed

+784
-9
lines changed

AGENTS.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ src/ascend_tools/
2828
├── ascend-tools-mcp/ # MCP server crate (depends on ascend-tools-core)
2929
│ └── src/
3030
│ ├── lib.rs # run_stdio() and run_http() entry points
31-
│ ├── server.rs # AscendMcpServer — 18 tools via rmcp #[tool_router]
31+
│ ├── server.rs # AscendMcpServer — 23 tools via rmcp #[tool_router]
3232
│ └── params.rs # typed parameter structs with JsonSchema for MCP tool schemas
3333
3434
├── ascend-tools-cli/ # Rust CLI crate (depends on ascend-tools-core, ascend-tools-mcp)
@@ -117,6 +117,14 @@ ascend-tools [-o text|json] [-V]
117117
deployment resume-automations <TITLE>
118118
deployment delete <TITLE>
119119
120+
environment list
121+
environment get <TITLE>
122+
123+
project list
124+
project get <TITLE>
125+
126+
profile list --workspace <TITLE> | --deployment <TITLE> | --project <NAME> --git-branch <BRANCH>
127+
120128
flow list --workspace <TITLE> | --deployment <TITLE>
121129
flow run <FLOW_NAME> --workspace <TITLE> | --deployment <TITLE> [--spec '{}'] [--resume]
122130
flow list-runs --workspace <TITLE> | --deployment <TITLE> [--status, -f/--flow, --since, --until, --offset, --limit]
@@ -148,6 +156,13 @@ client = Client(
148156
instance_api_url="https://api.instance.ascend.io",
149157
)
150158

159+
# Environments & Projects
160+
client.list_environments()
161+
client.get_environment(title="Production")
162+
client.list_projects()
163+
client.get_project(title="My Project")
164+
client.list_profiles(workspace="My Workspace")
165+
151166
# Workspaces
152167
client.list_workspaces()
153168
client.list_workspaces(environment="Production", project="My Project")
@@ -200,6 +215,11 @@ The `mcp` subcommand starts an MCP (Model Context Protocol) server, exposing Asc
200215
| `pause_deployment_automations` | Pause automations on a deployment |
201216
| `resume_deployment_automations` | Resume automations on a deployment |
202217
| `delete_deployment` | Delete a deployment |
218+
| `list_environments` | List environments |
219+
| `get_environment` | Get an environment by title |
220+
| `list_projects` | List projects |
221+
| `get_project` | Get a project by title |
222+
| `list_profiles` | List profiles for a workspace, deployment, or project+branch |
203223
| `list_flows` | List flows in a workspace or deployment |
204224
| `run_flow` | Trigger a flow run with typed spec (resume, full_refresh, components, parameters, etc.) |
205225
| `list_flow_runs` | List flow runs with filters (status, flow, since, until, offset, limit) |
@@ -301,6 +321,9 @@ The SDK/CLI calls the Instance API's `/api/v1/` endpoints, defined in `ascend-ba
301321
| `/api/v1/runtimes/{uuid}` | GET | Get a runtime |
302322
| `/api/v1/runtimes/{uuid}` | PATCH | Update a runtime |
303323
| `/api/v1/runtimes/{uuid}` | DELETE | Delete a runtime |
324+
| `/api/v1/environments` | GET | List environments (filter: title) |
325+
| `/api/v1/projects` | GET | List projects (filter: title) |
326+
| `/api/v1/profiles` | GET | List profiles (filters: runtime_uuid, project, branch) |
304327
| `/api/v1/runtimes/{uuid}/flows` | GET | List flows in a runtime |
305328
| `/api/v1/runtimes/{uuid}/flows/{name}:run` | POST | Trigger a flow run |
306329
| `/api/v1/flow-runs` | GET | List flow runs (requires runtime_uuid, filters: status, flow, since, until) |

docs/cli.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,22 @@ ascend-tools deployment resume-automations "My Deployment"
126126
ascend-tools deployment delete "My Deployment"
127127
```
128128

129+
## Environments, projects, and profiles
130+
131+
```bash
132+
# List environments
133+
ascend-tools environment list
134+
ascend-tools environment get "Production"
135+
136+
# List projects
137+
ascend-tools project list
138+
ascend-tools project get "My Project"
139+
140+
# List profiles (requires a workspace/deployment or project+branch)
141+
ascend-tools profile list --workspace "My Workspace"
142+
ascend-tools profile list --project "My Project" --git-branch main
143+
```
144+
129145
## Manage flows
130146

131147
### List flows

docs/mcp.md

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Set up the MCP server
22

3-
Connect AI assistants to Ascend using the [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server. Exposes 18 tools for managing workspaces, deployments, and flows. Works with Claude Code, Claude Desktop, Codex CLI, Cursor, and other MCP-compatible clients.
3+
Connect AI assistants to Ascend using the [MCP](https://modelcontextprotocol.io/) (Model Context Protocol) server. Exposes 23 tools for managing workspaces, deployments, environments, projects, profiles, and flows. Works with Claude Code, Claude Desktop, Codex CLI, Cursor, and other MCP-compatible clients.
44

55
## Remote MCP server (recommended)
66

@@ -236,6 +236,42 @@ Delete a deployment.
236236
|-----------|----------|------|-------------|
237237
| `title` | yes | string | Deployment title |
238238

239+
### list_environments
240+
241+
List environments. No parameters.
242+
243+
### get_environment
244+
245+
Get an environment by title.
246+
247+
| Parameter | Required | Type | Description |
248+
|-----------|----------|------|-------------|
249+
| `title` | yes | string | Environment title |
250+
251+
### list_projects
252+
253+
List projects. No parameters.
254+
255+
### get_project
256+
257+
Get a project by title.
258+
259+
| Parameter | Required | Type | Description |
260+
|-----------|----------|------|-------------|
261+
| `title` | yes | string | Project title |
262+
263+
### list_profiles
264+
265+
List available profiles for a workspace, deployment, or project+branch.
266+
267+
| Parameter | Required | Type | Description |
268+
|-----------|----------|------|-------------|
269+
| `workspace` | no | string | Workspace title |
270+
| `deployment` | no | string | Deployment title |
271+
| `uuid` | no | string | UUID (direct override) |
272+
| `project` | no | string | Project name (or UUID) — use with branch |
273+
| `branch` | no | string | Git branch (required with project) |
274+
239275
### list_flows
240276

241277
List flows in a workspace or deployment.

docs/python.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,46 @@ client = Client(
4040

4141
All parameters are keyword-only.
4242

43+
## Environments and projects
44+
45+
### List environments
46+
47+
```python
48+
environments = client.list_environments()
49+
```
50+
51+
### Get an environment by title
52+
53+
```python
54+
env = client.get_environment(title="Production")
55+
```
56+
57+
Returns `dict` with the matching environment. Raises an error if not found or ambiguous.
58+
59+
### List projects
60+
61+
```python
62+
projects = client.list_projects()
63+
```
64+
65+
### Get a project by title
66+
67+
```python
68+
project = client.get_project(title="My Project")
69+
```
70+
71+
Returns `dict` with the matching project. Raises an error if not found or ambiguous.
72+
73+
### List profiles
74+
75+
```python
76+
profiles = client.list_profiles(workspace="My Workspace")
77+
profiles = client.list_profiles(deployment="My Deployment")
78+
profiles = client.list_profiles(project="My Project", branch="main")
79+
```
80+
81+
Returns `list[str]` of profile names. Provide exactly one of workspace/deployment/uuid, or project+branch.
82+
4383
## Manage workspaces and deployments
4484

4585
### Workspaces

src/ascend_tools/ascend-tools-cli/src/cli.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ use std::ffi::OsString;
66

77
use crate::common::OutputMode;
88
use crate::deployment::DeploymentCommands;
9+
use crate::environment::EnvironmentCommands;
910
use crate::flow::FlowCommands;
11+
use crate::profile::ProfileCommands;
12+
use crate::project::ProjectCommands;
1013
use crate::skill::SkillCommands;
1114
use crate::workspace::WorkspaceCommands;
1215

@@ -79,6 +82,33 @@ enum Commands {
7982
#[command(subcommand)]
8083
command: Option<FlowCommands>,
8184
},
85+
/// Manage environments
86+
#[command(long_about = "Manage Ascend environments.\n\n\
87+
Examples:\n \
88+
ascend-tools environment list\n \
89+
ascend-tools environment get Production")]
90+
Environment {
91+
#[command(subcommand)]
92+
command: Option<EnvironmentCommands>,
93+
},
94+
/// Manage projects
95+
#[command(long_about = "Manage Ascend projects.\n\n\
96+
Examples:\n \
97+
ascend-tools project list\n \
98+
ascend-tools project get \"My Project\"")]
99+
Project {
100+
#[command(subcommand)]
101+
command: Option<ProjectCommands>,
102+
},
103+
/// Manage profiles
104+
#[command(long_about = "Manage Ascend profiles.\n\n\
105+
Examples:\n \
106+
ascend-tools profile list --workspace my-ws\n \
107+
ascend-tools profile list --project MyProject --git-branch main")]
108+
Profile {
109+
#[command(subcommand)]
110+
command: Option<ProfileCommands>,
111+
},
82112
/// Start an MCP server
83113
Mcp {
84114
/// Use HTTP transport instead of stdio
@@ -141,6 +171,15 @@ where
141171
Commands::Deployment { command } => {
142172
crate::deployment::handle_deployment(&client, command, &cli.output)
143173
}
174+
Commands::Environment { command } => {
175+
crate::environment::handle_environment(&client, command, &cli.output)
176+
}
177+
Commands::Project { command } => {
178+
crate::project::handle_project(&client, command, &cli.output)
179+
}
180+
Commands::Profile { command } => {
181+
crate::profile::handle_profile(&client, command, &cli.output)
182+
}
144183
Commands::Flow { command } => crate::flow::handle_flow(&client, command, &cli.output),
145184
Commands::Mcp { .. } | Commands::Skill { .. } => unreachable!(),
146185
}
@@ -509,4 +548,81 @@ mod tests {
509548
Some(Commands::Mcp { http: true, .. })
510549
));
511550
}
551+
552+
#[test]
553+
fn test_cli_parses_environment_list() {
554+
let cli = CliParser::parse_from(["ascend-tools", "environment", "list"]);
555+
assert!(matches!(
556+
cli.command,
557+
Some(Commands::Environment {
558+
command: Some(EnvironmentCommands::List)
559+
})
560+
));
561+
}
562+
563+
#[test]
564+
fn test_cli_parses_environment_bare_is_none() {
565+
let cli = CliParser::parse_from(["ascend-tools", "environment"]);
566+
assert!(matches!(
567+
cli.command,
568+
Some(Commands::Environment { command: None })
569+
));
570+
}
571+
572+
#[test]
573+
fn test_cli_parses_project_list() {
574+
let cli = CliParser::parse_from(["ascend-tools", "project", "list"]);
575+
assert!(matches!(
576+
cli.command,
577+
Some(Commands::Project {
578+
command: Some(ProjectCommands::List)
579+
})
580+
));
581+
}
582+
583+
#[test]
584+
fn test_cli_parses_profile_list_with_workspace() {
585+
let cli = CliParser::parse_from(["ascend-tools", "profile", "list", "--workspace", "Cody"]);
586+
match cli.command {
587+
Some(Commands::Profile {
588+
command:
589+
Some(ProfileCommands::List {
590+
workspace,
591+
deployment,
592+
project,
593+
..
594+
}),
595+
}) => {
596+
assert_eq!(workspace.as_deref(), Some("Cody"));
597+
assert!(deployment.is_none());
598+
assert!(project.is_none());
599+
}
600+
_ => panic!("expected Profile List command"),
601+
}
602+
}
603+
604+
#[test]
605+
fn test_cli_parses_profile_list_with_project_and_branch() {
606+
let cli = CliParser::parse_from([
607+
"ascend-tools",
608+
"profile",
609+
"list",
610+
"--project",
611+
"MyProject",
612+
"--git-branch",
613+
"main",
614+
]);
615+
match cli.command {
616+
Some(Commands::Profile {
617+
command:
618+
Some(ProfileCommands::List {
619+
project, branch, ..
620+
}),
621+
}) => {
622+
assert_eq!(project.as_deref(), Some("MyProject"));
623+
assert_eq!(branch.as_deref(), Some("main"));
624+
}
625+
_ => panic!("expected Profile List command"),
626+
}
627+
}
512628
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
use anyhow::Result;
2+
use ascend_tools::client::AscendClient;
3+
use clap::Subcommand;
4+
5+
use crate::common::{OutputMode, print_json, print_subcommand_help, print_table};
6+
7+
#[derive(Subcommand)]
8+
pub(crate) enum EnvironmentCommands {
9+
/// List environments
10+
List,
11+
/// Get an environment by title
12+
#[command(arg_required_else_help = true)]
13+
Get {
14+
/// Environment title
15+
title: String,
16+
},
17+
}
18+
19+
pub(crate) fn handle_environment(
20+
client: &AscendClient,
21+
cmd: Option<EnvironmentCommands>,
22+
output: &OutputMode,
23+
) -> Result<()> {
24+
let Some(cmd) = cmd else {
25+
return print_subcommand_help("environment");
26+
};
27+
match cmd {
28+
EnvironmentCommands::List => {
29+
let envs = client.list_environments()?;
30+
match output {
31+
OutputMode::Json => print_json(&envs)?,
32+
OutputMode::Text => {
33+
let rows: Vec<Vec<String>> = envs
34+
.iter()
35+
.map(|e| vec![e.title.clone(), e.uuid.clone()])
36+
.collect();
37+
print_table(&["TITLE", "UUID"], &rows);
38+
}
39+
}
40+
Ok(())
41+
}
42+
EnvironmentCommands::Get { title } => {
43+
let env = client.get_environment(&title)?;
44+
match output {
45+
OutputMode::Json => print_json(&env)?,
46+
OutputMode::Text => {
47+
println!("Title: {}", env.title);
48+
println!("UUID: {}", env.uuid);
49+
println!("ID: {}", env.id);
50+
}
51+
}
52+
Ok(())
53+
}
54+
}
55+
}

src/ascend_tools/ascend-tools-cli/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
mod cli;
44
mod common;
55
mod deployment;
6+
mod environment;
67
mod flow;
8+
mod profile;
9+
mod project;
710
mod skill;
811
mod workspace;
912

0 commit comments

Comments
 (0)