workfront-fusion-mcp
by chaunceyplum
README.md
# Workfront & Workfront Fusion MCP
One repository, **14 independently deployable Lambda functions** -- one per
Workfront/Fusion domain -- each exposing only its own scoped set of MCP
tools at its own HTTP route. **282 tools total**, covering every endpoint
listed in `WORKFRONT_MCP_SOLUTIONS.md` (full CRUD per object where the
underlying API supports it, not just the common list/get/create path).
See `WORKFRONT_MCP_SOLUTIONS.md` for the full
architecture writeup (endpoint tables, call-order dependencies per server,
auth details, and the single-repo-vs-separate-repos tradeoff this repo
settled on).
This mirrors the pattern already proven in `chaunceyplum/mcp` (the AEC
Adobe Experience Cloud MCP server): a stateless JSON-RPC 2.0 Lambda
handler per server, a single flat `TOOLS` dict per function, and
credentials resolved from SSM at deploy time via `{{resolve:ssm:...}}`.
The difference here: instead of one Lambda with all tools, **each server
is its own function with its own route**, so an MCP client only ever sees
the tools relevant to whichever endpoint(s) it's wired to.
## Why one repo instead of separate repos per server
We evaluated splitting each server into its own GitHub repo (see the
"one repo per server" alternative in `WORKFRONT_MCP_SOLUTIONS.md`) and
settled on a single repo with N independently deployable functions
instead, because:
- **The actual goal -- "the LLM only sees relevant tools" -- is a runtime
endpoint property, not a source-control property.** A client hitting
`/mcp/workfront/core` never sees another server's
tools regardless of which repo the code lives in.
- **Isolation that matters (auth, blast radius) comes from per-function
config, not per-repo config**, and SAM gives us that directly: every
function below has its **own** `Environment` block resolving its
**own** SSM namespace -- nothing is shared via `Globals`, so a bug in
one server's code can't read another server's credentials.
- **One CI pipeline, one set of shared tooling (`common/`)** is simpler
to maintain solo/small-team than 14 repos + a versioned shared package,
and repos are cheap to split out later if team ownership ever actually
diverges.
## Servers
| Folder | Route | Covers |
|---|---|---|
| Folder | Route | Tools | Covers |
|---|---|---|---|
| `servers/core/` | /mcp/workfront/core | 43 | Core Work Management: portfolios, programs, templates, template tasks, projects, tasks, and issues (incl. issue→task conversion). |
| `servers/users/` | /mcp/workfront/users | 36 | User & Resource Management: companies, roles, users, teams, resource pools, and resource allocations. |
| `servers/documents/` | /mcp/workfront/documents | 27 | Document Management: folders, documents, versions, approvals, and document webhook subscriptions. |
| `servers/time_approval/` | /mcp/workfront/time-approval | 25 | Time & Approval: approval paths, timesheets (incl. submit), hour entries, and approval instances. |
| `servers/metadata/` | /mcp/workfront/metadata | 26 | Custom Fields & Metadata: parameters, parameter groups, categories (custom forms, incl. field-attach and category-assign), and category-parameter mappings. |
| `servers/search/` | /mcp/workfront/search | 5 | Search & Query: object-scoped search, generic search, named queries, and saved report execution. |
| `servers/comments/` | /mcp/workfront/comments | 11 | Commenting & Collaboration (Comment Stream API v1): full CRUD on comments, replies, and reactions. |
| `servers/planning/` | /mcp/workfront/planning | 24 | Planning API (v2): full CRUD on workspaces, record types, fields, views, and bulk records. |
| `servers/misc/` | /mcp/workfront/misc | 48 | Miscellaneous Objects: notes, messages, reports, calendars, preferences, config, activity updates, and journal entries. |
| `servers/fusion_org/` | /mcp/fusion/org | 11 | Fusion Organization & Team: organizations (read), teams (full CRUD), and user-team assignment. |
| `servers/fusion_connections/` | /mcp/fusion/connections | 6 | Fusion Connection Management: app connections (full CRUD) and connection-specs. |
| `servers/fusion_hooks/` | /mcp/fusion/hooks | 7 | Fusion Webhook & Trigger: webhooks (full CRUD), payload history, trigger definitions. |
| `servers/fusion_scenarios/` | /mcp/fusion/scenarios | 9 | Fusion Scenario Management: scenarios, metadata updates, drafts, templates, manual execution. |
| `servers/fusion_executions/` | /mcp/fusion/executions | 4 | Fusion Execution & Monitoring: execution history, logs, run history. |
## Shared code (`common/`)
| Module | Purpose |
|---|---|
| `common/auth.py` | Generic Adobe IMS OAuth2 Server-to-Server (`client_credentials`) token fetch, cached per `(client_id, scope)`. |
| `common/workfront_client.py` | Classic Workfront REST API client (`/attask/api/v{version}`) + generic per-objcode CRUD helpers + `wf_set_custom_fields` (the `DE:` custom-form-value mechanism). |
| `common/crud_factory.py` | Builds a standard list/get/create/update/delete/set-custom-fields tool set for a single object code -- most server tool modules are a one-line call into this. |
| `common/comment_stream_client.py` | Comment Stream API v1 client (separate host + IMS scope from classic REST). |
| `common/fusion_client.py` | Workfront Fusion API client (separate host + IMS scope; Fusion is experimental per Adobe's own labeling). |
| `common/planning_client.py` | Planning API v2 client (workspaces / record-types / fields / views / records). |
| `common/dispatcher.py` | MCP JSON-RPC 2.0 Lambda handler factory (`initialize`, `tools/list`, `tools/call`) -- every server's `lambda_handler.py` is a few lines wiring its own `TOOLS` dict into this. |
## Deploy
```bash
sam build
sam deploy --guided # first time -- saves samconfig.toml (gitignored; see samconfig.toml.example)
sam deploy # subsequent deploys -- updates all 14 functions in one stack
```
`sam deploy` updates the whole stack in one pass; you're still deploying
14 independent functions with independent env/config, just via one
command. If you later want to deploy a single server's fix without
touching the others, target it directly:
```bash
sam deploy --resolve-s3 # or use `aws lambda update-function-code` for a hotfix to one function
```
### Populate SSM parameters
Each server reads only its own namespace. Populate the ones for the
servers you're actually standing up first (see the build order in
`WORKFRONT_MCP_SOLUTIONS.md` -- Users, then Core, tend to unblock the rest):
| SSM namespace | Env vars it feeds |
|---|---|
| `wf-mcp/core` | `WF_CLIENT_ID`, `WF_CLIENT_SECRET`, `WF_DOMAIN` |
| `wf-mcp/users` | `WF_CLIENT_ID`, `WF_CLIENT_SECRET`, `WF_DOMAIN` |
| `wf-mcp/documents` | `WF_CLIENT_ID`, `WF_CLIENT_SECRET`, `WF_DOMAIN` |
| `wf-mcp/time-approval` | `WF_CLIENT_ID`, `WF_CLIENT_SECRET`, `WF_DOMAIN` |
| `wf-mcp/metadata` | `WF_CLIENT_ID`, `WF_CLIENT_SECRET`, `WF_DOMAIN` |
| `wf-mcp/search` | `WF_CLIENT_ID`, `WF_CLIENT_SECRET`, `WF_DOMAIN` |
| `wf-mcp/comments` | `WF_COMMENTS_CLIENT_ID`, `WF_COMMENTS_CLIENT_SECRET`, `WF_COMMENTS_ORG_ID` |
| `wf-mcp/planning` | `WF_PLANNING_CLIENT_ID`, `WF_PLANNING_CLIENT_SECRET`, `WF_DOMAIN` |
| `wf-mcp/misc` | `WF_CLIENT_ID`, `WF_CLIENT_SECRET`, `WF_DOMAIN` |
| `wf-mcp/fusion-org` | `FUSION_CLIENT_ID`, `FUSION_CLIENT_SECRET`, `FUSION_API_BASE` |
| `wf-mcp/fusion-connections` | `FUSION_CLIENT_ID`, `FUSION_CLIENT_SECRET`, `FUSION_API_BASE` |
| `wf-mcp/fusion-hooks` | `FUSION_CLIENT_ID`, `FUSION_CLIENT_SECRET`, `FUSION_API_BASE` |
| `wf-mcp/fusion-scenarios` | `FUSION_CLIENT_ID`, `FUSION_CLIENT_SECRET`, `FUSION_API_BASE` |
| `wf-mcp/fusion-executions` | `FUSION_CLIENT_ID`, `FUSION_CLIENT_SECRET`, `FUSION_API_BASE` |
Example for the core server:
```bash
aws ssm put-parameter --name /wf-mcp/core/client-id --type SecureString --value "..."
aws ssm put-parameter --name /wf-mcp/core/client-secret --type SecureString --value "..."
aws ssm put-parameter --name /wf-mcp/core/domain --type String --value "yourtenant"
```
### Wire up an MCP client
Each server gets its own entry, pointing at its own route from the SAM
outputs:
```json
{
"mcpServers": {
"workfront-core": { "url": "https://<api-id>.execute-api.<region>.amazonaws.com/mcp/workfront/core" },
"workfront-users": { "url": "https://<api-id>.execute-api.<region>.amazonaws.com/mcp/workfront/users" }
}
}
```
Only wire up the servers relevant to a given client/session -- that's
what actually keeps the model's tool list scoped.
## Status
Every endpoint listed in `WORKFRONT_MCP_SOLUTIONS.md` has at least one
corresponding tool (282 total), and every server's `lambda_handler.py`
was re-verified end-to-end (import + a live `tools/list`/`tools/call`
dispatch against mocked HTTP) after each round of additions -- see the
commit history for the specific tool-by-tool audit.
That said, this is still a **scaffold** in one sense: the JSON-RPC
dispatcher, auth clients, and CRUD tool wiring are real and complete, but
several endpoint details are flagged inline (`# NOTE` / docstrings) as
pending verification against a live tenant -- there is no Workfront/Fusion
tenant credentialed against this repo yet. Before relying on any tool in
production, confirm against your own Adobe Developer Console app
registration:
- The exact OAuth scope string(s) for classic Workfront REST, Comment
Stream, Fusion, and Planning (placeholders are marked in `common/*_client.py`).
- The issue→task conversion endpoint/verb (`servers/core/tools/issues.py`).
- The Planning API v2 base path (`common/planning_client.py`).
- The Fusion API base host (`FUSION_API_BASE`).
- The Document Webhooks API registration path/payload shape
(`servers/documents/tools/document_webhooks.py` guesses `/documentwebhook`).
- The named-queries path (`servers/search/tools/named_queries.py` guesses
`/namedqueries` and `/namedqueries/run`).
- Whether Fusion actually exposes `PATCH /scenarios/{id}` for metadata
separately from `/scenarios/{id}/draft` for blueprint edits
(`servers/fusion_scenarios/tools/scenarios.py`).
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues