@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
package/dist/cli.js CHANGED
@@ -1,29 +1,25 @@
1
1
  #!/usr/bin/env node
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/server/stdio';
3
3
  import { resolveConfig } from './config.js';
4
- import { createSyncMcpServer } from './server.js';
4
+ import { createMcpServerFactory, createSyncMcpServer } from './server.js';
5
+ import { parseToolsets } from './toolsets.js';
6
+ import { startConfiguredUpdateCheck } from './update-check.js';
7
+ import { PACKAGE_VERSION } from './version.js';
5
8
  async function main() {
6
9
  const args = process.argv.slice(2);
7
10
  const config = resolveConfig(parseArgs(args));
8
- const server = await createSyncMcpServer(config);
9
11
  if (config.transport === 'stdio') {
12
+ // Started before auth and the spec fetch, and never awaited.
13
+ const updateCheck = startConfiguredUpdateCheck(config, PACKAGE_VERSION);
14
+ const server = await createSyncMcpServer(config, { updateCheck });
10
15
  const transport = new StdioServerTransport();
11
16
  await server.connect(transport);
12
17
  }
13
18
  else {
14
- // HTTP transport - import dynamically to avoid bundling when unused
15
- const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
16
- const http = await import('node:http');
17
- const transport = new StreamableHTTPServerTransport({
18
- sessionIdGenerator: undefined,
19
- });
20
- await server.connect(transport);
21
- const httpServer = http.createServer(async (req, res) => {
22
- await transport.handleRequest(req, res);
23
- });
24
- httpServer.listen(config.port, () => {
25
- process.stderr.write(`Sync MCP server listening on http://localhost:${config.port}\n`);
26
- });
19
+ // HTTP transport: each session gets its own McpServer instance
20
+ const factory = await createMcpServerFactory(config);
21
+ const { startHttpServer } = await import('./http-server.js');
22
+ await startHttpServer(factory, config);
27
23
  }
28
24
  }
