adoptai-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +70 -0
  2. package/bin/adoptai-mcp.js +2 -0
  3. package/dist/apps/canva.js +1 -0
  4. package/dist/apps/figma.js +1 -0
  5. package/dist/apps/github.js +2 -0
  6. package/dist/apps/notion.js +1 -0
  7. package/dist/apps/registry.js +20 -0
  8. package/dist/apps/salesforce.js +1 -0
  9. package/dist/cli/add.js +532 -0
  10. package/dist/cli/index.js +39 -0
  11. package/dist/cli/list.js +19 -0
  12. package/dist/cli/remove.js +37 -0
  13. package/dist/cli/serve.js +27 -0
  14. package/dist/cli/status.js +24 -0
  15. package/dist/config/clients.js +118 -0
  16. package/dist/config/credentials.js +34 -0
  17. package/dist/core/auth-manager.js +237 -0
  18. package/dist/core/config-writer.js +161 -0
  19. package/dist/core/doctor.js +199 -0
  20. package/dist/core/package.json +3 -0
  21. package/dist/core/server-base.js +81 -0
  22. package/dist/integrations/canva/.env +3 -0
  23. package/dist/integrations/canva/auth.js +287 -0
  24. package/dist/integrations/canva/env.js +9 -0
  25. package/dist/integrations/canva/index.js +12 -0
  26. package/dist/integrations/canva/package.json +31 -0
  27. package/dist/integrations/canva/publish-to-adoptai.js +365 -0
  28. package/dist/integrations/canva/setup.js +90 -0
  29. package/dist/integrations/canva/tools.js +1315 -0
  30. package/dist/integrations/canva/tools.original.js +1315 -0
  31. package/dist/integrations/figma/auth.js +48 -0
  32. package/dist/integrations/figma/index.js +11 -0
  33. package/dist/integrations/figma/package.json +27 -0
  34. package/dist/integrations/figma/publish-to-adoptai.js +384 -0
  35. package/dist/integrations/figma/setup.js +90 -0
  36. package/dist/integrations/figma/tools.js +1137 -0
  37. package/dist/integrations/github/auth.js +53 -0
  38. package/dist/integrations/github/index.js +11 -0
  39. package/dist/integrations/github/package.json +28 -0
  40. package/dist/integrations/github/publish-to-adoptai.js +240 -0
  41. package/dist/integrations/github/setup.js +103 -0
  42. package/dist/integrations/github/tools.js +78 -0
  43. package/dist/integrations/github-actions/auth.js +53 -0
  44. package/dist/integrations/github-actions/index.js +11 -0
  45. package/dist/integrations/github-actions/package.json +27 -0
  46. package/dist/integrations/github-actions/setup.js +103 -0
  47. package/dist/integrations/github-actions/tools.js +5642 -0
  48. package/dist/integrations/github-activity/auth.js +53 -0
  49. package/dist/integrations/github-activity/index.js +11 -0
  50. package/dist/integrations/github-activity/package.json +27 -0
  51. package/dist/integrations/github-activity/setup.js +103 -0
  52. package/dist/integrations/github-activity/tools.js +925 -0
  53. package/dist/integrations/github-apps/auth.js +53 -0
  54. package/dist/integrations/github-apps/index.js +11 -0
  55. package/dist/integrations/github-apps/package.json +27 -0
  56. package/dist/integrations/github-apps/setup.js +103 -0
  57. package/dist/integrations/github-apps/tools.js +791 -0
  58. package/dist/integrations/github-billing/auth.js +53 -0
  59. package/dist/integrations/github-billing/index.js +11 -0
  60. package/dist/integrations/github-billing/package.json +27 -0
  61. package/dist/integrations/github-billing/setup.js +103 -0
  62. package/dist/integrations/github-billing/tools.js +438 -0
  63. package/dist/integrations/github-checks/auth.js +53 -0
  64. package/dist/integrations/github-checks/index.js +11 -0
  65. package/dist/integrations/github-checks/package.json +27 -0
  66. package/dist/integrations/github-checks/setup.js +103 -0
  67. package/dist/integrations/github-checks/tools.js +607 -0
  68. package/dist/integrations/github-code-scanning/auth.js +53 -0
  69. package/dist/integrations/github-code-scanning/index.js +11 -0
  70. package/dist/integrations/github-code-scanning/package.json +27 -0
  71. package/dist/integrations/github-code-scanning/setup.js +103 -0
  72. package/dist/integrations/github-code-scanning/tools.js +987 -0
  73. package/dist/integrations/github-dependabot/auth.js +53 -0
  74. package/dist/integrations/github-dependabot/index.js +11 -0
  75. package/dist/integrations/github-dependabot/package.json +27 -0
  76. package/dist/integrations/github-dependabot/setup.js +103 -0
  77. package/dist/integrations/github-dependabot/tools.js +915 -0
  78. package/dist/integrations/github-gists/auth.js +53 -0
  79. package/dist/integrations/github-gists/index.js +11 -0
  80. package/dist/integrations/github-gists/package.json +27 -0
  81. package/dist/integrations/github-gists/setup.js +103 -0
  82. package/dist/integrations/github-gists/tools.js +545 -0
  83. package/dist/integrations/github-git/auth.js +53 -0
  84. package/dist/integrations/github-git/index.js +11 -0
  85. package/dist/integrations/github-git/package.json +27 -0
  86. package/dist/integrations/github-git/setup.js +103 -0
  87. package/dist/integrations/github-git/tools.js +513 -0
  88. package/dist/integrations/github-issues/auth.js +53 -0
  89. package/dist/integrations/github-issues/index.js +11 -0
  90. package/dist/integrations/github-issues/package.json +27 -0
  91. package/dist/integrations/github-issues/setup.js +103 -0
  92. package/dist/integrations/github-issues/tools.js +2232 -0
  93. package/dist/integrations/github-orgs/auth.js +53 -0
  94. package/dist/integrations/github-orgs/index.js +11 -0
  95. package/dist/integrations/github-orgs/package.json +27 -0
  96. package/dist/integrations/github-orgs/setup.js +103 -0
  97. package/dist/integrations/github-orgs/tools.js +3512 -0
  98. package/dist/integrations/github-packages/auth.js +53 -0
  99. package/dist/integrations/github-packages/index.js +11 -0
  100. package/dist/integrations/github-packages/package.json +27 -0
  101. package/dist/integrations/github-packages/setup.js +103 -0
  102. package/dist/integrations/github-packages/tools.js +1088 -0
  103. package/dist/integrations/github-pulls/auth.js +53 -0
  104. package/dist/integrations/github-pulls/index.js +11 -0
  105. package/dist/integrations/github-pulls/package.json +27 -0
  106. package/dist/integrations/github-pulls/setup.js +103 -0
  107. package/dist/integrations/github-pulls/tools.js +1252 -0
  108. package/dist/integrations/github-reactions/auth.js +53 -0
  109. package/dist/integrations/github-reactions/index.js +11 -0
  110. package/dist/integrations/github-reactions/package.json +27 -0
  111. package/dist/integrations/github-reactions/setup.js +103 -0
  112. package/dist/integrations/github-reactions/tools.js +706 -0
  113. package/dist/integrations/github-repos/auth.js +53 -0
  114. package/dist/integrations/github-repos/index.js +11 -0
  115. package/dist/integrations/github-repos/package.json +27 -0
  116. package/dist/integrations/github-repos/setup.js +103 -0
  117. package/dist/integrations/github-repos/tools.js +7286 -0
  118. package/dist/integrations/github-search/auth.js +53 -0
  119. package/dist/integrations/github-search/index.js +11 -0
  120. package/dist/integrations/github-search/package.json +27 -0
  121. package/dist/integrations/github-search/setup.js +103 -0
  122. package/dist/integrations/github-search/tools.js +370 -0
  123. package/dist/integrations/github-teams/auth.js +53 -0
  124. package/dist/integrations/github-teams/index.js +11 -0
  125. package/dist/integrations/github-teams/package.json +27 -0
  126. package/dist/integrations/github-teams/setup.js +103 -0
  127. package/dist/integrations/github-teams/tools.js +633 -0
  128. package/dist/integrations/github-users/auth.js +53 -0
  129. package/dist/integrations/github-users/index.js +11 -0
  130. package/dist/integrations/github-users/package.json +27 -0
  131. package/dist/integrations/github-users/setup.js +103 -0
  132. package/dist/integrations/github-users/tools.js +1118 -0
  133. package/dist/integrations/notion/api.js +108 -0
  134. package/dist/integrations/notion/auth.js +59 -0
  135. package/dist/integrations/notion/endpoints.json +630 -0
  136. package/dist/integrations/notion/index.js +11 -0
  137. package/dist/integrations/notion/package.json +33 -0
  138. package/dist/integrations/notion/publish-to-adoptai.js +271 -0
  139. package/dist/integrations/notion/scripts/generate-endpoints.mjs +306 -0
  140. package/dist/integrations/notion/setup.js +89 -0
  141. package/dist/integrations/notion/tools.js +586 -0
  142. package/dist/integrations/notion/tools.original.js +568 -0
  143. package/dist/integrations/salesforce/.env +8 -0
  144. package/dist/integrations/salesforce/.env.example +15 -0
  145. package/dist/integrations/salesforce/auth.js +311 -0
  146. package/dist/integrations/salesforce/endpoints.json +1359 -0
  147. package/dist/integrations/salesforce/env.js +9 -0
  148. package/dist/integrations/salesforce/index.js +12 -0
  149. package/dist/integrations/salesforce/package.json +42 -0
  150. package/dist/integrations/salesforce/publish-smart-specs.js +890 -0
  151. package/dist/integrations/salesforce/publish-to-adoptai.js +386 -0
  152. package/dist/integrations/salesforce/scripts/extract-postman.mjs +222 -0
  153. package/dist/integrations/salesforce/setup.js +112 -0
  154. package/dist/integrations/salesforce/tools.js +4544 -0
  155. package/dist/integrations/salesforce/tools.original.js +4487 -0
  156. package/dist/server/mcp-server.js +50 -0
  157. package/dist/server/tool-loader.js +47 -0
  158. package/dist/specs/figma-api.json +13621 -0
  159. package/dist/specs/split/salesforce-auth.json +3931 -0
  160. package/dist/specs/split/salesforce-bulk-v1.json +1489 -0
  161. package/dist/specs/split/salesforce-bulk-v2.json +1951 -0
  162. package/dist/specs/split/salesforce-composite.json +1246 -0
  163. package/dist/specs/split/salesforce-connect.json +11639 -0
  164. package/dist/specs/split/salesforce-einstein-prediction-service.json +576 -0
  165. package/dist/specs/split/salesforce-event-platform.json +2682 -0
  166. package/dist/specs/split/salesforce-graphql.json +1754 -0
  167. package/dist/specs/split/salesforce-industries.json +4115 -0
  168. package/dist/specs/split/salesforce-metadata.json +555 -0
  169. package/dist/specs/split/salesforce-rest.json +4798 -0
  170. package/dist/specs/split/salesforce-soap.json +210 -0
  171. package/dist/specs/split/salesforce-subscription-management.json +1299 -0
  172. package/dist/specs/split/salesforce-tooling.json +2026 -0
  173. package/dist/specs/split/salesforce-ui.json +7426 -0
  174. package/package.json +47 -0
