@metalabdesign/mcp-client 1.0.1
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/CHANGELOG.md +43 -0
- package/README.md +243 -0
- package/dist/bin/cli.d.ts +14 -0
- package/dist/bin/cli.d.ts.map +1 -0
- package/dist/bin/cli.js +216 -0
- package/dist/bin/cli.js.map +1 -0
- package/dist/bin/metalab-mcp-config.d.ts +9 -0
- package/dist/bin/metalab-mcp-config.d.ts.map +1 -0
- package/dist/bin/metalab-mcp-config.js +224 -0
- package/dist/bin/metalab-mcp-config.js.map +1 -0
- package/dist/bin/metalab-mcp.d.ts +14 -0
- package/dist/bin/metalab-mcp.d.ts.map +1 -0
- package/dist/bin/metalab-mcp.js +242 -0
- package/dist/bin/metalab-mcp.js.map +1 -0
- package/dist/config/aws-sso.d.ts +21 -0
- package/dist/config/aws-sso.d.ts.map +1 -0
- package/dist/config/aws-sso.js +67 -0
- package/dist/config/aws-sso.js.map +1 -0
- package/dist/config/defaults.d.ts +14 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +20 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/index.d.ts +5 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +3 -0
- package/dist/config/index.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/oauth.d.ts +34 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +401 -0
- package/dist/oauth.js.map +1 -0
- package/dist/proxy.d.ts +39 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +203 -0
- package/dist/proxy.js.map +1 -0
- package/dist/storage.d.ts +15 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +64 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +78 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +40 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +42 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +89 -0
- package/dist/utils.js.map +1 -0
- package/package.json +51 -0
- package/src/bin/cli.ts +242 -0
- package/src/bin/metalab-mcp-config.ts +262 -0
- package/src/bin/metalab-mcp.ts +284 -0
- package/src/config/aws-sso.ts +78 -0
- package/src/config/defaults.ts +26 -0
- package/src/config/index.ts +8 -0
- package/src/index.ts +54 -0
- package/src/oauth.ts +540 -0
- package/src/proxy.ts +274 -0
- package/src/storage.ts +81 -0
- package/src/types.ts +79 -0
- package/src/utils.ts +115 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Metalab MCP Client - Zero-config connection to Metalab's MCP Gateway
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* metalab-mcp [options]
|
|
7
|
+
*
|
|
8
|
+
* The gateway URL is auto-detected from:
|
|
9
|
+
* 1. AWS SSM Parameter Store (requires AWS credentials)
|
|
10
|
+
* 2. MCP_SERVER_URL environment variable
|
|
11
|
+
* 3. Hardcoded production URL (fallback)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { spawn } from 'node:child_process';
|
|
15
|
+
import { createProxy } from '../index.js';
|
|
16
|
+
import { resolveGatewayUrl, getSourceDescription, DEFAULTS } from '../config/index.js';
|
|
17
|
+
|
|
18
|
+
interface CliArgs {
|
|
19
|
+
profile?: string;
|
|
20
|
+
callbackUrl?: string;
|
|
21
|
+
tokenStorageDir?: string;
|
|
22
|
+
timeout?: number;
|
|
23
|
+
openBrowser?: boolean;
|
|
24
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
25
|
+
serviceTokens?: Record<string, string>;
|
|
26
|
+
inspect?: boolean;
|
|
27
|
+
help?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function printBanner(config: {
|
|
31
|
+
gatewayUrl: string;
|
|
32
|
+
source: string;
|
|
33
|
+
profile?: string;
|
|
34
|
+
callbackUrl: string;
|
|
35
|
+
figmaToken?: string;
|
|
36
|
+
notionToken?: string;
|
|
37
|
+
}): void {
|
|
38
|
+
const truncateUrl = (url: string, maxLen: number = 50): string => {
|
|
39
|
+
if (url.length <= maxLen) return url;
|
|
40
|
+
return url.substring(0, maxLen - 3) + '...';
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const checkmark = '\u2713';
|
|
44
|
+
const cross = '\u2717';
|
|
45
|
+
|
|
46
|
+
console.error('');
|
|
47
|
+
console.error('\u2554' + '\u2550'.repeat(62) + '\u2557');
|
|
48
|
+
console.error('\u2551' + ' Metalab MCP Client '.substring(0, 62) + '\u2551');
|
|
49
|
+
console.error('\u2560' + '\u2550'.repeat(62) + '\u2563');
|
|
50
|
+
console.error(`\u2551 Gateway URL: ${truncateUrl(config.gatewayUrl, 45).padEnd(46)}\u2551`);
|
|
51
|
+
console.error(`\u2551 Source: ${config.source.padEnd(46)}\u2551`);
|
|
52
|
+
if (config.profile) {
|
|
53
|
+
console.error(`\u2551 AWS Profile: ${config.profile.padEnd(46)}\u2551`);
|
|
54
|
+
}
|
|
55
|
+
console.error(`\u2551 Callback: ${truncateUrl(config.callbackUrl, 45).padEnd(46)}\u2551`);
|
|
56
|
+
console.error(`\u2551 Figma Token: ${(config.figmaToken ? `${checkmark} configured` : `${cross} not configured`).padEnd(46)}\u2551`);
|
|
57
|
+
console.error(`\u2551 Notion Token: ${(config.notionToken ? `${checkmark} configured` : `${cross} not configured`).padEnd(45)}\u2551`);
|
|
58
|
+
console.error('\u255A' + '\u2550'.repeat(62) + '\u255D');
|
|
59
|
+
console.error('');
|
|
60
|
+
console.error('Press Ctrl+C to stop');
|
|
61
|
+
console.error('');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printHelp(): void {
|
|
65
|
+
console.error(`
|
|
66
|
+
Metalab MCP Client - Zero-config connection to Metalab's MCP Gateway
|
|
67
|
+
|
|
68
|
+
USAGE:
|
|
69
|
+
metalab-mcp [options]
|
|
70
|
+
|
|
71
|
+
The gateway URL is automatically detected from:
|
|
72
|
+
1. AWS SSM Parameter Store (if AWS credentials available)
|
|
73
|
+
2. MCP_SERVER_URL environment variable
|
|
74
|
+
3. Hardcoded production URL (fallback)
|
|
75
|
+
|
|
76
|
+
OPTIONS:
|
|
77
|
+
--profile <name> AWS SSO profile to use for SSM lookup
|
|
78
|
+
--callback-url <url> OAuth callback URL (default: ${DEFAULTS.CALLBACK_URL})
|
|
79
|
+
--token-dir <dir> Directory for token storage (default: ${DEFAULTS.TOKEN_DIR})
|
|
80
|
+
--timeout <ms> Request timeout in milliseconds (default: ${DEFAULTS.TIMEOUT_MS})
|
|
81
|
+
--no-browser Don't auto-open browser for auth
|
|
82
|
+
--log-level <level> Log level: debug, info, warn, error (default: info)
|
|
83
|
+
--figma-token <token> Figma access token (or set FIGMA_ACCESS_TOKEN env var)
|
|
84
|
+
--notion-token <token> Notion access token (or set NOTION_ACCESS_TOKEN env var)
|
|
85
|
+
--inspect Run with MCP Inspector for debugging
|
|
86
|
+
--help, -h Show this help message
|
|
87
|
+
|
|
88
|
+
EXAMPLES:
|
|
89
|
+
# Basic usage (auto-detects everything)
|
|
90
|
+
metalab-mcp
|
|
91
|
+
|
|
92
|
+
# With AWS SSO profile
|
|
93
|
+
metalab-mcp --profile metalab-dev
|
|
94
|
+
|
|
95
|
+
# With Figma token
|
|
96
|
+
metalab-mcp --figma-token figd_xxxxx
|
|
97
|
+
|
|
98
|
+
# Debug mode with inspector
|
|
99
|
+
metalab-mcp --inspect
|
|
100
|
+
|
|
101
|
+
# Via environment variables
|
|
102
|
+
FIGMA_ACCESS_TOKEN=figd_xxxxx metalab-mcp
|
|
103
|
+
|
|
104
|
+
QUICK SETUP:
|
|
105
|
+
1. (Optional) Login to AWS SSO: aws sso login --profile metalab-dev
|
|
106
|
+
2. Generate config: npx @metalabdesign/mcp-client-config --client claude
|
|
107
|
+
3. Restart your MCP client (Claude Desktop, etc.)
|
|
108
|
+
|
|
109
|
+
For more information, see:
|
|
110
|
+
https://github.com/metalabdesign/metalab-core-mcps
|
|
111
|
+
`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseArgs(argv: string[]): CliArgs {
|
|
115
|
+
const args: CliArgs = {
|
|
116
|
+
serviceTokens: {},
|
|
117
|
+
};
|
|
118
|
+
let i = 2; // Skip 'node' and script path
|
|
119
|
+
|
|
120
|
+
while (i < argv.length) {
|
|
121
|
+
const arg = argv[i];
|
|
122
|
+
|
|
123
|
+
switch (arg) {
|
|
124
|
+
case '--profile':
|
|
125
|
+
args.profile = argv[++i];
|
|
126
|
+
break;
|
|
127
|
+
case '--callback-url':
|
|
128
|
+
args.callbackUrl = argv[++i];
|
|
129
|
+
break;
|
|
130
|
+
case '--token-dir':
|
|
131
|
+
args.tokenStorageDir = argv[++i];
|
|
132
|
+
break;
|
|
133
|
+
case '--timeout':
|
|
134
|
+
args.timeout = Number.parseInt(argv[++i] ?? '', 10);
|
|
135
|
+
break;
|
|
136
|
+
case '--no-browser':
|
|
137
|
+
args.openBrowser = false;
|
|
138
|
+
break;
|
|
139
|
+
case '--log-level':
|
|
140
|
+
args.logLevel = argv[++i] as CliArgs['logLevel'];
|
|
141
|
+
break;
|
|
142
|
+
case '--figma-token':
|
|
143
|
+
case '--figma':
|
|
144
|
+
args.serviceTokens!.figma = argv[++i] ?? '';
|
|
145
|
+
break;
|
|
146
|
+
case '--notion-token':
|
|
147
|
+
case '--notion':
|
|
148
|
+
args.serviceTokens!.notion = argv[++i] ?? '';
|
|
149
|
+
break;
|
|
150
|
+
case '--inspect':
|
|
151
|
+
args.inspect = true;
|
|
152
|
+
break;
|
|
153
|
+
case '--help':
|
|
154
|
+
case '-h':
|
|
155
|
+
args.help = true;
|
|
156
|
+
break;
|
|
157
|
+
default:
|
|
158
|
+
if (arg?.startsWith('-')) {
|
|
159
|
+
console.error(`Unknown option: ${arg}`);
|
|
160
|
+
console.error('Run with --help for usage information');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Load service tokens from environment variables (fallback)
|
|
168
|
+
if (process.env.FIGMA_ACCESS_TOKEN) {
|
|
169
|
+
args.serviceTokens!.figma = args.serviceTokens!.figma || process.env.FIGMA_ACCESS_TOKEN;
|
|
170
|
+
}
|
|
171
|
+
if (process.env.NOTION_ACCESS_TOKEN) {
|
|
172
|
+
args.serviceTokens!.notion = args.serviceTokens!.notion || process.env.NOTION_ACCESS_TOKEN;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Clean up empty serviceTokens object
|
|
176
|
+
if (Object.keys(args.serviceTokens!).length === 0) {
|
|
177
|
+
delete args.serviceTokens;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return args;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function runWithInspector(args: CliArgs, gatewayUrl: string): Promise<void> {
|
|
184
|
+
// Build args for the inspector
|
|
185
|
+
const inspectorArgs = [
|
|
186
|
+
'@modelcontextprotocol/inspector',
|
|
187
|
+
'node',
|
|
188
|
+
new URL('../bin/cli.js', import.meta.url).pathname,
|
|
189
|
+
'--remote-url',
|
|
190
|
+
gatewayUrl + '/mcp',
|
|
191
|
+
'--callback-url',
|
|
192
|
+
args.callbackUrl || DEFAULTS.CALLBACK_URL,
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
if (args.tokenStorageDir) {
|
|
196
|
+
inspectorArgs.push('--token-dir', args.tokenStorageDir);
|
|
197
|
+
}
|
|
198
|
+
if (args.timeout) {
|
|
199
|
+
inspectorArgs.push('--timeout', args.timeout.toString());
|
|
200
|
+
}
|
|
201
|
+
if (args.openBrowser === false) {
|
|
202
|
+
inspectorArgs.push('--no-browser');
|
|
203
|
+
}
|
|
204
|
+
if (args.logLevel) {
|
|
205
|
+
inspectorArgs.push('--log-level', args.logLevel);
|
|
206
|
+
}
|
|
207
|
+
if (args.serviceTokens?.figma) {
|
|
208
|
+
inspectorArgs.push('--figma-token', args.serviceTokens.figma);
|
|
209
|
+
}
|
|
210
|
+
if (args.serviceTokens?.notion) {
|
|
211
|
+
inspectorArgs.push('--notion-token', args.serviceTokens.notion);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.error('Starting MCP Inspector...');
|
|
215
|
+
console.error('');
|
|
216
|
+
|
|
217
|
+
const child = spawn('npx', inspectorArgs, {
|
|
218
|
+
stdio: 'inherit',
|
|
219
|
+
shell: true,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
child.on('exit', (code) => {
|
|
223
|
+
process.exit(code ?? 0);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function main(): Promise<void> {
|
|
228
|
+
const args = parseArgs(process.argv);
|
|
229
|
+
|
|
230
|
+
if (args.help) {
|
|
231
|
+
printHelp();
|
|
232
|
+
process.exit(0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Resolve gateway URL
|
|
236
|
+
const config = await resolveGatewayUrl(args.profile);
|
|
237
|
+
const callbackUrl = args.callbackUrl || DEFAULTS.CALLBACK_URL;
|
|
238
|
+
|
|
239
|
+
// Print banner
|
|
240
|
+
printBanner({
|
|
241
|
+
gatewayUrl: config.gatewayUrl,
|
|
242
|
+
source: getSourceDescription(config.source),
|
|
243
|
+
profile: config.profile,
|
|
244
|
+
callbackUrl,
|
|
245
|
+
figmaToken: args.serviceTokens?.figma,
|
|
246
|
+
notionToken: args.serviceTokens?.notion,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// If --inspect flag, run with MCP Inspector
|
|
250
|
+
if (args.inspect) {
|
|
251
|
+
await runWithInspector(args, config.gatewayUrl);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const proxy = await createProxy({
|
|
257
|
+
remoteUrl: config.gatewayUrl + '/mcp',
|
|
258
|
+
callbackUrl,
|
|
259
|
+
tokenStorageDir: args.tokenStorageDir,
|
|
260
|
+
timeout: args.timeout,
|
|
261
|
+
openBrowser: args.openBrowser ?? true,
|
|
262
|
+
logLevel: args.logLevel ?? 'info',
|
|
263
|
+
serviceTokens: args.serviceTokens,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Handle shutdown gracefully
|
|
267
|
+
const shutdown = async (): Promise<void> => {
|
|
268
|
+
console.error('Shutting down...');
|
|
269
|
+
await proxy.disconnect();
|
|
270
|
+
process.exit(0);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
process.on('SIGINT', shutdown);
|
|
274
|
+
process.on('SIGTERM', shutdown);
|
|
275
|
+
|
|
276
|
+
// Start the proxy
|
|
277
|
+
await proxy.start();
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error('Failed to start proxy:', error);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
main();
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { DEFAULTS } from './defaults.js';
|
|
3
|
+
|
|
4
|
+
export interface AwsConfig {
|
|
5
|
+
gatewayUrl: string;
|
|
6
|
+
source: 'ssm' | 'env' | 'hardcoded';
|
|
7
|
+
profile?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolution order:
|
|
12
|
+
* 1. AWS SSO/credentials → SSM Parameter Store → gateway URL
|
|
13
|
+
* 2. MCP_SERVER_URL environment variable
|
|
14
|
+
* 3. Hardcoded production URL (fallback)
|
|
15
|
+
*/
|
|
16
|
+
export async function resolveGatewayUrl(profile?: string): Promise<AwsConfig> {
|
|
17
|
+
// Try SSM Parameter Store first (requires AWS credentials)
|
|
18
|
+
const ssmUrl = await getUrlFromSsm(profile);
|
|
19
|
+
if (ssmUrl) {
|
|
20
|
+
return { gatewayUrl: ssmUrl, source: 'ssm', profile };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Fallback to environment variable
|
|
24
|
+
const envUrl = process.env.MCP_SERVER_URL;
|
|
25
|
+
if (envUrl) {
|
|
26
|
+
return { gatewayUrl: envUrl, source: 'env' };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Final fallback to hardcoded URL
|
|
30
|
+
return { gatewayUrl: DEFAULTS.GATEWAY_URL, source: 'hardcoded' };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function getUrlFromSsm(profile?: string): Promise<string | null> {
|
|
34
|
+
try {
|
|
35
|
+
// Check if AWS CLI is available
|
|
36
|
+
execSync('which aws', { encoding: 'utf-8', stdio: 'pipe' });
|
|
37
|
+
|
|
38
|
+
// Build the AWS CLI command
|
|
39
|
+
const profileArg = profile ? `--profile ${profile}` : '';
|
|
40
|
+
const cmd = `aws ssm get-parameter ${profileArg} --name ${DEFAULTS.SSM_GATEWAY_URL_PARAM} --region ${DEFAULTS.AWS_REGION} --query 'Parameter.Value' --output text`;
|
|
41
|
+
|
|
42
|
+
const result = execSync(cmd, { encoding: 'utf-8', stdio: 'pipe' }).trim();
|
|
43
|
+
return result || null;
|
|
44
|
+
} catch {
|
|
45
|
+
// AWS CLI not available, not authenticated, or parameter doesn't exist
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if AWS SSO session is active for the given profile
|
|
52
|
+
*/
|
|
53
|
+
export function isSsoAuthenticated(profile?: string): boolean {
|
|
54
|
+
try {
|
|
55
|
+
const profileArg = profile ? `--profile ${profile}` : '';
|
|
56
|
+
execSync(`aws sts get-caller-identity ${profileArg}`, {
|
|
57
|
+
encoding: 'utf-8',
|
|
58
|
+
stdio: 'pipe',
|
|
59
|
+
});
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get a human-readable description of the gateway URL source
|
|
68
|
+
*/
|
|
69
|
+
export function getSourceDescription(source: AwsConfig['source']): string {
|
|
70
|
+
switch (source) {
|
|
71
|
+
case 'ssm':
|
|
72
|
+
return 'SSM Parameter Store';
|
|
73
|
+
case 'env':
|
|
74
|
+
return 'MCP_SERVER_URL environment variable';
|
|
75
|
+
case 'hardcoded':
|
|
76
|
+
return 'Default (hardcoded)';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default configuration values for @metalabdesign/mcp-client
|
|
3
|
+
*/
|
|
4
|
+
export const DEFAULTS = {
|
|
5
|
+
// Production MCP Gateway URL (hardcoded fallback)
|
|
6
|
+
// This is the current API Gateway URL - will be updated if infrastructure changes
|
|
7
|
+
GATEWAY_URL: 'https://qe40bdp1jh.execute-api.us-east-2.amazonaws.com',
|
|
8
|
+
|
|
9
|
+
// SSM Parameter path for dynamic gateway URL
|
|
10
|
+
SSM_GATEWAY_URL_PARAM: '/mcp/gateway-url',
|
|
11
|
+
|
|
12
|
+
// AWS Region for SSM lookups
|
|
13
|
+
AWS_REGION: 'us-east-2',
|
|
14
|
+
|
|
15
|
+
// OAuth callback URL (matches Okta redirect URI configuration)
|
|
16
|
+
CALLBACK_URL: 'http://localhost:9861/oauth/callback',
|
|
17
|
+
|
|
18
|
+
// Token storage directory
|
|
19
|
+
TOKEN_DIR: '~/.metalab/mcp-client/tokens',
|
|
20
|
+
|
|
21
|
+
// Timeouts
|
|
22
|
+
TIMEOUT_MS: 30000,
|
|
23
|
+
AUTH_TIMEOUT_MS: 300000,
|
|
24
|
+
} as const;
|
|
25
|
+
|
|
26
|
+
export type Defaults = typeof DEFAULTS;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Local Proxy - Factory and exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { McpProxy } from './proxy.js';
|
|
6
|
+
|
|
7
|
+
export interface CreateProxyOptions {
|
|
8
|
+
/** Remote MCP server URL */
|
|
9
|
+
remoteUrl: string;
|
|
10
|
+
/** OAuth callback URL (port is extracted from this URL) */
|
|
11
|
+
callbackUrl: string;
|
|
12
|
+
/** Directory for token storage */
|
|
13
|
+
tokenStorageDir?: string;
|
|
14
|
+
/** Custom headers for remote requests */
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
/** Request timeout in ms (default: 30000) */
|
|
17
|
+
timeout?: number;
|
|
18
|
+
/** Auto-open browser for auth (default: true) */
|
|
19
|
+
openBrowser?: boolean;
|
|
20
|
+
/** Log level (default: 'info') */
|
|
21
|
+
logLevel?: 'debug' | 'info' | 'warn' | 'error';
|
|
22
|
+
/** Service-specific access tokens (e.g., { figma: "token", notion: "token" }) */
|
|
23
|
+
serviceTokens?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Create and configure an MCP OAuth proxy
|
|
28
|
+
*/
|
|
29
|
+
export async function createProxy(options: CreateProxyOptions): Promise<McpProxy> {
|
|
30
|
+
const { ProxyConfigSchema } = await import('./types.js');
|
|
31
|
+
const { TokenStorage } = await import('./storage.js');
|
|
32
|
+
const { OAuthManager } = await import('./oauth.js');
|
|
33
|
+
const { McpProxy } = await import('./proxy.js');
|
|
34
|
+
const { StderrLogger } = await import('./utils.js');
|
|
35
|
+
|
|
36
|
+
// Validate and parse configuration
|
|
37
|
+
const config = ProxyConfigSchema.parse({
|
|
38
|
+
remoteUrl: options.remoteUrl,
|
|
39
|
+
callbackUrl: options.callbackUrl,
|
|
40
|
+
tokenStorageDir: options.tokenStorageDir,
|
|
41
|
+
headers: options.headers,
|
|
42
|
+
timeout: options.timeout ?? 30000,
|
|
43
|
+
openBrowser: options.openBrowser ?? true,
|
|
44
|
+
serviceTokens: options.serviceTokens,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Create components
|
|
48
|
+
const logger = new StderrLogger(options.logLevel ?? 'info');
|
|
49
|
+
const storage = new TokenStorage(config.tokenStorageDir);
|
|
50
|
+
const oauthManager = new OAuthManager(config, storage, logger);
|
|
51
|
+
const proxy = new McpProxy(config, oauthManager, logger);
|
|
52
|
+
|
|
53
|
+
return proxy;
|
|
54
|
+
}
|