@sync.so/mcp-server 0.1.3 → 0.2.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 (261) hide show
  1. package/README.md +261 -86
  2. package/deploy/generation-status/app.html.gz +0 -0
  3. package/deploy/generation-status/release.json +6 -0
  4. package/dist/abort-signals.d.ts +6 -0
  5. package/dist/abort-signals.d.ts.map +1 -0
  6. package/dist/abort-signals.js +23 -0
  7. package/dist/account-features.d.ts +7 -0
  8. package/dist/account-features.d.ts.map +1 -0
  9. package/dist/account-features.js +48 -0
  10. package/dist/app-upload-relay.d.ts +13 -0
  11. package/dist/app-upload-relay.d.ts.map +1 -0
  12. package/dist/app-upload-relay.js +136 -0
  13. package/dist/asset-thumbnail.d.ts +3 -0
  14. package/dist/asset-thumbnail.d.ts.map +1 -0
  15. package/dist/asset-thumbnail.js +2 -0
  16. package/dist/auth/async-context.d.ts +5 -0
  17. package/dist/auth/async-context.d.ts.map +1 -0
  18. package/dist/auth/async-context.js +14 -0
  19. package/dist/auth/bearer-auth.d.ts +16 -0
  20. package/dist/auth/bearer-auth.d.ts.map +1 -0
  21. package/dist/auth/bearer-auth.js +55 -0
  22. package/dist/auth/client-credentials.d.ts +13 -0
  23. package/dist/auth/client-credentials.d.ts.map +1 -0
  24. package/dist/auth/client-credentials.js +67 -0
  25. package/dist/auth/device-auth.d.ts.map +1 -1
  26. package/dist/auth/device-auth.js +3 -2
  27. package/dist/auth/fixture-cleanup.d.ts +4 -0
  28. package/dist/auth/fixture-cleanup.d.ts.map +1 -0
  29. package/dist/auth/fixture-cleanup.js +9 -0
  30. package/dist/auth/index.d.ts +2 -0
  31. package/dist/auth/index.d.ts.map +1 -1
  32. package/dist/auth/index.js +2 -0
  33. package/dist/auth/oauth-diagnostics.d.ts +11 -0
  34. package/dist/auth/oauth-diagnostics.d.ts.map +1 -0
  35. package/dist/auth/oauth-diagnostics.js +78 -0
  36. package/dist/auth/oauth-provider.d.ts +16 -0
  37. package/dist/auth/oauth-provider.d.ts.map +1 -0
  38. package/dist/auth/oauth-provider.js +107 -0
  39. package/dist/auth/resource-metadata.d.ts +2 -0
  40. package/dist/auth/resource-metadata.d.ts.map +1 -0
  41. package/dist/auth/resource-metadata.js +3 -0
  42. package/dist/auth/token-verification.d.ts +11 -0
  43. package/dist/auth/token-verification.d.ts.map +1 -0
  44. package/dist/auth/token-verification.js +99 -0
  45. package/dist/auth/verification-cache.d.ts +8 -0
  46. package/dist/auth/verification-cache.d.ts.map +1 -0
  47. package/dist/auth/verification-cache.js +111 -0
  48. package/dist/chatgpt-analytics.d.ts +22 -0
  49. package/dist/chatgpt-analytics.d.ts.map +1 -0
  50. package/dist/chatgpt-analytics.js +172 -0
  51. package/dist/chatgpt-app.d.ts +15 -0
  52. package/dist/chatgpt-app.d.ts.map +1 -0
  53. package/dist/chatgpt-app.js +133 -0
  54. package/dist/chatgpt-release.d.ts +12 -0
  55. package/dist/chatgpt-release.d.ts.map +1 -0
  56. package/dist/chatgpt-release.js +30 -0
  57. package/dist/cli.js +30 -16
  58. package/dist/client-profile.d.ts +10 -0
  59. package/dist/client-profile.d.ts.map +1 -0
  60. package/dist/client-profile.js +45 -0
  61. package/dist/config.d.ts +17 -0
  62. package/dist/config.d.ts.map +1 -1
  63. package/dist/config.js +52 -0
  64. package/dist/generation-status-html.d.ts +2 -0
  65. package/dist/generation-status-html.d.ts.map +1 -0
  66. package/dist/generation-status-html.js +26 -0
  67. package/dist/generation-status.d.ts +6 -0
  68. package/dist/generation-status.d.ts.map +1 -0
  69. package/dist/generation-status.js +37 -0
  70. package/dist/http-client.d.ts +45 -1
  71. package/dist/http-client.d.ts.map +1 -1
  72. package/dist/http-client.js +171 -34
  73. package/dist/http-server.d.ts +53 -0
  74. package/dist/http-server.d.ts.map +1 -0
  75. package/dist/http-server.js +753 -0
  76. package/dist/http-session.d.ts +18 -0
  77. package/dist/http-session.d.ts.map +1 -0
  78. package/dist/http-session.js +189 -0
  79. package/dist/index.d.ts +4 -3
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +3 -2
  82. package/dist/oauth-response.d.ts +3 -0
  83. package/dist/oauth-response.d.ts.map +1 -0
  84. package/dist/oauth-response.js +30 -0
  85. package/dist/openapi/index.d.ts +2 -2
  86. package/dist/openapi/index.d.ts.map +1 -1
  87. package/dist/openapi/index.js +1 -1
  88. package/dist/openapi/parser.d.ts +2 -0
  89. package/dist/openapi/parser.d.ts.map +1 -1
  90. package/dist/openapi/parser.js +41 -1
  91. package/dist/openapi/types.d.ts +3 -0
  92. package/dist/openapi/types.d.ts.map +1 -1
  93. package/dist/organization-selection.d.ts +61 -0
  94. package/dist/organization-selection.d.ts.map +1 -0
  95. package/dist/organization-selection.js +142 -0
  96. package/dist/rate-limits.d.ts +13 -0
  97. package/dist/rate-limits.d.ts.map +1 -0
  98. package/dist/rate-limits.js +67 -0
  99. package/dist/revision.d.ts +6 -0
  100. package/dist/revision.d.ts.map +1 -0
  101. package/dist/revision.js +17 -0
  102. package/dist/runtime-diagnostics.d.ts +32 -0
  103. package/dist/runtime-diagnostics.d.ts.map +1 -0
  104. package/dist/runtime-diagnostics.js +58 -0
  105. package/dist/server.d.ts +23 -2
  106. package/dist/server.d.ts.map +1 -1
  107. package/dist/server.js +893 -32
  108. package/dist/session-registry.d.ts +54 -0
  109. package/dist/session-registry.d.ts.map +1 -0
  110. package/dist/session-registry.js +202 -0
  111. package/dist/session-telemetry.d.ts +13 -0
  112. package/dist/session-telemetry.d.ts.map +1 -0
  113. package/dist/session-telemetry.js +87 -0
  114. package/dist/spend-confirmation.d.ts +21 -0
  115. package/dist/spend-confirmation.d.ts.map +1 -0
  116. package/dist/spend-confirmation.js +105 -0
  117. package/dist/tool-analytics-context.d.ts +11 -0
  118. package/dist/tool-analytics-context.d.ts.map +1 -0
  119. package/dist/tool-analytics-context.js +8 -0
  120. package/dist/tool-names.d.ts +35 -0
  121. package/dist/tool-names.d.ts.map +1 -0
  122. package/dist/tool-names.js +133 -0
  123. package/dist/tools/account-context.d.ts +5 -0
  124. package/dist/tools/account-context.d.ts.map +1 -0
  125. package/dist/tools/account-context.js +131 -0
  126. package/dist/tools/account-management.d.ts +4 -0
  127. package/dist/tools/account-management.d.ts.map +1 -0
  128. package/dist/tools/account-management.js +111 -0
  129. package/dist/tools/app-tools.d.ts +31 -0
  130. package/dist/tools/app-tools.d.ts.map +1 -0
  131. package/dist/tools/app-tools.js +350 -0
  132. package/dist/tools/asset-analysis.d.ts +4 -0
  133. package/dist/tools/asset-analysis.d.ts.map +1 -0
  134. package/dist/tools/asset-analysis.js +172 -0
  135. package/dist/tools/asset-creators.d.ts +4 -0
  136. package/dist/tools/asset-creators.d.ts.map +1 -0
  137. package/dist/tools/asset-creators.js +38 -0
  138. package/dist/tools/audio-trim.d.ts +6 -0
  139. package/dist/tools/audio-trim.d.ts.map +1 -0
  140. package/dist/tools/audio-trim.js +79 -0
  141. package/dist/tools/batch-results.d.ts +16 -0
  142. package/dist/tools/batch-results.d.ts.map +1 -0
  143. package/dist/tools/batch-results.js +103 -0
  144. package/dist/tools/batches.d.ts +28 -0
  145. package/dist/tools/batches.d.ts.map +1 -0
  146. package/dist/tools/batches.js +293 -0
  147. package/dist/tools/dialogue.d.ts +6 -0
  148. package/dist/tools/dialogue.d.ts.map +1 -0
  149. package/dist/tools/dialogue.js +309 -0
  150. package/dist/tools/estimate-duration.d.ts +35 -0
  151. package/dist/tools/estimate-duration.d.ts.map +1 -0
  152. package/dist/tools/estimate-duration.js +66 -0
  153. package/dist/tools/feedback.d.ts +14 -0
  154. package/dist/tools/feedback.d.ts.map +1 -0
  155. package/dist/tools/feedback.js +72 -0
  156. package/dist/tools/generated-adapters.d.ts +25 -0
  157. package/dist/tools/generated-adapters.d.ts.map +1 -0
  158. package/dist/tools/generated-adapters.js +143 -0
  159. package/dist/tools/generation-context.d.ts +14 -0
  160. package/dist/tools/generation-context.d.ts.map +1 -0
  161. package/dist/tools/generation-context.js +39 -0
  162. package/dist/tools/generation-continuation.d.ts +4 -0
  163. package/dist/tools/generation-continuation.d.ts.map +1 -0
  164. package/dist/tools/generation-continuation.js +124 -0
  165. package/dist/tools/generation-costs.d.ts +6 -0
  166. package/dist/tools/generation-costs.d.ts.map +1 -0
  167. package/dist/tools/generation-costs.js +67 -0
  168. package/dist/tools/generation-output.d.ts +3 -0
  169. package/dist/tools/generation-output.d.ts.map +1 -0
  170. package/dist/tools/generation-output.js +39 -0
  171. package/dist/tools/generator.d.ts +35 -2
  172. package/dist/tools/generator.d.ts.map +1 -1
  173. package/dist/tools/generator.js +127 -9
  174. package/dist/tools/job-status.d.ts +35 -0
  175. package/dist/tools/job-status.d.ts.map +1 -0
  176. package/dist/tools/job-status.js +92 -0
  177. package/dist/tools/job-wait.d.ts +41 -0
  178. package/dist/tools/job-wait.d.ts.map +1 -0
  179. package/dist/tools/job-wait.js +84 -0
  180. package/dist/tools/jobs.d.ts +3 -0
  181. package/dist/tools/jobs.d.ts.map +1 -0
  182. package/dist/tools/jobs.js +210 -0
  183. package/dist/tools/media-upload.d.ts +27 -0
  184. package/dist/tools/media-upload.d.ts.map +1 -0
  185. package/dist/tools/media-upload.js +247 -0
  186. package/dist/tools/model-options.d.ts +36 -0
  187. package/dist/tools/model-options.d.ts.map +1 -0
  188. package/dist/tools/model-options.js +48 -0
  189. package/dist/tools/organization-choice.d.ts +9 -0
  190. package/dist/tools/organization-choice.d.ts.map +1 -0
  191. package/dist/tools/organization-choice.js +136 -0
  192. package/dist/tools/organizations.d.ts +56 -0
  193. package/dist/tools/organizations.d.ts.map +1 -0
  194. package/dist/tools/organizations.js +235 -0
  195. package/dist/tools/output-schemas.d.ts +54 -0
  196. package/dist/tools/output-schemas.d.ts.map +1 -0
  197. package/dist/tools/output-schemas.js +58 -0
  198. package/dist/tools/overrides.d.ts +3 -0
  199. package/dist/tools/overrides.d.ts.map +1 -1
  200. package/dist/tools/overrides.js +113 -15
  201. package/dist/tools/playback-metadata.d.ts +4 -0
  202. package/dist/tools/playback-metadata.d.ts.map +1 -0
  203. package/dist/tools/playback-metadata.js +51 -0
  204. package/dist/tools/project-destination.d.ts +13 -0
  205. package/dist/tools/project-destination.d.ts.map +1 -0
  206. package/dist/tools/project-destination.js +45 -0
  207. package/dist/tools/project-history.d.ts +6 -0
  208. package/dist/tools/project-history.d.ts.map +1 -0
  209. package/dist/tools/project-history.js +45 -0
  210. package/dist/tools/project-intent.d.ts +8 -0
  211. package/dist/tools/project-intent.d.ts.map +1 -0
  212. package/dist/tools/project-intent.js +87 -0
  213. package/dist/tools/running-submissions.d.ts +7 -0
  214. package/dist/tools/running-submissions.d.ts.map +1 -0
  215. package/dist/tools/running-submissions.js +91 -0
  216. package/dist/tools/speech.d.ts +6 -0
  217. package/dist/tools/speech.d.ts.map +1 -0
  218. package/dist/tools/speech.js +116 -0
  219. package/dist/tools/studio-draft.d.ts +7 -0
  220. package/dist/tools/studio-draft.d.ts.map +1 -0
  221. package/dist/tools/studio-draft.js +195 -0
  222. package/dist/tools/studio-view-contract.d.ts +184 -0
  223. package/dist/tools/studio-view-contract.d.ts.map +1 -0
  224. package/dist/tools/studio-view-contract.js +67 -0
  225. package/dist/tools/studio-view.d.ts +6 -0
  226. package/dist/tools/studio-view.d.ts.map +1 -0
  227. package/dist/tools/studio-view.js +126 -0
  228. package/dist/tools/timeline-lipsync.d.ts +316 -0
  229. package/dist/tools/timeline-lipsync.d.ts.map +1 -0
  230. package/dist/tools/timeline-lipsync.js +304 -0
  231. package/dist/tools/translation-preview.d.ts +8 -0
  232. package/dist/tools/translation-preview.d.ts.map +1 -0
  233. package/dist/tools/translation-preview.js +146 -0
  234. package/dist/tools/translation.d.ts +6 -0
  235. package/dist/tools/translation.d.ts.map +1 -0
  236. package/dist/tools/translation.js +131 -0
  237. package/dist/tools/upload-widget.d.ts +13 -0
  238. package/dist/tools/upload-widget.d.ts.map +1 -0
  239. package/dist/tools/upload-widget.js +788 -0
  240. package/dist/toolsets.d.ts +18 -0
  241. package/dist/toolsets.d.ts.map +1 -0
  242. package/dist/toolsets.js +157 -0
  243. package/dist/update-check.d.ts +38 -0
  244. package/dist/update-check.d.ts.map +1 -0
  245. package/dist/update-check.js +150 -0
  246. package/dist/upload-runtime.d.ts +45 -0
  247. package/dist/upload-runtime.d.ts.map +1 -0
  248. package/dist/upload-runtime.js +156 -0
  249. package/dist/upload-source.d.ts +4 -0
  250. package/dist/upload-source.d.ts.map +1 -0
  251. package/dist/upload-source.js +46 -0
  252. package/dist/version.d.ts +2 -0
  253. package/dist/version.d.ts.map +1 -0
  254. package/dist/version.js +3 -0
  255. package/package.json +79 -33
  256. package/dist/openapi/parser.test.d.ts +0 -2
  257. package/dist/openapi/parser.test.d.ts.map +0 -1
  258. package/dist/openapi/parser.test.js +0 -188
  259. package/dist/tools/generator.test.d.ts +0 -2
  260. package/dist/tools/generator.test.d.ts.map +0 -1
  261. package/dist/tools/generator.test.js +0 -178
