agent-gateway-mcp 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/src/init.ts ADDED
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+
3
+ import http from "http";
4
+ import {
5
+ loadConfig,
6
+ saveConfig,
7
+ storeIdentity,
8
+ isInitialized,
9
+ getIdentity,
10
+ syncTokensFromCloud,
11
+ setRegistryUrl,
12
+ } from "./config.js";
13
+ import type { UserIdentity, IdentityProvider } from "./types.js";
14
+
15
+ const IDENTITY_PROVIDERS: Record<IdentityProvider, string> = {
16
+ google: "Google",
17
+ github: "GitHub",
18
+ microsoft: "Microsoft",
19
+ };
20
+
21
+ interface InitOptions {
22
+ registry?: string;
23
+ provider?: IdentityProvider;
24
+ }
25
+
26
+ function parseArgs(): InitOptions {
27
+ const args = process.argv.slice(2);
28
+ const opts: InitOptions = {};
29
+
30
+ for (let i = 0; i < args.length; i++) {
31
+ if (args[i] === "--registry" && args[i + 1]) {
32
+ opts.registry = args[i + 1];
33
+ i++;
34
+ }
35
+ if (args[i] === "--provider" && args[i + 1]) {
36
+ opts.provider = args[i + 1] as IdentityProvider;
37
+ i++;
38
+ }
39
+ }
40
+
41
+ return opts;
42
+ }
43
+
44
+ async function init(): Promise<void> {
45
+ const opts = parseArgs();
46
+
47
+ if (opts.registry) {
48
+ setRegistryUrl(opts.registry);
49
+ }
50
+
51
+ const config = loadConfig();
52
+ const registryUrl = config.registry_url;
53
+
54
+ console.log("");
55
+ console.log(" ╔══════════════════════════════════════╗");
56
+ console.log(" ║ Agent Gateway — Setup ║");
57
+ console.log(" ╚══════════════════════════════════════╝");
58
+ console.log("");
59
+
60
+ if (isInitialized()) {
61
+ const identity = getIdentity()!;
62
+ console.log(` Already signed in as ${identity.email} (${identity.provider})`);
63
+ console.log("");
64
+
65
+ // Sync connections
66
+ console.log(" Syncing connections from cloud...");
67
+ const sync = await syncTokensFromCloud();
68
+ if (sync.success) {
69
+ console.log(` Synced ${sync.count} new connection(s).`);
70
+ } else {
71
+ console.log(` Sync skipped: ${sync.error}`);
72
+ }
73
+
74
+ console.log("");
75
+ console.log(" You're all set! Your agent can now use the gateway.");
76
+ console.log("");
77
+ printMcpConfig(registryUrl);
78
+ return;
79
+ }
80
+
81
+ // Determine provider
82
+ const provider = opts.provider ?? "google";
83
+ const providerName = IDENTITY_PROVIDERS[provider] ?? provider;
84
+
85
+ console.log(` Signing in with ${providerName}...`);
86
+ console.log("");
87
+
88
+ // Start OAuth flow with registry
89
+ const port = config.auth_callback_port;
90
+ const redirectUri = `http://localhost:${port}/callback`;
91
+ const state = Math.random().toString(36).substring(2, 15);
92
+
93
+ const authUrl = `${registryUrl}/auth/init?provider=${provider}&redirect_uri=${encodeURIComponent(redirectUri)}&state=${state}`;
94
+
95
+ // Start local callback server
96
+ const identityPromise = new Promise<UserIdentity>((resolve, reject) => {
97
+ const timeout = setTimeout(() => {
98
+ server.close();
99
+ reject(new Error("Authentication timeout — no callback received within 120 seconds."));
100
+ }, 120000);
101
+
102
+ const server = http.createServer(async (req, res) => {
103
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
104
+
105
+ if (url.pathname !== "/callback") {
106
+ res.writeHead(404);
107
+ res.end("Not found");
108
+ return;
109
+ }
110
+
111
+ const error = url.searchParams.get("error");
112
+ if (error) {
113
+ res.writeHead(200, { "Content-Type": "text/html" });
114
+ res.end(html("Authentication Failed", `<p>${error}</p><p>You can close this tab.</p>`));
115
+ clearTimeout(timeout);
116
+ server.close();
117
+ reject(new Error(`Auth error: ${error}`));
118
+ return;
119
+ }
120
+
121
+ const returnedState = url.searchParams.get("state");
122
+ if (returnedState !== state) {
123
+ res.writeHead(400, { "Content-Type": "text/html" });
124
+ res.end(html("Invalid Callback", "<p>State mismatch. Please try again.</p>"));
125
+ return;
126
+ }
127
+
128
+ // Registry returns identity data in the callback
129
+ const registryToken = url.searchParams.get("token");
130
+ const refreshToken = url.searchParams.get("refresh_token");
131
+ const email = url.searchParams.get("email");
132
+ const name = url.searchParams.get("name");
133
+ const providerId = url.searchParams.get("provider_id");
134
+
135
+ if (!registryToken || !email || !providerId) {
136
+ res.writeHead(400, { "Content-Type": "text/html" });
137
+ res.end(html("Invalid Callback", "<p>Missing authentication data. Please try again.</p>"));
138
+ return;
139
+ }
140
+
141
+ const identity: UserIdentity = {
142
+ provider,
143
+ provider_id: providerId,
144
+ email,
145
+ name: name ?? undefined,
146
+ registry_token: registryToken,
147
+ registry_refresh_token: refreshToken ?? undefined,
148
+ connected_at: new Date().toISOString(),
149
+ };
150
+
151
+ res.writeHead(200, { "Content-Type": "text/html" });
152
+ res.end(html(
153
+ "Connected to Agent Gateway!",
154
+ `<p>Signed in as <strong>${email}</strong></p><p>You can close this tab and return to your terminal.</p>`
155
+ ));
156
+
157
+ clearTimeout(timeout);
158
+ server.close();
159
+ resolve(identity);
160
+ });
161
+
162
+ server.listen(port, () => {
163
+ // Server ready
164
+ });
165
+ });
166
+
167
+ // Open browser
168
+ try {
169
+ const open = (await import("open")).default;
170
+ await open(authUrl);
171
+ console.log(" Browser opened for sign-in.");
172
+ } catch {
173
+ console.log(` Open this URL in your browser:`);
174
+ console.log("");
175
+ console.log(` ${authUrl}`);
176
+ }
177
+
178
+ console.log("");
179
+ console.log(` Waiting for sign-in on http://localhost:${port}/callback...`);
180
+ console.log("");
181
+
182
+ try {
183
+ const identity = await identityPromise;
184
+ storeIdentity(identity);
185
+
186
+ console.log(` Signed in as ${identity.email}`);
187
+ console.log("");
188
+
189
+ // Sync existing connections from cloud
190
+ console.log(" Syncing connections from cloud...");
191
+ const sync = await syncTokensFromCloud();
192
+ if (sync.success) {
193
+ if (sync.count > 0) {
194
+ console.log(` Restored ${sync.count} connection(s) from your account.`);
195
+ } else {
196
+ console.log(" No existing connections found. Start fresh!");
197
+ }
198
+ } else {
199
+ console.log(" Cloud sync skipped (will retry on next run).");
200
+ }
201
+
202
+ console.log("");
203
+ console.log(" Setup complete! Add this to your MCP client config:");
204
+ console.log("");
205
+ printMcpConfig(registryUrl);
206
+ } catch (err) {
207
+ console.error(` Setup failed: ${err instanceof Error ? err.message : "unknown error"}`);
208
+ process.exit(1);
209
+ }
210
+ }
211
+
212
+ function printMcpConfig(registryUrl: string): void {
213
+ const config: Record<string, unknown> = {
214
+ mcpServers: {
215
+ gateway: {
216
+ command: "agent-gateway-mcp",
217
+ args: registryUrl !== "https://agentdns.dev"
218
+ ? ["--registry", registryUrl]
219
+ : [],
220
+ },
221
+ },
222
+ };
223
+
224
+ console.log(" " + JSON.stringify(config, null, 2).split("\n").join("\n "));
225
+ console.log("");
226
+ }
227
+
228
+ function html(title: string, body: string): string {
229
+ return `<!DOCTYPE html>
230
+ <html>
231
+ <head>
232
+ <meta charset="utf-8">
233
+ <title>${title}</title>
234
+ <style>
235
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #0a0a0a; color: #e5e5e5; }
236
+ .card { text-align: center; padding: 3rem; border: 1px solid #262626; border-radius: 12px; background: #171717; max-width: 400px; }
237
+ h2 { color: #10b981; margin-bottom: 1rem; }
238
+ p { color: #a3a3a3; line-height: 1.6; }
239
+ strong { color: #e5e5e5; }
240
+ </style>
241
+ </head>
242
+ <body>
243
+ <div class="card">
244
+ <h2>${title}</h2>
245
+ ${body}
246
+ </div>
247
+ </body>
248
+ </html>`;
249
+ }
250
+
251
+ init().catch((err) => {
252
+ console.error(`Fatal error: ${err}`);
253
+ process.exit(1);
254
+ });
package/src/types.ts ADDED
@@ -0,0 +1,165 @@
1
+ // ─── Manifest types ──────────────────────────────────────────────
2
+
3
+ export interface ManifestCapability {
4
+ name: string;
5
+ description: string;
6
+ detail_url: string;
7
+ }
8
+
9
+ export interface ManifestAuth {
10
+ type: "oauth2" | "api_key" | "none";
11
+ authorization_url?: string;
12
+ token_url?: string;
13
+ scopes?: string[];
14
+ header?: string;
15
+ prefix?: string;
16
+ setup_url?: string;
17
+ }
18
+
19
+ export interface ManifestPricing {
20
+ type: "free" | "freemium" | "paid";
21
+ plans?: Array<{ name: string; price: string; limits: string }>;
22
+ plans_url?: string;
23
+ }
24
+
25
+ export interface Manifest {
26
+ spec_version: string;
27
+ name: string;
28
+ description: string;
29
+ base_url: string;
30
+ auth: ManifestAuth;
31
+ pricing?: ManifestPricing;
32
+ capabilities: ManifestCapability[];
33
+ }
34
+
35
+ export interface CapabilityDetail {
36
+ name: string;
37
+ description: string;
38
+ endpoint: string;
39
+ method: string;
40
+ parameters: Array<{
41
+ name: string;
42
+ type: string;
43
+ description: string;
44
+ required: boolean;
45
+ example: unknown;
46
+ }>;
47
+ request_example: {
48
+ method: string;
49
+ url: string;
50
+ headers: Record<string, string>;
51
+ body?: unknown;
52
+ };
53
+ response_example: {
54
+ status: number;
55
+ body: unknown;
56
+ };
57
+ auth_scopes?: string[];
58
+ rate_limits?: {
59
+ requests_per_minute?: number;
60
+ daily_limit?: number;
61
+ };
62
+ }
63
+
64
+ // ─── Identity & auth ─────────────────────────────────────────────
65
+
66
+ export type IdentityProvider = "google" | "github" | "microsoft";
67
+
68
+ export interface UserIdentity {
69
+ provider: IdentityProvider;
70
+ provider_id: string;
71
+ email: string;
72
+ name?: string;
73
+ avatar_url?: string;
74
+ registry_token: string; // JWT from registry after OAuth
75
+ registry_refresh_token?: string;
76
+ connected_at: string;
77
+ }
78
+
79
+ export interface StoredToken {
80
+ domain: string;
81
+ type: "oauth2" | "api_key";
82
+ access_token: string;
83
+ refresh_token?: string;
84
+ expires_at?: number;
85
+ scopes?: string[];
86
+ }
87
+
88
+ export interface Connection {
89
+ domain: string;
90
+ service_name: string;
91
+ auth_type: string;
92
+ token?: StoredToken;
93
+ subscription?: {
94
+ plan: string;
95
+ status: "active" | "cancelled";
96
+ };
97
+ connected_at: string;
98
+ }
99
+
100
+ // ─── Cloud sync ──────────────────────────────────────────────────
101
+
102
+ export interface CloudSyncState {
103
+ last_synced_at?: string;
104
+ sync_token?: string; // ETag or version for incremental sync
105
+ }
106
+
107
+ export interface CloudTokenBundle {
108
+ tokens: Record<string, StoredToken>;
109
+ connections: Record<string, Connection>;
110
+ synced_at: string;
111
+ }
112
+
113
+ // ─── Cache ───────────────────────────────────────────────────────
114
+
115
+ export interface CacheEntry<T> {
116
+ data: T;
117
+ cached_at: number;
118
+ ttl: number; // milliseconds
119
+ }
120
+
121
+ export const CACHE_TTLS = {
122
+ manifest: 24 * 60 * 60 * 1000, // 24 hours
123
+ capability: 60 * 60 * 1000, // 1 hour
124
+ discovery: 15 * 60 * 1000, // 15 minutes
125
+ } as const;
126
+
127
+ // ─── Config ──────────────────────────────────────────────────────
128
+
129
+ export interface GatewayConfig {
130
+ registry_url: string;
131
+ auth_callback_port: number;
132
+ identity?: UserIdentity;
133
+ cloud_sync?: CloudSyncState;
134
+ }
135
+
136
+ // ─── Registry API types ──────────────────────────────────────────
137
+
138
+ export interface DiscoverResult {
139
+ service: {
140
+ name: string;
141
+ domain: string;
142
+ description: string;
143
+ base_url: string;
144
+ auth_type: string;
145
+ pricing_type: string;
146
+ verified: boolean;
147
+ };
148
+ matching_capabilities: Array<{
149
+ name: string;
150
+ description: string;
151
+ detail_url: string;
152
+ }>;
153
+ all_capabilities: Array<{
154
+ name: string;
155
+ description: string;
156
+ detail_url: string;
157
+ }>;
158
+ }
159
+
160
+ export interface RegistryDiscoverResponse {
161
+ success: boolean;
162
+ query: string;
163
+ result_count: number;
164
+ data: DiscoverResult[];
165
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }