@sync.so/mcp-server 0.1.4 → 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.
- package/README.md +261 -86
- package/deploy/generation-status/app.html.gz +0 -0
- package/deploy/generation-status/release.json +6 -0
- package/dist/abort-signals.d.ts +6 -0
- package/dist/abort-signals.d.ts.map +1 -0
- package/dist/abort-signals.js +23 -0
- package/dist/account-features.d.ts +7 -0
- package/dist/account-features.d.ts.map +1 -0
- package/dist/account-features.js +48 -0
- package/dist/app-upload-relay.d.ts +13 -0
- package/dist/app-upload-relay.d.ts.map +1 -0
- package/dist/app-upload-relay.js +136 -0
- package/dist/asset-thumbnail.d.ts +3 -0
- package/dist/asset-thumbnail.d.ts.map +1 -0
- package/dist/asset-thumbnail.js +2 -0
- package/dist/auth/async-context.d.ts +5 -0
- package/dist/auth/async-context.d.ts.map +1 -0
- package/dist/auth/async-context.js +14 -0
- package/dist/auth/bearer-auth.d.ts +16 -0
- package/dist/auth/bearer-auth.d.ts.map +1 -0
- package/dist/auth/bearer-auth.js +55 -0
- package/dist/auth/client-credentials.d.ts +13 -0
- package/dist/auth/client-credentials.d.ts.map +1 -0
- package/dist/auth/client-credentials.js +67 -0
- package/dist/auth/device-auth.d.ts.map +1 -1
- package/dist/auth/device-auth.js +3 -2
- package/dist/auth/fixture-cleanup.d.ts +4 -0
- package/dist/auth/fixture-cleanup.d.ts.map +1 -0
- package/dist/auth/fixture-cleanup.js +9 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +2 -0
- package/dist/auth/oauth-diagnostics.d.ts +11 -0
- package/dist/auth/oauth-diagnostics.d.ts.map +1 -0
- package/dist/auth/oauth-diagnostics.js +78 -0
- package/dist/auth/oauth-provider.d.ts +16 -0
- package/dist/auth/oauth-provider.d.ts.map +1 -0
- package/dist/auth/oauth-provider.js +107 -0
- package/dist/auth/resource-metadata.d.ts +2 -0
- package/dist/auth/resource-metadata.d.ts.map +1 -0
- package/dist/auth/resource-metadata.js +3 -0
- package/dist/auth/token-verification.d.ts +11 -0
- package/dist/auth/token-verification.d.ts.map +1 -0
- package/dist/auth/token-verification.js +99 -0
- package/dist/auth/verification-cache.d.ts +8 -0
- package/dist/auth/verification-cache.d.ts.map +1 -0
- package/dist/auth/verification-cache.js +111 -0
- package/dist/chatgpt-analytics.d.ts +22 -0
- package/dist/chatgpt-analytics.d.ts.map +1 -0
- package/dist/chatgpt-analytics.js +172 -0
- package/dist/chatgpt-app.d.ts +15 -0
- package/dist/chatgpt-app.d.ts.map +1 -0
- package/dist/chatgpt-app.js +133 -0
- package/dist/chatgpt-release.d.ts +12 -0
- package/dist/chatgpt-release.d.ts.map +1 -0
- package/dist/chatgpt-release.js +30 -0
- package/dist/cli.js +30 -16
- package/dist/client-profile.d.ts +10 -0
- package/dist/client-profile.d.ts.map +1 -0
- package/dist/client-profile.js +45 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +52 -0
- package/dist/generation-status-html.d.ts +2 -0
- package/dist/generation-status-html.d.ts.map +1 -0
- package/dist/generation-status-html.js +26 -0
- package/dist/generation-status.d.ts +6 -0
- package/dist/generation-status.d.ts.map +1 -0
- package/dist/generation-status.js +37 -0
- package/dist/http-client.d.ts +45 -1
- package/dist/http-client.d.ts.map +1 -1
- package/dist/http-client.js +171 -34
- package/dist/http-server.d.ts +53 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +753 -0
- package/dist/http-session.d.ts +18 -0
- package/dist/http-session.d.ts.map +1 -0
- package/dist/http-session.js +189 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/oauth-response.d.ts +3 -0
- package/dist/oauth-response.d.ts.map +1 -0
- package/dist/oauth-response.js +30 -0
- package/dist/openapi/index.d.ts +2 -2
- package/dist/openapi/index.d.ts.map +1 -1
- package/dist/openapi/index.js +1 -1
- package/dist/openapi/parser.d.ts +2 -0
- package/dist/openapi/parser.d.ts.map +1 -1
- package/dist/openapi/parser.js +41 -1
- package/dist/openapi/types.d.ts +3 -0
- package/dist/openapi/types.d.ts.map +1 -1
- package/dist/organization-selection.d.ts +61 -0
- package/dist/organization-selection.d.ts.map +1 -0
- package/dist/organization-selection.js +142 -0
- package/dist/rate-limits.d.ts +13 -0
- package/dist/rate-limits.d.ts.map +1 -0
- package/dist/rate-limits.js +67 -0
- package/dist/revision.d.ts +6 -0
- package/dist/revision.d.ts.map +1 -0
- package/dist/revision.js +17 -0
- package/dist/runtime-diagnostics.d.ts +32 -0
- package/dist/runtime-diagnostics.d.ts.map +1 -0
- package/dist/runtime-diagnostics.js +58 -0
- package/dist/server.d.ts +23 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +893 -32
- package/dist/session-registry.d.ts +54 -0
- package/dist/session-registry.d.ts.map +1 -0
- package/dist/session-registry.js +202 -0
- package/dist/session-telemetry.d.ts +13 -0
- package/dist/session-telemetry.d.ts.map +1 -0
- package/dist/session-telemetry.js +87 -0
- package/dist/spend-confirmation.d.ts +21 -0
- package/dist/spend-confirmation.d.ts.map +1 -0
- package/dist/spend-confirmation.js +105 -0
- package/dist/tool-analytics-context.d.ts +11 -0
- package/dist/tool-analytics-context.d.ts.map +1 -0
- package/dist/tool-analytics-context.js +8 -0
- package/dist/tool-names.d.ts +35 -0
- package/dist/tool-names.d.ts.map +1 -0
- package/dist/tool-names.js +133 -0
- package/dist/tools/account-context.d.ts +5 -0
- package/dist/tools/account-context.d.ts.map +1 -0
- package/dist/tools/account-context.js +131 -0
- package/dist/tools/account-management.d.ts +4 -0
- package/dist/tools/account-management.d.ts.map +1 -0
- package/dist/tools/account-management.js +111 -0
- package/dist/tools/app-tools.d.ts +31 -0
- package/dist/tools/app-tools.d.ts.map +1 -0
- package/dist/tools/app-tools.js +350 -0
- package/dist/tools/asset-analysis.d.ts +4 -0
- package/dist/tools/asset-analysis.d.ts.map +1 -0
- package/dist/tools/asset-analysis.js +172 -0
- package/dist/tools/asset-creators.d.ts +4 -0
- package/dist/tools/asset-creators.d.ts.map +1 -0
- package/dist/tools/asset-creators.js +38 -0
- package/dist/tools/audio-trim.d.ts +6 -0
- package/dist/tools/audio-trim.d.ts.map +1 -0
- package/dist/tools/audio-trim.js +79 -0
- package/dist/tools/batch-results.d.ts +16 -0
- package/dist/tools/batch-results.d.ts.map +1 -0
- package/dist/tools/batch-results.js +103 -0
- package/dist/tools/batches.d.ts +28 -0
- package/dist/tools/batches.d.ts.map +1 -0
- package/dist/tools/batches.js +293 -0
- package/dist/tools/dialogue.d.ts +6 -0
- package/dist/tools/dialogue.d.ts.map +1 -0
- package/dist/tools/dialogue.js +309 -0
- package/dist/tools/estimate-duration.d.ts +35 -0
- package/dist/tools/estimate-duration.d.ts.map +1 -0
- package/dist/tools/estimate-duration.js +66 -0
- package/dist/tools/feedback.d.ts +14 -0
- package/dist/tools/feedback.d.ts.map +1 -0
- package/dist/tools/feedback.js +72 -0
- package/dist/tools/generated-adapters.d.ts +25 -0
- package/dist/tools/generated-adapters.d.ts.map +1 -0
- package/dist/tools/generated-adapters.js +143 -0
- package/dist/tools/generation-context.d.ts +14 -0
- package/dist/tools/generation-context.d.ts.map +1 -0
- package/dist/tools/generation-context.js +39 -0
- package/dist/tools/generation-continuation.d.ts +4 -0
- package/dist/tools/generation-continuation.d.ts.map +1 -0
- package/dist/tools/generation-continuation.js +124 -0
- package/dist/tools/generation-costs.d.ts +6 -0
- package/dist/tools/generation-costs.d.ts.map +1 -0
- package/dist/tools/generation-costs.js +67 -0
- package/dist/tools/generation-output.d.ts +3 -0
- package/dist/tools/generation-output.d.ts.map +1 -0
- package/dist/tools/generation-output.js +39 -0
- package/dist/tools/generator.d.ts +35 -2
- package/dist/tools/generator.d.ts.map +1 -1
- package/dist/tools/generator.js +127 -9
- package/dist/tools/job-status.d.ts +35 -0
- package/dist/tools/job-status.d.ts.map +1 -0
- package/dist/tools/job-status.js +92 -0
- package/dist/tools/job-wait.d.ts +41 -0
- package/dist/tools/job-wait.d.ts.map +1 -0
- package/dist/tools/job-wait.js +84 -0
- package/dist/tools/jobs.d.ts +3 -0
- package/dist/tools/jobs.d.ts.map +1 -0
- package/dist/tools/jobs.js +210 -0
- package/dist/tools/media-upload.d.ts +27 -0
- package/dist/tools/media-upload.d.ts.map +1 -0
- package/dist/tools/media-upload.js +247 -0
- package/dist/tools/model-options.d.ts +36 -0
- package/dist/tools/model-options.d.ts.map +1 -0
- package/dist/tools/model-options.js +48 -0
- package/dist/tools/organization-choice.d.ts +9 -0
- package/dist/tools/organization-choice.d.ts.map +1 -0
- package/dist/tools/organization-choice.js +136 -0
- package/dist/tools/organizations.d.ts +56 -0
- package/dist/tools/organizations.d.ts.map +1 -0
- package/dist/tools/organizations.js +235 -0
- package/dist/tools/output-schemas.d.ts +54 -0
- package/dist/tools/output-schemas.d.ts.map +1 -0
- package/dist/tools/output-schemas.js +58 -0
- package/dist/tools/overrides.d.ts +3 -0
- package/dist/tools/overrides.d.ts.map +1 -1
- package/dist/tools/overrides.js +113 -15
- package/dist/tools/playback-metadata.d.ts +4 -0
- package/dist/tools/playback-metadata.d.ts.map +1 -0
- package/dist/tools/playback-metadata.js +51 -0
- package/dist/tools/project-destination.d.ts +13 -0
- package/dist/tools/project-destination.d.ts.map +1 -0
- package/dist/tools/project-destination.js +45 -0
- package/dist/tools/project-history.d.ts +6 -0
- package/dist/tools/project-history.d.ts.map +1 -0
- package/dist/tools/project-history.js +45 -0
- package/dist/tools/project-intent.d.ts +8 -0
- package/dist/tools/project-intent.d.ts.map +1 -0
- package/dist/tools/project-intent.js +87 -0
- package/dist/tools/running-submissions.d.ts +7 -0
- package/dist/tools/running-submissions.d.ts.map +1 -0
- package/dist/tools/running-submissions.js +91 -0
- package/dist/tools/speech.d.ts +6 -0
- package/dist/tools/speech.d.ts.map +1 -0
- package/dist/tools/speech.js +116 -0
- package/dist/tools/studio-draft.d.ts +7 -0
- package/dist/tools/studio-draft.d.ts.map +1 -0
- package/dist/tools/studio-draft.js +195 -0
- package/dist/tools/studio-view-contract.d.ts +184 -0
- package/dist/tools/studio-view-contract.d.ts.map +1 -0
- package/dist/tools/studio-view-contract.js +67 -0
- package/dist/tools/studio-view.d.ts +6 -0
- package/dist/tools/studio-view.d.ts.map +1 -0
- package/dist/tools/studio-view.js +126 -0
- package/dist/tools/timeline-lipsync.d.ts +316 -0
- package/dist/tools/timeline-lipsync.d.ts.map +1 -0
- package/dist/tools/timeline-lipsync.js +304 -0
- package/dist/tools/translation-preview.d.ts +8 -0
- package/dist/tools/translation-preview.d.ts.map +1 -0
- package/dist/tools/translation-preview.js +146 -0
- package/dist/tools/translation.d.ts +6 -0
- package/dist/tools/translation.d.ts.map +1 -0
- package/dist/tools/translation.js +131 -0
- package/dist/tools/upload-widget.d.ts +13 -0
- package/dist/tools/upload-widget.d.ts.map +1 -0
- package/dist/tools/upload-widget.js +788 -0
- package/dist/toolsets.d.ts +18 -0
- package/dist/toolsets.d.ts.map +1 -0
- package/dist/toolsets.js +157 -0
- package/dist/update-check.d.ts +38 -0
- package/dist/update-check.d.ts.map +1 -0
- package/dist/update-check.js +150 -0
- package/dist/upload-runtime.d.ts +45 -0
- package/dist/upload-runtime.d.ts.map +1 -0
- package/dist/upload-runtime.js +156 -0
- package/dist/upload-source.d.ts +4 -0
- package/dist/upload-source.d.ts.map +1 -0
- package/dist/upload-source.js +46 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/package.json +77 -31
- package/dist/openapi/parser.test.d.ts +0 -2
- package/dist/openapi/parser.test.d.ts.map +0 -1
- package/dist/openapi/parser.test.js +0 -188
- package/dist/tools/generator.test.d.ts +0 -2
- package/dist/tools/generator.test.d.ts.map +0 -1
- package/dist/tools/generator.test.js +0 -178
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolsets for the local (stdio) server. It used to list a tool for every public API
|
|
3
|
+
* operation (65+), which crowds an agent's context; by default it now lists the curated
|
|
4
|
+
* set the hosted server offers, plus what those workflows need. An operator picks more
|
|
5
|
+
* with SYNC_MCP_TOOLSETS or --toolsets; agents cannot. The hosted server ignores this.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TOOLSETS: readonly ['default', 'lipsync', 'dubbing', 'assets', 'projects', 'transcription', 'voices', 'admin', 'all'];
|
|
8
|
+
export type Toolset = (typeof TOOLSETS)[number];
|
|
9
|
+
type Category = Exclude<Toolset, 'default' | 'all'>;
|
|
10
|
+
/** The toolset a tool belongs to, or undefined for a tool only `all` lists. */
|
|
11
|
+
export declare function toolCategory(name: string): Category | undefined;
|
|
12
|
+
export declare function toolsetIncludes(selection: readonly Toolset[] | undefined, name: string, hostedTools: ReadonlySet<string>): boolean;
|
|
13
|
+
/** Comma- or space-separated toolset names; empty means the default. */
|
|
14
|
+
export declare function parseToolsets(value: string | undefined): Toolset[] | undefined;
|
|
15
|
+
/** The server instructions' note on what this connection lists, or '' when it lists everything. */
|
|
16
|
+
export declare function toolsetInstruction(selection: readonly Toolset[] | undefined): string;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=toolsets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"toolsets.d.ts","sourceRoot":"","sources":["../src/toolsets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,YACnB,SAAS,EACT,SAAS,EACT,SAAS,EACT,QAAQ,EACR,UAAU,EACV,eAAe,EACf,QAAQ,EACR,OAAO,EACP,KAAK,CACG,CAAC;AACX,MAAM,MAAM,OAAO,GAAG,CAAC,OAAO,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;AAChD,KAAK,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;AAiGpD,+EAA+E;AAC/E,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAK/D;AAYD,wBAAgB,eAAe,CAC7B,SAAS,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,EACzC,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,GAC/B,OAAO,CAOT;AAED,wEAAwE;AACxE,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,EAAE,GAAG,SAAS,CAY9E;AAED,mGAAmG;AACnG,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,GAAG,MAAM,CAIpF"}
|
package/dist/toolsets.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toolsets for the local (stdio) server. It used to list a tool for every public API
|
|
3
|
+
* operation (65+), which crowds an agent's context; by default it now lists the curated
|
|
4
|
+
* set the hosted server offers, plus what those workflows need. An operator picks more
|
|
5
|
+
* with SYNC_MCP_TOOLSETS or --toolsets; agents cannot. The hosted server ignores this.
|
|
6
|
+
*/
|
|
7
|
+
export const TOOLSETS = [
|
|
8
|
+
'default',
|
|
9
|
+
'lipsync',
|
|
10
|
+
'dubbing',
|
|
11
|
+
'assets',
|
|
12
|
+
'projects',
|
|
13
|
+
'transcription',
|
|
14
|
+
'voices',
|
|
15
|
+
'admin',
|
|
16
|
+
'all',
|
|
17
|
+
];
|
|
18
|
+
// Listed with every selection: reading jobs, account state and the app's own helpers.
|
|
19
|
+
const CORE = new Set([
|
|
20
|
+
'jobs_get',
|
|
21
|
+
'jobs_wait',
|
|
22
|
+
'account_get-context',
|
|
23
|
+
'organizations_list',
|
|
24
|
+
'organizations_select',
|
|
25
|
+
'account_get-usage',
|
|
26
|
+
'models_list',
|
|
27
|
+
'feedback_send',
|
|
28
|
+
'generations_get',
|
|
29
|
+
'generations_list',
|
|
30
|
+
'generations_estimate-cost',
|
|
31
|
+
'generations_get-costs',
|
|
32
|
+
'generations_get-continuation',
|
|
33
|
+
'assets_get-playback-metadata',
|
|
34
|
+
'assets_list-creators',
|
|
35
|
+
// App-only tools; isToolVisible still hides them from clients without the app UI.
|
|
36
|
+
'analytics_track-event',
|
|
37
|
+
'app_open',
|
|
38
|
+
'studio-views_get',
|
|
39
|
+
'studio-views_open-project',
|
|
40
|
+
'studio-views_refresh-project',
|
|
41
|
+
'studio-views_refresh-assets',
|
|
42
|
+
'studio-views_show-generation',
|
|
43
|
+
'studio-views_continue-generation',
|
|
44
|
+
'studio-views_register',
|
|
45
|
+
'studio-views_ack',
|
|
46
|
+
'studio-drafts_get',
|
|
47
|
+
'studio-drafts_update',
|
|
48
|
+
'studio-drafts_save',
|
|
49
|
+
'studio-drafts_ack',
|
|
50
|
+
]);
|
|
51
|
+
// Hand-written tools. Generated tools fall back to their API controller below.
|
|
52
|
+
const CATEGORY_BY_TOOL = {
|
|
53
|
+
lipsync_create: 'lipsync',
|
|
54
|
+
'lipsync_create-from-assets': 'lipsync',
|
|
55
|
+
'lipsync_create-timeline': 'lipsync',
|
|
56
|
+
'audio-trims_create': 'lipsync',
|
|
57
|
+
'audio-trims_get': 'lipsync',
|
|
58
|
+
'dialogue_get-options': 'lipsync',
|
|
59
|
+
'dialogue_upload-source': 'lipsync',
|
|
60
|
+
'dialogue-transcriptions_create': 'lipsync',
|
|
61
|
+
'dialogue-transcriptions_get': 'lipsync',
|
|
62
|
+
'dialogue-previews_create': 'lipsync',
|
|
63
|
+
'dialogue-previews_get': 'lipsync',
|
|
64
|
+
'dialogue-videos_estimate': 'lipsync',
|
|
65
|
+
'dialogue-videos_create': 'lipsync',
|
|
66
|
+
dubbing_create: 'dubbing',
|
|
67
|
+
'dubbing_get-options': 'dubbing',
|
|
68
|
+
'translation-previews_estimate': 'dubbing',
|
|
69
|
+
'translation-previews_create': 'dubbing',
|
|
70
|
+
'translation-previews_get': 'dubbing',
|
|
71
|
+
// Feature-gated: listed only to accounts with the feature (account-features.ts).
|
|
72
|
+
scenes_detect: 'lipsync',
|
|
73
|
+
stems_separate: 'assets',
|
|
74
|
+
assets_upload: 'assets',
|
|
75
|
+
'assets_open-upload-widget': 'assets',
|
|
76
|
+
projects_rename: 'projects',
|
|
77
|
+
'speech-previews_get-options': 'voices',
|
|
78
|
+
'speech-previews_create': 'voices',
|
|
79
|
+
'speech-previews_get': 'voices',
|
|
80
|
+
// Creates a persistent voice from someone's recording; not idempotent.
|
|
81
|
+
voices_clone: 'admin',
|
|
82
|
+
// Batches of /v2/generate bodies; dubbing lists them too, for localizing many videos.
|
|
83
|
+
batches_create: 'lipsync',
|
|
84
|
+
batches_get: 'lipsync',
|
|
85
|
+
batches_list: 'lipsync',
|
|
86
|
+
};
|
|
87
|
+
// Tools a second toolset lists as well.
|
|
88
|
+
const ALSO_IN = {
|
|
89
|
+
batches_create: 'dubbing',
|
|
90
|
+
batches_get: 'dubbing',
|
|
91
|
+
batches_list: 'dubbing',
|
|
92
|
+
};
|
|
93
|
+
const CATEGORY_BY_CONTROLLER = {
|
|
94
|
+
generate: 'lipsync',
|
|
95
|
+
generations: 'lipsync',
|
|
96
|
+
'dialogue-edits': 'lipsync',
|
|
97
|
+
integrations: 'dubbing',
|
|
98
|
+
dubbing: 'dubbing',
|
|
99
|
+
assets: 'assets',
|
|
100
|
+
projects: 'projects',
|
|
101
|
+
transcriptions: 'transcription',
|
|
102
|
+
voices: 'voices',
|
|
103
|
+
tts: 'voices',
|
|
104
|
+
'tts-jobs': 'voices',
|
|
105
|
+
};
|
|
106
|
+
// Deletes stay in admin whatever their controller.
|
|
107
|
+
const DELETES = /(?:^|[_-])(?:delete|remove)(?:[_-]|$)/;
|
|
108
|
+
/** The toolset a tool belongs to, or undefined for a tool only `all` lists. */
|
|
109
|
+
export function toolCategory(name) {
|
|
110
|
+
if (DELETES.test(name))
|
|
111
|
+
return 'admin';
|
|
112
|
+
if (CATEGORY_BY_TOOL[name])
|
|
113
|
+
return CATEGORY_BY_TOOL[name];
|
|
114
|
+
const controller = name.includes('_') ? name.slice(0, name.indexOf('_')) : undefined;
|
|
115
|
+
return controller ? CATEGORY_BY_CONTROLLER[controller] : undefined;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* The default: what the hosted server lists, without admin tools, plus what the curated
|
|
119
|
+
* workflows need there: jobs_get and jobs_wait, and the transcription tools whose speaker
|
|
120
|
+
* face tracks become lipsync_create-timeline clips.
|
|
121
|
+
*/
|
|
122
|
+
function inDefaultToolset(name, hostedTools) {
|
|
123
|
+
if (toolCategory(name) === 'admin')
|
|
124
|
+
return false;
|
|
125
|
+
return CORE.has(name) || hostedTools.has(name) || toolCategory(name) === 'transcription';
|
|
126
|
+
}
|
|
127
|
+
export function toolsetIncludes(selection, name, hostedTools) {
|
|
128
|
+
const chosen = selection?.length ? selection : ['default'];
|
|
129
|
+
if (chosen.includes('all'))
|
|
130
|
+
return true;
|
|
131
|
+
if (chosen.includes('default') && inDefaultToolset(name, hostedTools))
|
|
132
|
+
return true;
|
|
133
|
+
if (CORE.has(name))
|
|
134
|
+
return true;
|
|
135
|
+
const categories = [toolCategory(name), ALSO_IN[name]];
|
|
136
|
+
return categories.some((category) => category !== undefined && chosen.includes(category));
|
|
137
|
+
}
|
|
138
|
+
/** Comma- or space-separated toolset names; empty means the default. */
|
|
139
|
+
export function parseToolsets(value) {
|
|
140
|
+
if (value === undefined || value.trim() === '')
|
|
141
|
+
return undefined;
|
|
142
|
+
const names = value
|
|
143
|
+
.split(/[\s,]+/)
|
|
144
|
+
.map((name) => name.trim().toLowerCase())
|
|
145
|
+
.filter(Boolean);
|
|
146
|
+
const unknown = names.filter((name) => !TOOLSETS.includes(name));
|
|
147
|
+
if (unknown.length > 0)
|
|
148
|
+
throw new Error(`Unknown toolset ${unknown.map((name) => `"${name}"`).join(', ')}; use ${TOOLSETS.join(', ')}.`);
|
|
149
|
+
return [...new Set(names)];
|
|
150
|
+
}
|
|
151
|
+
/** The server instructions' note on what this connection lists, or '' when it lists everything. */
|
|
152
|
+
export function toolsetInstruction(selection) {
|
|
153
|
+
const chosen = selection?.length ? selection : ['default'];
|
|
154
|
+
if (chosen.includes('all'))
|
|
155
|
+
return '';
|
|
156
|
+
return `This local server lists the ${chosen.join(', ')} Sync toolset${chosen.length > 1 ? 's' : ''}, not every API tool; delete tools and voice cloning are in admin. If the user needs a tool that is not listed, tell them to restart the server with SYNC_MCP_TOOLSETS=all (or --toolsets all), or to add toolsets such as default,admin.`;
|
|
157
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local (stdio) installs go stale silently: npx keeps running whatever it cached. At
|
|
3
|
+
* startup the CLI asks the npm registry for the latest version, at most once a day, and
|
|
4
|
+
* the server tells the agent when this copy is older. The hosted server deploys on every
|
|
5
|
+
* merge, so it never checks. Any failure means no notice.
|
|
6
|
+
*/
|
|
7
|
+
export declare const UPDATE_CHECK_TIMEOUT_MS = 2000;
|
|
8
|
+
export declare const UPDATE_CHECK_TTL_MS: number;
|
|
9
|
+
export type AvailableUpdate = {
|
|
10
|
+
current: string;
|
|
11
|
+
latest: string;
|
|
12
|
+
};
|
|
13
|
+
export type UpdateCheck = {
|
|
14
|
+
/** The newer version once the check has found one; never waits for it. */
|
|
15
|
+
available: () => AvailableUpdate | undefined;
|
|
16
|
+
/** Settles when the check finishes. Never rejects. */
|
|
17
|
+
done: Promise<void>;
|
|
18
|
+
};
|
|
19
|
+
/** SYNC_MCP_UPDATE_CHECK accepts off/false/0/no and on/true/1/yes (default). */
|
|
20
|
+
export declare function parseUpdateCheck(value: string | undefined): boolean | undefined;
|
|
21
|
+
/** Semver precedence: negative when a < b, positive when a > b, undefined when unparsable. */
|
|
22
|
+
export declare function compareVersions(a: string, b: string): number | undefined;
|
|
23
|
+
export declare function updateInstruction({ current, latest }: AvailableUpdate): string;
|
|
24
|
+
export declare function updateToolNotice({ current, latest }: AvailableUpdate): string;
|
|
25
|
+
/** The CLI's gate: local (stdio) installs only, unless opted out or running in CI. */
|
|
26
|
+
export declare function startConfiguredUpdateCheck(config: {
|
|
27
|
+
transport: 'stdio' | 'http';
|
|
28
|
+
updateCheck?: boolean;
|
|
29
|
+
}, currentVersion: string, env?: NodeJS.ProcessEnv, options?: Omit<Parameters<typeof startUpdateCheck>[0], 'currentVersion'>): UpdateCheck | undefined;
|
|
30
|
+
/** Starts the check in the background and returns at once. */
|
|
31
|
+
export declare function startUpdateCheck(options: {
|
|
32
|
+
currentVersion: string;
|
|
33
|
+
cacheFile?: string;
|
|
34
|
+
fetch?: typeof fetch;
|
|
35
|
+
now?: () => number;
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
}): UpdateCheck;
|
|
38
|
+
//# sourceMappingURL=update-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check.d.ts","sourceRoot":"","sources":["../src/update-check.ts"],"names":[],"mappings":"AAIA;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AAC7C,eAAO,MAAM,mBAAmB,QAAsB,CAAC;AAOvD,MAAM,MAAM,eAAe,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE,MAAM,MAAM,WAAW,GAAG;IACxB,0EAA0E;IAC1E,SAAS,EAAE,MAAM,eAAe,GAAG,SAAS,CAAC;IAC7C,sDAAsD;IACtD,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CACrB,CAAC;AAIF,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,CAM/E;AAWD,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CASxE;AAqBD,wBAAgB,iBAAiB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,eAAe,GAAG,MAAM,CAE9E;AAED,wBAAgB,gBAAgB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,EAAE,eAAe,GAAG,MAAM,CAE7E;AAED,sFAAsF;AACtF,wBAAgB,0BAA0B,CACxC,MAAM,EAAE;IAAE,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,EAC9D,cAAc,EAAE,MAAM,EACtB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,OAAO,GAAE,IAAI,CAAC,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC,EAAE,gBAAgB,CAAM,GAC3E,WAAW,GAAG,SAAS,CAGzB;AAED,8DAA8D;AAC9D,wBAAgB,gBAAgB,CAAC,OAAO,EAAE;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,WAAW,CASd"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Local (stdio) installs go stale silently: npx keeps running whatever it cached. At
|
|
6
|
+
* startup the CLI asks the npm registry for the latest version, at most once a day, and
|
|
7
|
+
* the server tells the agent when this copy is older. The hosted server deploys on every
|
|
8
|
+
* merge, so it never checks. Any failure means no notice.
|
|
9
|
+
*/
|
|
10
|
+
export const UPDATE_CHECK_TIMEOUT_MS = 2_000;
|
|
11
|
+
export const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
12
|
+
const LATEST_URL = 'https://registry.npmjs.org/@sync.so/mcp-server/latest';
|
|
13
|
+
const CACHE_FILE = path.join(os.homedir(), '.config', 'sync', 'mcp-update-check.json');
|
|
14
|
+
const HOSTED_MCP_URL = 'https://mcp.sync.so/mcp';
|
|
15
|
+
const UPDATE_COMMAND = 'npx -y @sync.so/mcp-server@latest';
|
|
16
|
+
/** SYNC_MCP_UPDATE_CHECK accepts off/false/0/no and on/true/1/yes (default). */
|
|
17
|
+
export function parseUpdateCheck(value) {
|
|
18
|
+
if (value === undefined || value.trim() === '')
|
|
19
|
+
return undefined;
|
|
20
|
+
const normalized = value.trim().toLowerCase();
|
|
21
|
+
if (['off', 'false', '0', 'no'].includes(normalized))
|
|
22
|
+
return false;
|
|
23
|
+
if (['on', 'true', '1', 'yes'].includes(normalized))
|
|
24
|
+
return true;
|
|
25
|
+
throw new Error(`SYNC_MCP_UPDATE_CHECK must be on or off, not "${value}"`);
|
|
26
|
+
}
|
|
27
|
+
/** CI runs are unattended and often offline; CI=false or CI=0 still checks. */
|
|
28
|
+
function isCi(env = process.env) {
|
|
29
|
+
const value = env.CI?.trim().toLowerCase();
|
|
30
|
+
return Boolean(value) && value !== 'false' && value !== '0';
|
|
31
|
+
}
|
|
32
|
+
const SEMVER = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
33
|
+
/** Semver precedence: negative when a < b, positive when a > b, undefined when unparsable. */
|
|
34
|
+
export function compareVersions(a, b) {
|
|
35
|
+
const left = SEMVER.exec(a.trim());
|
|
36
|
+
const right = SEMVER.exec(b.trim());
|
|
37
|
+
if (!left || !right)
|
|
38
|
+
return undefined;
|
|
39
|
+
for (let index = 1; index <= 3; index++) {
|
|
40
|
+
const difference = Number(left[index]) - Number(right[index]);
|
|
41
|
+
if (difference !== 0)
|
|
42
|
+
return Math.sign(difference);
|
|
43
|
+
}
|
|
44
|
+
return comparePrerelease(left[4], right[4]);
|
|
45
|
+
}
|
|
46
|
+
function comparePrerelease(a, b) {
|
|
47
|
+
// A release outranks any of its prereleases.
|
|
48
|
+
if (a === undefined || b === undefined)
|
|
49
|
+
return a === b ? 0 : a === undefined ? 1 : -1;
|
|
50
|
+
const left = a.split('.');
|
|
51
|
+
const right = b.split('.');
|
|
52
|
+
for (let index = 0; index < Math.max(left.length, right.length); index++) {
|
|
53
|
+
const x = left[index];
|
|
54
|
+
const y = right[index];
|
|
55
|
+
if (x === undefined || y === undefined)
|
|
56
|
+
return x === undefined ? -1 : 1;
|
|
57
|
+
if (x === y)
|
|
58
|
+
continue;
|
|
59
|
+
const xNumeric = /^\d+$/.test(x);
|
|
60
|
+
const yNumeric = /^\d+$/.test(y);
|
|
61
|
+
if (xNumeric && yNumeric)
|
|
62
|
+
return Math.sign(Number(x) - Number(y));
|
|
63
|
+
if (xNumeric !== yNumeric)
|
|
64
|
+
return xNumeric ? -1 : 1;
|
|
65
|
+
return x < y ? -1 : 1;
|
|
66
|
+
}
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
export function updateInstruction({ current, latest }) {
|
|
70
|
+
return `This sync. MCP server is version ${current}; version ${latest} is available. Tell the user to update: restart with \`${UPDATE_COMMAND}\` (or reinstall), or switch to the hosted server at ${HOSTED_MCP_URL}, which is always current.`;
|
|
71
|
+
}
|
|
72
|
+
export function updateToolNotice({ current, latest }) {
|
|
73
|
+
return `Update available: this sync. MCP server is ${current} and ${latest} is out. Tell the user to restart it with \`${UPDATE_COMMAND}\`, or to use the hosted server at ${HOSTED_MCP_URL}.`;
|
|
74
|
+
}
|
|
75
|
+
/** The CLI's gate: local (stdio) installs only, unless opted out or running in CI. */
|
|
76
|
+
export function startConfiguredUpdateCheck(config, currentVersion, env = process.env, options = {}) {
|
|
77
|
+
if (config.transport !== 'stdio' || config.updateCheck === false || isCi(env))
|
|
78
|
+
return undefined;
|
|
79
|
+
return startUpdateCheck({ ...options, currentVersion });
|
|
80
|
+
}
|
|
81
|
+
/** Starts the check in the background and returns at once. */
|
|
82
|
+
export function startUpdateCheck(options) {
|
|
83
|
+
let available;
|
|
84
|
+
const current = options.currentVersion;
|
|
85
|
+
const done = findLatest(options)
|
|
86
|
+
.then((latest) => {
|
|
87
|
+
if (latest && (compareVersions(latest, current) ?? 0) > 0)
|
|
88
|
+
available = { current, latest };
|
|
89
|
+
})
|
|
90
|
+
.catch(() => undefined);
|
|
91
|
+
return { done, available: () => available };
|
|
92
|
+
}
|
|
93
|
+
async function findLatest(options) {
|
|
94
|
+
const cacheFile = options.cacheFile ?? CACHE_FILE;
|
|
95
|
+
const now = options.now?.() ?? Date.now();
|
|
96
|
+
const cached = await readCache(cacheFile);
|
|
97
|
+
const age = cached ? now - Date.parse(cached.checkedAt) : Number.NaN;
|
|
98
|
+
if (cached && age >= 0 && age < UPDATE_CHECK_TTL_MS)
|
|
99
|
+
return cached.latest;
|
|
100
|
+
const latest = await fetchLatest(options.fetch ?? fetch, options.timeoutMs);
|
|
101
|
+
// Failures are cached too, so an offline machine still asks at most once a day.
|
|
102
|
+
await writeCache(cacheFile, {
|
|
103
|
+
checkedAt: new Date(now).toISOString(),
|
|
104
|
+
...(latest ? { latest } : {}),
|
|
105
|
+
});
|
|
106
|
+
return latest;
|
|
107
|
+
}
|
|
108
|
+
async function fetchLatest(fetchImpl, timeoutMs = UPDATE_CHECK_TIMEOUT_MS) {
|
|
109
|
+
try {
|
|
110
|
+
const signal = AbortSignal.timeout(timeoutMs);
|
|
111
|
+
const response = await fetchImpl(LATEST_URL, {
|
|
112
|
+
headers: { accept: 'application/json' },
|
|
113
|
+
signal,
|
|
114
|
+
});
|
|
115
|
+
if (!response.ok)
|
|
116
|
+
return undefined;
|
|
117
|
+
const body = await response.json();
|
|
118
|
+
const version = body && typeof body === 'object' && 'version' in body ? body.version : undefined;
|
|
119
|
+
return typeof version === 'string' && SEMVER.test(version) ? version : undefined;
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function readCache(file) {
|
|
126
|
+
try {
|
|
127
|
+
const parsed = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
128
|
+
if (!parsed || typeof parsed !== 'object')
|
|
129
|
+
return undefined;
|
|
130
|
+
const { checkedAt, latest } = parsed;
|
|
131
|
+
if (typeof checkedAt !== 'string' || Number.isNaN(Date.parse(checkedAt)))
|
|
132
|
+
return undefined;
|
|
133
|
+
return {
|
|
134
|
+
checkedAt,
|
|
135
|
+
...(typeof latest === 'string' && SEMVER.test(latest) ? { latest } : {}),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function writeCache(file, entry) {
|
|
143
|
+
try {
|
|
144
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
145
|
+
await fs.writeFile(file, `${JSON.stringify(entry)}\n`, 'utf8');
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
// A read-only home only means checking again next start.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type UploadRuntimeConfig = {
|
|
2
|
+
maxBytes: number;
|
|
3
|
+
maxConcurrent: number;
|
|
4
|
+
maxQueued: number;
|
|
5
|
+
retryAfterMs: number;
|
|
6
|
+
timeoutMs: number;
|
|
7
|
+
};
|
|
8
|
+
export type UploadRuntimeStats = {
|
|
9
|
+
active: number;
|
|
10
|
+
queued: number;
|
|
11
|
+
rejected: number;
|
|
12
|
+
completed: number;
|
|
13
|
+
failed: number;
|
|
14
|
+
cleanupFailures: number;
|
|
15
|
+
downloadedBytes: number;
|
|
16
|
+
uploadedBytes: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function getUploadRuntimeConfig(env?: Record<string, string | undefined>): UploadRuntimeConfig;
|
|
19
|
+
export declare class UploadOverloadedError extends Error {
|
|
20
|
+
readonly code = "UPLOAD_CAPACITY_EXCEEDED";
|
|
21
|
+
readonly retryable = true;
|
|
22
|
+
readonly retryAfterMs: number;
|
|
23
|
+
constructor(retryAfterMs: number);
|
|
24
|
+
}
|
|
25
|
+
export declare class UploadTimeoutError extends Error {
|
|
26
|
+
readonly code = "UPLOAD_TIMEOUT";
|
|
27
|
+
readonly retryable = true;
|
|
28
|
+
readonly retryAfterMs: number;
|
|
29
|
+
constructor(timeoutMs: number, retryAfterMs: number);
|
|
30
|
+
}
|
|
31
|
+
export declare class UploadRuntime {
|
|
32
|
+
#private;
|
|
33
|
+
readonly config: UploadRuntimeConfig;
|
|
34
|
+
constructor(config?: UploadRuntimeConfig);
|
|
35
|
+
run<T>(operation: (signal: AbortSignal) => Promise<T>, options?: {
|
|
36
|
+
timeoutMs?: number;
|
|
37
|
+
signal?: AbortSignal;
|
|
38
|
+
}): Promise<T>;
|
|
39
|
+
recordDownloadedBytes(bytes: number): void;
|
|
40
|
+
recordUploadedBytes(bytes: number): void;
|
|
41
|
+
recordCleanupFailure(): void;
|
|
42
|
+
snapshot(): UploadRuntimeStats;
|
|
43
|
+
}
|
|
44
|
+
export declare const uploadRuntime: UploadRuntime;
|
|
45
|
+
//# sourceMappingURL=upload-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-runtime.d.ts","sourceRoot":"","sources":["../src/upload-runtime.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAgBF,wBAAgB,sBAAsB,CACpC,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAe,GACpD,mBAAmB,CAQrB;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,8BAA8B;IAC3C,QAAQ,CAAC,SAAS,QAAQ;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B,YAAY,YAAY,EAAE,MAAM,EAI/B;CACF;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,oBAAoB;IACjC,QAAQ,CAAC,SAAS,QAAQ;IAC1B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAE9B,YAAY,SAAS,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAIlD;CACF;AAQD,qBAAa,aAAa;;IACxB,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAC;IAarC,YAAY,MAAM,GAAE,mBAA8C,EAEjE;IAEK,GAAG,CAAC,CAAC,EACT,SAAS,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,EAC9C,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAO,GACzD,OAAO,CAAC,CAAC,CAAC,CAmCZ;IAED,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEzC;IAED,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEvC;IAED,oBAAoB,IAAI,IAAI,CAE3B;IAED,QAAQ,IAAI,kBAAkB,CAE7B;CAgDF;AAED,eAAO,MAAM,aAAa,eAAsB,CAAC"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { combineSignals } from './abort-signals.js';
|
|
2
|
+
const MIB = 1024 * 1024;
|
|
3
|
+
function positiveInteger(value, fallback) {
|
|
4
|
+
if (!value)
|
|
5
|
+
return fallback;
|
|
6
|
+
const parsed = Number(value);
|
|
7
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
8
|
+
}
|
|
9
|
+
function nonNegativeInteger(value, fallback) {
|
|
10
|
+
if (!value)
|
|
11
|
+
return fallback;
|
|
12
|
+
const parsed = Number(value);
|
|
13
|
+
return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : fallback;
|
|
14
|
+
}
|
|
15
|
+
export function getUploadRuntimeConfig(env = process.env) {
|
|
16
|
+
return {
|
|
17
|
+
maxBytes: positiveInteger(env.MCP_UPLOAD_MAX_BYTES, 512 * MIB),
|
|
18
|
+
maxConcurrent: positiveInteger(env.MCP_UPLOAD_CONCURRENCY, 2),
|
|
19
|
+
maxQueued: nonNegativeInteger(env.MCP_UPLOAD_MAX_QUEUED, 8),
|
|
20
|
+
retryAfterMs: positiveInteger(env.MCP_UPLOAD_RETRY_AFTER_MS, 5_000),
|
|
21
|
+
timeoutMs: positiveInteger(env.MCP_UPLOAD_TIMEOUT_MS, 15 * 60_000),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export class UploadOverloadedError extends Error {
|
|
25
|
+
code = 'UPLOAD_CAPACITY_EXCEEDED';
|
|
26
|
+
retryable = true;
|
|
27
|
+
retryAfterMs;
|
|
28
|
+
constructor(retryAfterMs) {
|
|
29
|
+
super('Upload capacity is temporarily full. Retry the request later.');
|
|
30
|
+
this.name = 'UploadOverloadedError';
|
|
31
|
+
this.retryAfterMs = retryAfterMs;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export class UploadTimeoutError extends Error {
|
|
35
|
+
code = 'UPLOAD_TIMEOUT';
|
|
36
|
+
retryable = true;
|
|
37
|
+
retryAfterMs;
|
|
38
|
+
constructor(timeoutMs, retryAfterMs) {
|
|
39
|
+
super(`Upload timed out after ${timeoutMs}ms. Retry the request later.`);
|
|
40
|
+
this.name = 'UploadTimeoutError';
|
|
41
|
+
this.retryAfterMs = retryAfterMs;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class UploadRuntime {
|
|
45
|
+
config;
|
|
46
|
+
#stats = {
|
|
47
|
+
active: 0,
|
|
48
|
+
queued: 0,
|
|
49
|
+
rejected: 0,
|
|
50
|
+
completed: 0,
|
|
51
|
+
failed: 0,
|
|
52
|
+
cleanupFailures: 0,
|
|
53
|
+
downloadedBytes: 0,
|
|
54
|
+
uploadedBytes: 0,
|
|
55
|
+
};
|
|
56
|
+
#waiters = [];
|
|
57
|
+
constructor(config = getUploadRuntimeConfig()) {
|
|
58
|
+
this.config = config;
|
|
59
|
+
}
|
|
60
|
+
async run(operation, options = {}) {
|
|
61
|
+
const timeoutMs = options.timeoutMs ?? this.config.timeoutMs;
|
|
62
|
+
const controller = new AbortController();
|
|
63
|
+
const timeout = setTimeout(() => {
|
|
64
|
+
controller.abort(new UploadTimeoutError(timeoutMs, this.config.retryAfterMs));
|
|
65
|
+
}, timeoutMs);
|
|
66
|
+
timeout.unref();
|
|
67
|
+
const { signal, dispose } = combineSignals(options.signal ? [options.signal, controller.signal] : [controller.signal]);
|
|
68
|
+
let release;
|
|
69
|
+
try {
|
|
70
|
+
release = await this.#acquire(signal);
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
clearTimeout(timeout);
|
|
74
|
+
dispose();
|
|
75
|
+
throw error;
|
|
76
|
+
}
|
|
77
|
+
let started = false;
|
|
78
|
+
try {
|
|
79
|
+
signal.throwIfAborted();
|
|
80
|
+
started = true;
|
|
81
|
+
const result = await operation(signal);
|
|
82
|
+
this.#stats.completed += 1;
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
// Admission cancellation is not a failed transfer: no operation ran.
|
|
87
|
+
if (started)
|
|
88
|
+
this.#stats.failed += 1;
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
clearTimeout(timeout);
|
|
93
|
+
dispose();
|
|
94
|
+
release();
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
recordDownloadedBytes(bytes) {
|
|
98
|
+
this.#stats.downloadedBytes += bytes;
|
|
99
|
+
}
|
|
100
|
+
recordUploadedBytes(bytes) {
|
|
101
|
+
this.#stats.uploadedBytes += bytes;
|
|
102
|
+
}
|
|
103
|
+
recordCleanupFailure() {
|
|
104
|
+
this.#stats.cleanupFailures += 1;
|
|
105
|
+
}
|
|
106
|
+
snapshot() {
|
|
107
|
+
return { ...this.#stats };
|
|
108
|
+
}
|
|
109
|
+
async #acquire(signal) {
|
|
110
|
+
if (signal.aborted)
|
|
111
|
+
throw signal.reason;
|
|
112
|
+
if (this.#stats.active < this.config.maxConcurrent) {
|
|
113
|
+
this.#stats.active += 1;
|
|
114
|
+
return this.#releaseOnce();
|
|
115
|
+
}
|
|
116
|
+
if (this.#stats.queued >= this.config.maxQueued) {
|
|
117
|
+
this.#stats.rejected += 1;
|
|
118
|
+
throw new UploadOverloadedError(this.config.retryAfterMs);
|
|
119
|
+
}
|
|
120
|
+
this.#stats.queued += 1;
|
|
121
|
+
return new Promise((resolve, reject) => {
|
|
122
|
+
const onAbort = () => {
|
|
123
|
+
const index = this.#waiters.indexOf(waiter);
|
|
124
|
+
if (index !== -1) {
|
|
125
|
+
this.#waiters.splice(index, 1);
|
|
126
|
+
this.#stats.queued -= 1;
|
|
127
|
+
}
|
|
128
|
+
reject(signal.reason);
|
|
129
|
+
};
|
|
130
|
+
const waiter = {
|
|
131
|
+
resolve,
|
|
132
|
+
reject,
|
|
133
|
+
cleanup: () => signal.removeEventListener('abort', onAbort),
|
|
134
|
+
};
|
|
135
|
+
this.#waiters.push(waiter);
|
|
136
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
#releaseOnce() {
|
|
140
|
+
let released = false;
|
|
141
|
+
return () => {
|
|
142
|
+
if (released)
|
|
143
|
+
return;
|
|
144
|
+
released = true;
|
|
145
|
+
const waiter = this.#waiters.shift();
|
|
146
|
+
if (waiter) {
|
|
147
|
+
this.#stats.queued -= 1;
|
|
148
|
+
waiter.cleanup();
|
|
149
|
+
waiter.resolve(this.#releaseOnce());
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
this.#stats.active = Math.max(0, this.#stats.active - 1);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export const uploadRuntime = new UploadRuntime();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-source.d.ts","sourceRoot":"","sources":["../src/upload-source.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAU/B,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAY9D;AAgBD,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,WAAW,GAAG,KAAK,CAKvE"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { lookup } from 'node:dns';
|
|
2
|
+
import { isIP } from 'node:net';
|
|
3
|
+
import ipaddr from 'ipaddr.js';
|
|
4
|
+
import { Agent } from 'undici';
|
|
5
|
+
function isPublicAddress(value) {
|
|
6
|
+
if (!isIP(value))
|
|
7
|
+
return false;
|
|
8
|
+
const address = ipaddr.parse(value);
|
|
9
|
+
if (address.range() !== 'unicast')
|
|
10
|
+
return false;
|
|
11
|
+
// IPv6 unallocated space is not public, even if it has no named special range.
|
|
12
|
+
return address.kind() === 'ipv4' || address.match(ipaddr.parse('2000::'), 3);
|
|
13
|
+
}
|
|
14
|
+
export function validatedUploadSourceUrl(value) {
|
|
15
|
+
const url = new URL(value);
|
|
16
|
+
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.port) {
|
|
17
|
+
throw new Error('Upload sources require HTTP(S), no credentials, and the protocol default port.');
|
|
18
|
+
}
|
|
19
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, '');
|
|
20
|
+
if (isIP(hostname) && !isPublicAddress(hostname)) {
|
|
21
|
+
throw new Error('Upload sources must have a public destination.');
|
|
22
|
+
}
|
|
23
|
+
return url.href;
|
|
24
|
+
}
|
|
25
|
+
const publicLookup = (hostname, options, callback) => {
|
|
26
|
+
// Resolve inside the socket connection, and return only these checked addresses
|
|
27
|
+
// to the connector. There is no subsequent DNS resolution for a rebind to alter.
|
|
28
|
+
lookup(hostname, { all: true, verbatim: true }, (error, addresses) => {
|
|
29
|
+
if (error)
|
|
30
|
+
return callback(error, '');
|
|
31
|
+
const first = addresses[0];
|
|
32
|
+
if (!first || addresses.some(({ address }) => !isPublicAddress(address))) {
|
|
33
|
+
return callback(new Error('Upload sources must resolve only to public destinations.'), '');
|
|
34
|
+
}
|
|
35
|
+
if (options.all)
|
|
36
|
+
callback(null, addresses);
|
|
37
|
+
else
|
|
38
|
+
callback(null, first.address, first.family);
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
export function createUploadSourceDispatcher(signal) {
|
|
42
|
+
// An explicit direct agent bypasses global/proxy dispatchers. A transfer owns
|
|
43
|
+
// its connections so arbitrary source hosts cannot accumulate cached pools.
|
|
44
|
+
const connect = { lookup: publicLookup, signal };
|
|
45
|
+
return new Agent({ connect });
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAGA,eAAO,MAAiB,eAAe,QAEtC,CAAC"}
|
package/dist/version.js
ADDED