feat(mcp): add list_cloud_workspaces and describe_cloud_organization tools#883
Conversation
…tools Add two new MCP tools for organization-scoped workspace discovery: - list_cloud_workspaces: Lists workspaces within a specific organization, requiring either organization_id or organization_name (exact match). Supports name_contains filtering and max_items_limit. - describe_cloud_organization: Gets details about a specific organization, useful for looking up org ID from name or vice versa. These tools enable safe workspace discovery by requiring explicit organization context, preventing accidental access to workspaces across organizations. Also adds supporting api_util functions: - list_organizations_for_user: Lists orgs accessible to current user - list_workspaces_in_organization: Lists workspaces in a specific org Co-Authored-By: AJ Steers <aj@airbyte.io>
Original prompt from AJ Steers |
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
👋 Greetings, Airbyte Team Member!Here are some helpful tips and reminders for your convenience. Testing This PyAirbyte VersionYou can test this version of PyAirbyte using the following: # Run PyAirbyte CLI from this branch:
uvx --from 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1764630057-org-workspace-mcp-tools' pyairbyte --help
# Install PyAirbyte from this branch for development:
pip install 'git+https://github.com/airbytehq/PyAirbyte.git@devin/1764630057-org-workspace-mcp-tools'Helpful ResourcesPR Slash CommandsAirbyte Maintainers can execute the following slash commands on your PR:
Community SupportQuestions? Join the #pyairbyte channel in our Slack workspace. |
📝 WalkthroughWalkthroughAdds two API utilities to list organizations and page workspaces, and MCP cloud tools/models to resolve organizations and list/describe cloud workspaces by ID or exact name. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant MCP as MCP Client
participant Tool as describe_cloud_organization()
participant Resolver as _resolve_organization_id()
participant APIUtil as api_util.list_organizations_for_user()
participant API as Airbyte API
User->>MCP: request org details (id or name)
MCP->>Tool: describe_cloud_organization(params)
Tool->>Resolver: resolve by id or exact name
Resolver->>APIUtil: list_organizations_for_user(api_root, client_id, client_secret)
APIUtil->>API: GET /api/v1/organizations
API-->>APIUtil: organizations list
APIUtil-->>Resolver: organizations
Resolver-->>Tool: resolved organization_id
Tool->>Tool: build CloudOrganizationResult
Tool-->>MCP: CloudOrganizationResult
MCP-->>User: {id, name, email}
sequenceDiagram
actor User
participant MCP as MCP Client
participant Tool as list_cloud_workspaces()
participant Resolver as _resolve_organization_id()
participant APIUtil as api_util.list_workspaces_in_organization()
participant API as Airbyte Config API
User->>MCP: request workspaces for org (id or name)
MCP->>Tool: list_cloud_workspaces(params)
Tool->>Resolver: resolve to organization_id
Resolver->>APIUtil: list_workspaces_in_organization(organization_id,...)
loop pagination
APIUtil->>API: POST /api/v1/config/workspaces (offset/limit/nameContains)
API-->>APIUtil: workspace page
APIUtil-->>Tool: workspace items
end
Tool->>Tool: map to CloudWorkspaceResult list
Tool-->>MCP: [CloudWorkspaceResult]
MCP-->>User: [{id, name, organization_id}, ...]
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Suggested reviewers
Could you take a look at the pagination and resolution edge cases above, wdyt? Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
airbyte/mcp/cloud_ops.py (3)
925-933: Consider restructuring error messages to decouple dynamic values.Based on learnings, error messages in PyAirbyte follow a structlog-inspired design where dynamic values (especially potential PII like organization names) are provided via the
contextparameter rather than embedded in the message string. This helps avoid including PII in telemetry.Would you consider restructuring these errors? For example:
if not matching_orgs: raise AirbyteMissingResourceError( resource_type="organization", context={ "organization_name": organization_name, - "message": f"No organization found with exact name '{organization_name}' " - "for the current user.", }, + message="No organization found with exact name for the current user.", ) if len(matching_orgs) > 1: raise PyAirbyteInputError( - message=f"Multiple organizations found with name '{organization_name}'. " - "Please use 'organization_id' instead to specify the exact organization." + message="Multiple organizations found with the specified name. " + "Please use 'organization_id' instead to specify the exact organization.", + context={"organization_name": organization_name}, )Based on learnings, this pattern helps prevent PII leakage in telemetry. Wdyt?
Also applies to: 935-939
1005-1012: Consider whether empty string defaults are appropriate here.Using
.get()with empty string defaults ("") means that if the API response is missingworkspaceId,name, ororganizationIdfields, we'll create result objects with empty strings rather than failing fast or usingNone. This could mask data quality issues.Would it be better to either:
- Let it fail if required fields are missing (don't use defaults)
- Use explicit validation to ensure required fields exist
- Use
Noneas the default if these fields can legitimately be absentWdyt? If these fields are always expected from the API, option 1 might be clearest.
1058-1090: Consider reusing_resolve_organization_idto avoid duplication.This function duplicates the organization lookup logic from
_resolve_organization_id(lines 878-942). The only difference is that this function needs to return the full organization object, while_resolve_organization_idreturns just the ID.What if you refactored to reuse the helper? For example:
@mcp_tool(...) def describe_cloud_organization( *, organization_id: ..., organization_name: ..., ) -> CloudOrganizationResult: """Get details about a specific organization.""" api_root = resolve_cloud_api_url() client_id = SecretString(resolve_cloud_client_id()) client_secret = SecretString(resolve_cloud_client_secret()) # Get all organizations orgs = api_util.list_organizations_for_user( api_root=api_root, client_id=client_id, client_secret=client_secret, ) # Find the matching one if organization_id: matching_orgs = [org for org in orgs if org.organization_id == organization_id] search_key = "organization_id" search_value = organization_id elif organization_name: matching_orgs = [org for org in orgs if org.organization_name == organization_name] search_key = "organization_name" search_value = organization_name else: raise PyAirbyteInputError( message="Either 'organization_id' or 'organization_name' must be provided." ) if not matching_orgs: raise AirbyteMissingResourceError( resource_type="organization", context={ search_key: search_value, }, message="No organization found for the current user.", ) if len(matching_orgs) > 1: raise PyAirbyteInputError( message="Multiple organizations found. Please use 'organization_id' instead.", context={search_key: search_value}, ) org = matching_orgs[0] return CloudOrganizationResult( id=org.organization_id, name=org.organization_name, email=org.email, )This also addresses the error message structure concerns by moving dynamic values to context. Wdyt?
airbyte/_util/api_util.py (1)
1646-1655: Consider adding validation for the API response structure.The function assumes
json_result.get("workspaces", [])will work, but if the API response structure changes or errors occur, this might mask issues. The function relies entirely on_make_config_api_requestto raise errors.Would it be worth adding a quick validation check? Something like:
json_result = _make_config_api_request( path="/workspaces/list_by_organization_id", json=payload, api_root=api_root, client_id=client_id, client_secret=client_secret, ) if "workspaces" not in json_result: raise AirbyteError( message="Unexpected API response structure: missing 'workspaces' field", context={ "organization_id": organization_id, "response_keys": list(json_result.keys()), }, ) workspaces = json_result["workspaces"]This would make debugging easier if the API changes. That said, it's a fairly defensive approach - the current implementation is reasonable too. Wdyt?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
airbyte/_util/api_util.py(1 hunks)airbyte/mcp/cloud_ops.py(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-06T23:44:31.534Z
Learnt from: aaronsteers
Repo: airbytehq/PyAirbyte PR: 411
File: airbyte/cli.py:111-160
Timestamp: 2024-10-06T23:44:31.534Z
Learning: In PyAirbyte, error messages in functions like `_resolve_source_job` in `airbyte/cli.py` are designed to decouple the message text from dynamic values, following a structlog-inspired design. Dynamic values are provided via parameters like `input_value`. This approach helps avoid including PII in the message strings, which may be used in telemetry.
Applied to files:
airbyte/mcp/cloud_ops.py
🧬 Code graph analysis (1)
airbyte/_util/api_util.py (2)
airbyte/secrets/base.py (1)
SecretString(38-143)airbyte/exceptions.py (1)
AirbyteError(432-447)
🪛 GitHub Actions: Run Linters
airbyte/mcp/cloud_ops.py
[warning] 594-594: Redundant cast: str is the same type as str [redundant-cast]
[warning] 643-643: Redundant cast: str is the same type as str [redundant-cast]
[error] 918-918: Argument str is not assignable to parameter client_id with type SecretString in function airbyte._util.api_util.list_organizations_for_user
[error] 919-919: Argument str is not assignable to parameter client_secret with type SecretString in function airbyte._util.api_util.list_organizations_for_user
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Pytest (All, Python 3.11, Ubuntu)
- GitHub Check: Pytest (All, Python 3.10, Ubuntu)
- GitHub Check: Pytest (No Creds)
- GitHub Check: Pytest (All, Python 3.11, Windows)
- GitHub Check: Pytest (All, Python 3.10, Windows)
- GitHub Check: Pytest (Fast)
🔇 Additional comments (2)
airbyte/_util/api_util.py (2)
1570-1605: LGTM! Function signature correctly usesSecretStringtypes.The function signature properly expects
SecretStringforclient_idandclient_secret(lines 1573-1574), which is the correct type. The type errors flagged by the pipeline are in the calling code (cloud_ops.py), not here. The error handling also looks appropriate.
1607-1668: Nice pagination implementation!The pagination logic correctly handles both
max_items_limit(lines 1658-1660) and the end-of-results condition (lines 1662-1664). The function properly builds up results incrementally and stops when appropriate.
Update _resolve_organization_id helper function to use SecretString type hints for client_id and client_secret parameters, matching the return types of resolve_cloud_client_id() and resolve_cloud_client_secret(). This fixes pyrefly type check errors where str was being passed to functions expecting SecretString. Co-Authored-By: AJ Steers <aj@airbyte.io>
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (3)
airbyte/mcp/cloud_ops.py (3)
917-921: Type mismatch issue may still be present.Previous review comments flagged that
resolve_cloud_client_id()andresolve_cloud_client_secret()returnstrrather thanSecretString, but this function expectsSecretStringparameters. Based on the relevant code snippets, these functions should returnSecretString, but if the pipeline is still failing with type errors, you may need to explicitly wrap the return values.The same issue applies to the callers at lines 989-995 and 1052-1056.
Could you verify whether the pipeline failures are resolved? If not, you might need to wrap the credentials like:
orgs = api_util.list_organizations_for_user( api_root=api_root, - client_id=client_id, - client_secret=client_secret, + client_id=SecretString(client_id) if isinstance(client_id, str) else client_id, + client_secret=SecretString(client_secret) if isinstance(client_secret, str) else client_secret, )What do you think?
989-1004: Type mismatch propagates here as well.Similar to the issue in
_resolve_organization_id, these calls pass credentials that may needSecretStringwrapping. This duplicates the concerns from the previous review.The same wrapping approach would apply here. Could you confirm if the pipeline failures are resolved?
1052-1056: Type mismatch here as well.Same issue as the previous calls: credentials may need
SecretStringwrapping per the previous review comments.The same fix would apply here, wdyt?
🧹 Nitpick comments (2)
airbyte/mcp/cloud_ops.py (2)
904-940: Consider structlog-inspired error message design for consistency.Based on learnings from the codebase, PyAirbyte error messages typically decouple the message text from dynamic values to avoid including potential PII in telemetry. Organization names and IDs might be considered sensitive.
Would you consider refactoring these error messages to use the
contextparameter for dynamic values? For example:raise PyAirbyteInputError( message="Either 'organization_id' or 'organization_name' must be provided.", context={ "organization_id": organization_id, "organization_name": organization_name, }, )This pattern is used elsewhere in the codebase (see lines 1064-1069 and 1076-1081 in
describe_cloud_organization), wdyt?Based on learnings, PyAirbyte prefers decoupling message text from dynamic values to avoid PII in telemetry strings.
1059-1091: Consider reducing code duplication with_resolve_organization_id.The logic here for resolving an organization by ID or name is very similar to what
_resolve_organization_iddoes (lines 879-943). The main difference is that this function returns the full organization object while_resolve_organization_idreturns just the ID.Would you consider refactoring to call
_resolve_organization_idfirst to get the ID, then look up the full org from the list? This would reduce duplication and make the validation logic consistent. For example:# Get all organizations for the user orgs = api_util.list_organizations_for_user( api_root=api_root, client_id=client_id, client_secret=client_secret, ) # Resolve to ID using shared helper resolved_org_id = _resolve_organization_id( organization_id=organization_id, organization_name=organization_name, api_root=api_root, client_id=client_id, client_secret=client_secret, ) # Find the full org object org = next((o for o in orgs if o.organization_id == resolved_org_id), None) if not org: raise AirbyteMissingResourceError(...)This is just a suggested refactor to improve maintainability, wdyt?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
airbyte/mcp/cloud_ops.py(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-06T23:44:31.534Z
Learnt from: aaronsteers
Repo: airbytehq/PyAirbyte PR: 411
File: airbyte/cli.py:111-160
Timestamp: 2024-10-06T23:44:31.534Z
Learning: In PyAirbyte, error messages in functions like `_resolve_source_job` in `airbyte/cli.py` are designed to decouple the message text from dynamic values, following a structlog-inspired design. Dynamic values are provided via parameters like `input_value`. This approach helps avoid including PII in the message strings, which may be used in telemetry.
Applied to files:
airbyte/mcp/cloud_ops.py
🧬 Code graph analysis (1)
airbyte/mcp/cloud_ops.py (4)
airbyte/exceptions.py (2)
AirbyteMissingResourceError(505-509)PyAirbyteInputError(201-210)airbyte/secrets/base.py (1)
SecretString(38-143)airbyte/_util/api_util.py (2)
list_organizations_for_user(1570-1604)list_workspaces_in_organization(1607-1668)airbyte/cloud/auth.py (3)
resolve_cloud_api_url(25-33)resolve_cloud_client_id(17-22)resolve_cloud_client_secret(9-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Pytest (All, Python 3.11, Ubuntu)
- GitHub Check: Pytest (All, Python 3.11, Windows)
- GitHub Check: Pytest (All, Python 3.10, Windows)
- GitHub Check: Pytest (Fast)
- GitHub Check: Pytest (All, Python 3.10, Ubuntu)
- GitHub Check: Pytest (No Creds)
🔇 Additional comments (3)
airbyte/mcp/cloud_ops.py (3)
11-11: LGTM on the new imports!The addition of
api_util, the exception types, andSecretStringare all appropriate for the new organization/workspace discovery features.Also applies to: 21-21, 29-29
126-146: LGTM on the new result models!
CloudOrganizationResultandCloudWorkspaceResultare well-structured with clear documentation. They follow the existing patterns in the codebase nicely.
1006-1013: Should missing workspace fields fail gracefully or raise errors?Using
.get()with empty string defaults means that if the API returns a workspace withoutworkspaceId,name, ororganizationId, you'll create a result object with empty strings rather than failing. Is this the intended behavior?If these fields are required, consider either:
- Using direct dictionary access (e.g.,
ws["workspaceId"]) to fail fast if the field is missing- Adding validation after construction to ensure no empty strings
What's your preference here? Should we validate the API response more strictly, wdyt?
…cloud_organization - Create _resolve_organization() that returns full OrganizationResponse object - Simplify _resolve_organization_id() to call _resolve_organization and extract ID - Refactor describe_cloud_organization() to use _resolve_organization (addresses AJ's comment) - Refactor list_workspaces_in_organization() to use explicit has_more_pages flag instead of while True Co-Authored-By: AJ Steers <aj@airbyte.io>
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (5)
airbyte/_util/api_util.py (1)
1607-1669: Workspace pagination logic looks good; consider reusing the bearer token for many-page orgs?The paging loop,
name_containswiring, andmax_items_limitbehavior all look solid and address the earlier “nowhile True” feedback nicely. One minor thing: each call to_make_config_api_requestre-does the bearer-token exchange, which is fine for typical “few-page” orgs but could add noticeable overhead if an admin ever has hundreds of workspaces.Would you consider a follow-up that lets
_make_config_api_requestoptionally accept a pre-fetched bearer token (so a single token can be reused across pages in this loop), or do you prefer to keep the simpler API and only revisit if this becomes a hot path, wdyt?airbyte/mcp/cloud_ops.py (4)
126-146: New CloudOrganizationResult / CloudWorkspaceResult models look clean; should org email allowNone?The shapes for
CloudOrganizationResultandCloudWorkspaceResultline up nicely with the underlying API fields and give MCP callers a simple, stable contract. One small question: depending on the Airbyte API schema,OrganizationResponse.emailmight be nullable in some edge cases; if that’s possible, would it be safer to declareemail: str | NoneonCloudOrganizationResult(or default to"") to avoid surprises when pydantic validates aNone, wdyt?
879-953: Org resolution logic is solid; consider aligning error messages with the “no PII in message text” convention.The overall flow here is great: you enforce “either id or name, but not both”, resolve against the current user’s orgs via
list_organizations_for_user, and handle both not-found and ambiguous-name cases with the right exception types.Given the prior guidance about keeping error messages static and putting dynamic values into context fields, would you consider:
- For the “multiple organizations found” branch, using a static
message(e.g."Multiple organizations found with this name. Please provide 'organization_id' instead.") and passingorganization_nameincontextorinput_valueinstead of interpolating it directly into themessagestring?- Similarly, for the not-found-by-name/ID cases, keeping any user- or org-specific identifiers out of the human-readable text and relying on
contextkeys like"organization_id"/"organization_name"?That would keep this consistent with the structlog-style pattern you’ve used elsewhere and avoid PII in top-level messages, wdyt?
985-1047: list_cloud_workspaces correctly enforces org scoping; would adding workspace URL/slug be useful to callers?This tool seems to hit all the design goals:
- It requires explicit organization context (ID or exact, case-sensitive name) and delegates validation to
_resolve_organization_id.- It uses the new
list_workspaces_in_organizationhelper so the Config API does the heavy lifting, including server-sidename_containsfiltering.- It returns a minimal, well-typed
CloudWorkspaceResultlist that’s easy for agents to consume.One question: since the underlying payload includes fields like
slugand (presumably) a way to construct the workspace web URL, would it be worth exposing eitherslugand/or aurlfield onCloudWorkspaceResultto make it easier for tools/agents to surface links without an extra lookup, or are you intentionally keeping this result schema minimal for now, wdyt?
1056-1096: describe_cloud_organization nicely reuses the resolver; just double-checkUsing
_resolve_organizationhere keeps the “exact name vs ID” behavior, ambiguity checks, and not-found handling perfectly in sync withlist_cloud_workspaces, which is great for predictability. Returning a compactCloudOrganizationResultwithid,name, andSimilar to the earlier model comment: if
OrganizationResponse.emailcan ever beNone, should this tool either (a) typestr | Noneor (b) normalizeNoneto""before constructingCloudOrganizationResult, to avoid subtle validation/runtime issues, wdyt?
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
airbyte/_util/api_util.py(1 hunks)airbyte/mcp/cloud_ops.py(4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-06T23:44:31.534Z
Learnt from: aaronsteers
Repo: airbytehq/PyAirbyte PR: 411
File: airbyte/cli.py:111-160
Timestamp: 2024-10-06T23:44:31.534Z
Learning: In PyAirbyte, error messages in functions like `_resolve_source_job` in `airbyte/cli.py` are designed to decouple the message text from dynamic values, following a structlog-inspired design. Dynamic values are provided via parameters like `input_value`. This approach helps avoid including PII in the message strings, which may be used in telemetry.
Applied to files:
airbyte/mcp/cloud_ops.py
🧬 Code graph analysis (2)
airbyte/_util/api_util.py (2)
airbyte/secrets/base.py (1)
SecretString(38-143)airbyte/exceptions.py (1)
AirbyteError(432-447)
airbyte/mcp/cloud_ops.py (4)
airbyte/exceptions.py (2)
AirbyteMissingResourceError(505-509)PyAirbyteInputError(201-210)airbyte/secrets/base.py (1)
SecretString(38-143)airbyte/_util/api_util.py (2)
list_organizations_for_user(1570-1604)list_workspaces_in_organization(1607-1669)airbyte/cloud/auth.py (3)
resolve_cloud_api_url(25-33)resolve_cloud_client_id(17-22)resolve_cloud_client_secret(9-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Pytest (All, Python 3.11, Windows)
- GitHub Check: Pytest (All, Python 3.10, Ubuntu)
- GitHub Check: Pytest (All, Python 3.11, Ubuntu)
- GitHub Check: Pytest (All, Python 3.10, Windows)
- GitHub Check: Pytest (Fast)
- GitHub Check: Pytest (No Creds)
🔇 Additional comments (2)
airbyte/_util/api_util.py (1)
1570-1604: Org listing helper looks correct and aligned with the public API.The wrapper cleanly delegates to
organizations.list_organizations_for_user(), checks for a successful response, and raises a richAirbyteErrorotherwise; this feels consistent with the rest ofapi_util’s patterns, and not adding pagination here matches the endpoint’s current contract. Looks good to me as‑is.airbyte/mcp/cloud_ops.py (1)
956-975: Convenience wrapper _resolve_organization_id is a nice narrow helper.Wrapping
_resolve_organizationto return just the ID keeps the public tools focused on IDs without duplicating resolution logic, and centralizes all ambiguity/not-found behavior in one place. This reads well and matches the PR’s intent.
PyTest Results (Full)388 tests ±0 371 ✅ - 1 25m 53s ⏱️ + 1m 8s For more details on these failures, see this check. Results for commit 0e133e4. ± Comparison against base commit ef69ae0. ♻️ This comment has been updated with latest results. |
…ge size - Add explicit check for empty results before extending result list - Exit early if no workspaces returned (handles out-of-range offset case) - Keep existing check for partial page to avoid unnecessary API calls Co-Authored-By: AJ Steers <aj@airbyte.io>
2d495ba
into
main
feat(mcp): add list_cloud_workspaces and describe_cloud_organization tools
Summary
Adds two new MCP tools for organization-scoped workspace discovery, addressing the safety concern that LLM agents could accidentally access workspaces across organizations when using instance admin credentials.
New MCP Tools:
list_cloud_workspaces: Lists workspaces within a specific organization, requiring eitherorganization_idororganization_name(exact match). Supportsname_containsfiltering andmax_items_limit.describe_cloud_organization: Gets details about a specific organization, useful for looking up org ID from name or vice versa.New api_util functions:
list_organizations_for_user: Uses public APIGET /organizationsto list orgs accessible to current userlist_workspaces_in_organization: Uses internal Config APIPOST /v1/workspaces/list_by_organization_idwith server-side name filtering and paginationDesign decisions:
list_organizationstool is exposed to prevent agents from enumerating orgs and picking "close matches"Review & Testing Checklist for Human
/v1/workspaces/list_by_organization_id). Confirm this endpoint exists and the request/response format matches the implementation.workspaceId,name,organizationIdfrom the API response via.get(). Verify these field names match the actual API response.Recommended test plan:
describe_cloud_organizationwith a known org name → verify it returns correct org IDlist_cloud_workspaceswith that org ID → verify it returns expected workspaceslist_cloud_workspaceswithname_containsfilter → verify filtering worksNotes
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.