arcade-mcp/toolkits/confluence/arcade_confluence/tools/space.py
Sam Partee b6b4cd0a4c
🏗️ Restructure: Multi-Package Architecture + uv Migration (#412)
### Overview
Major restructuring from monolithic `arcade-ai` package to modular
library architecture with standardized uv-based dependency management.

![arcade-ai Monorepo
(2)](https://github.com/user-attachments/assets/25f102b0-bb87-4a04-9701-d227d05664b1)

### New Package Structure
- **`arcade-tdk`** - Lightweight toolkit development kit (core
decorators, auth)
- **`arcade-core`** - Core execution engine and catalog functionality  
- **`arcade-serve`** - FastAPI/MCP server components
- **`arcade-ai`** - Meta package that includes CLI functionality.
Optionally include evals via the `evals` extra. Optionally include all
packages via the `all` extra.

### Key Benefits
- **Lighter Dependencies**: Toolkits now depend only on `arcade-tdk` (~2
deps) vs full `arcade-ai` (~30+ deps)
- **Faster Builds**: uv provides 10-100x faster dependency resolution
and installation
- **Better Modularity**: Clear separation of concerns, consumers import
only what they need
- **Standard Tooling**: Eliminates custom poetry scripts, uses standard
Python packaging

### Migration Impact
- All 20 toolkits converted from poetry → uv with `arcade-tdk`
dependencies plus `arcade-ai[evals]` and `arcade-serve` dev
dependencies. When developing locally, devs should install toolkits via
`make install-local`.
- Modern Python 3.10+ type hints throughout
- Standardized build system with hatchling backend
- Enhanced Makefile with robust toolkit management commands
- Removed `arcade dev` CLI command
- Reduce the number of files created by `arcade new` and add an option
to not generate a tests and evals folder.

This foundation enables faster development cycles and cleaner dependency
chains for the growing toolkit ecosystem.

### Todo After this PR is merged
- [ ] Post-merge workflow(s) (release & publish containers, etc)
- [ ] Release order plan. @EricGustin suggests releasing in the
following order:
    1. `arcade-core` version 0.1.0
    2. `arcade-serve` version 0.1.0 and `arcade-tdk` version 0.1.0
    3. `arcade-ai` version 2.0.0
4. Patch release for all toolkits (all changes in toolkits are internal
refactors)
- [ ] [Update docs](https://github.com/ArcadeAI/docs/pull/318)

---------

Co-authored-by: Eric Gustin <eric@arcade.dev>
Co-authored-by: Eric Gustin <34000337+EricGustin@users.noreply.github.com>
2025-06-11 16:48:17 -07:00

95 lines
3.2 KiB
Python

import re
from typing import Annotated
from arcade_tdk import ToolContext, tool
from arcade_tdk.auth import Atlassian
from arcade_confluence.client import ConfluenceClientV2
from arcade_confluence.utils import remove_none_values
@tool(
requires_auth=Atlassian(
scopes=["read:space:confluence"],
)
)
async def get_space(
context: ToolContext,
space_identifier: Annotated[
str, "Can be a space's ID or key. Numerical keys are NOT supported"
],
) -> Annotated[dict, "The space"]:
"""Get the details of a space by its ID or key."""
client = ConfluenceClientV2(context.get_auth_token_or_empty())
if space_identifier.isdigit():
return await client.get_space_by_id(space_identifier)
else:
return await client.get_space_by_key(space_identifier)
@tool(
requires_auth=Atlassian(
scopes=["read:space:confluence"],
)
)
async def list_spaces(
context: ToolContext,
limit: Annotated[
int, "The maximum number of spaces to return. Defaults to 25. Max is 250"
] = 25,
pagination_token: Annotated[
str | None, "The pagination token to use for the next page of results"
] = None,
) -> Annotated[dict, "The spaces"]:
"""List all spaces sorted by name in ascending order."""
client = ConfluenceClientV2(context.get_auth_token_or_empty())
params = {"limit": max(1, min(limit, 250)), "sort": "name", "cursor": pagination_token}
params = remove_none_values(params)
spaces = await client.get("spaces", params=params)
return client.transform_get_spaces_response(spaces)
@tool(
requires_auth=Atlassian(
scopes=[
"read:page:confluence", # needed for getting the space's root pages
"read:space:confluence", # needed for when a space key is provided
"read:hierarchical-content:confluence", # needed for getting the descendents of a page
],
)
)
async def get_space_hierarchy(
context: ToolContext,
space_identifier: Annotated[
str, "Can be a space's ID or key. Numerical keys are NOT supported"
],
) -> Annotated[dict, "The space hierarchy"]:
"""Retrieve the full hierarchical structure of a Confluence space as a tree structure
Only structural metadata is returned (not content).
The response is akin to the sidebar in the Confluence UI.
Includes all pages, folders, whiteboards, databases,
smart links, etc. organized by parent-child relationships.
"""
client = ConfluenceClientV2(context.get_auth_token_or_empty())
space = await client.get_space(space_identifier)
tree = client.create_space_tree(space)
# Get root pages
root_pages = await client.get_root_pages_in_space(space["space"]["id"])
tree["children"] = client.convert_root_pages_to_tree_nodes(root_pages["pages"])
if not tree["children"]:
return {}
# Extract base URL for children URLs. The base URL is the space's URL.
root_page_url = tree["url"]
match = re.match(r"(.*?/spaces/[^/]+)", root_page_url)
children_base_url = match.group(1) if match else ""
# Get and descendants for each root page
await client.process_page_descendants(tree["children"], children_base_url)
return tree