@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,136 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { request } from 'node:https';
3
+ import { Transform } from 'node:stream';
4
+ import { pipeline } from 'node:stream/promises';
5
+ import cors from 'cors';
6
+ import { z } from 'zod';
7
+ import { uploadRuntime } from './upload-runtime.js';
8
+ /** Browser origins are configured separately from storage destinations. Tickets remain required. */
9
+ export function createAppUploadCors(origins) {
10
+ return cors({
11
+ origin: origins,
12
+ credentials: false,
13
+ methods: ['PUT'],
14
+ allowedHeaders: ['content-type'],
15
+ });
16
+ }
17
+ const grantSchema = z.object({
18
+ uploadUrl: z.url(),
19
+ url: z.url(),
20
+ expiresIn: z.number().positive().optional(),
21
+ });
22
+ const inputSchema = z.object({
23
+ contentType: z.string().regex(/^(video|audio|image)\/[\w.+-]+$/),
24
+ size: z.number().int().positive(),
25
+ });
26
+ const tickets = new Map();
27
+ /** A ticket grants one bounded PUT to one server-issued storage URL, never arbitrary proxying. */
28
+ export function relayUploadTool(tool, origin, storageOrigin) {
29
+ if (tool.name !== 'assets_create-upload-url')
30
+ return tool;
31
+ return {
32
+ ...tool,
33
+ handler: async (args, context) => {
34
+ const input = inputSchema.parse(args);
35
+ if (input.size > 5 * 1024 ** 3)
36
+ throw new Error(`This server supports uploads up to ${5 * 1024} MB.`);
37
+ for (const [key, ticket] of tickets)
38
+ if (ticket.expiresAt <= Date.now())
39
+ tickets.delete(key);
40
+ if (tickets.size >= 128)
41
+ throw new Error('Upload capacity is full. Try again shortly.');
42
+ const signed = grantSchema.parse(await tool.handler(args, context));
43
+ const destination = new URL(signed.uploadUrl);
44
+ if (destination.protocol !== 'https:' ||
45
+ destination.origin !== storageOrigin ||
46
+ destination.username ||
47
+ destination.password)
48
+ throw new Error('Unexpected storage upload destination.');
49
+ const token = randomBytes(32).toString('hex');
50
+ const expiresIn = Math.min(signed.expiresIn ?? 300, 300);
51
+ process.stderr.write(JSON.stringify({
52
+ event: 'app_upload_grant',
53
+ storageOrigin: destination.origin,
54
+ bytes: input.size,
55
+ }) + '\n');
56
+ tickets.set(token, {
57
+ destination: signed.uploadUrl,
58
+ ...input,
59
+ expiresAt: Date.now() + expiresIn * 1000,
60
+ });
61
+ return { ...signed, expiresIn, uploadUrl: `${origin}/app-upload?ticket=${token}` };
62
+ },
63
+ };
64
+ }
65
+ export const appUploadHandler = async (req, res) => {
66
+ res.setHeader('Cache-Control', 'no-store');
67
+ const token = typeof req.query.ticket === 'string' ? req.query.ticket : '';
68
+ const ticket = tickets.get(token);
69
+ if (!ticket || ticket.expiresAt <= Date.now()) {
70
+ tickets.delete(token);
71
+ res.status(403).json({ error: 'Upload permission expired. Choose the file again.' });
72
+ return;
73
+ }
74
+ if (req.headers['content-type'] !== ticket.contentType ||
75
+ Number(req.headers['content-length']) !== ticket.size) {
76
+ res.status(400).json({ error: 'Upload size or content type did not match.' });
77
+ return;
78
+ }
79
+ tickets.delete(token);
80
+ const controller = new AbortController();
81
+ const abort = () => {
82
+ if (!res.writableFinished)
83
+ controller.abort();
84
+ };
85
+ res.once('close', abort);
86
+ try {
87
+ await uploadRuntime.run(async (signal) => {
88
+ let size = 0;
89
+ const bounded = new Transform({
90
+ transform(chunk, _encoding, callback) {
91
+ size += chunk.length;
92
+ if (size > ticket.size)
93
+ callback(new Error('Upload exceeded its declared size.'));
94
+ else
95
+ callback(null, chunk);
96
+ },
97
+ flush(callback) {
98
+ callback(size === ticket.size ? null : new Error('Incomplete upload.'));
99
+ },
100
+ });
101
+ let resolveResponse;
102
+ let rejectResponse;
103
+ const response = new Promise((resolve, reject) => {
104
+ resolveResponse = resolve;
105
+ rejectResponse = reject;
106
+ });
107
+ const upstream = request(ticket.destination, {
108
+ method: 'PUT',
109
+ signal,
110
+ headers: { 'Content-Type': ticket.contentType, 'Content-Length': ticket.size },
111
+ }, (incoming) => {
112
+ incoming.resume();
113
+ process.stderr.write(JSON.stringify({
114
+ event: 'app_upload_storage_response',
115
+ status: incoming.statusCode,
116
+ }) + '\n');
117
+ incoming.on('error', rejectResponse);
118
+ incoming.on('end', () => incoming.statusCode && incoming.statusCode >= 200 && incoming.statusCode < 300
119
+ ? resolveResponse()
120
+ : rejectResponse(new Error('Storage rejected the upload.')));
121
+ });
122
+ upstream.on('error', rejectResponse);
123
+ // Both promises have rejection handlers immediately; backpressure bounds memory.
124
+ await Promise.all([pipeline(req, bounded, upstream, { signal }), response]);
125
+ uploadRuntime.recordUploadedBytes(size);
126
+ }, { signal: controller.signal });
127
+ res.status(204).end();
128
+ }
129
+ catch {
130
+ if (!res.destroyed)
131
+ res.status(502).json({ error: 'Upload did not complete. Please retry.' });
132
+ }
133
+ finally {
134
+ res.off('close', abort);
135
+ }
136
+ };
@@ -0,0 +1,3 @@
1
+ export declare const ASSET_THUMBNAIL_INSTRUCTIONS = "For asset previews, use only the exact thumbnailUrl returned by Sync for that same asset, preserving its URL and query string. Never search the web, generate an image, guess a URL, or substitute another asset, a stock image, a voice portrait, url or proxyPath as its thumbnail. If thumbnailUrl is missing or fails to load, use the black Sync-logo placeholder at https://mcp.sync.so/asset-placeholder.svg and label it \"Thumbnail unavailable\"; it is a placeholder, not a frame from the asset. If the host cannot display the placeholder, say \"Thumbnail unavailable\" without substituting an image. Actual playback still uses the asset media URL. ";
2
+ export declare const ASSET_THUMBNAIL_PLACEHOLDER = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"640\" height=\"360\" viewBox=\"0 0 640 360\"><title>Sync asset thumbnail unavailable</title><rect width=\"640\" height=\"360\" fill=\"black\"/><g transform=\"translate(296 149.28) scale(3)\" fill=\"white\"><path fill-rule=\"evenodd\" clip-rule=\"evenodd\" d=\"M7.99969 17.0347V13.6457C4.513 13.6457 1.54824 12.2179 0.452561 10.2309C0.804942 9.59381 1.34998 9.01497 2.04423 8.52279C1.35768 8.03061 0.816852 7.45312 0.46377 6.8208C0.163931 7.35631 0 7.9338 0 8.53565C0 9.12938 0.159727 9.7001 0.452561 10.2309C0.159727 10.761 0 11.3303 0 11.9247C0 14.7417 3.58126 17.0347 7.99969 17.0347ZM7.99969 3.44324V0C3.58126 0 0 2.28353 0 5.10122C0 5.70307 0.163931 6.28259 0.464471 6.82148C1.56925 4.85005 4.52561 3.44324 7.99969 3.44324ZM7.99969 6.83299C5.6346 6.83299 3.5091 7.48494 2.04423 8.52347C3.5091 9.57417 5.6339 10.2397 7.99969 10.2397V6.83299ZM13.9466 11.9653C14.6449 11.4711 15.1934 10.8889 15.5472 10.2485C15.84 10.7786 15.9997 11.3486 15.999 11.9424C15.999 12.5456 15.8351 13.123 15.5339 13.6592C15.1801 13.0276 14.6365 12.4542 13.9465 11.9655C12.4823 13.0026 10.3611 13.6457 8.00032 13.6457V10.239C10.3604 10.239 12.4824 10.9281 13.9466 11.9653ZM8.00032 20.478V17.0347C11.473 17.0347 14.4286 15.6293 15.5348 13.6592C15.8354 14.1961 16 14.7742 16 15.3768C16 18.1944 12.418 20.478 8.00032 20.478ZM8.00032 3.44327V6.83235C11.487 6.83235 14.4518 8.26353 15.5474 10.2478C15.8403 9.71773 16 9.14702 16 8.55329C16 5.73628 12.418 3.44327 8.00032 3.44327Z\"/></g></svg>";
3
+ //# sourceMappingURL=asset-thumbnail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-thumbnail.d.ts","sourceRoot":"","sources":["../src/asset-thumbnail.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,4BAA4B,2oBAC6lB,CAAC;AAEvoB,eAAO,MAAM,2BAA2B,g/CAC+6C,CAAC"}
@@ -0,0 +1,2 @@
1
+ export const ASSET_THUMBNAIL_INSTRUCTIONS = 'For asset previews, use only the exact thumbnailUrl returned by Sync for that same asset, preserving its URL and query string. Never search the web, generate an image, guess a URL, or substitute another asset, a stock image, a voice portrait, url or proxyPath as its thumbnail. If thumbnailUrl is missing or fails to load, use the black Sync-logo placeholder at https://mcp.sync.so/asset-placeholder.svg and label it "Thumbnail unavailable"; it is a placeholder, not a frame from the asset. If the host cannot display the placeholder, say "Thumbnail unavailable" without substituting an image. Actual playback still uses the asset media URL. ';
2
+ export const ASSET_THUMBNAIL_PLACEHOLDER = '<svg xmlns="http://www.w3.org/2000/svg" width="640" height="360" viewBox="0 0 640 360"><title>Sync asset thumbnail unavailable</title><rect width="640" height="360" fill="black"/><g transform="translate(296 149.28) scale(3)" fill="white"><path fill-rule="evenodd" clip-rule="evenodd" d="M7.99969 17.0347V13.6457C4.513 13.6457 1.54824 12.2179 0.452561 10.2309C0.804942 9.59381 1.34998 9.01497 2.04423 8.52279C1.35768 8.03061 0.816852 7.45312 0.46377 6.8208C0.163931 7.35631 0 7.9338 0 8.53565C0 9.12938 0.159727 9.7001 0.452561 10.2309C0.159727 10.761 0 11.3303 0 11.9247C0 14.7417 3.58126 17.0347 7.99969 17.0347ZM7.99969 3.44324V0C3.58126 0 0 2.28353 0 5.10122C0 5.70307 0.163931 6.28259 0.464471 6.82148C1.56925 4.85005 4.52561 3.44324 7.99969 3.44324ZM7.99969 6.83299C5.6346 6.83299 3.5091 7.48494 2.04423 8.52347C3.5091 9.57417 5.6339 10.2397 7.99969 10.2397V6.83299ZM13.9466 11.9653C14.6449 11.4711 15.1934 10.8889 15.5472 10.2485C15.84 10.7786 15.9997 11.3486 15.999 11.9424C15.999 12.5456 15.8351 13.123 15.5339 13.6592C15.1801 13.0276 14.6365 12.4542 13.9465 11.9655C12.4823 13.0026 10.3611 13.6457 8.00032 13.6457V10.239C10.3604 10.239 12.4824 10.9281 13.9466 11.9653ZM8.00032 20.478V17.0347C11.473 17.0347 14.4286 15.6293 15.5348 13.6592C15.8354 14.1961 16 14.7742 16 15.3768C16 18.1944 12.418 20.478 8.00032 20.478ZM8.00032 3.44327V6.83235C11.487 6.83235 14.4518 8.26353 15.5474 10.2478C15.8403 9.71773 16 9.14702 16 8.55329C16 5.73628 12.418 3.44327 8.00032 3.44327Z"/></g></svg>';
@@ -0,0 +1,5 @@
1
+ export declare function runWithAuth<T>(token: string, clientName: string | undefined, fn: () => T, owner?: string): T;
2
+ export declare function getAuthToken(): string | undefined;
3
+ export declare function getAuthOwner(): string | undefined;
4
+ export declare function getClientName(): string | undefined;
5
+ //# sourceMappingURL=async-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-context.d.ts","sourceRoot":"","sources":["../../src/auth/async-context.ts"],"names":[],"mappings":"AAWA,wBAAgB,WAAW,CAAC,CAAC,EAC3B,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,EAAE,EAAE,MAAM,CAAC,EACX,KAAK,CAAC,EAAE,MAAM,GACb,CAAC,CAEH;AAED,wBAAgB,YAAY,IAAI,MAAM,GAAG,SAAS,CAEjD;AAED,wBAAgB,YAAY,IAAI,MAAM,GAAG,SAAS,CAEjD;AAED,wBAAgB,aAAa,IAAI,MAAM,GAAG,SAAS,CAElD"}
@@ -0,0 +1,14 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ const authContext = new AsyncLocalStorage();
3
+ export function runWithAuth(token, clientName, fn, owner) {
4
+ return authContext.run({ token, clientName, owner }, fn);
5
+ }
6
+ export function getAuthToken() {
7
+ return authContext.getStore()?.token;
8
+ }
9
+ export function getAuthOwner() {
10
+ return authContext.getStore()?.owner;
11
+ }
12
+ export function getClientName() {
13
+ return authContext.getStore()?.clientName;
14
+ }
@@ -0,0 +1,16 @@
1
+ import type { AuthInfo } from '@modelcontextprotocol/server';
2
+ import type { BearerAuthMiddlewareOptions } from '@modelcontextprotocol/server-legacy/auth';
3
+ import type { RequestHandler } from 'express';
4
+ type Options = Omit<BearerAuthMiddlewareOptions, 'verifier'> & {
5
+ verifier: {
6
+ verifyAccessToken(token: string, signal?: AbortSignal): Promise<AuthInfo>;
7
+ };
8
+ };
9
+ /**
10
+ * Preserve SDK authentication checks; only translate dependency failures locally.
11
+ * Uses the frozen v1 middleware from server-legacy: the token verifier throws its
12
+ * error classes, and the challenge and status mapping in docs/ depend on it.
13
+ */
14
+ export declare function requireBearerAuth(options: Options): RequestHandler;
15
+ export {};
16
+ //# sourceMappingURL=bearer-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer-auth.d.ts","sourceRoot":"","sources":["../../src/auth/bearer-auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AAE5F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C,KAAK,OAAO,GAAG,IAAI,CAAC,2BAA2B,EAAE,UAAU,CAAC,GAAG;IAC7D,QAAQ,EAAE;QAAE,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;KAAE,CAAC;CACzF,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,cAAc,CAwClE"}
@@ -0,0 +1,55 @@
1
+ import { requireBearerAuth as sdkRequireBearerAuth } from '@modelcontextprotocol/server-legacy/auth';
2
+ import { VerificationUnavailableError } from './token-verification.js';
3
+ /**
4
+ * Preserve SDK authentication checks; only translate dependency failures locally.
5
+ * Uses the frozen v1 middleware from server-legacy: the token verifier throws its
6
+ * error classes, and the challenge and status mapping in docs/ depend on it.
7
+ */
8
+ export function requireBearerAuth(options) {
9
+ return async (req, res, next) => {
10
+ const [scheme, token] = (req.headers.authorization ?? '').split(' ');
11
+ if (scheme?.toLowerCase() !== 'bearer' || !token) {
12
+ return sdkRequireBearerAuth(options)(req, res, next);
13
+ }
14
+ const controller = new AbortController();
15
+ const cancel = () => controller.abort();
16
+ req.once('aborted', cancel);
17
+ res.once('close', cancel);
18
+ if (req.aborted || res.destroyed)
19
+ cancel();
20
+ let auth;
21
+ let failure;
22
+ try {
23
+ auth = await options.verifier.verifyAccessToken(token, controller.signal);
24
+ }
25
+ catch (error) {
26
+ failure = error;
27
+ }
28
+ finally {
29
+ req.off('aborted', cancel);
30
+ res.off('close', cancel);
31
+ }
32
+ if (controller.signal.aborted || res.destroyed)
33
+ return;
34
+ if (failure instanceof VerificationUnavailableError) {
35
+ res.set('Cache-Control', 'no-store');
36
+ if (failure.retryAfter)
37
+ res.set('Retry-After', failure.retryAfter);
38
+ res.status(failure.status).json(failure.toResponseObject());
39
+ return;
40
+ }
41
+ // SDK still owns invalid-token challenges, expiration and scope enforcement.
42
+ return sdkRequireBearerAuth({
43
+ ...options,
44
+ verifier: {
45
+ verifyAccessToken: async () => {
46
+ if (failure)
47
+ throw failure;
48
+ if (!auth)
49
+ throw new Error('Token verification failed');
50
+ return auth;
51
+ },
52
+ },
53
+ })(req, res, next);
54
+ };
55
+ }
@@ -0,0 +1,13 @@
1
+ import { InvalidClientError } from '@modelcontextprotocol/server-legacy/auth';
2
+ export declare const CLIENT_AUTH_METHODS: string[];
3
+ export declare const BASIC_CHALLENGE = "Basic realm=\"oauth\"";
4
+ export declare class ClientAuthenticationError extends InvalidClientError {
5
+ readonly status: 400 | 401;
6
+ constructor(status: 400 | 401);
7
+ }
8
+ export declare function extractBasicClientCredentials(authorization: string | undefined): {
9
+ clientId: string;
10
+ clientSecret: string;
11
+ } | undefined;
12
+ export declare function mergeBasicClientCredentials(body: Record<string, unknown>, authorization: string | undefined): Record<string, unknown>;
13
+ //# sourceMappingURL=client-credentials.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-credentials.d.ts","sourceRoot":"","sources":["../../src/auth/client-credentials.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAuB,MAAM,0CAA0C,CAAC;AAInG,eAAO,MAAM,mBAAmB,UAAgD,CAAC;AACjF,eAAO,MAAM,eAAe,0BAAwB,CAAC;AAErD,qBAAa,yBAA0B,SAAQ,kBAAkB;IACnD,QAAQ,CAAC,MAAM,EAAE,GAAG,GAAG,GAAG;IAAtC,YAAqB,MAAM,EAAE,GAAG,GAAG,GAAG,EAErC;CACF;AAED,wBAAgB,6BAA6B,CAC3C,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAyBxD;AAED,wBAAgB,2BAA2B,CACzC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,aAAa,EAAE,MAAM,GAAG,SAAS,GAChC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CA2BzB"}
@@ -0,0 +1,67 @@
1
+ import { InvalidClientError, InvalidRequestError } from '@modelcontextprotocol/server-legacy/auth';
2
+ // Basic is an existing ChatGPT compatibility path; both methods become a single
3
+ // confidential body-secret request to Sync. Public clients are not supported.
4
+ export const CLIENT_AUTH_METHODS = ['client_secret_post', 'client_secret_basic'];
5
+ export const BASIC_CHALLENGE = 'Basic realm="oauth"';
6
+ export class ClientAuthenticationError extends InvalidClientError {
7
+ status;
8
+ constructor(status) {
9
+ super('Invalid client authentication');
10
+ this.status = status;
11
+ }
12
+ }
13
+ export function extractBasicClientCredentials(authorization) {
14
+ if (authorization === undefined)
15
+ return undefined;
16
+ const match = /^Basic +([A-Za-z0-9+/]+={0,2})$/i.exec(authorization);
17
+ if (!match?.[1])
18
+ throw new ClientAuthenticationError(401);
19
+ try {
20
+ const bytes = Buffer.from(match[1], 'base64');
21
+ // Buffer's decoder otherwise ignores malformed input. Anchored parsing allows
22
+ // at most two padding characters; index/slice comparisons are linear.
23
+ const canonical = bytes.toString('base64');
24
+ const padding = canonical.indexOf('=');
25
+ const unpadded = padding < 0 ? canonical : canonical.slice(0, padding);
26
+ if (match[1] !== canonical && match[1] !== unpadded)
27
+ throw new Error('Basic credentials are not canonical base64');
28
+ const decoded = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
29
+ const colon = decoded.indexOf(':');
30
+ if (colon < 0)
31
+ throw new Error('Basic credentials are missing the client ID separator');
32
+ const decode = (value) => decodeURIComponent(value.replace(/\+/g, ' '));
33
+ const clientId = decode(decoded.slice(0, colon));
34
+ const clientSecret = decode(decoded.slice(colon + 1));
35
+ if (!clientId || !clientSecret)
36
+ throw new Error('Basic credentials need a client ID and secret');
37
+ return { clientId, clientSecret };
38
+ }
39
+ catch {
40
+ throw new ClientAuthenticationError(401);
41
+ }
42
+ }
43
+ export function mergeBasicClientCredentials(body, authorization) {
44
+ // RFC 6749 2.3: one authentication method per request, even if values match.
45
+ if (authorization !== undefined &&
46
+ (Object.hasOwn(body, 'client_id') || Object.hasOwn(body, 'client_secret'))) {
47
+ throw new InvalidRequestError('Multiple client authentication methods are not allowed');
48
+ }
49
+ if (Object.hasOwn(body, 'client_assertion') || Object.hasOwn(body, 'client_assertion_type')) {
50
+ throw new InvalidRequestError('Unsupported client authentication method');
51
+ }
52
+ for (const key of ['client_id', 'client_secret']) {
53
+ if (Object.hasOwn(body, key) && typeof body[key] !== 'string') {
54
+ throw new InvalidRequestError('Client credentials must be single strings');
55
+ }
56
+ }
57
+ const basic = extractBasicClientCredentials(authorization);
58
+ if (basic)
59
+ return { ...body, client_id: basic.clientId, client_secret: basic.clientSecret };
60
+ if (typeof body.client_id !== 'string' ||
61
+ !body.client_id ||
62
+ typeof body.client_secret !== 'string' ||
63
+ !body.client_secret) {
64
+ throw new ClientAuthenticationError(400);
65
+ }
66
+ return { ...body };
67
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"device-auth.d.ts","sourceRoot":"","sources":["../../src/auth/device-auth.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,CAAC;AAgBF,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,OAAO,CAAC,eAAe,CAAC,CA8B1B"}
1
+ {"version":3,"file":"device-auth.d.ts","sourceRoot":"","sources":["../../src/auth/device-auth.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,CAAC;AAgBF,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC7B,OAAO,CAAC,eAAe,CAAC,CA8B1B"}
@@ -1,10 +1,11 @@
1
+ import { PACKAGE_VERSION } from '../version.js';
1
2
  import { saveToken } from './token-store.js';
2
3
  const POLL_INTERVAL_MS = 5000;
3
4
  const CLIENT_ID = 'sync-mcp-server';
4
5
  export async function performDeviceAuth(baseUrl, log) {
5
6
  const startResponse = await fetch(`${baseUrl}/v2/device-auth/start`, {
6
7
  method: 'POST',
7
- headers: { 'Content-Type': 'application/json' },
8
+ headers: { 'Content-Type': 'application/json', 'x-sync-mcp-version': PACKAGE_VERSION },
8
9
  body: JSON.stringify({ clientId: CLIENT_ID }),
9
10
  });
10
11
  if (!startResponse.ok) {
@@ -26,7 +27,7 @@ export async function performDeviceAuth(baseUrl, log) {
26
27
  async function pollForToken(baseUrl, deviceCode) {
27
28
  while (true) {
28
29
  await sleep(POLL_INTERVAL_MS);
29
- const pollResponse = await fetch(`${baseUrl}/v2/device-auth/poll?deviceCode=${encodeURIComponent(deviceCode)}`);
30
+ const pollResponse = await fetch(`${baseUrl}/v2/device-auth/poll?deviceCode=${encodeURIComponent(deviceCode)}`, { headers: { 'x-sync-mcp-version': PACKAGE_VERSION } });
30
31
  if (!pollResponse.ok) {
31
32
  if (pollResponse.status === 404) {
32
33
  throw new Error('Device auth code expired. Please try again.');
@@ -0,0 +1,4 @@
1
+ import type { Server } from 'node:http';
2
+ /** Test fixture disposal also works after partial startup or repeated cleanup. */
3
+ export declare function closeFixtureServers(...servers: Array<Server | undefined>): Promise<void>;
4
+ //# sourceMappingURL=fixture-cleanup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fixture-cleanup.d.ts","sourceRoot":"","sources":["../../src/auth/fixture-cleanup.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAExC,kFAAkF;AAClF,wBAAsB,mBAAmB,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ9F"}
@@ -0,0 +1,9 @@
1
+ /** Test fixture disposal also works after partial startup or repeated cleanup. */
2
+ export async function closeFixtureServers(...servers) {
3
+ await Promise.all(servers.map(async (server) => {
4
+ if (!server)
5
+ return;
6
+ server.closeAllConnections();
7
+ await new Promise((resolve) => server.close(() => resolve()));
8
+ }));
9
+ }
@@ -1,6 +1,8 @@
1
1
  export type { ApiKeyAuth } from './api-key.js';
2
2
  export { createApiKeyAuth } from './api-key.js';
3
+ export { getAuthToken, runWithAuth } from './async-context.js';
3
4
  export type { DeviceAuthToken } from './device-auth.js';
4
5
  export { performDeviceAuth } from './device-auth.js';
6
+ export { createOAuthProvider } from './oauth-provider.js';
5
7
  export { clearToken, loadToken, saveToken } from './token-store.js';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAC/D,YAAY,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC"}
@@ -1,3 +1,5 @@
1
1
  export { createApiKeyAuth } from './api-key.js';
2
+ export { getAuthToken, runWithAuth } from './async-context.js';
2
3
  export { performDeviceAuth } from './device-auth.js';
4
+ export { createOAuthProvider } from './oauth-provider.js';
3
5
  export { clearToken, loadToken, saveToken } from './token-store.js';
@@ -0,0 +1,11 @@
1
+ export declare function oauthFailureDiagnostics(response: unknown): {
2
+ oauthError: string;
3
+ reason: string;
4
+ };
5
+ export declare function oauthGrantType(value: unknown): string;
6
+ export declare function oauthRequestDiagnostics(body: Record<string, unknown>, authorization: string | undefined): {
7
+ bodyClientId: string;
8
+ bodyClientSecret: string;
9
+ authorizationScheme: string;
10
+ };
11
+ //# sourceMappingURL=oauth-diagnostics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-diagnostics.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-diagnostics.ts"],"names":[],"mappings":"AAoCA,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,OAAO,GAAG;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;CAChB,CAmBA;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAGrD;AAGD,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,aAAa,EAAE,MAAM,GAAG,SAAS;;;;EAiBlC"}
@@ -0,0 +1,78 @@
1
+ // Diagnostics must never copy upstream text: OAuth errors can echo credentials.
2
+ // Only exact, known values are converted to fixed labels.
3
+ const OAUTH_ERRORS = new Set([
4
+ 'invalid_request',
5
+ 'invalid_client',
6
+ 'invalid_grant',
7
+ 'unauthorized_client',
8
+ 'unsupported_grant_type',
9
+ 'invalid_scope',
10
+ 'access_denied',
11
+ 'server_error',
12
+ 'temporarily_unavailable',
13
+ ]);
14
+ const KNOWN_REASONS = new Map([
15
+ ['Invalid, revoked, or expired refresh token', 'refresh_token_invalid_revoked_or_expired'],
16
+ ['Refresh token not found', 'refresh_token_not_found'],
17
+ ['Account no longer has access to this organization', 'organization_access_revoked'],
18
+ ['refresh_token and client_id are required', 'missing_refresh_parameters'],
19
+ ['Unknown client_id', 'unknown_client'],
20
+ ['Invalid client_secret', 'invalid_client_secret'],
21
+ ['client_secret is required', 'missing_client_secret'],
22
+ ['Invalid or expired authorization code', 'authorization_code_invalid_or_expired'],
23
+ ['code, client_id, and redirect_uri are required', 'missing_code_parameters'],
24
+ ['client_id mismatch', 'client_id_mismatch'],
25
+ ['redirect_uri mismatch', 'redirect_uri_mismatch'],
26
+ ['code_verifier required for PKCE challenge', 'missing_pkce_verifier'],
27
+ ['PKCE code_verifier mismatch', 'pkce_verifier_mismatch'],
28
+ ['Multiple Authorization headers are not allowed', 'duplicate_authorization_headers'],
29
+ ['OAuth parameters must be single strings', 'non_scalar_oauth_parameters'],
30
+ ['Multiple client authentication methods are not allowed', 'mixed_client_authentication'],
31
+ ['Unsupported client authentication method', 'unsupported_client_authentication'],
32
+ ['Client credentials must be single strings', 'non_scalar_client_credentials'],
33
+ ['Invalid client authentication', 'invalid_client_authentication'],
34
+ ]);
35
+ export function oauthFailureDiagnostics(response) {
36
+ let value = response;
37
+ if (typeof value === 'string') {
38
+ try {
39
+ value = JSON.parse(value);
40
+ }
41
+ catch {
42
+ return { oauthError: 'unknown', reason: 'unrecognized_response' };
43
+ }
44
+ }
45
+ const body = value && typeof value === 'object' ? value : {};
46
+ const reason = [body.message, body.error_description]
47
+ .filter((message) => typeof message === 'string')
48
+ .map((message) => KNOWN_REASONS.get(message))
49
+ .find((known) => known !== undefined);
50
+ return {
51
+ oauthError: typeof body.error === 'string' && OAUTH_ERRORS.has(body.error) ? body.error : 'unknown',
52
+ reason: reason ?? 'unrecognized_response',
53
+ };
54
+ }
55
+ export function oauthGrantType(value) {
56
+ if (value === 'authorization_code' || value === 'refresh_token')
57
+ return value;
58
+ return value === undefined ? 'missing' : 'other';
59
+ }
60
+ // Describe only presence and the scheme. Never decode or retain credential values.
61
+ export function oauthRequestDiagnostics(body, authorization) {
62
+ const presence = (value) => {
63
+ if (value === undefined)
64
+ return 'missing';
65
+ if (typeof value !== 'string')
66
+ return 'non_string';
67
+ return value.length === 0 ? 'empty' : 'present';
68
+ };
69
+ return {
70
+ bodyClientId: presence(body.client_id),
71
+ bodyClientSecret: presence(body.client_secret),
72
+ authorizationScheme: authorization === undefined
73
+ ? 'none'
74
+ : /^Basic(?:\s|$)/i.test(authorization)
75
+ ? 'basic'
76
+ : 'other',
77
+ };
78
+ }
@@ -0,0 +1,16 @@
1
+ import type { AuthInfo } from '@modelcontextprotocol/server';
2
+ import { ProxyOAuthServerProvider } from '@modelcontextprotocol/server-legacy/auth';
3
+ /**
4
+ * Creates an OAuth provider that proxies to the Sync API's OAuth endpoints.
5
+ *
6
+ * Registered clients are cached in memory so the SDK's authorize handler can
7
+ * look them up (it calls getClient before proxying the authorize request). The
8
+ * cache doesn't survive a restart, so on a miss we read the client back from
9
+ * the API (RFC 7592) — otherwise every redeploy would invalidate every
10
+ * already-connected client with `invalid_client`.
11
+ */
12
+ export declare function createOAuthProvider(apiBaseUrl: string, authorizationUrl?: string): ProxyOAuthServerProvider & {
13
+ verifyAccessToken(token: string, signal?: AbortSignal): Promise<AuthInfo>;
14
+ clearVerificationCache(): void;
15
+ };
16
+ //# sourceMappingURL=oauth-provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth-provider.d.ts","sourceRoot":"","sources":["../../src/auth/oauth-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAA8B,MAAM,8BAA8B,CAAC;AACzF,OAAO,EAAE,wBAAwB,EAAE,MAAM,0CAA0C,CAAC;AAwBpF;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,gBAAgB,SAAqC,GACpD,wBAAwB,GAAG;IAC5B,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC1E,sBAAsB,IAAI,IAAI,CAAC;CAChC,CAgGA"}
@@ -0,0 +1,107 @@
1
+ import { ProxyOAuthServerProvider } from '@modelcontextprotocol/server-legacy/auth';
2
+ import { createTokenVerifier } from './verification-cache.js';
3
+ const CONFIDENTIAL_CLIENT_AUTH_METHOD = 'client_secret_post';
4
+ const NON_EXPIRING_CLIENT_SECRET = 0;
5
+ function normalizeClientInfo(client) {
6
+ return {
7
+ ...client,
8
+ response_types: client.response_types ?? ['code'],
9
+ token_endpoint_auth_method: CONFIDENTIAL_CLIENT_AUTH_METHOD,
10
+ ...(client.client_secret && client.client_secret_expires_at == null
11
+ ? { client_secret_expires_at: NON_EXPIRING_CLIENT_SECRET }
12
+ : {}),
13
+ };
14
+ }
15
+ function asConfidentialClient(client) {
16
+ return {
17
+ ...client,
18
+ token_endpoint_auth_method: CONFIDENTIAL_CLIENT_AUTH_METHOD,
19
+ };
20
+ }
21
+ /**
22
+ * Creates an OAuth provider that proxies to the Sync API's OAuth endpoints.
23
+ *
24
+ * Registered clients are cached in memory so the SDK's authorize handler can
25
+ * look them up (it calls getClient before proxying the authorize request). The
26
+ * cache doesn't survive a restart, so on a miss we read the client back from
27
+ * the API (RFC 7592) — otherwise every redeploy would invalidate every
28
+ * already-connected client with `invalid_client`.
29
+ */
30
+ export function createOAuthProvider(apiBaseUrl, authorizationUrl = `${apiBaseUrl}/v2/oauth/authorize`) {
31
+ const verifier = createTokenVerifier(apiBaseUrl);
32
+ const clientCache = new Map();
33
+ const registrationSecret = process.env.OAUTH_REGISTRATION_SECRET;
34
+ const registrationUrl = `${apiBaseUrl}/v2/oauth/register`;
35
+ // Cache-first client lookup with an API fallback. The API persists clients;
36
+ // the in-memory cache is only a fast path that's empty after a restart.
37
+ async function resolveClient(clientId) {
38
+ const cached = clientCache.get(clientId);
39
+ if (cached)
40
+ return cached;
41
+ try {
42
+ const res = await fetch(`${registrationUrl}/${encodeURIComponent(clientId)}`, {
43
+ headers: registrationSecret ? { Authorization: `Bearer ${registrationSecret}` } : {},
44
+ });
45
+ if (!res.ok)
46
+ return undefined;
47
+ const info = (await res.json());
48
+ const client = normalizeClientInfo({
49
+ client_id: info.client_id,
50
+ client_secret: info.client_secret,
51
+ client_secret_expires_at: info.client_secret_expires_at,
52
+ redirect_uris: info.redirect_uris,
53
+ client_name: info.client_name,
54
+ grant_types: info.grant_types,
55
+ response_types: info.response_types,
56
+ scope: info.scope,
57
+ token_endpoint_auth_method: info.token_endpoint_auth_method,
58
+ });
59
+ clientCache.set(client.client_id, client);
60
+ return client;
61
+ }
62
+ catch {
63
+ return undefined;
64
+ }
65
+ }
66
+ const provider = new ProxyOAuthServerProvider({
67
+ endpoints: {
68
+ authorizationUrl,
69
+ tokenUrl: `${apiBaseUrl}/v2/oauth/token`,
70
+ revocationUrl: `${apiBaseUrl}/v2/oauth/revoke`,
71
+ registrationUrl,
72
+ },
73
+ verifyAccessToken: verifier.verify,
74
+ getClient: resolveClient,
75
+ // Custom fetch that adds the registration secret header
76
+ fetch: async (url, init) => {
77
+ const headers = new Headers(init?.headers);
78
+ if (typeof url === 'string' && url === registrationUrl && registrationSecret) {
79
+ headers.set('Authorization', `Bearer ${registrationSecret}`);
80
+ }
81
+ return fetch(url, { ...init, headers });
82
+ },
83
+ });
84
+ // Wrap the clientsStore so a freshly registered client is cached (fast path)
85
+ // and lookups go through the cache-then-API resolver.
86
+ const originalStore = provider.clientsStore;
87
+ const originalRegister = originalStore.registerClient?.bind(originalStore);
88
+ Object.defineProperty(provider, 'clientsStore', {
89
+ get() {
90
+ return {
91
+ ...originalStore,
92
+ getClient: resolveClient,
93
+ ...(originalRegister && {
94
+ registerClient: async (client) => {
95
+ const registered = normalizeClientInfo(await originalRegister(asConfidentialClient(client)));
96
+ clientCache.set(registered.client_id, registered);
97
+ return registered;
98
+ },
99
+ }),
100
+ };
101
+ },
102
+ });
103
+ return Object.assign(provider, {
104
+ verifyAccessToken: verifier.verify,
105
+ clearVerificationCache: verifier.clear,
106
+ });
107
+ }
@@ -0,0 +1,2 @@
1
+ export declare function resourceMetadataUrl(port: number): string;
2
+ //# sourceMappingURL=resource-metadata.d.ts.map