@@ -0,0 +1,6 @@
1
+ import type { HttpClient } from '../http-client.js';
2
+ import type { ParsedOperation } from '../openapi/types.js';
3
+ import { type McpToolDefinition } from './generator.js';
4
+ export declare function projectHistoryOperations(operations: ParsedOperation[]): ParsedOperation[];
5
+ export declare function createProjectHistoryTools(operations: ParsedOperation[], httpClient: HttpClient): McpToolDefinition[];
6
+ //# sourceMappingURL=project-history.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-history.d.ts","sourceRoot":"","sources":["../../src/tools/project-history.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEvE,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,eAAe,EAAE,CAazF;AAED,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,eAAe,EAAE,EAC7B,UAAU,EAAE,UAAU,GACrB,iBAAiB,EAAE,CA8CrB"}
@@ -0,0 +1,45 @@
1
+ import { generateTools } from './generator.js';
2
+ export function projectHistoryOperations(operations) {
3
+ const history = operations.find((operation) => operation.operationId === 'GenerateController_getGenerations' &&
4
+ operation.method === 'get' &&
5
+ operation.parameters.some((parameter) => parameter.in === 'query' && parameter.name === 'projectId'));
6
+ const project = operations.find((operation) => operation.operationId === 'ProjectsController_get' && operation.method === 'get');
7
+ return history && project ? [history, project] : [];
8
+ }
9
+ export function createProjectHistoryTools(operations, httpClient) {
10
+ const [history, project] = generateTools(projectHistoryOperations(operations), httpClient);
11
+ if (!history || !project)
12
+ return [];
13
+ return [
14
+ {
15
+ name: 'projects_list-generations',
16
+ title: 'Read project history',
17
+ description: 'Read generation history for an accessible Sync project. Requires a projectId returned by projects_list. Pass limit and the last returned generation id as cursor to load another page. These are the same records used by the Sync web app.',
18
+ inputSchema: {
19
+ ...history.inputSchema,
20
+ projectId: history.inputSchema.projectId.nonoptional(),
21
+ },
22
+ annotations: history.annotations,
23
+ handler: async (args, context) => {
24
+ // Check access independently before a read that an old API instance may
25
+ // treat as an organization feed during a rolling deployment.
26
+ const selected = await project.handler({ id: args.projectId }, context);
27
+ if (!selected ||
28
+ typeof selected !== 'object' ||
29
+ !('id' in selected) ||
30
+ selected.id !== args.projectId) {
31
+ throw new Error('Sync could not verify access to this project. Refresh and try again.');
32
+ }
33
+ const result = await history.handler(args, context);
34
+ if (!Array.isArray(result) ||
35
+ result.some((item) => !item ||
36
+ typeof item !== 'object' ||
37
+ !('projectId' in item) ||
38
+ item.projectId !== args.projectId)) {
39
+ throw new Error('Project history is temporarily unavailable. Sync did not return history scoped to this project.');
40
+ }
41
+ return result;
42
+ },
43
+ },
44
+ ];
45
+ }
@@ -0,0 +1,8 @@
1
+ import { ApiRequestError } from '../http-client.js';
2
+ import type { McpToolDefinition } from './generator.js';
3
+ /** Whether writing into projectId is allowed: explicit intent, or a project created here. */
4
+ export declare function projectUseAllowed(projectId: unknown, projectIntent: unknown): boolean;
5
+ /** The refusal every write tool gives for an existing project without explicit intent. */
6
+ export declare function projectIntentError(prefix?: string): ApiRequestError;
7
+ export declare function withProjectIntent(tool: McpToolDefinition): McpToolDefinition;
8
+ //# sourceMappingURL=project-intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-intent.d.ts","sourceRoot":"","sources":["../../src/tools/project-intent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,KAAK,EAAE,iBAAiB,EAAsB,MAAM,gBAAgB,CAAC;AA+B5E,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,OAAO,EAAE,aAAa,EAAE,OAAO,GAAG,OAAO,CAMrF;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,MAAM,SAAK,GAAG,eAAe,CAE/D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,CAmD5E"}
@@ -0,0 +1,87 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { z } from 'zod';
3
+ import { getAuthOwner, getAuthToken } from '../auth/async-context.js';
4
+ import { ApiRequestError } from '../http-client.js';
5
+ import { projectTargetTools } from './generated-adapters.js';
6
+ // Clients holding a tool list from before projectIntent existed create a project, then
7
+ // pass its id without projectIntent. Accept only projects this same user just created.
8
+ const CREATED_PROJECT_TTL_MS = 30 * 60_000;
9
+ const createdProjects = new Map();
10
+ function createdProjectKey(projectId) {
11
+ const owner = getAuthOwner() ??
12
+ createHash('sha256')
13
+ .update(getAuthToken() ?? '')
14
+ .digest('hex');
15
+ return `${owner}\0${projectId}`;
16
+ }
17
+ function rememberCreatedProject(result) {
18
+ const project = z.object({ id: z.string().min(1) }).safeParse(result);
19
+ if (!project.success)
20
+ return;
21
+ const now = Date.now();
22
+ for (const [key, expires] of createdProjects)
23
+ if (expires <= now)
24
+ createdProjects.delete(key);
25
+ createdProjects.set(createdProjectKey(project.data.id), now + CREATED_PROJECT_TTL_MS);
26
+ }
27
+ function createdHere(projectId) {
28
+ return (createdProjects.get(createdProjectKey(projectId)) ?? 0) > Date.now();
29
+ }
30
+ const PROJECT_INTENT_MESSAGE = 'Existing project use requires an explicit user selection. Create a new project for a new workflow request; do not reuse a matching name or the active editor.';
31
+ /** Whether writing into projectId is allowed: explicit intent, or a project created here. */
32
+ export function projectUseAllowed(projectId, projectIntent) {
33
+ return (projectId === undefined ||
34
+ projectIntent === 'explicit' ||
35
+ (typeof projectId === 'string' && createdHere(projectId)));
36
+ }
37
+ /** The refusal every write tool gives for an existing project without explicit intent. */
38
+ export function projectIntentError(prefix = '') {
39
+ return new ApiRequestError(`${prefix}${PROJECT_INTENT_MESSAGE}`, 400, 'PROJECT_INTENT_REQUIRED');
40
+ }
41
+ export function withProjectIntent(tool) {
42
+ if (tool.name === 'projects_create' && tool.resultFormat !== 'mcp')
43
+ return {
44
+ ...tool,
45
+ handler: async (args, context) => {
46
+ const result = await tool.handler(args, context);
47
+ rememberCreatedProject(result);
48
+ return result;
49
+ },
50
+ };
51
+ if (!('projectId' in tool.inputSchema) ||
52
+ tool.annotations?.readOnlyHint ||
53
+ projectTargetTools.has(tool))
54
+ return tool;
55
+ const ui = tool.meta?.ui;
56
+ if (ui?.visibility?.length === 1 && ui.visibility[0] === 'app')
57
+ return tool;
58
+ const definition = {
59
+ ...tool,
60
+ description: tool.description +
61
+ ' When supplying projectId, projectIntent=explicit is required: the user must explicitly ask to work in that project, select it in the interface, or this must be the project just created for this request. A matching workflow/name or an already-open editor is never permission to modify it. Default new work to a new project.',
62
+ inputSchema: {
63
+ ...tool.inputSchema,
64
+ projectIntent: z
65
+ .literal('explicit')
66
+ .optional()
67
+ .describe('Required with projectId. Set only for an explicit user project selection/update, or the new project just created for this request. Never infer intent from a matching project name, workflow name or active editor.'),
68
+ },
69
+ };
70
+ const input = (args) => {
71
+ const { projectIntent, ...fields } = args;
72
+ if (!projectUseAllowed(fields.projectId, projectIntent))
73
+ throw projectIntentError();
74
+ return fields;
75
+ };
76
+ return tool.resultFormat === 'mcp'
77
+ ? {
78
+ ...definition,
79
+ resultFormat: 'mcp',
80
+ handler: (args, context) => tool.handler(input(args), context),
81
+ }
82
+ : {
83
+ ...definition,
84
+ resultFormat: 'json',
85
+ handler: (args, context) => tool.handler(input(args), context),
86
+ };
87
+ }
@@ -0,0 +1,7 @@
1
+ import { type HttpClient } from '../http-client.js';
2
+ export type RunningSubmissions = {
3
+ /** Runs `submit` unless an identical request is still submitting or rendering. */
4
+ once: (request: unknown, submit: () => Promise<unknown>, signal?: AbortSignal) => Promise<unknown>;
5
+ };
6
+ export declare function createRunningSubmissions(http: HttpClient): RunningSubmissions;
7
+ //# sourceMappingURL=running-submissions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"running-submissions.d.ts","sourceRoot":"","sources":["../../src/tools/running-submissions.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAerE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,kFAAkF;IAClF,IAAI,EAAE,CACJ,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,EAC9B,MAAM,CAAC,EAAE,WAAW,KACjB,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB,CAAC;AAYF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,UAAU,GAAG,kBAAkB,CA8D7E"}
@@ -0,0 +1,91 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { getAuthOwner, getAuthToken } from '../auth/async-context.js';
3
+ import { ApiRequestError } from '../http-client.js';
4
+ import { chosenOrganization } from '../organization-selection.js';
5
+ // Agents resubmit a request every few seconds while its generation is still rendering,
6
+ // uploading the same files again each time. A free plan runs one generation at a time,
7
+ // so every resubmission failed with concurrency_limit_reached. Answer an identical
8
+ // request from the same user with the generation that is still running instead.
9
+ const REMEMBER_MS = 30 * 60_000;
10
+ const TERMINAL_STATUSES = new Set(['COMPLETED', 'FAILED', 'REJECTED', 'CANCELED', 'CANCELLED']);
11
+ function untilAborted(promise, signal) {
12
+ if (!signal)
13
+ return promise;
14
+ signal.throwIfAborted();
15
+ return new Promise((resolve, reject) => {
16
+ const abort = () => reject(signal.reason);
17
+ signal.addEventListener('abort', abort, { once: true });
18
+ promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));
19
+ });
20
+ }
21
+ export function createRunningSubmissions(http) {
22
+ const entries = new Map();
23
+ const keyOf = (request) => {
24
+ const owner = getAuthOwner() ??
25
+ createHash('sha256')
26
+ .update(getAuthToken() ?? '')
27
+ .digest('hex');
28
+ // The same request in another organization is another generation.
29
+ return createHash('sha256')
30
+ .update(JSON.stringify([owner, chosenOrganization()?.id ?? null, request]))
31
+ .digest('hex');
32
+ };
33
+ // A failed read leaves the entry in place: the generation may still be running.
34
+ async function stillRunning(id, signal) {
35
+ try {
36
+ const generation = await http.request('get', `/v2/generate/${encodeURIComponent(id)}`, {
37
+ signal,
38
+ });
39
+ const status = generation?.status;
40
+ return typeof status === 'string' && !TERMINAL_STATUSES.has(status) ? generation : undefined;
41
+ }
42
+ catch (error) {
43
+ if (error instanceof ApiRequestError && error.status === 404)
44
+ return undefined;
45
+ throw error;
46
+ }
47
+ }
48
+ return {
49
+ async once(request, submit, signal) {
50
+ const key = keyOf(request);
51
+ const entry = entries.get(key);
52
+ if (entry && entry.expires > Date.now()) {
53
+ // A failed or uncertain first attempt is the answer for every call that waited on it.
54
+ if (entry.submitting)
55
+ return untilAborted(entry.submitting, signal);
56
+ const generation = await stillRunning(entry.id, signal);
57
+ if (generation)
58
+ return generation;
59
+ // Another identical call may have started a submission during the read.
60
+ const current = entries.get(key);
61
+ if (current?.submitting)
62
+ return untilAborted(current.submitting, signal);
63
+ }
64
+ if (entries.get(key) === entry)
65
+ entries.delete(key);
66
+ const now = Date.now();
67
+ for (const [other, { expires }] of entries)
68
+ if (expires <= now)
69
+ entries.delete(other);
70
+ const submitting = submit();
71
+ const reservation = { submitting, expires: now + REMEMBER_MS };
72
+ entries.set(key, reservation);
73
+ try {
74
+ const generation = await submitting;
75
+ const id = generation?.id;
76
+ if (entries.get(key) === reservation) {
77
+ if (typeof id === 'string')
78
+ entries.set(key, { id, expires: Date.now() + REMEMBER_MS });
79
+ else
80
+ entries.delete(key);
81
+ }
82
+ return generation;
83
+ }
84
+ catch (error) {
85
+ if (entries.get(key) === reservation)
86
+ entries.delete(key);
87
+ throw error;
88
+ }
89
+ },
90
+ };
91
+ }
@@ -0,0 +1,6 @@
1
+ import type { HttpClient } from '../http-client.js';
2
+ import type { ParsedOperation } from '../openapi/types.js';
3
+ import type { McpToolDefinition } from './generator.js';
4
+ export declare function speechOperations(operations: ParsedOperation[]): ParsedOperation[];
5
+ export declare function createSpeechTools(operations: ParsedOperation[], httpClient: HttpClient): McpToolDefinition[];
6
+ //# sourceMappingURL=speech.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"speech.d.ts","sourceRoot":"","sources":["../../src/tools/speech.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAIxD,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,eAAe,EAAE,qBAI7D;AA+CD,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,eAAe,EAAE,EAC7B,UAAU,EAAE,UAAU,GACrB,iBAAiB,EAAE,CA4ErB"}
@@ -0,0 +1,116 @@
1
+ import { z } from 'zod';
2
+ const createPath = '/v2/tts/jobs';
3
+ const readPath = '/v2/tts/jobs/{id}';
4
+ export function speechOperations(operations) {
5
+ return operations.filter((operation) => operation.path === createPath || operation.path === readPath);
6
+ }
7
+ const projectSchema = z.object({ projectId: z.uuid() });
8
+ const requestSchema = projectSchema.extend({
9
+ idempotencyKey: z
10
+ .uuid()
11
+ .describe('Persist this request identity before synthesis. Reuse it with the identical script and voice after an uncertain response.'),
12
+ script: z.string().min(1).max(5000),
13
+ voiceId: z
14
+ .string()
15
+ .min(1)
16
+ .describe('An id returned by voices_list. Restricted voices cannot synthesize.'),
17
+ stability: z.number().min(0).max(1).optional(),
18
+ similarityBoost: z.number().min(0).max(1).optional(),
19
+ });
20
+ const jobSchema = z.object({
21
+ id: z.uuid(),
22
+ status: z.enum(['PENDING', 'PROCESSING', 'SUCCESS', 'FAILED']),
23
+ url: z.url().nullable(),
24
+ duration: z.number().finite().nonnegative().nullable(),
25
+ error: z.object({ code: z.string(), message: z.string() }).nullable(),
26
+ createdAt: z.string(),
27
+ startedAt: z.string().nullable(),
28
+ finishedAt: z.string().nullable(),
29
+ });
30
+ // Return only the fields needed for Studio's speech quote and admission UI.
31
+ const billingSchema = z.object({
32
+ plan: z.string().nullable(),
33
+ billingVersion: z.enum(['v2', 'v3']).optional(),
34
+ hasActiveCredits: z.boolean(),
35
+ freeTtsExhausted: z.boolean(),
36
+ freeGenerationsExhausted: z.boolean().optional(),
37
+ paymentFailureCountExceededLimit: z.boolean(),
38
+ freeMonthlyLimits: z
39
+ .object({
40
+ tts: z.object({ used: z.number().nonnegative(), max: z.number().nonnegative() }),
41
+ generations: z
42
+ .object({ used: z.number().nonnegative(), max: z.number().nonnegative() })
43
+ .optional(),
44
+ sync3: z.object({ used: z.number().nonnegative(), max: z.number().nonnegative() }).optional(),
45
+ })
46
+ .nullable(),
47
+ });
48
+ const optionsSchema = billingSchema.extend({ available: z.literal(true) });
49
+ const readHints = { readOnlyHint: true, destructiveHint: false, openWorldHint: false };
50
+ export function createSpeechTools(operations, httpClient) {
51
+ const selected = speechOperations(operations);
52
+ if (!selected.some((op) => op.path === createPath && op.method === 'post') ||
53
+ !selected.some((op) => op.path === readPath && op.method === 'get'))
54
+ return [];
55
+ async function checkProject(args, signal) {
56
+ signal?.throwIfAborted();
57
+ const { projectId } = projectSchema.parse(args);
58
+ const project = z
59
+ .object({ id: z.uuid() })
60
+ .parse(await httpClient.request('get', `/v2/projects/${projectId}`, { signal }));
61
+ if (project.id !== projectId)
62
+ throw new Error('Could not verify project access.');
63
+ signal?.throwIfAborted();
64
+ }
65
+ return [
66
+ {
67
+ name: 'speech-previews_get-options',
68
+ title: 'Check speech preview availability',
69
+ description: 'Read speech-preview availability and the connected organization’s billing mode/free allowance for a Studio quote. Does not synthesize or change billing.',
70
+ inputSchema: projectSchema.shape,
71
+ outputSchema: optionsSchema.shape,
72
+ annotations: readHints,
73
+ handler: async (args, context) => {
74
+ await checkProject(args, context?.signal);
75
+ const billing = billingSchema.parse(await httpClient.request('get', '/v2/billing', { signal: context?.signal }));
76
+ return { ...billing, available: true };
77
+ },
78
+ },
79
+ {
80
+ name: 'speech-previews_create',
81
+ title: 'Generate a speech preview',
82
+ description: 'Synthesize the confirmed script and selected available voice into a durable audio job for the project. This is a potentially paid action. Persist idempotencyKey before calling, reuse that same key and identical input after uncertain admission, and poll speech-previews_get by returned id. Never create again merely because attaching the resulting audio failed.',
83
+ inputSchema: requestSchema.shape,
84
+ outputSchema: jobSchema.shape,
85
+ annotations: {
86
+ readOnlyHint: false,
87
+ destructiveHint: true,
88
+ openWorldHint: true,
89
+ // Paid, and only the model-chosen idempotencyKey deduplicates it: a retry with a new key bills again.
90
+ idempotentHint: false,
91
+ },
92
+ handler: async (args, context) => {
93
+ const { projectId: _projectId, idempotencyKey, ...body } = requestSchema.parse(args);
94
+ await checkProject(args, context?.signal);
95
+ return jobSchema.parse(await httpClient.request('post', createPath, {
96
+ body: { ...body, provider: 'elevenlabs' },
97
+ headers: { 'Idempotency-Key': idempotencyKey },
98
+ signal: context?.signal,
99
+ }));
100
+ },
101
+ },
102
+ {
103
+ name: 'speech-previews_get',
104
+ title: 'Check a speech preview',
105
+ description: 'Read the same durable speech job until SUCCESS or FAILED. Reads do not synthesize. Requires an accessible project in the connected organization; the audio is attached separately by the app.',
106
+ inputSchema: projectSchema.extend({ id: z.uuid() }).shape,
107
+ outputSchema: jobSchema.shape,
108
+ annotations: readHints,
109
+ handler: async (args, context) => {
110
+ const id = z.uuid().parse(args.id);
111
+ await checkProject(args, context?.signal);
112
+ return jobSchema.parse(await httpClient.request('get', `${createPath}/${id}`, { signal: context?.signal }));
113
+ },
114
+ },
115
+ ];
116
+ }
@@ -0,0 +1,7 @@
1
+ import { type HttpClient } from '../http-client.js';
2
+ import type { ParsedOperation } from '../openapi/types.js';
3
+ import type { McpToolDefinition } from './generator.js';
4
+ export declare function supportsStudioDrafts(operations: ParsedOperation[]): boolean;
5
+ /** The API owns authorization, persistence and compare-and-set across MCP sessions/pods. */
6
+ export declare function createStudioDraftTools(operations: ParsedOperation[], http: HttpClient): McpToolDefinition[];
7
+ //# sourceMappingURL=studio-draft.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"studio-draft.d.ts","sourceRoot":"","sources":["../../src/tools/studio-draft.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AA0ExD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,eAAe,EAAE,GAAG,OAAO,CAU3E;AAED,4FAA4F;AAC5F,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,eAAe,EAAE,EAC7B,IAAI,EAAE,UAAU,GACf,iBAAiB,EAAE,CAqIrB"}
@@ -0,0 +1,195 @@
1
+ import { z } from 'zod';
2
+ import { ApiRequestError } from '../http-client.js';
3
+ import { modelOptionsSchema } from './model-options.js';
4
+ const speech = z.strictObject({
5
+ text: z.string().max(5000),
6
+ voiceId: z.string().min(1).max(200).nullable(),
7
+ stability: z.number().min(0).max(1),
8
+ similarityBoost: z.number().min(0).max(1),
9
+ });
10
+ // Stored drafts retain the public API's legacy model modes and speaker data.
11
+ // New chat edits still use the narrower visible-control schema below.
12
+ const storedOptions = modelOptionsSchema.extend({
13
+ model_mode: z.string().optional(),
14
+ active_speaker_detection: z
15
+ .union([
16
+ z.boolean(),
17
+ z.object({
18
+ auto_detect: z.boolean().optional(),
19
+ use_v2: z.boolean().optional(),
20
+ v3: z.boolean().optional(),
21
+ frame_number: z.number().optional(),
22
+ coordinates: z.tuple([z.number(), z.number()]).optional(),
23
+ bounding_boxes: z.array(z.array(z.number()).nullable()).optional(),
24
+ bounding_boxes_url: z.string().optional(),
25
+ face_image: z.string().optional(),
26
+ }),
27
+ ])
28
+ .optional(),
29
+ });
30
+ const state = z.strictObject({
31
+ workflow: z.enum(['lipsync', 'image_to_video', 'translate_and_dub', 'edit_dialogue']),
32
+ visualId: z.uuid().nullable(),
33
+ audioId: z.uuid().nullable(),
34
+ derivedAudioVisualId: z.uuid().nullable().default(null),
35
+ audioRemoved: z.boolean().default(false),
36
+ parentGenerationId: z.uuid().optional(),
37
+ model: z.string().min(1).max(100),
38
+ options: storedOptions,
39
+ targetLang: z.string().max(20),
40
+ sourceLang: z.string().max(20),
41
+ speech,
42
+ });
43
+ const draft = z.object({
44
+ id: z.uuid(),
45
+ projectId: z.uuid(),
46
+ revision: z.number().int().positive(),
47
+ appliedRevision: z.number().int().nonnegative(),
48
+ editable: z.boolean(),
49
+ updatedAt: z.string(),
50
+ state,
51
+ });
52
+ const identity = { projectId: z.uuid(), draftId: z.uuid() };
53
+ const edit = z.strictObject({
54
+ ...identity,
55
+ expectedRevision: z.number().int().positive(),
56
+ patch: state
57
+ .omit({ parentGenerationId: true, derivedAudioVisualId: true, audioRemoved: true })
58
+ .partial()
59
+ .extend({
60
+ options: modelOptionsSchema.optional(),
61
+ speech: speech.partial().optional(),
62
+ })
63
+ .strict()
64
+ .refine((patch) => Object.keys(patch).length > 0, 'Supply a draft change.'),
65
+ });
66
+ const save = z.object({
67
+ ...identity,
68
+ expectedRevision: z.number().int().nonnegative(),
69
+ state,
70
+ editable: z.boolean(),
71
+ });
72
+ const acknowledge = z.object({ ...identity, revision: z.number().int().positive() });
73
+ export function supportsStudioDrafts(operations) {
74
+ return [
75
+ ['StudioDraftsController_get', 'get'],
76
+ ['StudioDraftsController_save', 'put'],
77
+ ['StudioDraftsController_acknowledge', 'post'],
78
+ ].every(([id, method]) => operations.some((op) => op.operationId === id && op.method === method && op.studioDraftVersion === 1));
79
+ }
80
+ /** The API owns authorization, persistence and compare-and-set across MCP sessions/pods. */
81
+ export function createStudioDraftTools(operations, http) {
82
+ if (!supportsStudioDrafts(operations))
83
+ return [];
84
+ const path = (args) => `/v2/projects/${args.projectId}/studio-drafts/${args.draftId}`;
85
+ async function read(args, signal) {
86
+ const result = draft.parse(await http.request('get', path(args), { signal }));
87
+ if (result.id !== args.draftId || result.projectId !== args.projectId)
88
+ throw new Error('Sync returned a different Studio draft.');
89
+ return result;
90
+ }
91
+ const writeAnnotations = {
92
+ readOnlyHint: false,
93
+ destructiveHint: true,
94
+ idempotentHint: false,
95
+ openWorldHint: false,
96
+ };
97
+ const appOnly = { ui: { visibility: ['app'] } };
98
+ return [
99
+ {
100
+ name: 'studio-drafts_get',
101
+ title: 'Read current Studio draft',
102
+ description: 'Read the draftId/projectId published by the open Studio. Use its current revision before changing selections or settings. appliedRevision equal to revision means Studio acknowledged the change; otherwise it is saved but awaiting Studio. Never infer a displayed selection from chat prose.',
103
+ inputSchema: identity,
104
+ outputSchema: draft.shape,
105
+ annotations: {
106
+ readOnlyHint: true,
107
+ destructiveHint: false,
108
+ idempotentHint: true,
109
+ openWorldHint: false,
110
+ },
111
+ handler: (args, context) => read(z.object(identity).parse(args), context?.signal),
112
+ },
113
+ {
114
+ name: 'studio-drafts_update',
115
+ title: 'Change Studio selections and settings',
116
+ description: 'Update only the open editable Studio draft, using its published draftId/projectId and the expectedRevision returned by studio-drafts_get. Replaces the specified media selections, workflow, model options, language or unsent speech settings; changing the visual can clear audio derived from the previous visual. Only make changes requested by the user. This does not generate, translate, clone, upload, or spend credits. On a conflict, read the draft and reconcile; never automatically overwrite newer edits. A successful save is awaiting Studio until appliedRevision equals revision; only then claim the UI applied it.',
117
+ inputSchema: edit.shape,
118
+ outputSchema: draft.shape,
119
+ annotations: writeAnnotations,
120
+ handler: async (args, context) => {
121
+ const input = edit.parse(args);
122
+ const current = await read(input, context?.signal);
123
+ if (current.revision !== input.expectedRevision)
124
+ throw new ApiRequestError('Studio has newer edits. Read the current draft before changing it.', 409, 'STUDIO_DRAFT_CONFLICT');
125
+ if (!current.editable)
126
+ throw new ApiRequestError('Studio is protecting an active request or recovery. Return to an editable draft first.', 409, 'STUDIO_DRAFT_BUSY');
127
+ const patch = input.patch;
128
+ const next = {
129
+ ...current.state,
130
+ ...patch,
131
+ options: { ...current.state.options, ...patch.options },
132
+ speech: { ...current.state.speech, ...patch.speech },
133
+ };
134
+ if (patch.audioId !== undefined) {
135
+ next.audioRemoved = patch.audioId === null;
136
+ next.derivedAudioVisualId = null;
137
+ }
138
+ else if (patch.visualId !== undefined &&
139
+ patch.visualId !== current.state.visualId &&
140
+ current.state.derivedAudioVisualId) {
141
+ next.audioId = null;
142
+ next.audioRemoved = true;
143
+ next.derivedAudioVisualId = null;
144
+ }
145
+ return draft.parse(await http.request('put', path(input), {
146
+ body: {
147
+ expectedRevision: input.expectedRevision,
148
+ state: state.parse(next),
149
+ editable: true,
150
+ },
151
+ signal: context?.signal,
152
+ }));
153
+ },
154
+ },
155
+ {
156
+ name: 'studio-drafts_save',
157
+ title: 'Save Studio draft',
158
+ description: 'Studio-only save of its own editable snapshot and recovery lock. Compare-and-set against the revision Studio last read, so it never replaces newer edits from chat or another Studio. No provider work is performed.',
159
+ inputSchema: save.shape,
160
+ outputSchema: draft.shape,
161
+ meta: appOnly,
162
+ // ChatGPT asks the user before a destructive widget call (and Android fails it), so a
163
+ // background autosave marked destructive never reached the API and Studio re-read
164
+ // the missing draft about once a minute. Compare-and-set cannot discard unseen edits.
165
+ annotations: { ...writeAnnotations, destructiveHint: false },
166
+ handler: async (args, context) => {
167
+ const input = save.parse(args);
168
+ return draft.parse(await http.request('put', path(input), {
169
+ body: {
170
+ expectedRevision: input.expectedRevision,
171
+ state: input.state,
172
+ editable: input.editable,
173
+ },
174
+ signal: context?.signal,
175
+ }));
176
+ },
177
+ },
178
+ {
179
+ name: 'studio-drafts_ack',
180
+ title: 'Acknowledge applied Studio draft',
181
+ description: 'Studio-only acknowledgement after validating, applying and persisting the exact revision. Does not edit settings or start jobs.',
182
+ inputSchema: acknowledge.shape,
183
+ outputSchema: draft.shape,
184
+ meta: appOnly,
185
+ annotations: { ...writeAnnotations, destructiveHint: false, idempotentHint: true },
186
+ handler: async (args, context) => {
187
+ const input = acknowledge.parse(args);
188
+ return draft.parse(await http.request('post', `${path(input)}/acknowledgement`, {
189
+ body: { revision: input.revision },
190
+ signal: context?.signal,
191
+ }));
192
+ },
193
+ },
194
+ ];
195
+ }