29
25
  function parseArgs(args) {
@@ -47,6 +43,16 @@ function parseArgs(args) {
47
43
  result.port = Number.parseInt(next, 10);
48
44
  i++;
49
45
  }
46
+ else if (arg === '--no-spend-confirmation') {
47
+ result.spendConfirmation = false;
48
+ }
49
+ else if (arg === '--no-update-check') {
50
+ result.updateCheck = false;
51
+ }
52
+ else if (arg === '--toolsets' && next) {
53
+ result.toolsets = parseToolsets(next);
54
+ i++;
55
+ }
50
56
  else if (arg === '--help' || arg === '-h') {
51
57
  printUsage();
52
58
  process.exit(0);
@@ -62,6 +68,14 @@ Options:
62
68
  --base-url <url> API base URL (default: https://api.sync.so)
63
69
  --transport <type> stdio (default) or http
64
70
  --port <port> HTTP port (default: 3002, only with --transport http)
71
+ --no-spend-confirmation
72
+ Unattended automation: drop the cost-quote and confirm-before-paying
73
+ instructions (or set SYNC_MCP_SPEND_CONFIRMATION=off)
74
+ --no-update-check Skip the daily npm check for a newer version (or set
75
+ SYNC_MCP_UPDATE_CHECK=off; skipped when CI is set)
76
+ --toolsets <list> stdio only: comma-separated tools to list (or set SYNC_MCP_TOOLSETS):
77
+ default, lipsync, dubbing, assets, projects, transcription,
78
+ voices, admin or all (default: the hosted set without admin)
65
79
  -h, --help Show this help message
66
80
  `);
67
81
  }
@@ -0,0 +1,10 @@
1
+ export type ClientProfile = Readonly<{
2
+ name: 'chatgpt' | 'codex' | 'claude' | 'generic';
3
+ supportsAppUi: boolean;
4
+ supportsUploads: boolean;
5
+ defaultProjectName: string;
6
+ }>;
7
+ /** Presentation only. These untrusted handshake names never grant permissions. */
8
+ export declare function resolveClientProfile(name?: string): ClientProfile;
9
+ export declare const UNSUPPORTED_UPLOAD_MESSAGE = "This client does not support the ChatGPT file bridge or upload widget. For a local file, call assets_create-upload-url, PUT the file bytes to the returned uploadUrl with the same Content-Type, register the returned url with assets_create, and pass the asset id as imageAssetId, videoAssetId or audioAssetId. Public or Sync-hosted URLs and existing Sync asset IDs also work in lipsync_create.";
10
+ //# sourceMappingURL=client-profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-profile.d.ts","sourceRoot":"","sources":["../src/client-profile.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG,QAAQ,CAAC;IACnC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IACjD,aAAa,EAAE,OAAO,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;CAC5B,CAAC,CAAC;AA6BH,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,aAAa,CAgBjE;AAED,eAAO,MAAM,0BAA0B,4YACoW,CAAC"}
@@ -0,0 +1,45 @@
1
+ const GENERIC = Object.freeze({
2
+ name: 'generic',
3
+ supportsAppUi: false,
4
+ supportsUploads: false,
5
+ defaultProjectName: 'Sync generations',
6
+ });
7
+ const CHATGPT = Object.freeze({
8
+ name: 'chatgpt',
9
+ supportsAppUi: true,
10
+ supportsUploads: true,
11
+ defaultProjectName: 'ChatGPT generations',
12
+ });
13
+ const CODEX = Object.freeze({
14
+ name: 'codex',
15
+ supportsAppUi: true,
16
+ // The hosted OpenAI connector supplies validated fileParams for this client too.
17
+ // Keep its project default separate without rejecting those files by host name.
18
+ supportsUploads: true,
19
+ defaultProjectName: 'Sync generations',
20
+ });
21
+ const CLAUDE = Object.freeze({
22
+ name: 'claude',
23
+ supportsAppUi: false,
24
+ supportsUploads: false,
25
+ defaultProjectName: 'Claude generations',
26
+ });
27
+ /** Presentation only. These untrusted handshake names never grant permissions. */
28
+ export function resolveClientProfile(name) {
29
+ switch ((name ?? '').toLowerCase()) {
30
+ case 'chatgpt':
31
+ case 'openai':
32
+ case 'openai-chatgpt':
33
+ case 'openai-mcp':
34
+ return CHATGPT;
35
+ case 'openai-mcp (codex)':
36
+ return CODEX;
37
+ case 'claude':
38
+ case 'claude-ai':
39
+ return CLAUDE;
40
+ // Muse remains generic until Meta documentation or a real handshake verifies its alias.
41
+ default:
42
+ return GENERIC;
43
+ }
44
+ }
45
+ export const UNSUPPORTED_UPLOAD_MESSAGE = 'This client does not support the ChatGPT file bridge or upload widget. For a local file, call assets_create-upload-url, PUT the file bytes to the returned uploadUrl with the same Content-Type, register the returned url with assets_create, and pass the asset id as imageAssetId, videoAssetId or audioAssetId. Public or Sync-hosted URLs and existing Sync asset IDs also work in lipsync_create.';
package/dist/config.d.ts CHANGED
@@ -1,8 +1,25 @@
1
+ import { type Toolset } from './toolsets.js';
1
2
  export type SyncMcpConfig = {
2
3
  apiKey?: string;
3
4
  baseUrl: string;
5
+ oauthAuthorizationUrl?: string;
4
6
  transport: 'stdio' | 'http';
5
7
  port: number;
8
+ /** False when the operator pre-approved spending; see spend-confirmation.ts. */
9
+ spendConfirmation?: boolean;
10
+ /** stdio only: false skips the daily npm check for a newer version; see update-check.ts. */
11
+ updateCheck?: boolean;
12
+ /** stdio only: which tools to list; undefined lists the default set. See toolsets.ts. */
13
+ toolsets?: Toolset[];
14
+ chatgptApp?: {
15
+ directory: string;
16
+ previousDirectories?: string[];
17
+ domain: string;
18
+ resourceDomains: string[];
19
+ connectDomains?: string[];
20
+ uploadStorageOrigin?: string;
21
+ uploadOrigins?: string[];
22
+ };
6
23
  };
7
24
  export declare const DEFAULT_CONFIG: SyncMcpConfig;
8
25
  export declare function resolveConfig(overrides?: Partial<SyncMcpConfig>): SyncMcpConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAI5B,CAAC;AAEF,wBAAgB,aAAa,CAAC,SAAS,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,aAAa,CAOnF"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AAG5D,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,4FAA4F;IAC5F,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE;QACX,SAAS,EAAE,MAAM,CAAC;QAClB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;QAC/B,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,EAAE,MAAM,EAAE,CAAC;QAC1B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;KAC1B,CAAC;CACH,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAI5B,CAAC;AAkCF,wBAAgB,aAAa,CAAC,SAAS,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,aAAa,CAgCnF"}
package/dist/config.js CHANGED
@@ -1,13 +1,65 @@
1
+ import { z } from 'zod';
2
+ import { resolveChatgptRelease } from './chatgpt-release.js';
3
+ import { parseSpendConfirmation } from './spend-confirmation.js';
4
+ import { parseToolsets } from './toolsets.js';
5
+ import { parseUpdateCheck } from './update-check.js';
1
6
  export const DEFAULT_CONFIG = {
2
7
  baseUrl: 'https://api.sync.so',
3
8
  transport: 'stdio',
4
9
  port: 3002,
5
10
  };
11
+ const OAuthAuthorizationUrlSchema = z
12
+ .url()
13
+ .refine((value) => {
14
+ const url = new URL(value);
15
+ return ((url.protocol === 'https:' || url.protocol === 'http:') &&
16
+ !url.username &&
17
+ !url.password &&
18
+ !url.search &&
19
+ !url.hash);
20
+ }, 'OAuth authorization URL must use HTTP(S) without credentials, query or fragment')
21
+ .optional();
22
+ /** The ChatGPT UI release to serve, from the release catalog or the legacy directory settings. */
23
+ function resolveChatgptAppRelease() {
24
+ const legacyDirectory = process.env.SYNC_CHATGPT_APP_DIR;
25
+ const catalogPath = process.env.SYNC_CHATGPT_APP_RELEASES;
26
+ if (!legacyDirectory && !(catalogPath && process.env.SYNC_CHATGPT_APP_DOMAIN))
27
+ return undefined;
28
+ const previous = z
29
+ .array(z.string().min(1))
30
+ .parse(JSON.parse(process.env.SYNC_CHATGPT_APP_PREVIOUS_DIRS ?? '[]'));
31
+ return catalogPath
32
+ ? resolveChatgptRelease(catalogPath, [
33
+ ...previous,
34
+ ...(legacyDirectory ? [legacyDirectory] : []),
35
+ ])
36
+ : { directory: legacyDirectory, previousDirectories: previous };
37
+ }
6
38
  export function resolveConfig(overrides = {}) {
39
+ const release = overrides.chatgptApp ? undefined : resolveChatgptAppRelease();
7
40
  return {
8
41
  apiKey: overrides.apiKey ?? process.env.SYNC_API_KEY,
9
42
  baseUrl: overrides.baseUrl ?? process.env.SYNC_BASE_URL ?? DEFAULT_CONFIG.baseUrl,
43
+ oauthAuthorizationUrl: OAuthAuthorizationUrlSchema.parse(overrides.oauthAuthorizationUrl ?? process.env.SYNC_OAUTH_AUTHORIZATION_URL),
10
44
  transport: overrides.transport ?? DEFAULT_CONFIG.transport,
11
45
  port: overrides.port ?? DEFAULT_CONFIG.port,
46
+ spendConfirmation: overrides.spendConfirmation ??
47
+ parseSpendConfirmation(process.env.SYNC_MCP_SPEND_CONFIRMATION) ??
48
+ true,
49
+ updateCheck: overrides.updateCheck ?? parseUpdateCheck(process.env.SYNC_MCP_UPDATE_CHECK) ?? true,
50
+ toolsets: overrides.toolsets ?? parseToolsets(process.env.SYNC_MCP_TOOLSETS),
51
+ chatgptApp: overrides.chatgptApp ??
52
+ (release
53
+ ? {
54
+ ...release,
55
+ domain: process.env.SYNC_CHATGPT_APP_DOMAIN ?? '',
56
+ uploadStorageOrigin: process.env.SYNC_APP_UPLOAD_STORAGE_ORIGIN,
57
+ uploadOrigins: process.env.SYNC_APP_UPLOAD_ORIGINS
58
+ ? JSON.parse(process.env.SYNC_APP_UPLOAD_ORIGINS)
59
+ : undefined,
60
+ connectDomains: JSON.parse(process.env.SYNC_CHATGPT_APP_CONNECT_DOMAINS ?? '[]'),
61
+ resourceDomains: JSON.parse(process.env.SYNC_CHATGPT_APP_RESOURCE_DOMAINS ?? '[]'),
62
+ }
63
+ : undefined),
12
64
  };
13
65
  }
@@ -0,0 +1,2 @@
1
+ export declare const GENERATION_STATUS_HTML: string;
2
+ //# sourceMappingURL=generation-status-html.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generation-status-html.d.ts","sourceRoot":"","sources":["../src/generation-status-html.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,sBAAsB,QAAwB,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { gunzipSync } from 'node:zlib';
4
+ import { z } from 'zod';
5
+ // Built from the companion workspace's real @sync/ui components and brand tokens.
6
+ const directory = new URL('../deploy/generation-status/', import.meta.url);
7
+ const release = z
8
+ .object({
9
+ repository: z.literal('synchronicity-labs/sync-api-v2'),
10
+ commit: z.string().regex(/^[a-f0-9]{40}$/),
11
+ htmlSha256: z.string().regex(/^[a-f0-9]{64}$/),
12
+ htmlBytes: z
13
+ .number()
14
+ .int()
15
+ .positive()
16
+ .max(8 * 1024 * 1024),
17
+ })
18
+ .parse(JSON.parse(readFileSync(new URL('release.json', directory), 'utf8')));
19
+ const html = gunzipSync(readFileSync(new URL('app.html.gz', directory)), {
20
+ maxOutputLength: 8 * 1024 * 1024,
21
+ });
22
+ if (html.length !== release.htmlBytes ||
23
+ createHash('sha256').update(html).digest('hex') !== release.htmlSha256) {
24
+ throw new Error('Generation status UI does not match its approved release.');
25
+ }
26
+ export const GENERATION_STATUS_HTML = html.toString('utf8');
@@ -0,0 +1,6 @@
1
+ import type { ResourceMetadata } from '@modelcontextprotocol/server';
2
+ import type { McpToolDefinition } from './tools/generator.js';
3
+ export declare const GENERATION_STATUS_URI = "ui://sync/generation-status-v1.html";
4
+ export declare function withGenerationStatusCard(tool: McpToolDefinition): McpToolDefinition;
5
+ export declare const GENERATION_STATUS_METADATA: ResourceMetadata;
6
+ //# sourceMappingURL=generation-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generation-status.d.ts","sourceRoot":"","sources":["../src/generation-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AACrE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAG9D,eAAO,MAAM,qBAAqB,wCAAwC,CAAC;AAU3E,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,CAanF;AAID,eAAO,MAAM,0BAA0B,EAAE,gBAUxC,CAAC"}
@@ -0,0 +1,37 @@
1
+ import { MCP_APP_RESOURCE_MIME_TYPE } from './tools/upload-widget.js';
2
+ export const GENERATION_STATUS_URI = 'ui://sync/generation-status-v1.html';
3
+ const submissionTools = new Set([
4
+ 'lipsync_create',
5
+ 'lipsync_create-from-assets',
6
+ 'lipsync_create-timeline',
7
+ 'dubbing_create',
8
+ 'dialogue-videos_create',
9
+ 'generations_create',
10
+ ]);
11
+ export function withGenerationStatusCard(tool) {
12
+ if (!submissionTools.has(tool.name))
13
+ return tool;
14
+ return {
15
+ ...tool,
16
+ meta: {
17
+ ...tool.meta,
18
+ ui: {
19
+ ...tool.meta?.ui,
20
+ resourceUri: GENERATION_STATUS_URI,
21
+ },
22
+ 'openai/outputTemplate': GENERATION_STATUS_URI,
23
+ },
24
+ };
25
+ }
26
+ const description = 'Status of this Sync generation, with estimated time remaining and a read-only Check status action.';
27
+ export const GENERATION_STATUS_METADATA = {
28
+ title: 'Sync generation status',
29
+ description,
30
+ mimeType: MCP_APP_RESOURCE_MIME_TYPE,
31
+ _meta: {
32
+ ui: { prefersBorder: true, csp: { connectDomains: [], resourceDomains: ['data:'] } },
33
+ 'openai/widgetDescription': description,
34
+ 'openai/widgetPrefersBorder': true,
35
+ 'openai/widgetCSP': { connect_domains: [], resource_domains: ['data:'] },
36
+ },
37
+ };
@@ -1,11 +1,55 @@
1
+ import { z } from 'zod';
2
+ export declare function resolveSyncSource(clientName?: string): string;
3
+ export declare const UPSTREAM_REQUEST_TIMEOUT_MS = 65000;
4
+ declare const importErrorDetailsSchema: z.ZodObject<{
5
+ importJobId: z.ZodOptional<z.ZodUUID>;
6
+ importStatus: z.ZodOptional<z.ZodEnum<{
7
+ completed: "completed";
8
+ downloading: "downloading";
9
+ failed: "failed";
10
+ processing: "processing";
11
+ queued: "queued";
12
+ }>>;
13
+ progress: z.ZodOptional<z.ZodNumber>;
14
+ }, z.core.$strip>;
15
+ declare const errorDetailsSchema: z.ZodObject<{
16
+ requiredPlan: z.ZodCatch<z.ZodOptional<z.ZodString>>;
17
+ billingUrl: z.ZodCatch<z.ZodOptional<z.ZodString>>;
18
+ activeBatches: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
19
+ concurrencyLimit: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
20
+ lineNumber: z.ZodCatch<z.ZodOptional<z.ZodNumber>>;
21
+ }, z.core.$strip>;
22
+ export type ApiErrorDetails = z.infer<typeof errorDetailsSchema>;
23
+ export declare class ApiRequestError extends Error {
24
+ readonly status: number;
25
+ readonly code?: string | undefined;
26
+ readonly retryAfterMs?: number | undefined;
27
+ readonly generationId?: string | undefined;
28
+ readonly importDetails?: z.infer<typeof importErrorDetailsSchema> | undefined;
29
+ readonly details?: ApiErrorDetails | undefined;
30
+ /** Set by a tool that knows a better next step than the generic one for this code. */
31
+ nextStep?: string;
32
+ /** The API's own advice for this error code, when the response carries one. */
33
+ suggestion?: string;
34
+ constructor(message: string, status: number, code?: string | undefined, retryAfterMs?: number | undefined, generationId?: string | undefined, importDetails?: z.infer<typeof importErrorDetailsSchema> | undefined, details?: ApiErrorDetails | undefined);
35
+ }
36
+ export declare function cameFromApi(error: unknown): boolean;
1
37
  type AuthHeaders = Record<string, string>;
2
38
  export type HttpClient = {
3
39
  request: (method: string, path: string, options?: {
4
40
  query?: Record<string, string>;
5
41
  body?: unknown;
6
42
  headers?: Record<string, string>;
43
+ signal?: AbortSignal;
7
44
  }) => Promise<unknown>;
8
45
  };
9
- export declare function createHttpClient(baseUrl: string, authHeaders: AuthHeaders): HttpClient;
46
+ /**
47
+ * Creates an HTTP client for the Sync API.
48
+ *
49
+ * Auth is resolved in this order:
50
+ * 1. Per-request token from AsyncLocalStorage (set by OAuth middleware in HTTP transport)
51
+ * 2. Static auth headers (API key or device auth token, set at startup for stdio transport)
52
+ */
53
+ export declare function createHttpClient(baseUrl: string, staticAuthHeaders?: AuthHeaders, stdioClientName?: () => string | undefined): HttpClient;
10
54
  export {};
11
55
  //# sourceMappingURL=http-client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1C,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,CACP,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,KACE,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,UAAU,CAkDtF"}
1
+ {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAkBxB,wBAAgB,iBAAiB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CAG7D;AAuBD,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD,QAAA,MAAM,wBAAwB;;;;;;;;;;iBAI5B,CAAC;AAIH,QAAA,MAAM,kBAAkB;;;;;;iBAMtB,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE,qBAAa,eAAgB,SAAQ,KAAK;IAQtC,QAAQ,CAAC,MAAM,EAAE,MAAM;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM;IACtB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM;IAC9B,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC;IACjE,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe;IAZpC,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,YACE,OAAO,EAAE,MAAM,EACN,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA,EACb,YAAY,CAAC,EAAE,MAAM,YAAA,EACrB,YAAY,CAAC,EAAE,MAAM,YAAA,EACrB,aAAa,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,YAAA,EACxD,OAAO,CAAC,EAAE,eAAe,YAAA,EAInC;CACF;AAmBD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEnD;AAED,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1C,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,CACP,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACjC,MAAM,CAAC,EAAE,WAAW,CAAC;KACtB,KACE,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,EACf,iBAAiB,GAAE,WAAgB,EACnC,eAAe,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,GACzC,UAAU,CAuGZ"}
@@ -1,43 +1,180 @@
1
- const SYNC_SOURCE = 'mcp';
2
- export function createHttpClient(baseUrl, authHeaders) {
1
+ import { z } from 'zod';
2
+ import { combineSignals } from './abort-signals.js';
3
+ import { getAuthToken, getClientName } from './auth/async-context.js';
4
+ import { chosenOrganization, ORGANIZATION_HEADER } from './organization-selection.js';
5
+ import { getToolAnalytics } from './tool-analytics-context.js';
6
+ import { PACKAGE_VERSION } from './version.js';
7
+ /** API-key analytics labels; bearer credentials use the API's MCP source namespace. */
8
+ const FIRST_CLASS_SOURCE_BY_CLIENT = {
9
+ chatgpt: 'chatgpt',
10
+ openai: 'chatgpt',
11
+ 'openai-chatgpt': 'chatgpt',
12
+ claude: 'claude',
13
+ 'claude-ai': 'claude',
14
+ gemini: 'gemini',
15
+ google: 'gemini',
16
+ };
17
+ export function resolveSyncSource(clientName) {
18
+ if (!clientName)
19
+ return 'mcp';
20
+ return FIRST_CLASS_SOURCE_BY_CLIENT[clientName.toLowerCase()] ?? `mcp:${clientName}`;
21
+ }
22
+ /**
23
+ * Who is calling: the analytics source, this server's version and the MCP client's
24
+ * handshake name when known. The API ignores headers it does not record.
25
+ */
26
+ function clientHeaders(authHeaders, clientName) {
27
+ const syncSource = authHeaders.Authorization?.startsWith('Bearer ')
28
+ ? clientName
29
+ ? `mcp:${clientName}`
30
+ : 'mcp'
31
+ : resolveSyncSource(clientName);
32
+ return {
33
+ 'x-sync-source': syncSource,
34
+ 'x-sync-mcp-version': PACKAGE_VERSION,
35
+ ...(clientName ? { 'x-sync-mcp-client': clientName } : {}),
36
+ };
37
+ }
38
+ // Bound upstream calls independently of the API-defined polling window. No write retries.
39
+ export const UPSTREAM_REQUEST_TIMEOUT_MS = 65_000;
40
+ const importErrorDetailsSchema = z.object({
41
+ importJobId: z.uuid().optional(),
42
+ importStatus: z.enum(['queued', 'downloading', 'processing', 'completed', 'failed']).optional(),
43
+ progress: z.number().min(0).max(100).optional(),
44
+ });
45
+ // Error fields worth showing the agent as they are: what a refusal needs (a 402's plan and
46
+ // billing page), a batch limit's counts, and the line a batch refusal points at.
47
+ const errorDetailsSchema = z.object({
48
+ requiredPlan: z.string().optional().catch(undefined),
49
+ billingUrl: z.string().optional().catch(undefined),
50
+ activeBatches: z.number().optional().catch(undefined),
51
+ concurrencyLimit: z.number().optional().catch(undefined),
52
+ lineNumber: z.number().int().positive().optional().catch(undefined),
53
+ });
54
+ export class ApiRequestError extends Error {
55
+ status;
56
+ code;
57
+ retryAfterMs;
58
+ generationId;
59
+ importDetails;
60
+ details;
61
+ /** Set by a tool that knows a better next step than the generic one for this code. */
62
+ nextStep;
63
+ /** The API's own advice for this error code, when the response carries one. */
64
+ suggestion;
65
+ constructor(message, status, code, retryAfterMs, generationId, importDetails, details) {
66
+ super(message);
67
+ this.status = status;
68
+ this.code = code;
69
+ this.retryAfterMs = retryAfterMs;
70
+ this.generationId = generationId;
71
+ this.importDetails = importDetails;
72
+ this.details = details;
73
+ this.name = 'ApiRequestError';
74
+ }
75
+ }
76
+ // The API's advice for a coded error, such as reconnecting to grant an organization.
77
+ function apiSuggestion(parsed) {
78
+ return z.object({ suggestion: z.string() }).safeParse(parsed).data?.suggestion;
79
+ }
80
+ function errorDetails(parsed) {
81
+ if (!parsed || typeof parsed !== 'object')
82
+ return undefined;
83
+ const details = errorDetailsSchema.parse(parsed);
84
+ const present = Object.fromEntries(Object.entries(details).filter(([, value]) => value !== undefined));
85
+ return Object.keys(present).length > 0 ? present : undefined;
86
+ }
87
+ // Errors built from an API response, as opposed to this server's own checks.
88
+ const apiResponseErrors = new WeakSet();
89
+ export function cameFromApi(error) {
90
+ return error instanceof ApiRequestError && apiResponseErrors.has(error);
91
+ }
92
+ /**
93
+ * Creates an HTTP client for the Sync API.
94
+ *
95
+ * Auth is resolved in this order:
96
+ * 1. Per-request token from AsyncLocalStorage (set by OAuth middleware in HTTP transport)
97
+ * 2. Static auth headers (API key or device auth token, set at startup for stdio transport)
98
+ */
99
+ export function createHttpClient(baseUrl, staticAuthHeaders = {}, stdioClientName) {
3
100
  return {
4
101
  async request(method, path, options = {}) {
5
- const url = new URL(path, baseUrl);
6
- if (options.query) {
7
- for (const [key, value] of Object.entries(options.query)) {
8
- if (value !== undefined && value !== '') {
9
- url.searchParams.set(key, value);
102
+ const deadline = AbortSignal.timeout(UPSTREAM_REQUEST_TIMEOUT_MS);
103
+ const { signal, dispose } = combineSignals(options.signal ? [options.signal, deadline] : [deadline]);
104
+ try {
105
+ signal.throwIfAborted();
106
+ const url = new URL(path.replace(/^\//, ''), `${baseUrl.replace(/\/$/, '')}/`);
107
+ if (options.query) {
108
+ for (const [key, value] of Object.entries(options.query)) {
109
+ if (value !== undefined && value !== '') {
110
+ url.searchParams.set(key, value);
111
+ }
10
112
  }
11
113
  }
114
+ // Per-request OAuth token takes priority over static headers
115
+ const perRequestToken = getAuthToken();
116
+ const authHeaders = perRequestToken
117
+ ? { Authorization: `Bearer ${perRequestToken}` }
118
+ : { ...staticAuthHeaders };
119
+ const organization = chosenOrganization();
120
+ const headers = {
121
+ ...authHeaders,
122
+ ...clientHeaders(authHeaders, getClientName() ?? stdioClientName?.()),
123
+ ...(organization ? { [ORGANIZATION_HEADER]: organization.id } : {}),
124
+ ...options.headers,
125
+ ...(getToolAnalytics() && { 'x-sync-analytics': JSON.stringify(getToolAnalytics()) }),
126
+ };
127
+ if (options.body && method !== 'get') {
128
+ headers['Content-Type'] = 'application/json';
129
+ }
130
+ const response = await fetch(url.toString(), {
131
+ method: method.toUpperCase(),
132
+ headers,
133
+ body: options.body ? JSON.stringify(options.body) : undefined,
134
+ signal,
135
+ });
136
+ const text = await response.text();
137
+ let parsed;
138
+ try {
139
+ parsed = JSON.parse(text);
140
+ }
141
+ catch {
142
+ parsed = text;
143
+ }
144
+ if (!response.ok) {
145
+ const message = typeof parsed === 'object' &&
146
+ parsed !== null &&
147
+ 'message' in parsed &&
148
+ typeof parsed.message === 'string'
149
+ ? parsed.message
150
+ : text;
151
+ const code = typeof parsed === 'object' &&
152
+ parsed !== null &&
153
+ 'errorCode' in parsed &&
154
+ typeof parsed.errorCode === 'string'
155
+ ? parsed.errorCode
156
+ : undefined;
157
+ const retryAfter = response.headers.get('Retry-After');
158
+ const seconds = retryAfter === null ? Number.NaN : Number(retryAfter);
159
+ const retryAfterMs = Number.isFinite(seconds) && seconds >= 0 ? seconds * 1000 : undefined;
160
+ const knownId = z
161
+ .uuid()
162
+ .safeParse(parsed && typeof parsed === 'object' && 'generationId' in parsed
163
+ ? parsed.generationId
164
+ : undefined);
165
+ const importDetails = code === 'import_in_progress' || code === 'import_failed'
166
+ ? importErrorDetailsSchema.safeParse(parsed)
167
+ : undefined;
168
+ const error = new ApiRequestError(`API request failed: ${response.status} ${response.statusText} - ${message}`, response.status, code, retryAfterMs, knownId.success ? knownId.data : undefined, importDetails?.success ? importDetails.data : undefined, errorDetails(parsed));
169
+ error.suggestion = apiSuggestion(parsed);
170
+ apiResponseErrors.add(error);
171
+ throw error;
172
+ }
173
+ return parsed;
12
174
  }
13
- const headers = {
14
- ...authHeaders,
15
- 'x-sync-source': SYNC_SOURCE,
16
- ...options.headers,
17
- };
18
- if (options.body && method !== 'get') {
19
- headers['Content-Type'] = 'application/json';
20
- }
21
- const response = await fetch(url.toString(), {
22
- method: method.toUpperCase(),
23
- headers,
24
- body: options.body ? JSON.stringify(options.body) : undefined,
25
- });
26
- const text = await response.text();
27
- let parsed;
28
- try {
29
- parsed = JSON.parse(text);
30
- }
31
- catch {
32
- parsed = text;
33
- }
34
- if (!response.ok) {
35
- const message = typeof parsed === 'object' && parsed !== null && 'message' in parsed
36
- ? parsed.message
37
- : text;
38
- throw new Error(`API request failed: ${response.status} ${response.statusText} - ${message}`);
175
+ finally {
176
+ dispose();
39
177
  }
40
- return parsed;
41
178
  },
42
179
  };
43
180
  }
@@ -0,0 +1,53 @@
1
+ import { getHeapStatistics } from 'node:v8';
2
+ import { NodeStreamableHTTPServerTransport } from '@modelcontextprotocol/node';
3
+ import type { McpServer } from '@modelcontextprotocol/server';
4
+ import type { SyncMcpConfig } from './config.js';
5
+ export { extractBasicClientCredentials, mergeBasicClientCredentials, } from './auth/client-credentials.js';
6
+ export declare const OAUTH_PROXY_TIMEOUT_MS = 10000;
7
+ export declare function hasSessionHeapCapacity(heap?: Pick<ReturnType<typeof getHeapStatistics>, 'used_heap_size' | 'heap_size_limit'>): boolean;
8
+ export declare function createSessionHeapAdmission({ readHeap, collect, now, }?: {
9
+ readHeap?: () => Pick<ReturnType<typeof getHeapStatistics>, 'used_heap_size' | 'heap_size_limit'>;
10
+ collect?: () => void;
11
+ now?: () => number;
12
+ }): () => boolean;
13
+ type SessionRuntimeConfig = {
14
+ idleTtlMs: number;
15
+ maxSessions: number;
16
+ maxInFlightRequests: number;
17
+ sweepIntervalMs: number;
18
+ shutdownGraceMs: number;
19
+ telemetryIntervalMs: number;
20
+ };
21
+ export declare function getSessionRuntimeConfig(env?: Record<string, string | undefined>): SessionRuntimeConfig;
22
+ export declare function runSessionSweepSafely(sessions: {
23
+ sweep: () => Promise<unknown>;
24
+ }, logEvent: (event: string, details: Record<string, unknown>) => void): Promise<void>;
25
+ export declare function sanitizeDiagnosticUrl(value: string): string;
26
+ export declare function createRequestId(value: string | string[] | undefined): string;
27
+ type HttpServerShutdownController = {
28
+ close: (callback: (error?: Error) => void) => unknown;
29
+ closeIdleConnections: () => void;
30
+ closeAllConnections: () => void;
31
+ };
32
+ export declare function closeHttpServerWithGrace(httpServer: HttpServerShutdownController, graceMs: number): Promise<{
33
+ forced: boolean;
34
+ }>;
35
+ export declare function listenWithCleanup<T>(listen: () => T, waitForStartup: (server: T) => Promise<void>, cleanup: () => void): Promise<T>;
36
+ type HttpServerStartupEmitter = {
37
+ on: (event: 'error', listener: (error: Error) => void) => unknown;
38
+ once: (event: 'listening', listener: () => void) => unknown;
39
+ };
40
+ export declare function waitForHttpServerStartup(httpServer: HttpServerStartupEmitter, logEvent: (event: string, details: Record<string, unknown>) => void): Promise<void>;
41
+ /**
42
+ * Validate browser origins before an HTTP request reaches the MCP transport.
43
+ * Non-browser MCP clients normally omit Origin, so an absent header is valid.
44
+ */
45
+ export declare function isAllowedMcpOrigin(origin: string | undefined): boolean;
46
+ export declare function encodeOAuthFormBody(body: Record<string, unknown>): string;
47
+ /** Complete each POST with a JSON response, including large HTML resources. */
48
+ export declare function createHttpMcpTransport(options: ConstructorParameters<typeof NodeStreamableHTTPServerTransport>[0]): NodeStreamableHTTPServerTransport;
49
+ export declare function startHttpServer(serverFactory: {
50
+ createServer: (clientName?: string, handshakeClientName?: string) => McpServer;
51
+ toolCount: number;
52
+ }, config: SyncMcpConfig): Promise<void>;
53
+ //# sourceMappingURL=http-server.d.ts.map