@@ -0,0 +1,1315 @@
1
+ import axios from 'axios';
2
+ import { Buffer } from 'node:buffer';
3
+ import { buildAuthHeaders, ensureAccessToken } from './auth.js';
4
+
5
+ const BASE_URL = "https://api.canva.com/rest/v1";
6
+
7
+ function handleError(err) {
8
+ const status = err.response?.status;
9
+ const data = err.response?.data;
10
+ const msg =
11
+ (typeof data?.message === 'string' && data.message) ||
12
+ (typeof data?.error_description === 'string' && data.error_description) ||
13
+ (typeof data?.error === 'string' && data.error) ||
14
+ err.message;
15
+ if (status === 401) throw new Error('Token invalid or expired.\nRun: npx adoptai-canva-mcp --client cursor');
16
+ if (status === 403) throw new Error('Insufficient permissions. Check OAuth scopes on your Canva integration.');
17
+ if (status === 404) throw new Error('Resource not found. Check your parameters.');
18
+ if (status === 429) throw new Error('Rate limit exceeded. Please wait and try again.');
19
+ throw new Error(msg || 'Canva API request failed');
20
+ }
21
+
22
+ function text(payload) {
23
+ return { content: [{ type: 'text', text: JSON.stringify(payload, null, 2) }] };
24
+ }
25
+
26
+ /**
27
+ * @param {string} method
28
+ * @param {string} pathTemplate - e.g. /designs/{designId}
29
+ * @param {Record<string, unknown>} params - path keys substituted; rest → query (GET/DELETE) or JSON body
30
+ * @param {{ headers?: Record<string, string>, rawBody?: unknown, responseType?: string }} [opts]
31
+ */
32
+ async function apiRequest(method, pathTemplate, params = {}, opts = {}) {
33
+ await ensureAccessToken();
34
+ const headers = {
35
+ ...(!['GET', 'DELETE'].includes(method) ? { 'Content-Type': 'application/json' } : {}),
36
+ ...buildAuthHeaders(),
37
+ ...opts.headers,
38
+ };
39
+ let path = pathTemplate;
40
+ const p = { ...params };
41
+ const pathKeys = [...path.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]);
42
+ for (const key of pathKeys) {
43
+ const v = p[key];
44
+ if (v === undefined || v === null || v === '') throw new Error(`Missing required path parameter: ${key}`);
45
+ path = path.replace(`{${key}}`, encodeURIComponent(String(v)));
46
+ delete p[key];
47
+ }
48
+ const url = BASE_URL.replace(/\/$/, '') + path;
49
+ const isBodyless = method === 'GET' || method === 'DELETE';
50
+ let data = opts.rawBody;
51
+ const query = {};
52
+ if (!isBodyless && opts.rawBody === undefined) {
53
+ const body = {};
54
+ for (const [k, v] of Object.entries(p)) {
55
+ if (v !== undefined && v !== null) body[k] = v;
56
+ }
57
+ if (Object.keys(body).length) data = body;
58
+ } else if (isBodyless) {
59
+ for (const [k, v] of Object.entries(p)) {
60
+ if (v !== undefined && v !== null && v !== '') query[k] = v;
61
+ }
62
+ }
63
+ try {
64
+ const res = await axios({
65
+ method,
66
+ url,
67
+ headers,
68
+ params: isBodyless && Object.keys(query).length ? query : undefined,
69
+ data,
70
+ timeout: 120000,
71
+ responseType: opts.responseType || 'json',
72
+ validateStatus: () => true,
73
+ });
74
+ if (res.status >= 400) {
75
+ const fake = new Error();
76
+ fake.response = res;
77
+ handleError(fake);
78
+ }
79
+ if (res.status === 204 || res.data === '' || res.data === undefined) return { ok: true, status: res.status };
80
+ return res.data;
81
+ } catch (e) {
82
+ if (e.response) handleError(e);
83
+ throw e;
84
+ }
85
+ }
86
+
87
+ // --- Smart / composite tools (see product spec) ---
88
+ function sleep(ms) {
89
+ return new Promise((r) => setTimeout(r, ms));
90
+ }
91
+
92
+ async function pollJob(getFn, { maxAttempts = 120, intervalMs = 1500 } = {}) {
93
+ let last;
94
+ for (let i = 0; i < maxAttempts; i++) {
95
+ last = await getFn();
96
+ const status = last?.job?.status ?? last?.status;
97
+ if (status === 'success' || status === 'failed') return last;
98
+ await sleep(intervalMs);
99
+ }
100
+ return last;
101
+ }
102
+
103
+
104
+ const generatedTools = [
105
+ {
106
+ name: "get_app_jwks",
107
+ description: "Returns the Json Web Key Set (public keys) of an app. These keys are used to verify JWTs sent to app backends.",
108
+ inputSchema: {
109
+ "type": "object",
110
+ "properties": {
111
+ "appId": {
112
+ "type": "string",
113
+ "description": "The app ID."
114
+ }
115
+ },
116
+ "required": [
117
+ "appId"
118
+ ]
119
+ },
120
+ handler: async (params) => text(await apiRequest("GET", "/apps/{appId}/jwks", params)),
121
+ },
122
+ {
123
+ name: "get_asset",
124
+ description: "You can retrieve the metadata of an asset by specifying its `assetId`.",
125
+ inputSchema: {
126
+ "type": "object",
127
+ "properties": {
128
+ "assetId": {
129
+ "type": "string",
130
+ "description": "The ID of the asset."
131
+ }
132
+ },
133
+ "required": [
134
+ "assetId"
135
+ ]
136
+ },
137
+ handler: async (params) => text(await apiRequest("GET", "/assets/{assetId}", params)),
138
+ },
139
+ {
140
+ name: "get_asset_upload_job",
141
+ description: "Get the result of an asset upload job that was created using the [Create asset upload job API](https://www.canva.dev/docs/connect/api-reference/assets/create-asset-upload-job/). You might need to make multiple requests to this endpoint until you get a `success` or `failed` status. For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints).",
142
+ inputSchema: {
143
+ "type": "object",
144
+ "properties": {
145
+ "jobId": {
146
+ "type": "string",
147
+ "description": "The asset upload job ID."
148
+ }
149
+ },
150
+ "required": [
151
+ "jobId"
152
+ ]
153
+ },
154
+ handler: async (params) => text(await apiRequest("GET", "/asset-uploads/{jobId}", params)),
155
+ },
156
+ {
157
+ name: "get_brand_template",
158
+ description: "WARNING: Brand templates were migrated to use a new ID format in September 2025. If your integration stores brand template IDs, you'll need to migrate to use the new IDs. Old brand template IDs will continue to be accepted for 6 months to give you time to migrate to the new IDs. AVAILABILITY: To use this API, your integration must act on behalf of a user that's a member of a [Canva Enterprise](https://www.canva.com/enterprise/) organization. Retrieves the metadata for a brand template.",
159
+ inputSchema: {
160
+ "type": "object",
161
+ "properties": {
162
+ "brandTemplateId": {
163
+ "type": "string",
164
+ "description": "The brand template ID."
165
+ }
166
+ },
167
+ "required": [
168
+ "brandTemplateId"
169
+ ]
170
+ },
171
+ handler: async (params) => text(await apiRequest("GET", "/brand-templates/{brandTemplateId}", params)),
172
+ },
173
+ {
174
+ name: "get_design",
175
+ description: "Gets the metadata for a design. This includes owner information, URLs for editing and viewing, and thumbnail information.",
176
+ inputSchema: {
177
+ "type": "object",
178
+ "properties": {
179
+ "designId": {
180
+ "type": "string",
181
+ "description": "The design ID."
182
+ }
183
+ },
184
+ "required": [
185
+ "designId"
186
+ ]
187
+ },
188
+ handler: async (params) => text(await apiRequest("GET", "/designs/{designId}", params)),
189
+ },
190
+ {
191
+ name: "get_design_autofill_job",
192
+ description: "AVAILABILITY: To use this API, your integration must act on behalf of a user that's a member of a [Canva Enterprise](https://www.canva.com/enterprise/) organization. Get the result of a design autofill job that was created using the [Create design autofill job API](https://www.canva.dev/docs/connect/api-reference/autofills/create-design-autofill-job/). You might need to make multiple requests to this endpoint until you get a `success` or `failed` status. For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints).",
193
+ inputSchema: {
194
+ "type": "object",
195
+ "properties": {
196
+ "jobId": {
197
+ "type": "string",
198
+ "description": "The design autofill job ID."
199
+ }
200
+ },
201
+ "required": [
202
+ "jobId"
203
+ ]
204
+ },
205
+ handler: async (params) => text(await apiRequest("GET", "/autofills/{jobId}", params)),
206
+ },
207
+ {
208
+ name: "get_design_export_formats",
209
+ description: "Lists the available file formats for [exporting a design](https://www.canva.dev/docs/connect/api-reference/exports/create-design-export-job/). <Note> The available export formats depend on the design type and the types of pages in the design. In general, the available export formats returned are only those that are supported by every page type in the design. </Note>",
210
+ inputSchema: {
211
+ "type": "object",
212
+ "properties": {
213
+ "designId": {
214
+ "type": "string",
215
+ "description": "The design ID."
216
+ }
217
+ },
218
+ "required": [
219
+ "designId"
220
+ ]
221
+ },
222
+ handler: async (params) => text(await apiRequest("GET", "/designs/{designId}/export-formats", params)),
223
+ },
224
+ {
225
+ name: "get_design_export_job",
226
+ description: "Gets the result of a design export job that was created using the [Create design export job API](https://www.canva.dev/docs/connect/api-reference/exports/create-design-export-job/). If the job is successful, the response includes an array of download URLs. Depending on the design type and export format, there is a download URL for each page in the design. The download URLs are only valid for 24 hours. You might need to make multiple requests to this endpoint until you get a `success` or `failed` status. For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints).",
227
+ inputSchema: {
228
+ "type": "object",
229
+ "properties": {
230
+ "exportId": {
231
+ "type": "string",
232
+ "description": "The export job ID."
233
+ }
234
+ },
235
+ "required": [
236
+ "exportId"
237
+ ]
238
+ },
239
+ handler: async (params) => text(await apiRequest("GET", "/exports/{exportId}", params)),
240
+ },
241
+ {
242
+ name: "get_design_import_job",
243
+ description: "Gets the result of a design import job created using the [Create design import job API](https://www.canva.dev/docs/connect/api-reference/design-imports/create-design-import-job/). You might need to make multiple requests to this endpoint until you get a `success` or `failed` status. For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints).",
244
+ inputSchema: {
245
+ "type": "object",
246
+ "properties": {
247
+ "jobId": {
248
+ "type": "string",
249
+ "description": "The design import job ID."
250
+ }
251
+ },
252
+ "required": [
253
+ "jobId"
254
+ ]
255
+ },
256
+ handler: async (params) => text(await apiRequest("GET", "/imports/{jobId}", params)),
257
+ },
258
+ {
259
+ name: "get_design_pages",
260
+ description: "<Warning> This API is currently provided as a preview. Be aware of the following: - There might be unannounced breaking changes. - Any breaking changes to preview APIs won't produce a new [API version](https://www.canva.dev/docs/connect/versions/). - Public integrations that use preview APIs will not pass the review process, and can't be made available to all Canva users. </Warning> Lists metadata for pages in a design, such as page-specific thumbnails. For the specified design, you can provide `offset` and `limit` values to specify the range of pages to return. NOTE: Some design types don't have pages (for example, Canva docs).",
261
+ inputSchema: {
262
+ "type": "object",
263
+ "properties": {
264
+ "designId": {
265
+ "type": "string",
266
+ "description": "The design ID."
267
+ },
268
+ "offset": {
269
+ "type": "number",
270
+ "description": "The page index to start the range of pages to return.\n\nPages are indexed using one-based numbering, so the first page in a design has the index value `1`.\n"
271
+ },
272
+ "limit": {
273
+ "type": "number",
274
+ "description": "The number of pages to return, starting at the page index specified using the `offset` parameter."
275
+ }
276
+ },
277
+ "required": [
278
+ "designId"
279
+ ]
280
+ },
281
+ handler: async (params) => text(await apiRequest("GET", "/designs/{designId}/pages", params)),
282
+ },
283
+ {
284
+ name: "get_design_resize_job",
285
+ description: "AVAILABILITY: To use this API, your integration must act on behalf of a user that's on a Canva plan with premium features (such as Canva Pro). Gets the result of a design resize job that was created using the [Create design resize job API](https://www.canva.dev/docs/connect/api-reference/resizes/create-design-resize-job/). If the job is successful, the response includes a summary of the new resized design, including its metadata. You might need to make multiple requests to this endpoint until you get a `success` or `failed` status. For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints).",
286
+ inputSchema: {
287
+ "type": "object",
288
+ "properties": {
289
+ "jobId": {
290
+ "type": "string",
291
+ "description": "The design resize job ID."
292
+ }
293
+ },
294
+ "required": [
295
+ "jobId"
296
+ ]
297
+ },
298
+ handler: async (params) => text(await apiRequest("GET", "/resizes/{jobId}", params)),
299
+ },
300
+ {
301
+ name: "get_folder",
302
+ description: "Gets the name and other details of a folder using a folder's `folderID`.",
303
+ inputSchema: {
304
+ "type": "object",
305
+ "properties": {
306
+ "folderId": {
307
+ "type": "string",
308
+ "description": "The folder ID."
309
+ }
310
+ },
311
+ "required": [
312
+ "folderId"
313
+ ]
314
+ },
315
+ handler: async (params) => text(await apiRequest("GET", "/folders/{folderId}", params)),
316
+ },
317
+ {
318
+ name: "get_oidc_jwks",
319
+ description: "Gets the JSON Web Key Set (public keys) for OIDC. These keys are used to verify JWTs in OpenID Connect flows.",
320
+ inputSchema: {
321
+ "type": "object",
322
+ "properties": {},
323
+ "required": []
324
+ },
325
+ handler: async (params) => text(await apiRequest("GET", "/oidc/jwks", params)),
326
+ },
327
+ {
328
+ name: "get_reply",
329
+ description: "<Warning> This API is currently provided as a preview. Be aware of the following: - There might be unannounced breaking changes. - Any breaking changes to preview APIs won't produce a new [API version](https://www.canva.dev/docs/connect/versions/). - Public integrations that use preview APIs will not pass the review process, and can't be made available to all Canva users. </Warning> Gets a reply to a comment or suggestion thread on a design. For information on comments and how they're used in the Canva UI, see the [Canva Help Center](https://www.canva.com/help/comments/).",
330
+ inputSchema: {
331
+ "type": "object",
332
+ "properties": {
333
+ "designId": {
334
+ "type": "string",
335
+ "description": "The design ID."
336
+ },
337
+ "threadId": {
338
+ "type": "string",
339
+ "description": "The ID of the thread."
340
+ },
341
+ "replyId": {
342
+ "type": "string",
343
+ "description": "The ID of the reply."
344
+ }
345
+ },
346
+ "required": [
347
+ "designId",
348
+ "threadId",
349
+ "replyId"
350
+ ]
351
+ },
352
+ handler: async (params) => text(await apiRequest("GET", "/designs/{designId}/comments/{threadId}/replies/{replyId}", params)),
353
+ },
354
+ {
355
+ name: "get_thread",
356
+ description: "<Warning> This API is currently provided as a preview. Be aware of the following: - There might be unannounced breaking changes. - Any breaking changes to preview APIs won't produce a new [API version](https://www.canva.dev/docs/connect/versions/). - Public integrations that use preview APIs will not pass the review process, and can't be made available to all Canva users. </Warning> Gets a comment or suggestion thread on a design. To retrieve a reply to a comment thread, use the [Get reply](https://www.canva.dev/docs/connect/api-reference/comments/get-reply/) API. For information on comments and how they're used in the Canva UI, see the [Canva Help Center](https://www.canva.com/help/comments/).",
357
+ inputSchema: {
358
+ "type": "object",
359
+ "properties": {
360
+ "designId": {
361
+ "type": "string",
362
+ "description": "The design ID."
363
+ },
364
+ "threadId": {
365
+ "type": "string",
366
+ "description": "The ID of the thread."
367
+ }
368
+ },
369
+ "required": [
370
+ "designId",
371
+ "threadId"
372
+ ]
373
+ },
374
+ handler: async (params) => text(await apiRequest("GET", "/designs/{designId}/comments/{threadId}", params)),
375
+ },
376
+ {
377
+ name: "get_url_asset_upload_job",
378
+ description: "<Warning> This API is currently provided as a preview. Be aware of the following: - There might be unannounced breaking changes. - Any breaking changes to preview APIs won't produce a new [API version](https://www.canva.dev/docs/connect/versions/). - Public integrations that use preview APIs will not pass the review process, and can't be made available to all Canva users. </Warning> Get the result of an asset upload job that was created using the [Create asset upload job via URL API](https://www.canva.dev/docs/connect/api-reference/assets/create-url-asset-upload-job/). You might need to make multiple requests to this endpoint until you get a `success` or `failed` status. For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints).",
379
+ inputSchema: {
380
+ "type": "object",
381
+ "properties": {
382
+ "jobId": {
383
+ "type": "string",
384
+ "description": "The asset upload job ID."
385
+ }
386
+ },
387
+ "required": [
388
+ "jobId"
389
+ ]
390
+ },
391
+ handler: async (params) => text(await apiRequest("GET", "/url-asset-uploads/{jobId}", params)),
392
+ },
393
+ {
394
+ name: "get_url_import_job",
395
+ description: "Gets the result of a URL import job created using the [Create URL import job API](https://www.canva.dev/docs/connect/api-reference/design-imports/create-url-import-job/). You might need to make multiple requests to this endpoint until you get a `success` or `failed` status. For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints).",
396
+ inputSchema: {
397
+ "type": "object",
398
+ "properties": {
399
+ "jobId": {
400
+ "type": "string",
401
+ "description": "The ID of the URL import job."
402
+ }
403
+ },
404
+ "required": [
405
+ "jobId"
406
+ ]
407
+ },
408
+ handler: async (params) => text(await apiRequest("GET", "/url-imports/{jobId}", params)),
409
+ },
410
+ {
411
+ name: "get_user_capabilities",
412
+ description: "Lists the API capabilities for the user account associated with the provided access token. For more information, see [Capabilities](https://www.canva.dev/docs/connect/capabilities/).",
413
+ inputSchema: {
414
+ "type": "object",
415
+ "properties": {},
416
+ "required": []
417
+ },
418
+ handler: async (params) => text(await apiRequest("GET", "/users/me/capabilities", params)),
419
+ },
420
+ {
421
+ name: "get_user_profile",
422
+ description: "Currently, this returns the display name of the user account associated with the provided access token. More user information is expected to be included in the future.",
423
+ inputSchema: {
424
+ "type": "object",
425
+ "properties": {},
426
+ "required": []
427
+ },
428
+ handler: async (params) => text(await apiRequest("GET", "/users/me/profile", params)),
429
+ },
430
+ {
431
+ name: "list_designs",
432
+ description: "Lists metadata for all the designs in a Canva user's [projects](https://www.canva.com/help/find-designs-and-folders/). You can also: - Use search terms to filter the listed designs. - Show designs either created by, or shared with the user. - Sort the results.",
433
+ inputSchema: {
434
+ "type": "object",
435
+ "properties": {
436
+ "query": {
437
+ "type": "string",
438
+ "description": "Lets you search the user's designs, and designs shared with the user, using a search term or terms."
439
+ },
440
+ "continuation": {
441
+ "type": "string",
442
+ "description": "If the success response contains a continuation token, the list contains more designs\nyou can list. You can use this token as a query parameter and retrieve more\ndesigns from the list, for example\n`/v1/designs?continuation={continuation}`.\n\nTo retrieve all of a user's designs, you might need to make multiple requests."
443
+ },
444
+ "ownership": {
445
+ "type": "string",
446
+ "description": "Filter the list of designs based on the user's ownership of the designs."
447
+ },
448
+ "sort_by": {
449
+ "type": "string",
450
+ "description": "Sort the list of designs."
451
+ },
452
+ "limit": {
453
+ "type": "number",
454
+ "description": "The number of designs to return."
455
+ }
456
+ },
457
+ "required": []
458
+ },
459
+ handler: async (params) => text(await apiRequest("GET", "/designs", params)),
460
+ },
461
+ {
462
+ name: "list_folder_items",
463
+ description: "Lists the items in a folder, including each item's `type`. Folders can contain: - Other folders. - Designs, such as Instagram posts, Presentations, and Documents ([Canva Docs](https://www.canva.com/create/documents/)). - Image assets. Currently, video assets are not returned in the response.",
464
+ inputSchema: {
465
+ "type": "object",
466
+ "properties": {
467
+ "folderId": {
468
+ "type": "string",
469
+ "description": "The folder ID."
470
+ },
471
+ "continuation": {
472
+ "type": "string",
473
+ "description": "If the success response contains a continuation token, the folder contains more items\nyou can list. You can use this token as a query parameter and retrieve more\nitems from the list, for example\n`/v1/folders/{folderId}/items?continuation={continuation}`.\n\nTo retrieve all the items in a folder, you might need to make multiple requests."
474
+ },
475
+ "limit": {
476
+ "type": "number",
477
+ "description": "The limit parameter"
478
+ },
479
+ "item_types": {
480
+ "type": "array",
481
+ "description": "Filter the folder items to only return specified types. The available types are:\n`design`, `folder`, and `image`. To filter for more than one item type, provide a comma-\ndelimited list."
482
+ },
483
+ "sort_by": {
484
+ "type": "string",
485
+ "description": "Sort the list of folder items."
486
+ },
487
+ "pin_status": {
488
+ "type": "string",
489
+ "description": "Filter the folder items by their pinned status."
490
+ }
491
+ },
492
+ "required": [
493
+ "folderId"
494
+ ]
495
+ },
496
+ handler: async (params) => text(await apiRequest("GET", "/folders/{folderId}/items", params)),
497
+ },
498
+ {
499
+ name: "list_replies",
500
+ description: "<Warning> This API is currently provided as a preview. Be aware of the following: - There might be unannounced breaking changes. - Any breaking changes to preview APIs won't produce a new [API version](https://www.canva.dev/docs/connect/versions/). - Public integrations that use preview APIs will not pass the review process, and can't be made available to all Canva users. </Warning> Retrieves a list of replies for a comment or suggestion thread on a design. For information on comments and how they're used in the Canva UI, see the [Canva Help Center](https://www.canva.com/help/comments/).",
501
+ inputSchema: {
502
+ "type": "object",
503
+ "properties": {
504
+ "designId": {
505
+ "type": "string",
506
+ "description": "The design ID."
507
+ },
508
+ "threadId": {
509
+ "type": "string",
510
+ "description": "The ID of the thread."
511
+ },
512
+ "limit": {
513
+ "type": "number",
514
+ "description": "The limit parameter"
515
+ },
516
+ "continuation": {
517
+ "type": "string",
518
+ "description": "If the success response contains a continuation token, the list contains more items you can list. You can use this token as a query parameter and retrieve more items from the list, for example `?continuation={continuation}`.\n\nTo retrieve all items, you might need to make multiple requests."
519
+ }
520
+ },
521
+ "required": [
522
+ "designId",
523
+ "threadId"
524
+ ]
525
+ },
526
+ handler: async (params) => text(await apiRequest("GET", "/designs/{designId}/comments/{threadId}/replies", params)),
527
+ },
528
+ {
529
+ name: "user_info",
530
+ description: "Fetches the current UserInfo claims for the authorized user. This is the same fields returned by a id_token returns during authorization.",
531
+ inputSchema: {
532
+ "type": "object",
533
+ "properties": {},
534
+ "required": []
535
+ },
536
+ handler: async (params) => text(await apiRequest("GET", "/oidc/userinfo", params)),
537
+ },
538
+ {
539
+ name: "create_asset_upload_job",
540
+ description: "Starts a new [asynchronous job](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints) to upload an asset to the user's content library. Supported file types for assets are listed in the [Assets API overview](https://www.canva.dev/docs/connect/api-reference/assets/). The request format for this endpoint is an `application/octet-stream` body of bytes. Attach information about the upload using an `Asset-Upload-Metadata` header. <Note> For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints). You can check the status and get the results of asset upload jobs created with this API using the [Get asset upload job API](https://www.canva.dev/docs/connect/api-reference/assets/get-asset-upload-job/). </Note>",
541
+ inputSchema: {
542
+ "type": "object",
543
+ "properties": {
544
+ "asset_upload_metadata": {
545
+ "type": "object",
546
+ "description": "Metadata for the binary upload (maps to Asset-Upload-Metadata header as JSON)."
547
+ },
548
+ "body_base64": {
549
+ "type": "string",
550
+ "description": "Base64-encoded file bytes for application/octet-stream body."
551
+ }
552
+ },
553
+ "required": [
554
+ "asset_upload_metadata",
555
+ "body_base64"
556
+ ]
557
+ },
558
+ handler: async (params) => {
559
+ const { body_base64, asset_upload_metadata, ...rest } = params;
560
+ await ensureAccessToken();
561
+ const pathTemplate = "/asset-uploads";
562
+ let path = pathTemplate;
563
+ const pathKeys = [...path.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]);
564
+ const p = { ...rest };
565
+ for (const key of pathKeys) {
566
+ const v = p[key];
567
+ if (v === undefined || v === null || v === '') throw new Error(`Missing required path parameter: ${key}`);
568
+ path = path.replace(`{${key}}`, encodeURIComponent(String(v)));
569
+ delete p[key];
570
+ }
571
+ const url = BASE_URL.replace(/\/$/, '') + path;
572
+ const buf = Buffer.from(String(body_base64), 'base64');
573
+ const metaHeader = typeof asset_upload_metadata === 'string' ? asset_upload_metadata : JSON.stringify(asset_upload_metadata);
574
+ try {
575
+ const res = await axios.post(url, buf, {
576
+ headers: {
577
+ ...buildAuthHeaders(),
578
+ 'Content-Type': 'application/octet-stream',
579
+ 'Asset-Upload-Metadata': metaHeader,
580
+ },
581
+ timeout: 120000,
582
+ validateStatus: () => true,
583
+ });
584
+ if (res.status >= 400) { const fake = new Error(); fake.response = res; handleError(fake); }
585
+ return text(res.data);
586
+ } catch (e) { if (e.response) handleError(e); throw e; }
587
+ },
588
+ },
589
+ {
590
+ name: "create_design",
591
+ description: "Creates a new Canva design. To create a new design, you can either: - Use a preset design type. - Set height and width dimensions for a custom design. Additionally, you can also provide the `asset_id` of an asset in the user's [projects](https://www.canva.com/help/find-designs-and-folders/) to add to the new design. Currently, this only supports image assets. To list the assets in a folder in the user's projects, use the [List folder items API](https://www.canva.dev/docs/connect/api-reference/folders/list-folder-items/). NOTE: Blank designs created with this API are automatically deleted if they're not edited within 7 days. These blank designs bypass the user's Canva trash and are permanently deleted.",
592
+ inputSchema: {
593
+ "type": "object",
594
+ "properties": {
595
+ "design_type": {
596
+ "type": "object",
597
+ "description": "The desired design type."
598
+ },
599
+ "asset_id": {
600
+ "type": "string",
601
+ "description": "The ID of an asset to insert into the created design. Currently, this only supports image assets."
602
+ },
603
+ "title": {
604
+ "type": "string",
605
+ "description": "The name of the design."
606
+ }
607
+ },
608
+ "required": []
609
+ },
610
+ handler: async (params) => text(await apiRequest("POST", "/designs", params)),
611
+ },
612
+ {
613
+ name: "create_design_autofill_job",
614
+ description: "WARNING: Brand templates were migrated to use a new ID format in September 2025. If your integration stores brand template IDs, you'll need to migrate to use the new IDs. Old brand template IDs will continue to be accepted for 6 months to give you time to migrate to the new IDs. AVAILABILITY: To use this API, your integration must act on behalf of a user that's a member of a [Canva Enterprise](https://www.canva.com/enterprise/) organization. Starts a new [asynchronous job](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints) to autofill a Canva design using a brand template and input data. To get a list of input data fields, use the [Get brand template dataset API](https://www.canva.dev/docs/connect/api-reference/brand-templates/get-brand-template-dataset/). Available data field types to autofill include: - Images - Text - Charts WARNING: Chart data fields are a [preview feature](https://www.canva.dev/docs/connect/#preview-apis). There might be unannounced breaking changes to this feature which won't produce a new API version. NOTE: For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints). You can check the status and get the results of autofill jobs created with this API using the [Get design autofill job API](https://www.canva.dev/docs/connect/api-reference/autofills/get-design-autofill-job/).",
615
+ inputSchema: {
616
+ "type": "object",
617
+ "properties": {
618
+ "brand_template_id": {
619
+ "type": "string",
620
+ "description": "ID of the input brand template."
621
+ },
622
+ "title": {
623
+ "type": "string",
624
+ "description": "Title to use for the autofilled design.\n\nIf no design title is provided, the autofilled design will have the same title as the brand template."
625
+ },
626
+ "data": {
627
+ "type": "object",
628
+ "description": "Data object containing the data fields and values to autofill."
629
+ }
630
+ },
631
+ "required": [
632
+ "brand_template_id",
633
+ "data"
634
+ ]
635
+ },
636
+ handler: async (params) => text(await apiRequest("POST", "/autofills", params)),
637
+ },
638
+ {
639
+ name: "create_design_export_job",
640
+ description: "Starts a new [asynchronous job](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints) to export a file from Canva. Once the exported file is generated, you can download it using the URL(s) provided. The download URLs are only valid for 24 hours. The request requires the design ID and the exported file format type. Supported file formats (and export file type values): PDF (`pdf`), JPG (`jpg`), PNG (`png`), GIF (`gif`), Microsoft PowerPoint (`pptx`), and MP4 (`mp4`). <Note> This endpoint has the following additional rate limits: - **Integration throttle:** Each integration can export a maximum of 750 times per 5-minute window, and 5,000 times per 24-hour window. - **Document throttle:** Each document can be exported a maximum of 75 times per 5-minute window. - **User throttle:** Each user can export a maximum of 75 times per 5-minute window, and 500 times per 24-hour window. </Note> <Note> For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints). You can check the status and get the results of export jobs created with this API using the [Get design export job API](https://www.canva.dev/docs/connect/api-reference/exports/get-design-export-job/). </Note>",
641
+ inputSchema: {
642
+ "type": "object",
643
+ "properties": {
644
+ "design_id": {
645
+ "type": "string",
646
+ "description": "The design ID."
647
+ },
648
+ "format": {
649
+ "type": "object",
650
+ "description": "Details about the desired export format."
651
+ }
652
+ },
653
+ "required": [
654
+ "design_id",
655
+ "format"
656
+ ]
657
+ },
658
+ handler: async (params) => text(await apiRequest("POST", "/exports", params)),
659
+ },
660
+ {
661
+ name: "create_design_import_job",
662
+ description: "Starts a new [asynchronous job](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints) to import an external file as a new design in Canva. The request format for this endpoint has an `application/octet-stream` body of bytes, and the information about the import is provided using an `Import-Metadata` header. Supported file types for imports are listed in [Design imports overview](https://www.canva.dev/docs/connect/api-reference/design-imports/#supported-file-types). <Note> For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints). You can check the status and get the results of design import jobs created with this API using the [Get design import job API](https://www.canva.dev/docs/connect/api-reference/design-imports/get-design-import-job/). </Note>",
663
+ inputSchema: {
664
+ "type": "object",
665
+ "properties": {
666
+ "body_base64": {
667
+ "type": "string",
668
+ "description": "Base64-encoded file bytes for application/octet-stream body."
669
+ }
670
+ },
671
+ "required": [
672
+ "body_base64"
673
+ ]
674
+ },
675
+ handler: async (params) => {
676
+ const { body_base64, asset_upload_metadata, ...rest } = params;
677
+ await ensureAccessToken();
678
+ const pathTemplate = "/imports";
679
+ let path = pathTemplate;
680
+ const pathKeys = [...path.matchAll(/\{([^}]+)\}/g)].map((m) => m[1]);
681
+ const p = { ...rest };
682
+ for (const key of pathKeys) {
683
+ const v = p[key];
684
+ if (v === undefined || v === null || v === '') throw new Error(`Missing required path parameter: ${key}`);
685
+ path = path.replace(`{${key}}`, encodeURIComponent(String(v)));
686
+ delete p[key];
687
+ }
688
+ const url = BASE_URL.replace(/\/$/, '') + path;
689
+ const buf = Buffer.from(String(body_base64), 'base64');
690
+ const metaHeader = typeof asset_upload_metadata === 'string' ? asset_upload_metadata : JSON.stringify(asset_upload_metadata);
691
+ try {
692
+ const res = await axios.post(url, buf, {
693
+ headers: {
694
+ ...buildAuthHeaders(),
695
+ 'Content-Type': 'application/octet-stream',
696
+ 'Asset-Upload-Metadata': metaHeader,
697
+ },
698
+ timeout: 120000,
699
+ validateStatus: () => true,
700
+ });
701
+ if (res.status >= 400) { const fake = new Error(); fake.response = res; handleError(fake); }
702
+ return text(res.data);
703
+ } catch (e) { if (e.response) handleError(e); throw e; }
704
+ },
705
+ },
706
+ {
707
+ name: "create_design_resize_job",
708
+ description: "AVAILABILITY: To use this API, your integration must act on behalf of a user that's on a Canva plan with premium features (such as Canva Pro). Starts a new [asynchronous job](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints) to create a resized copy of a design. The new resized design is added to the top level of the user's [projects](https://www.canva.com/help/find-designs-and-folders/) (`root` folder). To resize a design into a new design, you can either: - Use a preset design type. - Set height and width dimensions for a custom design. Note the following behaviors and restrictions when resizing designs: - Designs can be resized to a maximum area of 25,000,000 pixels squared. - Resizing designs using the Connect API always creates a new design. In-place resizing is currently not available in the Connect API, but can be done in the Canva UI. - Resizing a multi-page design results in all pages of the design being resized. Resizing a section of a design is only available in the Canva UI. - [Canva docs](https://www.canva.com/create/documents/) can't be resized, and other design types can't be resized to a Canva doc. - Canva Code designs can't be resized, and other design types can't be resized to a Canva Code design. <Note> For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints). You can check the status and get the results of resize jobs created with this API using the [Get design resize job API](https://www.canva.dev/docs/connect/api-reference/resizes/get-design-resize-job/). </Note>",
709
+ inputSchema: {
710
+ "type": "object",
711
+ "properties": {
712
+ "design_id": {
713
+ "type": "string",
714
+ "description": "The design ID."
715
+ },
716
+ "design_type": {
717
+ "type": "object",
718
+ "description": "The desired design type."
719
+ }
720
+ },
721
+ "required": [
722
+ "design_id",
723
+ "design_type"
724
+ ]
725
+ },
726
+ handler: async (params) => text(await apiRequest("POST", "/resizes", params)),
727
+ },
728
+ {
729
+ name: "create_folder",
730
+ description: "Creates a folder in one of the following locations: - The top level of a Canva user's [projects](https://www.canva.com/help/find-designs-and-folders/) (using the ID `root`), - The user's Uploads folder (using the ID `uploads`), - Another folder (using the parent folder's ID). When a folder is successfully created, the endpoint returns its folder ID, along with other information.",
731
+ inputSchema: {
732
+ "type": "object",
733
+ "properties": {
734
+ "name": {
735
+ "type": "string",
736
+ "description": "The name of the folder."
737
+ },
738
+ "parent_folder_id": {
739
+ "type": "string",
740
+ "description": "The folder ID of the parent folder. To create a new folder at the top level of a user's\n[projects](https://www.canva.com/help/find-designs-and-folders/), use the ID `root`.\nTo create it in their Uploads folder, use `uploads`."
741
+ }
742
+ },
743
+ "required": [
744
+ "name",
745
+ "parent_folder_id"
746
+ ]
747
+ },
748
+ handler: async (params) => text(await apiRequest("POST", "/folders", params)),
749
+ },
750
+ {
751
+ name: "create_reply",
752
+ description: "<Warning> This API is currently provided as a preview. Be aware of the following: - There might be unannounced breaking changes. - Any breaking changes to preview APIs won't produce a new [API version](https://www.canva.dev/docs/connect/versions/). - Public integrations that use preview APIs will not pass the review process, and can't be made available to all Canva users. </Warning> Creates a reply to a comment or suggestion thread on a design. To reply to an existing thread, you must provide the ID of the thread which is returned when a thread is created, or from the `thread_id` value of an existing reply in the thread. Each thread can have a maximum of 100 replies created for it. For information on comments and how they're used in the Canva UI, see the [Canva Help Center](https://www.canva.com/help/comments/).",
753
+ inputSchema: {
754
+ "type": "object",
755
+ "properties": {
756
+ "designId": {
757
+ "type": "string",
758
+ "description": "The design ID."
759
+ },
760
+ "threadId": {
761
+ "type": "string",
762
+ "description": "The ID of the thread."
763
+ },
764
+ "message_plaintext": {
765
+ "type": "string",
766
+ "description": "The comment message of the reply in plaintext. This is the reply comment shown in the Canva UI.\n\nYou can also mention users in your message by specifying their User ID and Team ID\nusing the format `[user_id:team_id]`."
767
+ }
768
+ },
769
+ "required": [
770
+ "designId",
771
+ "threadId",
772
+ "message_plaintext"
773
+ ]
774
+ },
775
+ handler: async (params) => text(await apiRequest("POST", "/designs/{designId}/comments/{threadId}/replies", params)),
776
+ },
777
+ {
778
+ name: "create_thread",
779
+ description: "<Warning> This API is currently provided as a preview. Be aware of the following: - There might be unannounced breaking changes. - Any breaking changes to preview APIs won't produce a new [API version](https://www.canva.dev/docs/connect/versions/). - Public integrations that use preview APIs will not pass the review process, and can't be made available to all Canva users. </Warning> Creates a new comment thread on a design. For information on comments and how they're used in the Canva UI, see the [Canva Help Center](https://www.canva.com/help/comments/).",
780
+ inputSchema: {
781
+ "type": "object",
782
+ "properties": {
783
+ "designId": {
784
+ "type": "string",
785
+ "description": "The design ID."
786
+ },
787
+ "message_plaintext": {
788
+ "type": "string",
789
+ "description": "The comment message in plaintext. This is the comment body shown in the Canva UI.\n\nYou can also mention users in your message by specifying their User ID and Team ID\nusing the format `[user_id:team_id]`. If the `assignee_id` parameter is specified, you\nmust mention the assignee in the message."
790
+ },
791
+ "assignee_id": {
792
+ "type": "string",
793
+ "description": "Lets you assign the comment to a Canva user using their User ID. You _must_ mention the\nassigned user in the `message`."
794
+ }
795
+ },
796
+ "required": [
797
+ "designId",
798
+ "message_plaintext"
799
+ ]
800
+ },
801
+ handler: async (params) => text(await apiRequest("POST", "/designs/{designId}/comments", params)),
802
+ },
803
+ {
804
+ name: "create_url_asset_upload_job",
805
+ description: "<Warning> This API is currently provided as a preview. Be aware of the following: - There might be unannounced breaking changes. - Any breaking changes to preview APIs won't produce a new [API version](https://www.canva.dev/docs/connect/versions/). - Public integrations that use preview APIs will not pass the review process, and can't be made available to all Canva users. </Warning> Starts a new [asynchronous job](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints) to upload an asset from a URL to the user's content library. Supported file types for assets are listed in the [Assets API overview](https://www.canva.dev/docs/connect/api-reference/assets/). <Note> Uploading a video asset from a URL is limited to a maximum 100MB file size. For importing larger video files, use the [Create asset upload job API](https://www.canva.dev/docs/connect/api-reference/assets/create-asset-upload-job/). </Note> <Note> For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints). You can check the status and get the results of asset upload jobs created with this API using the [Get asset upload job via URL API](https://www.canva.dev/docs/connect/api-reference/assets/get-url-asset-upload-job/). </Note>",
806
+ inputSchema: {
807
+ "type": "object",
808
+ "properties": {
809
+ "name": {
810
+ "type": "string",
811
+ "description": "A name for the asset."
812
+ },
813
+ "url": {
814
+ "type": "string",
815
+ "description": "The URL of the file to import. This URL must be accessible from the internet and be publicly available."
816
+ }
817
+ },
818
+ "required": [
819
+ "name",
820
+ "url"
821
+ ]
822
+ },
823
+ handler: async (params) => text(await apiRequest("POST", "/url-asset-uploads", params)),
824
+ },
825
+ {
826
+ name: "create_url_import_job",
827
+ description: "Starts a new [asynchronous job](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints) to import an external file from a URL as a new design in Canva. Supported file types for imports are listed in [Design imports overview](https://www.canva.dev/docs/connect/api-reference/design-imports/#supported-file-types). <Note> For more information on the workflow for using asynchronous jobs, see [API requests and responses](https://www.canva.dev/docs/connect/api-requests-responses/#asynchronous-job-endpoints). You can check the status and get the results of design import jobs created with this API using the [Get URL import job API](https://www.canva.dev/docs/connect/api-reference/design-imports/get-url-import-job/). </Note>",
828
+ inputSchema: {
829
+ "type": "object",
830
+ "properties": {
831
+ "title": {
832
+ "type": "string",
833
+ "description": "A title for the design."
834
+ },
835
+ "url": {
836
+ "type": "string",
837
+ "description": "The URL of the file to import. This URL must be accessible from the internet and be publicly available."
838
+ },
839
+ "mime_type": {
840
+ "type": "string",
841
+ "description": "The MIME type of the file being imported. If not provided, Canva attempts to automatically detect the type of the file."
842
+ }
843
+ },
844
+ "required": [
845
+ "title",
846
+ "url"
847
+ ]
848
+ },
849
+ handler: async (params) => text(await apiRequest("POST", "/url-imports", params)),
850
+ },
851
+ {
852
+ name: "move_folder_item",
853
+ description: "Moves an item to another folder. You must specify the folder ID of the destination folder, as well as the ID of the item you want to move. NOTE: In some situations, a single item can exist in multiple folders. If you attempt to move an item that exists in multiple folders, the API returns an `item_in_multiple_folders` error. In this case, you must use the Canva UI to move the item to another folder.",
854
+ inputSchema: {
855
+ "type": "object",
856
+ "properties": {
857
+ "to_folder_id": {
858
+ "type": "string",
859
+ "description": "The ID of the folder you want to move the item to (the destination folder).\nIf you want to move the item to the top level of a Canva user's\n[projects](https://www.canva.com/help/find-designs-and-folders/), use the ID `root`."
860
+ },
861
+ "item_id": {
862
+ "type": "string",
863
+ "description": "The ID of the item you want to move. Currently, video assets are not supported."
864
+ }
865
+ },
866
+ "required": [
867
+ "item_id",
868
+ "to_folder_id"
869
+ ]
870
+ },
871
+ handler: async (params) => text(await apiRequest("POST", "/folders/move", params)),
872
+ },
873
+ {
874
+ name: "update_asset",
875
+ description: "You can update the name and tags of an asset by specifying its `assetId`. Updating the tags replaces all existing tags of the asset.",
876
+ inputSchema: {
877
+ "type": "object",
878
+ "properties": {
879
+ "assetId": {
880
+ "type": "string",
881
+ "description": "The ID of the asset."
882
+ },
883
+ "name": {
884
+ "type": "string",
885
+ "description": "The name of the asset. This is shown in the Canva UI.\nWhen this field is undefined or empty, nothing is updated."
886
+ },
887
+ "tags": {
888
+ "type": "array",
889
+ "description": "The replacement tags for the asset.\nWhen this field is undefined, nothing is updated."
890
+ }
891
+ },
892
+ "required": [
893
+ "assetId"
894
+ ]
895
+ },
896
+ handler: async (params) => text(await apiRequest("PATCH", "/assets/{assetId}", params)),
897
+ },
898
+ {
899
+ name: "update_folder",
900
+ description: "Updates a folder's details using its `folderID`. Currently, you can only update a folder's name.",
901
+ inputSchema: {
902
+ "type": "object",
903
+ "properties": {
904
+ "folderId": {
905
+ "type": "string",
906
+ "description": "The folder ID."
907
+ },
908
+ "name": {
909
+ "type": "string",
910
+ "description": "The folder name, as shown in the Canva UI."
911
+ }
912
+ },
913
+ "required": [
914
+ "folderId",
915
+ "name"
916
+ ]
917
+ },
918
+ handler: async (params) => text(await apiRequest("PATCH", "/folders/{folderId}", params)),
919
+ },
920
+ {
921
+ name: "delete_asset",
922
+ description: "You can delete an asset by specifying its `assetId`. This operation mirrors the behavior in the Canva UI. Deleting an item moves it to the trash. Deleting an asset doesn't remove it from designs that already use it.",
923
+ inputSchema: {
924
+ "type": "object",
925
+ "properties": {
926
+ "assetId": {
927
+ "type": "string",
928
+ "description": "The ID of the asset."
929
+ }
930
+ },
931
+ "required": [
932
+ "assetId"
933
+ ]
934
+ },
935
+ handler: async (params) => text(await apiRequest("DELETE", "/assets/{assetId}", params)),
936
+ },
937
+ {
938
+ name: "delete_folder",
939
+ description: "Deletes a folder with the specified `folderID`. Deleting a folder moves the user's content in the folder to the [Trash](https://www.canva.com/help/deleted-designs/) and content owned by other users is moved to the top level of the owner's [projects](https://www.canva.com/help/find-designs-and-folders/).",
940
+ inputSchema: {
941
+ "type": "object",
942
+ "properties": {
943
+ "folderId": {
944
+ "type": "string",
945
+ "description": "The folder ID."
946
+ }
947
+ },
948
+ "required": [
949
+ "folderId"
950
+ ]
951
+ },
952
+ handler: async (params) => text(await apiRequest("DELETE", "/folders/{folderId}", params)),
953
+ },
954
+ {
955
+ name: 'create_design_from_template',
956
+ description: "Creates a new Canva design from a brand template. Sends POST /designs with brand_template_id (Canva may require this field alongside title and optional asset_id). If the API rejects the body, use autofill_brand_template_and_wait with template data instead. Requires brand_template_id from list_brand_templates.",
957
+ inputSchema: {
958
+ type: 'object',
959
+ properties: {
960
+ brand_template_id: { type: 'string', description: 'Brand template ID from list_brand_templates.' },
961
+ title: { type: 'string', description: 'Design title.' },
962
+ asset_id: { type: 'string', description: 'Optional image asset_id to include.' },
963
+ },
964
+ required: ['brand_template_id'],
965
+ },
966
+ handler: async ({ brand_template_id, title, asset_id }) => {
967
+ const body = { brand_template_id, title, asset_id };
968
+ return text(await apiRequest('POST', '/designs', body));
969
+ },
970
+ },
971
+ {
972
+ name: 'export_design_and_wait',
973
+ description: "Exports a Canva design to PDF, PNG, JPG, GIF, PPTX, or MP4 and polls GET /exports/{exportId} until the job completes, returning download URLs. Use instead of create_design_export_job when you need the file URL without manual polling.",
974
+ inputSchema: {
975
+ type: 'object',
976
+ properties: {
977
+ design_id: { type: 'string', description: 'Design ID to export.' },
978
+ format: {
979
+ type: 'string',
980
+ description: 'Export format.',
981
+ enum: ['pdf', 'png', 'jpg', 'gif', 'pptx', 'mp4'],
982
+ },
983
+ pages: {
984
+ type: 'array',
985
+ items: { type: 'number' },
986
+ description: 'Optional 1-based page indices to export.',
987
+ },
988
+ },
989
+ required: ['design_id'],
990
+ },
991
+ handler: async (params) => {
992
+ const design_id = params.design_id;
993
+ const fmt = params.format || 'pdf';
994
+ const format = { type: fmt };
995
+ if (params.pages?.length) format.pages = params.pages;
996
+ const body = { design_id, format };
997
+ const created = await apiRequest('POST', '/exports', body);
998
+ const exportId = created?.job?.id;
999
+ if (!exportId) return text(created);
1000
+ const final = await pollJob(() => apiRequest('GET', '/exports/{exportId}', { exportId }));
1001
+ return text(final);
1002
+ },
1003
+ },
1004
+ {
1005
+ name: 'list_all_designs',
1006
+ description: "Lists all designs in the user's Canva account using continuation tokens until exhausted. Optional query, ownership filter, page size.",
1007
+ inputSchema: {
1008
+ type: 'object',
1009
+ properties: {
1010
+ query: { type: 'string', description: 'Optional search query.' },
1011
+ ownership: { type: 'string', description: 'owned | shared | any', enum: ['owned', 'shared', 'any'] },
1012
+ page_size: { type: 'number', description: 'Items per request (API limit applies).' },
1013
+ sort_by: { type: 'string', description: 'Optional sort (e.g. relevance).' },
1014
+ },
1015
+ required: [],
1016
+ },
1017
+ handler: async (params) => {
1018
+ const all = [];
1019
+ let continuation;
1020
+ do {
1021
+ const q = { continuation };
1022
+ if (params.query != null && params.query !== '') q.query = params.query;
1023
+ if (params.ownership != null && params.ownership !== '') q.ownership = params.ownership;
1024
+ if (params.sort_by != null && params.sort_by !== '') q.sort_by = params.sort_by;
1025
+ if (params.page_size != null) q.limit = params.page_size;
1026
+ const page = await apiRequest('GET', '/designs', q);
1027
+ const items = page?.items || [];
1028
+ all.push(...items);
1029
+ continuation = page?.continuation;
1030
+ } while (continuation);
1031
+ return text({ items: all, count: all.length });
1032
+ },
1033
+ },
1034
+ {
1035
+ name: 'get_design_with_pages',
1036
+ description: "Returns design metadata from GET /designs/{designId} and all pages from GET /designs/{designId}/pages (paginated via offset/limit).",
1037
+ inputSchema: {
1038
+ type: 'object',
1039
+ properties: { design_id: { type: 'string', description: 'Design ID.' } },
1040
+ required: ['design_id'],
1041
+ },
1042
+ handler: async ({ design_id }) => {
1043
+ const design = await apiRequest('GET', '/designs/{designId}', { designId: design_id });
1044
+ const pages = [];
1045
+ let offset = 1;
1046
+ const limit = 200;
1047
+ while (true) {
1048
+ const chunk = await apiRequest('GET', '/designs/{designId}/pages', {
1049
+ designId: design_id,
1050
+ offset,
1051
+ limit,
1052
+ });
1053
+ const items = chunk?.items || [];
1054
+ pages.push(...items);
1055
+ if (items.length < limit) break;
1056
+ offset += limit;
1057
+ }
1058
+ return text({ design, pages });
1059
+ },
1060
+ },
1061
+ {
1062
+ name: 'duplicate_design',
1063
+ description: "Creates a copy of a design by posting to POST /designs with source_design_id and title. If the API shape differs, use create_design_resize_job or the Canva UI to duplicate.",
1064
+ inputSchema: {
1065
+ type: 'object',
1066
+ properties: {
1067
+ design_id: { type: 'string', description: 'Source design ID.' },
1068
+ new_title: { type: 'string', description: 'Title for the duplicate.' },
1069
+ },
1070
+ required: ['design_id', 'new_title'],
1071
+ },
1072
+ handler: async ({ design_id, new_title }) =>
1073
+ text(
1074
+ await apiRequest('POST', '/designs', {
1075
+ title: new_title,
1076
+ source_design_id: design_id,
1077
+ })
1078
+ ),
1079
+ },
1080
+ {
1081
+ name: 'upload_asset_and_wait',
1082
+ description: "Uploads an asset from a URL via POST /url-asset-uploads, then polls until the job completes. Use before create_design_from_template for custom images.",
1083
+ inputSchema: {
1084
+ type: 'object',
1085
+ properties: {
1086
+ asset_url: { type: 'string', description: 'Public URL of the image or video.' },
1087
+ asset_name: { type: 'string', description: 'Display name for the asset.' },
1088
+ },
1089
+ required: ['asset_url', 'asset_name'],
1090
+ },
1091
+ handler: async ({ asset_url, asset_name }) => {
1092
+ const body = { url: asset_url, name: asset_name };
1093
+ const created = await apiRequest('POST', '/url-asset-uploads', body);
1094
+ const jobId = created?.job?.id;
1095
+ if (!jobId) return text(created);
1096
+ const final = await pollJob(() => apiRequest('GET', '/url-asset-uploads/{jobId}', { jobId }));
1097
+ return text(final);
1098
+ },
1099
+ },
1100
+ {
1101
+ name: 'list_all_assets',
1102
+ description: "Connect API lists uploads as folder items: paginates GET /folders/{folderId}/items for the uploads folder (folder_id default uploads) with item_types image. There is no GET /assets list in the OpenAPI spec.",
1103
+ inputSchema: {
1104
+ type: 'object',
1105
+ properties: {
1106
+ folder_id: { type: 'string', description: 'Defaults to uploads — the user uploads library.' },
1107
+ query: { type: 'string', description: 'Passed through if the API supports search on folder items.' },
1108
+ page_size: { type: 'number' },
1109
+ tags: { type: 'array', items: { type: 'string' }, description: 'Reserved; folder list may not support tag filter.' },
1110
+ },
1111
+ required: [],
1112
+ },
1113
+ handler: async (params) => {
1114
+ const folderId = params.folder_id || 'uploads';
1115
+ const all = [];
1116
+ let continuation;
1117
+ do {
1118
+ const q = { folderId, continuation, item_types: 'image' };
1119
+ if (params.query) q.query = params.query;
1120
+ if (params.page_size != null) q.limit = params.page_size;
1121
+ const page = await apiRequest('GET', '/folders/{folderId}/items', q);
1122
+ const items = page?.items || [];
1123
+ all.push(...items);
1124
+ continuation = page?.continuation;
1125
+ } while (continuation);
1126
+ return text({ items: all, count: all.length, folder_id: folderId });
1127
+ },
1128
+ },
1129
+ {
1130
+ name: 'get_folder_contents_full',
1131
+ description: "Lists all items in a folder via GET /folders/{folderId}/items with continuation. item_type filters map to item_types when set.",
1132
+ inputSchema: {
1133
+ type: 'object',
1134
+ properties: {
1135
+ folder_id: { type: 'string' },
1136
+ item_type: {
1137
+ type: 'string',
1138
+ description: 'design | folder | image | all',
1139
+ enum: ['design', 'folder', 'image', 'all'],
1140
+ },
1141
+ page_size: { type: 'number' },
1142
+ },
1143
+ required: ['folder_id'],
1144
+ },
1145
+ handler: async ({ folder_id, item_type, page_size }) => {
1146
+ const all = [];
1147
+ let continuation;
1148
+ const folderId = folder_id;
1149
+ do {
1150
+ const q = { folderId, continuation };
1151
+ if (page_size != null) q.limit = page_size;
1152
+ if (item_type && item_type !== 'all') {
1153
+ const t = item_type === 'asset' ? 'image' : item_type;
1154
+ q.item_types = t;
1155
+ }
1156
+ const page = await apiRequest('GET', '/folders/{folderId}/items', q);
1157
+ const items = page?.items || [];
1158
+ all.push(...items);
1159
+ continuation = page?.continuation;
1160
+ } while (continuation);
1161
+ return text({ items: all, count: all.length });
1162
+ },
1163
+ },
1164
+ {
1165
+ name: 'create_project_folder',
1166
+ description: "Creates a folder via POST /folders. Use parent_folder_id or parent_folder_id root/uploads; omit for top-level by passing parent_folder_id root.",
1167
+ inputSchema: {
1168
+ type: 'object',
1169
+ properties: {
1170
+ name: { type: 'string', description: 'Folder name.' },
1171
+ parent_folder_id: {
1172
+ type: 'string',
1173
+ description: 'Parent folder ID, or root | uploads for special roots.',
1174
+ },
1175
+ },
1176
+ required: ['name'],
1177
+ },
1178
+ handler: async ({ name, parent_folder_id }) => {
1179
+ const body = { name, parent_folder_id: parent_folder_id || 'root' };
1180
+ return text(await apiRequest('POST', '/folders', body));
1181
+ },
1182
+ },
1183
+ {
1184
+ name: 'move_design_to_folder',
1185
+ description: "Moves a design or asset using POST /folders/move (to_folder_id + item_id). item_type is accepted for documentation; API uses item_id only.",
1186
+ inputSchema: {
1187
+ type: 'object',
1188
+ properties: {
1189
+ folder_id: { type: 'string', description: 'Destination folder ID (or root).' },
1190
+ item_id: { type: 'string', description: 'Design or asset ID to move.' },
1191
+ item_type: { type: 'string', description: 'design | asset (informational).' },
1192
+ },
1193
+ required: ['folder_id', 'item_id'],
1194
+ },
1195
+ handler: async ({ folder_id, item_id }) =>
1196
+ text(await apiRequest('POST', '/folders/move', { to_folder_id: folder_id, item_id })),
1197
+ },
1198
+ {
1199
+ name: 'list_brand_templates',
1200
+ description: "Lists all brand templates with automatic continuation pagination. Use before create_design_from_template or autofill_brand_template_and_wait.",
1201
+ inputSchema: {
1202
+ type: 'object',
1203
+ properties: {
1204
+ query: { type: 'string' },
1205
+ page_size: { type: 'number', description: 'Mapped to limit per request when applicable.' },
1206
+ },
1207
+ required: [],
1208
+ },
1209
+ handler: async (params) => {
1210
+ const all = [];
1211
+ let continuation;
1212
+ do {
1213
+ const q = { continuation };
1214
+ if (params.query) q.query = params.query;
1215
+ if (params.page_size != null) q.limit = params.page_size;
1216
+ const page = await apiRequest('GET', '/brand-templates', q);
1217
+ const items = page?.items || [];
1218
+ all.push(...items);
1219
+ continuation = page?.continuation;
1220
+ } while (continuation);
1221
+ return text({ items: all, count: all.length });
1222
+ },
1223
+ },
1224
+ {
1225
+ name: 'get_brand_template_dataset',
1226
+ description: "Returns GET /brand-templates/{brandTemplateId}/dataset — autofill field definitions for a template.",
1227
+ inputSchema: {
1228
+ type: 'object',
1229
+ properties: { brand_template_id: { type: 'string' } },
1230
+ required: ['brand_template_id'],
1231
+ },
1232
+ handler: async ({ brand_template_id }) =>
1233
+ text(
1234
+ await apiRequest('GET', '/brand-templates/{brandTemplateId}/dataset', {
1235
+ brandTemplateId: brand_template_id,
1236
+ })
1237
+ ),
1238
+ },
1239
+ {
1240
+ name: 'autofill_brand_template_and_wait',
1241
+ description: "POST /autofills then polls GET /autofills/{jobId} until success or failure. data maps field names to dataset values (text/image/chart objects).",
1242
+ inputSchema: {
1243
+ type: 'object',
1244
+ properties: {
1245
+ brand_template_id: { type: 'string' },
1246
+ title: { type: 'string' },
1247
+ data: { type: 'object', description: 'Field name → DatasetValue object per Canva API.' },
1248
+ },
1249
+ required: ['brand_template_id', 'data'],
1250
+ },
1251
+ handler: async ({ brand_template_id, title, data }) => {
1252
+ const body = { brand_template_id, data };
1253
+ if (title) body.title = title;
1254
+ const created = await apiRequest('POST', '/autofills', body);
1255
+ const jobId = created?.job?.id;
1256
+ if (!jobId) return text(created);
1257
+ const final = await pollJob(() => apiRequest('GET', '/autofills/{jobId}', { jobId }));
1258
+ return text(final);
1259
+ },
1260
+ },
1261
+ {
1262
+ name: 'list_all_design_comments',
1263
+ description: "Attempts GET /designs/{designId}/comments with continuation pagination. Note: the shipped OpenAPI spec may only define POST for this path; if the API returns 404, list threads via webhooks or known thread IDs and get_thread.",
1264
+ inputSchema: {
1265
+ type: 'object',
1266
+ properties: { design_id: { type: 'string' } },
1267
+ required: ['design_id'],
1268
+ },
1269
+ handler: async ({ design_id }) => {
1270
+ const all = [];
1271
+ let continuation;
1272
+ do {
1273
+ const page = await apiRequest('GET', '/designs/{designId}/comments', {
1274
+ designId: design_id,
1275
+ continuation,
1276
+ });
1277
+ const items = page?.items || page?.comments || page?.threads || [];
1278
+ if (Array.isArray(items)) all.push(...items);
1279
+ continuation = page?.continuation;
1280
+ if (!continuation && !Array.isArray(items)) break;
1281
+ } while (continuation);
1282
+ return text({ items: all, count: all.length });
1283
+ },
1284
+ },
1285
+ {
1286
+ name: 'resolve_comment',
1287
+ description: "PATCH /designs/{designId}/comments/{commentId} marking resolved. comment_id is the thread or comment ID. Preview APIs may change; verify against current Canva docs if this fails.",
1288
+ inputSchema: {
1289
+ type: 'object',
1290
+ properties: {
1291
+ design_id: { type: 'string' },
1292
+ comment_id: { type: 'string', description: 'Thread or comment ID.' },
1293
+ },
1294
+ required: ['design_id', 'comment_id'],
1295
+ },
1296
+ handler: async ({ design_id, comment_id }) =>
1297
+ text(
1298
+ await apiRequest(
1299
+ 'PATCH',
1300
+ '/designs/{designId}/comments/{commentId}',
1301
+ { designId: design_id, commentId: comment_id },
1302
+ { rawBody: { resolved: true } }
1303
+ )
1304
+ ),
1305
+ },
1306
+ {
1307
+ name: 'get_current_user_profile',
1308
+ description: "GET /users/me — authenticated user's profile (user id, team, display context).",
1309
+ inputSchema: { type: 'object', properties: {}, required: [] },
1310
+ handler: async () => text(await apiRequest('GET', '/users/me', {})),
1311
+ },
1312
+
1313
+ ];
1314
+
1315
+ export const tools = generatedTools;