@sync.so/mcp-server 0.1.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/LICENSE +21 -0
- package/README.md +186 -0
- package/dist/auth/api-key.d.ts +6 -0
- package/dist/auth/api-key.d.ts.map +1 -0
- package/dist/auth/api-key.js +6 -0
- package/dist/auth/device-auth.d.ts +6 -0
- package/dist/auth/device-auth.d.ts.map +1 -0
- package/dist/auth/device-auth.js +44 -0
- package/dist/auth/index.d.ts +6 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +3 -0
- package/dist/auth/token-store.d.ts +4 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +34 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +71 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +13 -0
- package/dist/http-client.d.ts +11 -0
- package/dist/http-client.d.ts.map +1 -0
- package/dist/http-client.js +43 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/openapi/fetcher.d.ts +4 -0
- package/dist/openapi/fetcher.d.ts.map +1 -0
- package/dist/openapi/fetcher.js +13 -0
- package/dist/openapi/index.d.ts +4 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +2 -0
- package/dist/openapi/parser.d.ts +5 -0
- package/dist/openapi/parser.d.ts.map +1 -0
- package/dist/openapi/parser.js +101 -0
- package/dist/openapi/parser.test.d.ts +2 -0
- package/dist/openapi/parser.test.d.ts.map +1 -0
- package/dist/openapi/parser.test.js +188 -0
- package/dist/openapi/types.d.ts +55 -0
- package/dist/openapi/types.d.ts.map +1 -0
- package/dist/openapi/types.js +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +65 -0
- package/dist/tools/generator.d.ts +12 -0
- package/dist/tools/generator.d.ts.map +1 -0
- package/dist/tools/generator.js +154 -0
- package/dist/tools/generator.test.d.ts +2 -0
- package/dist/tools/generator.test.d.ts.map +1 -0
- package/dist/tools/generator.test.js +178 -0
- package/dist/tools/index.d.ts +6 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +3 -0
- package/dist/tools/overrides.d.ts +6 -0
- package/dist/tools/overrides.d.ts.map +1 -0
- package/dist/tools/overrides.js +29 -0
- package/dist/tools/registry.d.ts +8 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +18 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Synchronicity Labs, Inc.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Sync MCP Server
|
|
2
|
+
|
|
3
|
+
An open-source [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server for the [Sync](https://sync.so) API. Gives AI agents — Claude Code, ChatGPT, Cursor, Codex, and any MCP-compatible client — the ability to create lipsync videos, manage assets, check generation status, and more.
|
|
4
|
+
|
|
5
|
+
Tools are **auto-generated from the Sync OpenAPI spec** at startup. As new API endpoints ship, they become available to agents automatically — no server update needed.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
### Claude Code
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add sync -- npx -y @sync.so/mcp-server --api-key YOUR_API_KEY
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or add to `.mcp.json` in your project root:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"sync": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["-y", "@sync.so/mcp-server"],
|
|
23
|
+
"env": {
|
|
24
|
+
"SYNC_API_KEY": "your-api-key"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Claude Desktop
|
|
32
|
+
|
|
33
|
+
Add to `claude_desktop_config.json`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"mcpServers": {
|
|
38
|
+
"sync": {
|
|
39
|
+
"command": "npx",
|
|
40
|
+
"args": ["-y", "@sync.so/mcp-server"],
|
|
41
|
+
"env": {
|
|
42
|
+
"SYNC_API_KEY": "your-api-key"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Cursor
|
|
50
|
+
|
|
51
|
+
Add to `.cursor/mcp.json` in your project:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"mcpServers": {
|
|
56
|
+
"sync": {
|
|
57
|
+
"command": "npx",
|
|
58
|
+
"args": ["-y", "@sync.so/mcp-server"],
|
|
59
|
+
"env": {
|
|
60
|
+
"SYNC_API_KEY": "your-api-key"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Without an API key (interactive login)
|
|
68
|
+
|
|
69
|
+
Omit `SYNC_API_KEY` and the server will start a device auth flow on first run:
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"mcpServers": {
|
|
74
|
+
"sync": {
|
|
75
|
+
"command": "npx",
|
|
76
|
+
"args": ["-y", "@sync.so/mcp-server"]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
You'll be prompted to visit a URL and enter a code. After approval, the token is cached at `~/.config/sync/mcp-credentials.json`.
|
|
83
|
+
|
|
84
|
+
## Getting an API Key
|
|
85
|
+
|
|
86
|
+
1. Sign up at [sync.so](https://sync.so)
|
|
87
|
+
2. Go to your dashboard settings
|
|
88
|
+
3. Generate an API key
|
|
89
|
+
|
|
90
|
+
See the [authentication guide](https://sync.so/docs/api-reference/guides/authentication) for details.
|
|
91
|
+
|
|
92
|
+
## Available Tools
|
|
93
|
+
|
|
94
|
+
Tools are dynamically generated from the Sync API. Core tools include:
|
|
95
|
+
|
|
96
|
+
| Tool | Description |
|
|
97
|
+
|------|-------------|
|
|
98
|
+
| `generate_create-generation` | Create a lipsync video from video + audio inputs |
|
|
99
|
+
| `generate_get-generation` | Get generation status — poll until COMPLETED |
|
|
100
|
+
| `models_get-public` | List available lipsync models |
|
|
101
|
+
| `assets_get-all` | List all assets in your organization |
|
|
102
|
+
| `assets_get` | Get a specific asset by ID |
|
|
103
|
+
| `generations_estimate` | Estimate generation cost before creating |
|
|
104
|
+
|
|
105
|
+
Plus every other public endpoint in the [Sync API](https://sync.so/docs/api-reference).
|
|
106
|
+
|
|
107
|
+
## Example Prompts
|
|
108
|
+
|
|
109
|
+
Once configured, ask your AI agent:
|
|
110
|
+
|
|
111
|
+
- *"List available Sync models"*
|
|
112
|
+
- *"Create a lipsync video with this video URL and audio URL using the lipsync-2 model"*
|
|
113
|
+
- *"Check the status of generation gen-abc123"*
|
|
114
|
+
- *"Show me my recent generations"*
|
|
115
|
+
- *"How much would it cost to generate a 30-second video?"*
|
|
116
|
+
|
|
117
|
+
## CLI Options
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
Usage: sync-mcp [options]
|
|
121
|
+
|
|
122
|
+
Options:
|
|
123
|
+
--api-key <key> API key (or set SYNC_API_KEY env var)
|
|
124
|
+
--base-url <url> API base URL (default: https://api.sync.so)
|
|
125
|
+
--transport <type> stdio (default) or http
|
|
126
|
+
--port <port> HTTP port (default: 3002, only with --transport http)
|
|
127
|
+
-h, --help Show this help message
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Environment Variables
|
|
131
|
+
|
|
132
|
+
| Variable | Description | Default |
|
|
133
|
+
|----------|-------------|---------|
|
|
134
|
+
| `SYNC_API_KEY` | Your Sync API key | — |
|
|
135
|
+
| `SYNC_BASE_URL` | API base URL | `https://api.sync.so` |
|
|
136
|
+
|
|
137
|
+
## How It Works
|
|
138
|
+
|
|
139
|
+
1. On startup, the server fetches the OpenAPI spec from `{baseUrl}/api-json`
|
|
140
|
+
2. Parses all public endpoints into operation definitions
|
|
141
|
+
3. Converts each operation into an MCP tool with a Zod input schema
|
|
142
|
+
4. Registers tools on the MCP server
|
|
143
|
+
5. Each tool call makes an authenticated HTTP request to the Sync API
|
|
144
|
+
|
|
145
|
+
This means **new API endpoints are automatically available** — just restart the MCP server.
|
|
146
|
+
|
|
147
|
+
## Programmatic Usage
|
|
148
|
+
|
|
149
|
+
You can also use the server as a library:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { createSyncMcpServer, resolveConfig } from '@sync.so/mcp-server';
|
|
153
|
+
|
|
154
|
+
const config = resolveConfig({ apiKey: 'your-key' });
|
|
155
|
+
const server = await createSyncMcpServer(config);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# Install dependencies
|
|
162
|
+
npm install
|
|
163
|
+
|
|
164
|
+
# Build
|
|
165
|
+
npm run build
|
|
166
|
+
|
|
167
|
+
# Run tests
|
|
168
|
+
npm test
|
|
169
|
+
|
|
170
|
+
# Lint
|
|
171
|
+
npm run lint
|
|
172
|
+
|
|
173
|
+
# Type check
|
|
174
|
+
npm run typecheck
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Learn More
|
|
178
|
+
|
|
179
|
+
- [Sync Documentation](https://sync.so/docs) — Full API reference and guides
|
|
180
|
+
- [Sync API Reference](https://sync.so/docs/api-reference) — Endpoint documentation
|
|
181
|
+
- [MCP Protocol](https://modelcontextprotocol.io) — Learn about the Model Context Protocol
|
|
182
|
+
- [Sync Website](https://sync.so) — Sign up and get started
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-key.d.ts","sourceRoot":"","sources":["../../src/auth/api-key.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAK3D"}
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { saveToken } from './token-store.js';
|
|
2
|
+
const POLL_INTERVAL_MS = 5000;
|
|
3
|
+
const CLIENT_ID = 'sync-mcp-server';
|
|
4
|
+
export async function performDeviceAuth(baseUrl, log) {
|
|
5
|
+
const startResponse = await fetch(`${baseUrl}/v2/device-auth/start`, {
|
|
6
|
+
method: 'POST',
|
|
7
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8
|
+
body: JSON.stringify({ clientId: CLIENT_ID }),
|
|
9
|
+
});
|
|
10
|
+
if (!startResponse.ok) {
|
|
11
|
+
throw new Error(`Device auth start failed: ${startResponse.status} ${startResponse.statusText}`);
|
|
12
|
+
}
|
|
13
|
+
const { deviceCode, userCode, verificationUri } = (await startResponse.json());
|
|
14
|
+
log(`\nTo authenticate, visit: ${verificationUri}\nEnter code: ${userCode}\n`);
|
|
15
|
+
const { accessToken, expiresAt } = await pollForToken(baseUrl, deviceCode);
|
|
16
|
+
await saveToken(accessToken, expiresAt);
|
|
17
|
+
log('Authentication successful!\n');
|
|
18
|
+
return {
|
|
19
|
+
type: 'bearer',
|
|
20
|
+
headers: {
|
|
21
|
+
Authorization: `Bearer ${accessToken}`,
|
|
22
|
+
'x-sync-source': 'mcp',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
async function pollForToken(baseUrl, deviceCode) {
|
|
27
|
+
while (true) {
|
|
28
|
+
await sleep(POLL_INTERVAL_MS);
|
|
29
|
+
const pollResponse = await fetch(`${baseUrl}/v2/device-auth/poll?deviceCode=${encodeURIComponent(deviceCode)}`);
|
|
30
|
+
if (!pollResponse.ok) {
|
|
31
|
+
if (pollResponse.status === 404) {
|
|
32
|
+
throw new Error('Device auth code expired. Please try again.');
|
|
33
|
+
}
|
|
34
|
+
throw new Error(`Device auth poll failed: ${pollResponse.status} ${pollResponse.statusText}`);
|
|
35
|
+
}
|
|
36
|
+
const result = (await pollResponse.json());
|
|
37
|
+
if (result.status === 'ready') {
|
|
38
|
+
return { accessToken: result.accessToken, expiresAt: result.expiresAt };
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function sleep(ms) {
|
|
43
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
44
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { ApiKeyAuth } from './api-key.js';
|
|
2
|
+
export { createApiKeyAuth } from './api-key.js';
|
|
3
|
+
export type { DeviceAuthToken } from './device-auth.js';
|
|
4
|
+
export { performDeviceAuth } from './device-auth.js';
|
|
5
|
+
export { clearToken, loadToken, saveToken } from './token-store.js';
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AAYA,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAaxD;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAOhF;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAMhD"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
const CREDENTIALS_DIR = path.join(os.homedir(), '.config', 'sync');
|
|
5
|
+
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'mcp-credentials.json');
|
|
6
|
+
export async function loadToken() {
|
|
7
|
+
try {
|
|
8
|
+
const content = await fs.readFile(CREDENTIALS_FILE, 'utf-8');
|
|
9
|
+
const creds = JSON.parse(content);
|
|
10
|
+
if (creds.expiresAt && new Date(creds.expiresAt) < new Date()) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
return creds.token;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export async function saveToken(token, expiresAt) {
|
|
20
|
+
await fs.mkdir(CREDENTIALS_DIR, { recursive: true });
|
|
21
|
+
const creds = { token, expiresAt };
|
|
22
|
+
await fs.writeFile(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), {
|
|
23
|
+
encoding: 'utf-8',
|
|
24
|
+
mode: 0o600,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export async function clearToken() {
|
|
28
|
+
try {
|
|
29
|
+
await fs.unlink(CREDENTIALS_FILE);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// File doesn't exist, that's fine
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { resolveConfig } from './config.js';
|
|
4
|
+
import { createSyncMcpServer } from './server.js';
|
|
5
|
+
async function main() {
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const config = resolveConfig(parseArgs(args));
|
|
8
|
+
const server = await createSyncMcpServer(config);
|
|
9
|
+
if (config.transport === 'stdio') {
|
|
10
|
+
const transport = new StdioServerTransport();
|
|
11
|
+
await server.connect(transport);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
// HTTP transport - import dynamically to avoid bundling when unused
|
|
15
|
+
const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
16
|
+
const http = await import('node:http');
|
|
17
|
+
const transport = new StreamableHTTPServerTransport({
|
|
18
|
+
sessionIdGenerator: undefined,
|
|
19
|
+
});
|
|
20
|
+
await server.connect(transport);
|
|
21
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
22
|
+
await transport.handleRequest(req, res);
|
|
23
|
+
});
|
|
24
|
+
httpServer.listen(config.port, () => {
|
|
25
|
+
process.stderr.write(`Sync MCP server listening on http://localhost:${config.port}\n`);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function parseArgs(args) {
|
|
30
|
+
const result = {};
|
|
31
|
+
for (let i = 0; i < args.length; i++) {
|
|
32
|
+
const arg = args[i];
|
|
33
|
+
const next = args[i + 1];
|
|
34
|
+
if (arg === '--api-key' && next) {
|
|
35
|
+
result.apiKey = next;
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
else if (arg === '--base-url' && next) {
|
|
39
|
+
result.baseUrl = next;
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
else if (arg === '--transport' && next) {
|
|
43
|
+
result.transport = next;
|
|
44
|
+
i++;
|
|
45
|
+
}
|
|
46
|
+
else if (arg === '--port' && next) {
|
|
47
|
+
result.port = Number.parseInt(next, 10);
|
|
48
|
+
i++;
|
|
49
|
+
}
|
|
50
|
+
else if (arg === '--help' || arg === '-h') {
|
|
51
|
+
printUsage();
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
function printUsage() {
|
|
58
|
+
process.stderr.write(`Usage: sync-mcp [options]
|
|
59
|
+
|
|
60
|
+
Options:
|
|
61
|
+
--api-key <key> API key (or set SYNC_API_KEY env var)
|
|
62
|
+
--base-url <url> API base URL (default: https://api.sync.so)
|
|
63
|
+
--transport <type> stdio (default) or http
|
|
64
|
+
--port <port> HTTP port (default: 3002, only with --transport http)
|
|
65
|
+
-h, --help Show this help message
|
|
66
|
+
`);
|
|
67
|
+
}
|
|
68
|
+
main().catch((error) => {
|
|
69
|
+
process.stderr.write(`Fatal error: ${error}\n`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
});
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type SyncMcpConfig = {
|
|
2
|
+
apiKey?: string;
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
transport: 'stdio' | 'http';
|
|
5
|
+
port: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const DEFAULT_CONFIG: SyncMcpConfig;
|
|
8
|
+
export declare function resolveConfig(overrides?: Partial<SyncMcpConfig>): SyncMcpConfig;
|
|
9
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GAAG;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,aAI5B,CAAC;AAEF,wBAAgB,aAAa,CAAC,SAAS,GAAE,OAAO,CAAC,aAAa,CAAM,GAAG,aAAa,CAOnF"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const DEFAULT_CONFIG = {
|
|
2
|
+
baseUrl: 'https://api.sync.so',
|
|
3
|
+
transport: 'stdio',
|
|
4
|
+
port: 3002,
|
|
5
|
+
};
|
|
6
|
+
export function resolveConfig(overrides = {}) {
|
|
7
|
+
return {
|
|
8
|
+
apiKey: overrides.apiKey ?? process.env.SYNC_API_KEY,
|
|
9
|
+
baseUrl: overrides.baseUrl ?? process.env.SYNC_BASE_URL ?? DEFAULT_CONFIG.baseUrl,
|
|
10
|
+
transport: overrides.transport ?? DEFAULT_CONFIG.transport,
|
|
11
|
+
port: overrides.port ?? DEFAULT_CONFIG.port,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type AuthHeaders = Record<string, string>;
|
|
2
|
+
export type HttpClient = {
|
|
3
|
+
request: (method: string, path: string, options?: {
|
|
4
|
+
query?: Record<string, string>;
|
|
5
|
+
body?: unknown;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
}) => Promise<unknown>;
|
|
8
|
+
};
|
|
9
|
+
export declare function createHttpClient(baseUrl: string, authHeaders: AuthHeaders): HttpClient;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=http-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"AAEA,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1C,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,CACP,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE;QACR,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC/B,IAAI,CAAC,EAAE,OAAO,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KAClC,KACE,OAAO,CAAC,OAAO,CAAC,CAAC;CACvB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,WAAW,GAAG,UAAU,CAkDtF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const SYNC_SOURCE = 'mcp';
|
|
2
|
+
export function createHttpClient(baseUrl, authHeaders) {
|
|
3
|
+
return {
|
|
4
|
+
async request(method, path, options = {}) {
|
|
5
|
+
const url = new URL(path, baseUrl);
|
|
6
|
+
if (options.query) {
|
|
7
|
+
for (const [key, value] of Object.entries(options.query)) {
|
|
8
|
+
if (value !== undefined && value !== '') {
|
|
9
|
+
url.searchParams.set(key, value);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const headers = {
|
|
14
|
+
...authHeaders,
|
|
15
|
+
'x-sync-source': SYNC_SOURCE,
|
|
16
|
+
...options.headers,
|
|
17
|
+
};
|
|
18
|
+
if (options.body && method !== 'get') {
|
|
19
|
+
headers['Content-Type'] = 'application/json';
|
|
20
|
+
}
|
|
21
|
+
const response = await fetch(url.toString(), {
|
|
22
|
+
method: method.toUpperCase(),
|
|
23
|
+
headers,
|
|
24
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
25
|
+
});
|
|
26
|
+
const text = await response.text();
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = JSON.parse(text);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
parsed = text;
|
|
33
|
+
}
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
const message = typeof parsed === 'object' && parsed !== null && 'message' in parsed
|
|
36
|
+
? parsed.message
|
|
37
|
+
: text;
|
|
38
|
+
throw new Error(`API request failed: ${response.status} ${response.statusText} - ${message}`);
|
|
39
|
+
}
|
|
40
|
+
return parsed;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type { ApiKeyAuth, DeviceAuthToken } from './auth/index.js';
|
|
2
|
+
export { clearToken, createApiKeyAuth, loadToken, performDeviceAuth, saveToken, } from './auth/index.js';
|
|
3
|
+
export type { SyncMcpConfig } from './config.js';
|
|
4
|
+
export { resolveConfig } from './config.js';
|
|
5
|
+
export type { HttpClient } from './http-client.js';
|
|
6
|
+
export { createHttpClient } from './http-client.js';
|
|
7
|
+
export type { JsonSchema, OpenApiSpec, ParsedOperation, ParsedParameter, } from './openapi/index.js';
|
|
8
|
+
export { fetchSpec, loadSpecFromFile, parseSpec } from './openapi/index.js';
|
|
9
|
+
export { createSyncMcpServer } from './server.js';
|
|
10
|
+
export type { McpToolDefinition, ToolRegistry } from './tools/index.js';
|
|
11
|
+
export { createToolRegistry, generateTools, getOverride, operationIdToToolName, } from './tools/index.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACnE,OAAO,EACL,UAAU,EACV,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,SAAS,GACV,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,YAAY,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,eAAe,GAChB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACxE,OAAO,EACL,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,qBAAqB,GACtB,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { clearToken, createApiKeyAuth, loadToken, performDeviceAuth, saveToken, } from './auth/index.js';
|
|
2
|
+
export { resolveConfig } from './config.js';
|
|
3
|
+
export { createHttpClient } from './http-client.js';
|
|
4
|
+
export { fetchSpec, loadSpecFromFile, parseSpec } from './openapi/index.js';
|
|
5
|
+
export { createSyncMcpServer } from './server.js';
|
|
6
|
+
export { createToolRegistry, generateTools, getOverride, operationIdToToolName, } from './tools/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetcher.d.ts","sourceRoot":"","sources":["../../src/openapi/fetcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAsB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CASrE;AAED,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAIzE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export async function fetchSpec(baseUrl) {
|
|
2
|
+
const url = `${baseUrl}/api-json`;
|
|
3
|
+
const response = await fetch(url);
|
|
4
|
+
if (!response.ok) {
|
|
5
|
+
throw new Error(`Failed to fetch OpenAPI spec from ${url}: ${response.status} ${response.statusText}`);
|
|
6
|
+
}
|
|
7
|
+
return (await response.json());
|
|
8
|
+
}
|
|
9
|
+
export async function loadSpecFromFile(path) {
|
|
10
|
+
const fs = await import('node:fs/promises');
|
|
11
|
+
const content = await fs.readFile(path, 'utf-8');
|
|
12
|
+
return JSON.parse(content);
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/openapi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACvE,YAAY,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { JsonSchema, OpenApiSpec, ParsedOperation } from './types.js';
|
|
2
|
+
export declare function parseSpec(spec: OpenApiSpec): ParsedOperation[];
|
|
3
|
+
export declare function resolveRef(spec: OpenApiSpec, ref: string): JsonSchema;
|
|
4
|
+
export declare function resolveSchemaRefs(schema: JsonSchema, spec: OpenApiSpec): JsonSchema;
|
|
5
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/openapi/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,eAAe,EAAmB,MAAM,YAAY,CAAC;AAI5F,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,EAAE,CAiC9D;AAgDD,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,MAAM,GAAG,UAAU,CAUrE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,GAAG,UAAU,CA2BnF"}
|