About Forma MCP
Forma MCP is an MCP server published by lfcastel in the AI category: a Forma MCP server for your LLM.
The repository has 5 stars and 1 forks, with the last commit 5 days ago. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/lfcastel/Forma-MCPThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Forma MCP
Powered by Claude · Grounded in docs
Security checks
Static checks derived from the repository 3 days ago. Heuristics on the source, not an audit.
- ✓README presentThe repository ships documentation.
- ✓License declaredUsable under a stated license.
- ✓Tests presentA test suite exists in the repository.
- !Dependencies pinnedNo lockfile. Installs can pull different code over time.
- ✓No dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
1 check has not been run yet.
Releases
README
APS MCP Server for Claude
A Model Context Protocol (MCP) stdio server that gives Claude Code access to your Autodesk Construction Cloud (ACC) environment via the Autodesk Platform Services (APS) API — the REST API that underlies ACC and Forma. Manage users, audit permissions, and explore projects through plain-language conversation.
Prerequisites
- uv, which provides
uvxto install and run the server (it fetches a suitable Python 3.11+ itself). No admin rights needed:- Windows:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" - macOS / Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
- Windows:
- Claude Code installed
- An APS app with a Client ID and Client Secret (create one here)
- An Autodesk account with Account Admin access to your ACC hub
Authentication
The server uses two OAuth flows, both backed by the same APS app credentials.
2-legged OAuth (client credentials)
Used for account-level operations that don't require a user context: listing all account users, looking up companies, resolving user lists for bulk operations.
- How it works: The server exchanges your Client ID + Secret directly for an access token. No browser login required. The token is cached in memory per process.
- Required scope:
account:read - When it's used:
list_account_users,list_project_companies,list_account_companies, and the hub-directory write tools (bulk_add_hub_users,bulk_add_hub_companies,deactivate_hub_users,deactivate_hub_companies)
3-legged OAuth (user context)
Used for all project, folder, and permission operations — the API acts on behalf of you, the logged-in Autodesk user.
- How it works: On first use, a browser window opens automatically. You log in with your Autodesk account and grant consent. The token is cached in
~/.forma-mcp/tokens.json(see Data directory) and refreshed automatically. You only need to re-authorize after ~14 days of inactivity. - Required scopes:
data:read,account:read,account:write(for bulk user management tools) - When it's used: All navigation, folder, and permission tools; all bulk user write operations
Setting up your APS app
1. Create the app
- Go to aps.autodesk.com/myapps and click Create App.
- Under API access, enable:
- Data Management API
- ACC Account Admin API
- BIM 360 API
- Add
http://localhost:8080/oauth/callbackas an allowed Callback URL. - Copy your Client ID and Client Secret.
2. Register the app as a Custom Integration in your ACC hub
Before the app can access your hub's data, an Account Admin must register it:
- Open ACC and go to Account Admin → Custom Integrations.
- Click Add Custom Integration.
- Enter your Client ID and follow the prompts to link the app to your hub.
Without this step, API calls will return 403 errors even with valid credentials.
Installation & configuration in Claude Code
No clone or virtualenv needed: uvx installs the server straight from this repo, pinned to a release tag, and runs it in an isolated environment. Register it with Claude Code in one command:
claude mcp add aps --scope user \
-e APS_CLIENT_ID=your_client_id \
-e APS_CLIENT_SECRET=your_client_secret \
-- uvx --from git+https://github.com/lfcastel/Forma-MCP@v0.2.0 forma-mcp
Or add it by hand to ~/.claude.json:
"mcpServers": {
"aps": {
"type": "stdio",
"command": "uvx",
"args": ["--from", "git+https://github.com/lfcastel/Forma-MCP@v0.2.0", "forma-mcp"],
"env": {
"APS_CLIENT_ID": "your_client_id",
"APS_CLIENT_SECRET": "your_client_secret"
}
}
}
Restart Claude Code (or /mcp → reconnect), then run claude mcp list. aps should show as connected. The first tool call opens a browser for the Autodesk login.
Upgrading: change the tag (@v0.2.0 → the new release) in the command or config and reconnect. Releases are listed on the repo's Tags page. To try an unreleased branch, use @<branch-name> instead of a tag.
Always the latest (unpinned): drop the @<tag> to track main — every merged PR, including changes not yet tagged as a release. There is no @latest ref; omitting the tag is how you get it. Add --refresh so uv checks for a newer main each time the server starts (otherwise it may keep reusing the cached build):
claude mcp add aps --scope user \
-e APS_CLIENT_ID=your_client_id \
-e APS_CLIENT_SECRET=your_client_secret \
-- uvx --refresh --from git+https://github.com/lfcastel/Forma-MCP forma-mcp
or in ~/.claude.json: "args": ["--refresh", "--from", "git+https://github.com/lfcastel/Forma-MCP", "forma-mcp"]. Pin a tag instead when you want everyone on the same reproducible build.
mcpis pinned to the 1.x line (mcp>=1.0.0,<2). The MCP Python SDK 2.0 removed the low-levelServer.list_tools()/Server.call_tool()decorator API this server is built on, so a 2.x install fails at import with'Server' object has no attribute 'list_tools'. Don't bump the pin without porting the server.
Optional environment variables
Add these to the -e flags or the env block as needed.
FORMA_MCP_HOME (optional, default ~/.forma-mcp) is the per-user data directory; see Data directory.
APS_RATE_HEADROOM (optional, default 0.8) is the fraction of each published Data Management rate limit the server allows itself — lower it if the same APS client ID is shared by several people/sessions; APS_RATE_LIMITING=0 switches the client-side pacing off entirely (see Known Limitations).
APS_ROLE_CACHE (optional) points at a role_id_cache.json mapping every hub role name → UUID, so you can assign a role by name even when no member holds it yet (an unused role is otherwise invisible — ACC has no project-roles catalog API). The same file is read in reverse (UUID → name) to label role columns in export_permission_matrix when the permissions endpoint returns a role with no inline name. Without the env var the server looks for role_id_cache.json in the data directory (~/.forma-mcp/), then next to aps_mcp.py (repo clone), then under ~/.claude/skills/aps/. Build/refresh the file from a Data Connector export (create_role_data_export → get_data_connector_requests). It contains real role UUIDs, so it's gitignored — keep it local.
Data directory
The server keeps its per-user state in ~/.forma-mcp/ (override with FORMA_MCP_HOME), not next to the code, because uvx can rebuild its cached environment at any time:
| File | Contents |
|---|---|
tokens.json |
Cached 3-legged OAuth token (delete it to force a fresh login) |
audit_logs/ |
Timestamped audit CSVs written by the bulk tools when run live |
role_id_cache.json |
Optional role-name cache (see APS_ROLE_CACHE above) |
Coming from a repo-clone install? An existing tokens.json next to aps_mcp.py is still read, and the next token refresh moves it into ~/.forma-mcp/.
Running from a clone (development)
git clone https://github.com/lfcastel/Forma-MCP.git
cd Forma-MCP
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
pip install -e ".[dev]"
Then point the MCP config at the clone: either "command": "uvx", "args": ["--from", "/path/to/Forma-MCP", "forma-mcp"], or the venv directly with "command": "/path/to/Forma-MCP/.venv/Scripts/python.exe", "args": ["/path/to/Forma-MCP/aps_mcp.py"].
Windows paths in the JSON config: use forward slashes or escaped backslashes (e.g. C:/Users/YourName/Documents/Forma-MCP).
Tools reference
Navigation & file exploration
| Tool | What it does | Auth |
|---|---|---|
list_hubs |
List all ACC / BIM360 hubs on your account | 3-legged |
list_projects |
List all projects in a hub | 3-legged |
resolve_project |
Resolve a project from a name, ID (b.xxx/bare UUID), or ACC URL → project + owning hub in one call |
2-legged (ID/URL) / 3-legged (name) |
list_top_folders |
List top-level folders in a project | 3-legged |
list_folder_contents |
List files and subfolders at a given path | 3-legged |
rename_folder |
Rename a folder by its display-name path | 3-legged |
rename_file |
Rename a file by creating a new version (no re-upload) | 3-legged |
create_folder |
Create a new folder inside an existing parent folder | 3-legged |
move_file |
Move a file into another folder (changes its parent; no re-upload) | 3-legged |
move_folder |
Move a folder (and its contents) into another parent folder | 3-legged |
find_files |
Search for files by name across a project | 3-legged |
list_all_files |
Recursively list every file in a project (or a folder + subfolders) with full paths | 3-legged |
export_deliverables_manifest |
Compact filename-only recursive list (deduped, sorted, no metadata) for cross-checking against an external deliverable list | 3-legged |
find_folder |
Search for folders by name across a project | 3-legged |
delete_folder |
Soft-delete (hide) an empty folder | 3-legged |
find_recent_activity |
Show files modified since a given date | 3-legged |
User & member management
| Tool | What it does | Auth |
|---|---|---|
list_project_members |
List all members of a project with their roles | 3-legged |
list_account_users |
List all users in the ACC account | 2-legged |
list_project_roles |
List all roles defined in a project | 3-legged |
list_project_companies |
List companies linked to a project | 2-legged |
list_account_companies |
List every company in the hub's directory (account-level) with id, trade, status | 2-legged |
Permissions
| Tool | What it does | Auth |
|---|---|---|
export_permission_matrix |
Export a full permission matrix for a folder tree (role columns labelled from the permissions endpoint, falling back to role_id_cache.json for nameless/empty roles) |
3-legged |
apply_permission_changes |
Apply batched permission changes to folders | 3-legged |
Role & data export
| Tool | What it does | Auth |
|---|---|---|
create_role_data_export |
Trigger a Data Connector admin export to resolve the full project-roles catalog (id ↔ name, empty roles included) — for assigning users to an unused role or labelling export_permission_matrix |
3-legged |
get_data_connector_requests |
Poll & download a completed Data Connector export; returns the role_id → name map |
3-legged |
Bulk user management
| Tool | What it does | Auth |
|---|---|---|
bulk_assign_users |
Add a list of users to one or more projects with specified role(s) | 3-legged + account:write |
update_user_roles |
Update the role(s) of existing project members | 3-legged + account:write |
remove_users_from_projects |
Remove users from one or more projects | 3-legged + account:write |
clone_user_access |
Copy one user's full project access (all roles) to another user | 3-legged + account:write |
bulk_assign_company_users |
Add all members of an ACC company to a project | 3-legged + account:write |
Hub-level directory & onboarding
| Tool | What it does | Auth |
|---|---|---|
bulk_add_hub_users |
Onboard users to the hub in bulk, each with a company (required) + optional default role; idempotent (already_exists) |
2-legged (account:write) |
bulk_add_hub_companies |
Import partner companies into the hub directory (name + trade required); idempotent (already_exists) |
2-legged (account:write) |
deactivate_hub_users |
Soft-offboard hub users (status: inactive; the API has no hard delete) |
2-legged (account:write) |
deactivate_hub_companies |
Deactivate companies in the hub directory (status: inactive) |
2-legged (account:write) |
Bulk folder operations
| Tool | What it does | Auth |
|---|---|---|
bulk_list_folder_contents |
Audit engine (read-only): list the immediate contents of many folders — or of every subfolder of one parent — in a single call, returning subfolders (name + id) and files | 3-legged |
audit_folder_naming_standards |
Naming-convention audit (read-only): walk a folder subtree (or the whole project) and report which naming standard each folder enforces, grouped by standard, and flag the folders with none | 3-legged |
bulk_create_folders |
Idempotent batch create: {parent, name} items; skip_if_exists (default) reports exists instead of duplicating |
3-legged |
bulk_delete_folders |
File-safe batch soft-delete: never deletes a folder with any file in its subtree — reports it as skipped_has_files with file_count + sample_files |
3-legged |
bulk_move_files |
Batch-move files into new folders (no re-upload); idempotent (already_there); C4R models that 403 are reported skipped_unmovable |
3-legged |
bulk_move_folders |
Batch-move folders (with contents) under new parents; idempotent (already_there) |
3-legged |
Output verbosity — response_detail
| Value | Returns |
|---|---|
full |
Every row (legacy behaviour — nothing dropped). |
changes (default) |
Summary + only the rows that need attention — moves keep error / skipped_unmovable / not_found; deletes keep skipped_has_files / error; the contents audit keeps only folders with files or subfolders; the naming-standards audit keeps only folders with no standard. The success/no-op noise (moved, already_there, deleted, empty folders, folders that already have a standard) is dropped. |
summary |
Only the summary counts — the per-item results array is omitted. Failures are never lost: any locked/unmovable/stuck row (or no-standard folder) is still surfaced under a small failures array (and the contents audit's separate errors array is always present). |
Issues
| Tool | What it does | Auth |
|---|---|---|
list_issues |
List issues (pushpin + general) with common filters (status, search, assignee, dates, type/subtype, linked file URN, deleted); auto-paginates; fields slims each row |
3-legged |
get_issue |
Get one issue by issue_id (UUID) or display_id (the friendly number) |
3-legged |
create_issue |
Create an issue — requires title, issue_subtype_id, status; posts immediately |
3-legged (data:write) |
update_issue |
Update an issue (edit fields, change status, reassign) by issue_id/display_id; posts only the fields you supply |
3-legged (data:write) |
list_issue_types |
List issue categories + their types (subtypes) with 3-char codes — source of a valid issue_subtype_id and the key to decode type UUIDs |
3-legged |
list_issue_attribute_definitions |
List project custom fields (id, title, dataType, dropdown options) | 3-legged |
list_issue_attribute_mappings |
List which custom fields are assigned to which categories/types | 3-legged |
Approval workflows
| Tool | What it does | Auth |
|---|---|---|
list_workflows |
List approval workflows; filter by status (ACTIVE default / INACTIVE) or initiator; sort; auto-paginates |
3-legged |
get_workflow |
Get one workflow by workflow_id (UUID) or name |
3-legged |
create_workflow |
Create a workflow — name + steps required; posts immediately (no dry_run) |
3-legged (data:write) |
bulk_create_workflows |
Create many workflows from a list of specs (the Excel batch); dry_run default, audit CSV, bounded concurrency, per-row results |
3-legged (data:write) |
File reviews
| Tool | What it does | Auth |
|---|---|---|
list_reviews |
List file reviews (instances started from a workflow); filter by status (OPEN/CLOSED/VOID/FAILED), workflow_id/workflow_name, name, sequence_id, date ranges (created_at, updated_at, finished_at, current_step_due_date, approved_at, archived_at as start..end), next-action user/role/company, approved_by, archived; sort; auto-paginates. Archived reviews are hidden unless archived=true |
3-legged |
get_review |
Get one review by review_id (UUID) or sequence_id (the UI number); include any of progress (per-step state, who acted, notes), versions (files in the latest round with approval status, filterable by approve_status label) and workflow (the workflow snapshot the review used) |
3-legged |
Tools & API endpoints
All tools grouped by function, with the APS API endpoints each one calls.
flowchart LR
classDef tool fill:#F0F4FF,stroke:#6C8EBF,color:#333
classDef get fill:#1A6FAF,stroke:#145A8B,color:#fff
classDef post fill:#1E7E34,stroke:#155724,color:#fff
classDef patch fill:#E67E22,stroke:#CA6F1E,color:#fff
classDef del fill:#C0392B,stroke:#962D22,color:#fff
subgraph NAV["Navigation & Files · 3-legged OAuth"]
T1(list_hubs)
T2(list_projects)
T38(resolve_project)
T3(list_top_folders)
T4(list_folder_contents)
T20(rename_folder)
T21(rename_file)
T22(create_folder)
T25(move_file)
T26(move_folder)
T5(find_recent_activity)
T6(find_files)
T39(list_all_files)
T40(export_deliverables_manifest)
T23(find_folder)
T24(delete_folder)
end
subgraph UMEM["User & Member Info · 3-legged + 2-legged"]
T7(list_project_members)
T8(list_account_users)
T9(list_project_roles)
T10(list_project_companies)
T33(list_account_companies)
end
subgraph PERMS["Permissions · 3-legged OAuth"]
T11(export_permission_matrix)
T12(apply_permission_changes)
end
subgraph ROLES["Role Export · 3-legged OAuth"]
T13(create_role_data_export)
T14(get_data_connector_requests)
end
subgraph BULK["Bulk User Management · 3-legged + account:write · dry_run=true by default"]
T15(bulk_assign_users)
T16(update_user_roles)
T17(remove_users_from_projects)
T18(clone_user_access)
T19(bulk_assign_company_users)
end
subgraph HUBDIR["Hub Directory & Onboarding · 2-legged account:write · dry_run=true by default"]
T34(bulk_add_hub_users)
T35(bulk_add_hub_companies)
T36(deactivate_hub_users)
T37(deactivate_hub_companies)
end
subgraph BULKF["Bulk Folder Ops · 3-legged OAuth · dry_run=true by default"]
T27(bulk_list_folder_contents)
T28(bulk_create_folders)
T29(bulk_delete_folders)
T30(bulk_move_files)
T31(bulk_move_folders)
T32(audit_folder_naming_standards)
end
subgraph ISSUES["Issues · 3-legged OAuth · x-ads-region: EMEA"]
T41(list_issues)
T42(create_issue)
T43(list_issue_types)
T44(list_issue_attribute_definitions)
T45(list_issue_attribute_mappings)
T46(get_issue)
T47(update_issue)
end
subgraph WORKFLOWS["Approval workflows · Reviews · 3-legged OAuth · x-ads-region: EMEA"]
T48(list_workflows)
T49(get_workflow)
T50(create_workflow)
T51(bulk_create_workflows)
end
subgraph REVIEWS["File reviews · Reviews · 3-legged OAuth · x-ads-region: EMEA"]
T52(list_reviews)
T53(get_review)
end
E1["GET /project/v1/hubs"]
E2["GET /project/v1/hubs/{hub}/projects"]
E3["GET /project/v1/.../projects/{p}/topFolders"]
E4["GET /data/v1/projects/{p}/folders/{f}/contents"]
E5["GET /construction/admin/v1/projects/{p}/users"]
E6["GET /hq/v1/accounts/{a}/users"]
E7["GET /projects/{p}/users"]
E8["GET /hq/v1/accounts/{a}/projects/{p}/companies"]
E9["GET /bim360/docs/v1/.../folders/{f}/permissions"]
E10["POST .../permissions:batch-create"]
E11["POST .../permissions:batch-delete"]
E12["POST /data-connector/v1/accounts/{a}/requests"]
E13["GET .../requests/{r}/jobs + ZIP download"]
E14["POST /construction/admin/v2/projects/{p}/users:import"]
E15["PATCH /construction/admin/v1/projects/{p}/users/{u}"]
E16["DELETE /construction/admin/v1/projects/{p}/users/{u}"]
E17["PATCH /data/v1/projects/{p}/folders/{f}"]
E18["POST /data/v1/projects/{p}/versions?copyFrom=..."]
E19["POST /data/v1/projects/{p}/folders"]
E20["PATCH /data/v1/projects/{p}/items/{i}"]
E21["GET /data/v1/projects/{p}/folders/{f}"]
E22["GET /construction/admin/v1/accounts/{a}/companies"]
E23["POST /hq/v1/accounts/{a}/users/import"]
E24["POST /hq/v1/accounts/{a}/companies/import"]
E25["PATCH /hq/v1/accounts/{a}/users/{u}"]
E26["PATCH /hq/v1/accounts/{a}/companies/{c}"]
E27["GET /construction/admin/v1/projects/{p}?fields=accountId,name,platform"]
E28["GET /construction/issues/v1/projects/{p}/issues"]
E29["POST /construction/issues/v1/projects/{p}/issues"]
E30["GET /construction/issues/v1/projects/{p}/issue-types"]
E31["GET /construction/issues/v1/projects/{p}/issue-attribute-definitions"]
E32["GET /construction/issues/v1/projects/{p}/issue-attribute-mappings"]
E33["GET /construction/issues/v1/projects/{p}/issues/{i}"]
E34["PATCH /construction/issues/v1/projects/{p}/issues/{i}"]
E35["GET /construction/reviews/v1/projects/{p}/workflows"]
E36["GET /construction/reviews/v1/projects/{p}/workflows/{w}"]
E37["POST /construction/reviews/v1/projects/{p}/workflows"]
E38["GET /construction/reviews/v1/projects/{p}/reviews"]
E39["GET /construction/reviews/v1/projects/{p}/reviews/{r}"]
E40["GET /construction/reviews/v1/projects/{p}/reviews/{r}/progress · /versions · /workflow"]
T1 --> E1
T2 --> E2
T38 --> E27
T38 --> E1
T38 --> E2
T3 --> E3
T4 --> E4
T20 --> E17
T21 --> E18
T22 --> E19
T25 --> E20
T26 --> E17
T5 --> E4
T6 --> E4
T39 --> E3
T39 --> E4
T40 --> E3
T40 --> E4
T23 --> E4
T24 --> E17
T7 --> E5
T7 --> E6
T8 --> E6
T9 --> E7
T10 --> E8
T11 --> E9
T12 --> E10
T12 --> E11
T13 --> E12
T14 --> E13
T15 --> E5
T15 --> E14
T16 --> E15
T17 --> E16
T18 --> E5
T18 --> E14
T19 --> E5
T19 --> E6
T19 --> E14
T33 --> E22
T34 --> E22
T34 --> E6
T34 --> E23
T35 --> E22
T35 --> E24
T36 --> E6
T36 --> E25
T37 --> E22
T37 --> E26
T27 --> E4
T28 --> E4
T28 --> E19
T29 --> E4
T29 --> E17
T30 --> E4
T30 --> E20
T31 --> E4
T31 --> E17
T32 --> E3
T32 --> E4
T32 --> E21
T41 --> E28
T42 --> E29
T43 --> E30
T44 --> E31
T45 --> E32
T46 --> E33
T47 --> E34
T48 --> E35
T49 --> E35
T49 --> E36
T50 --> E37
T50 --> E5
T50 --> E8
T51 --> E37
T51 --> E5
T51 --> E8
T52 --> E38
T52 --> E35
T53 --> E38
T53 --> E39
T53 --> E40
class T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,T17,T18,T19,T20,T21,T22,T23,T24,T25,T26,T27,T28,T29,T30,T31,T32,T33,T34,T35,T36,T37,T38,T39,T40,T41,T42,T43,T44,T45,T46,T47,T48,T49,T50,T51,T52,T53 tool
class E1,E2,E3,E4,E5,E6,E7,E8,E9,E13,E21,E22,E27,E28,E30,E31,E32,E33,E35,E36,E38,E39,E40 get
class E10,E11,E12,E14,E18,E19,E23,E24,E29,E37 post
class E15,E17,E20,E25,E26,E34 patch
class E16 del
| Colour | HTTP method |
|---|---|
| Blue | GET — read only |
| Green | POST — create / bulk import |
| Orange | PATCH — update |
| Red | DELETE — remove |
| Light blue | MCP tool node |
Example prompts
Navigation & search
List all my ACC hubs
Show all projects in my hub
Show all projects in the "My Company - EU Hub" hub
Resolve https://acc.autodesk.eu/docs/files/projects/<project-id> — which hub is it in?
List the top folders of project b.<project-id> (paste the ID, no hub name needed)
What are the top folders in "Northgate Tower"?
List the contents of Project Files/Drawings/Structural in "Northgate Tower"
List the contents of Project Files/Drawings in "Northgate Tower" in hub "My Company - EU Hub"
Find all files named "facade" in "Northgate Tower"
List every file in "Northgate Tower"
List all files under Project Files/Drawings in "Northgate Tower" (including subfolders)
What files were modified in the last 7 days in "Northgate Tower"?
User & access management
Who are the members of the "Riverside Bridge" project?
List all users in the account who work for Acme Engineering
What roles exist in the "Central Station" project?
Hub onboarding (always previewed before execution)
List all companies in the hub directory
Onboard john.doe@acme.com and jane.roe@acme.com to the hub under company "Acme Engineering"
Import a new company "Beta Builders" (Electrical) into the hub
Deactivate leaver@acme.com in the hub
Bulk operations (always previewed before execution)
Add all users from company Acme Engineering to the "Northgate Tower" project as Design Team members
Clone the project access of john.doe@example.com to jane.smith@example.com
Remove alice@contractor.com from all projects she's a member of
Update the role of all Acme Engineering members in "Central Station" to Viewer
Show me a dry run of adding Acme Engineering to the "Riverside Bridge" project
Permission auditing
Export the full permission matrix for Project Files in "Northgate Tower"
Naming-convention auditing
Which folders in "Northgate Tower" have a naming standard assigned, and which have none?
Audit the naming conventions under Project Files/Drawings in "Northgate Tower"
Show me only the folders with no naming standard in "Northgate Tower"
Issues
List all open issues in "Northgate Tower"
Show the issue categories and types in "Northgate Tower"
Find issues assigned to <autodesk-id> that are due this month in "Central Station"
What custom fields are defined for issues in "Riverside Bridge"?
Create an issue in "Northgate Tower" titled "Cracked slab on L2" with status open using the "Defect" type
Show me issue 191 in "Northgate Tower"
Mark issue 191 in "Northgate Tower" as closed
Approval workflows
List the approval workflows in "Northgate Tower"
Show the "Design Review" workflow in "Northgate Tower"
Create an approval workflow "IFC Sign-off" in "Northgate Tower" with an approver step where Alice and the "BIM Manager" role review, 5 workday duration
Read workflows.xlsx and push every row into "Northgate Tower" (preview first, then create)
File reviews
List the open reviews in "Northgate Tower" whose current step is due this week
Show the progress of review 42 in "Northgate Tower"
Which files in review 42 were rejected?
List every closed review created from the "Design Review" workflow in "Northgate Tower"
Security & credentials
- 3-legged OAuth — the user token is cached in
~/.forma-mcp/tokens.jsonafter your first browser login. That's outside the repo, so it can't be committed by accident; treat it like a password. - 2-legged OAuth — uses your APS Client ID and Secret (stored in
~/.claude.json, Claude Code's config). The token is derived from these credentials at runtime and cached in memory only — it is never written to disk. - Each user authenticates with their own Autodesk account via 3-legged OAuth. The server acts with their permissions, not with elevated app-level rights.
- Bulk write tools default to
dry_run: true— no changes are made without explicit confirmation.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| 403 on any API call | App not registered as Custom Integration in the hub | Account Admin must add it via ACC Account Admin → Custom Integrations |
| 403 on folder or permission calls | Token expired | Delete ~/.forma-mcp/tokens.json to force re-login |
| MCP server not listed / failed to connect | uv not installed or not on PATH, or credentials missing |
Run uvx --version. Run the server command in a terminal: a missing APS_CLIENT_ID/APS_CLIENT_SECRET prints a clear error. Check claude mcp list and ~/.claude.json |
| Still on an old version after upgrading | uvx reused its cached build of a branch |
Pin a tag, or run uvx --refresh --from git+https://github.com/lfcastel/Forma-MCP@<ref> forma-mcp once |
| Browser doesn't open for login | Port 8080 already in use | Free up port 8080 and retry |
| Company not found | Not yet added to ACC account | Add it via bulk_add_hub_companies, or Account Admin adds it via ACC Account Admin → Companies |
| Company not in project | Added to account but not linked to project | Project Admin adds it via ACC Admin → Project Admin → Companies |
Known Limitations
- User last-activity date — the
last_sign_infield is only populated for BIM360 projects. The underlying ACC Account Admin API does not return this field for Forma projects, so activity timestamps will be absent for those users. - APS rate limits — Autodesk enforces Data Management rate limits per endpoint, per client ID, per minute (e.g. 300 rpm on
folders/{id}/contents, 50 rpm onhubs/projectsand on folder create/rename). The server paces itself to stay under them: every Data Management request goes through a per-endpoint token bucket (published limit ×APS_RATE_HEADROOM, default 0.8, with a ~10 s burst allowance), and the recursive tree walks (find_files,list_all_files,find_folder,find_recent_activity,export_permission_matrix) keep at most 8 folder listings in flight. A 429 (APS's body reads"Quota limit exceeded."even for an ordinary rate limit) is retried after the fullRetry-Afterperiod (capped at 60 s, up to 3 times) and also blocks that endpoint's bucket so concurrent calls back off together; only after the retries are exhausted does a tool return a cleanquota_exceededresult with theRetry-Afterhint — wait and retry. The limits are shared by everyone using the same APS client ID, so if several people run this server, give each their own app registration. SetAPS_RATE_LIMITING=0to disable the client-side pacing. - Cloud-workshared Revit models can't be moved by API — C4R models (
C4RModel) return 403 on a move and cannot be relocated programmatically. During abulk_delete_foldersreorg they are what keeps a folder from being deleted: it is reported asskipped_has_filesso a human can move them in the Revit/ACC UI first. - No hard delete for hub users or companies — the HQ API only supports soft-offboarding, so
deactivate_hub_users/deactivate_hub_companiessetstatus: inactiverather than removing the record. Deactivated entries remain visible (as inactive) in the account directory. - Hub onboarding can't grant account-admin —
bulk_add_hub_userssets each user's company and (optional) default role, but the underlyingusers/importendpoint cannot elevate someone to account administrator; do that in the ACC Account Admin UI. - Issues can't be deleted via the API — the ACC Issues API (v1) exposes no delete route:
deletedis read-only on the update endpoint (aPATCH {"deleted": true}returns 400) and a raw HTTP DELETE is rejected (403). There is therefore nodelete_issuetool — issues can only be deleted in the ACC UI.list_issuescan still surface UI-deleted issues viadeleted: true. - Empty project roles are invisible; role assignment isn't pre-checked — ACC exposes no project-roles catalog endpoint, so
list_project_roles(and role-name resolution in the bulk-user tools) is derived from the roles current members hold — a role assigned to nobody yet doesn't appear. Because of this the bulk-user tools do not pre-reject a role: a value that resolves to a member-held role is used as-is, and any other value is passed through to the ACC API as a raw role ID for it to validate on import (a bogus value fails per-user at the API). To assign the first person to an empty/newly-created role, pass that role's ID rather than its name. The reliable way to discover the ID of a role nobody holds yet is a Data Connectoradminexport, which contains the full project-roles catalog (not just member-held roles): callcreate_role_data_exportfor the project, thenget_data_connector_requestswith the returnedrequest_id— it polls the job and returns a{role_id: role_name}map including empty roles. (Data Connector is rate-limited to 24 jobs/24h per hub, so grab everyrole_idyou need from one export and reuse them.) Alternatively, an ID can come fromlist_project_roleson a project where the role does have a member, or fromget_workflowcandidates. The same export also carries each role'srole_oxygen_id(a separate numeric ID) whichget_data_connector_requestsreturns asroles_name_to_oxygen_id— the approval-workflow tools use this (cached inrole_id_cache.json) to resolve reviewer roles by name, including empty ones (see the Approval workflows section).
License
Licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0).
Contributing
git clone https://github.com/lfcastel/Forma-MCP.git
cd Forma-MCP
python -m venv .venv && .venv\Scripts\activate # Windows
pip install -e ".[dev]"
python -m pytest tests/ -v # all tests must pass before pushing
All changes go through a pull request. GitHub Actions runs the test suite and a package build automatically on every PR — the PR cannot be merged until CI is green.
Releasing: bump version in pyproject.toml in the PR. After it merges, tag main (git tag v0.2.0 && git push origin v0.2.0) and update the @v… tag in the install command above.
One-time repo setup (owner only): after the first CI run completes, go to Settings → Branches → Add rule for
main, tick Require status checks to pass, and select thetestjob.
Repository layout
forma-mcp/
├── aps_mcp.py # MCP server — 53 tools
├── tests/ # pytest test suite
├── .github/workflows/ci.yml # GitHub Actions CI (tests + package build)
├── pyproject.toml # packaging: `forma-mcp` console script for uvx
├── requirements.txt
├── README.md
└── .gitignore
README fetched from the repository 3 days ago. The original is authoritative.