Skip to content

Commit a235d17

Browse files
Merge pull request #419 from corbitsdev/cl-7083-fix-canva-mcp-connect-oauth-succeeds-then-the-connection
Fix Canva MCP connect after OAuth succeeds
2 parents 0e94954 + 6ec8741 commit a235d17

30 files changed

Lines changed: 1113 additions & 89 deletions

‎ARCHITECTURE.md‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,30 @@ sidecar a given run lands on) rather than reimplementing execution.
199199
Provisioning and allocation follow Interchange's own contracts; workbench
200200
does not maintain a parallel scheduler.
201201

202+
## MCP connect
203+
204+
Remote MCP servers connect through Plugins (curated presets and
205+
add-by-URL). The connect-time probe and later tool calls share one
206+
origin-pinned fetch: every first hop must be the stored origin or an
207+
explicit extra origin for that pin — not a host-suffix match — and a 3xx
208+
is never followed, even to an allowlisted origin. Canva is the one
209+
shipped extra: the stored MCP origin may also first-hop the protocol
210+
origin that is not the stored `apiBaseUrl`.
211+
212+
OAuth presets that list advertised scopes send those scopes on RFC 7591
213+
dynamic client registration; presets that omit the list stay on the SDK's
214+
protected-resource metadata fallback. When `/start` fails, the return
215+
distinguishes `client_rejected` (the authorization server refused
216+
Workbench as a client — including RFC 7591 `invalid_redirect_uri` and
217+
sibling client-metadata codes, even when the SDK maps an unknown code
218+
onto a generic server error) from `discovery_failed` (the authorization
219+
server could not be reached). A successful callback re-probes with the
220+
new token and may put that probe's tool count on the Plugins return so
221+
the row can show it.
222+
223+
Live Canva OAuth against Canva's own servers is not verified; this is
224+
the shipped control flow, not a proven live handshake.
225+
202226
## Related docs
203227

204228
- [docs/GLOSSARY.md](docs/GLOSSARY.md) — product-term to platform-term

‎IMPLEMENTATION.md‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,39 @@ specialist's own 1:1 — never an invite into Myra's DM.
203203
A create-succeeded / mint-failed split is a completed tool result that
204204
names both halves, not a bare error.
205205

206+
## Canva MCP connect (shipped)
207+
208+
Canva is the `canva` MCP preset (`packages/connections/src/mcp-presets.ts`):
209+
`https://mcp.canva.com/mcp`, `connectionMode: "oauth"`, with the 16
210+
advertised PRM scopes space-joined onto RFC 7591 DCR `clientMetadata.scope`
211+
(`createMcpOAuthProvider` in `packages/connections/src/mcp-oauth.ts`).
212+
Other presets omit `oauthScopes` and stay on the SDK's SEP-835 PRM
213+
fallback.
214+
215+
Connect-time probe and credential fetch share
216+
`mcpOriginPinnedFetch` (`packages/credential-providers/src/mcp-origin-pinned-fetch.ts`):
217+
pin to the stored origin, extra first hop only
218+
`https://mcp.canva.com` → `https://canva.ai` (not a host suffix),
219+
`redirect: "manual"` so a 302 is never followed. `/start` classifies
220+
DCR/client refusal as `client_rejected` versus unreachable discovery as
221+
`discovery_failed` (`packages/connections/src/mcp-oauth-routes.ts`).
222+
RFC 7591 `invalid_redirect_uri` (and `invalid_client_metadata`,
223+
`invalid_client`, `unauthorized_client`) count as `client_rejected`; the
224+
route clones 4xx/5xx JSON before the MCP SDK 1.30.0 maps unknown codes
225+
onto `ServerError`.
226+
227+
A successful OAuth callback probes with the new token and, on success,
228+
appends `toolCount` to the Plugins return query. The Canva row
229+
(`packages/plugins-ui/src/mcp-preset-cards.tsx`) shows that count when
230+
it is a non-negative integer; otherwise the row stays "Connected".
231+
`@corbits/mcp-tools` per-request timeout is two minutes
232+
(`MCP_REQUEST_TIMEOUT_MS` in `packages/mcp-tools/src/mcp-client.ts`) —
233+
above the SDK's 60s default, below a five-minute chat turn.
234+
235+
These are unit-tested control-flow facts. Live Canva OAuth against
236+
Canva's own servers is **not** verified; do not document a proven live
237+
handshake.
238+
206239
## Related docs
207240

208241
- [README.md](README.md) — quickstart, local setup, repo layout, e2e detail
@@ -223,3 +256,5 @@ names both halves, not a bare error.
223256
- Whether Pulumi stacks/config live in this repo or a separate
224257
infrastructure repo is not established in the docs reviewed for this
225258
pass.
259+
- Live Canva MCP OAuth (DCR, redirect allowlist, and post-OAuth probe
260+
against `mcp.canva.com` / `canva.ai`) is not verified as of CL-7083.

‎PRODUCT.md‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,20 @@ extend what an agent knows — both are installable, both are scoped to the
137137
bench or workbench that installs them, and neither requires touching
138138
platform internals.
139139

140+
The Plugins rail is how a person connects remote MCP servers: curated
141+
preset cards plus an add-by-URL path. Canva is an OAuth preset — Connect
142+
sends them through that app's sign-in, then back to Plugins. After a
143+
successful OAuth return, the row can show how many tools the connect
144+
probe found; a missing or non-integer count stays a bare "Connected". If
145+
sign-in cannot start, Plugins distinguishes an unreachable authorization
146+
server from the app rejecting Workbench as a client (redirect URL or
147+
registration). Agent MCP tool calls are allowed two minutes so a slow
148+
design tool can finish inside a chat turn.
149+
150+
Live Canva OAuth against Canva's own servers is **not** verified as of
151+
CL-7083. This documents the shipped connect path, not a proven live
152+
handshake.
153+
140154
## Workbench settings
141155

142156
Each workbench has its own full-stage settings surface, not a dialog —
@@ -217,6 +231,14 @@ user-facing surfaces use the rest of the product vocabulary above.
217231
generic `connections/pending` still wakes the asking agent. A leftover
218232
agent 401 after GitHub already succeeded is still a first-minute bug
219233
— see IMPLEMENTATION.md; do not document that it cannot happen.
234+
- Connected/settle honesty (no stale Connect after success; settle never
235+
posting as the signed-in user; no agent 401 after GitHub already
236+
succeeded) stays **target** until CL-6737 and CL-6738 land — see
237+
IMPLEMENTATION.md open questions; do not document those guarantees as
238+
shipped.
239+
- Live Canva MCP OAuth (sign-in, DCR, and post-OAuth probe against
240+
Canva's own servers) is not verified; do not document a proven live
241+
Canva handshake.
220242
- The precise boundary of what Insights surfaces to a non-admin bench
221243
member (all tenant activity vs. only their own) is not spelled out in
222244
`packages/insights`'s own docs as of this writing.

‎apps/hub/src/launch-caches.test.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,24 @@ describe("createLaunchCaches: assetService reads", () => {
154154
expect(inner.listCalls.length).toBe(1);
155155
});
156156

157+
test("lists an empty catalog when the package-registry has no resolvable main", async () => {
158+
const heads = new Map<string, string | null>();
159+
const inner = countingAssetService(heads);
160+
const { repoStore } = countingRepoStore(heads);
161+
const caches = createLaunchCaches({
162+
assetService: inner.assetService,
163+
repoStore,
164+
});
165+
166+
const listed = await caches.assetService.listAssetBlobs({
167+
assetId: ASSET_ID,
168+
dir: "tarballs",
169+
});
170+
171+
expect(listed).toEqual([]);
172+
expect(inner.listCalls.length).toBe(0);
173+
});
174+
157175
test("delegates createAsset and populateAsset untouched", () => {
158176
const heads = new Map([[HEAD_REF, "sha-1"]]);
159177
const inner = countingAssetService(heads);

‎apps/hub/src/launch-caches.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,11 @@ export function createLaunchCaches(deps: {
158158
params: ListAssetBlobsParams,
159159
): Promise<string[]> {
160160
const sha = await resolvePackageRegistryHeadSha(params.assetId, params.ref);
161-
if (sha === null) return assetService.listAssetBlobs(params);
161+
// No resolvable `main` means no tarballs yet. Match the tarball REST
162+
// list, which returns [] on this same not_found rather than failing
163+
// the launch. Pins against a missing tarball still fail as unknown
164+
// package.
165+
if (sha === null) return [];
162166
const key = `${params.assetId}:${sha}:${params.dir}`;
163167
const cached = listCache.get(key);
164168
if (cached !== undefined) return cached;

‎bun.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)