@relayrail/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/README.md ADDED
@@ -0,0 +1,74 @@
1
+ # @relayrail/server
2
+
3
+ RelayRail MCP Server - SMS and email connectivity for AI agents via Model Context Protocol.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ # Use with npx (recommended)
9
+ npx @relayrail/server start
10
+
11
+ # Or install globally
12
+ npm install -g @relayrail/server
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ### 1. Get your API key
18
+
19
+ Sign up at [relayrail.dev](https://relayrail.dev) and create an agent to get your API key.
20
+
21
+ ### 2. Configure your MCP client
22
+
23
+ **Claude Code (CLI):**
24
+
25
+ ```bash
26
+ claude mcp add --transport stdio relayrail \
27
+ --env RELAYRAIL_API_KEY=rr_your_api_key \
28
+ -- npx @relayrail/server start
29
+ ```
30
+
31
+ **Claude Desktop:**
32
+
33
+ Add to `~/.config/claude/claude_desktop_config.json`:
34
+
35
+ ```json
36
+ {
37
+ "mcpServers": {
38
+ "relayrail": {
39
+ "command": "npx",
40
+ "args": ["@relayrail/server", "start"],
41
+ "env": {
42
+ "RELAYRAIL_API_KEY": "rr_your_api_key"
43
+ }
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ## Available Tools
50
+
51
+ | Tool | Description |
52
+ |------|-------------|
53
+ | `request_approval` | Request explicit user approval before proceeding |
54
+ | `send_notification` | Send a one-way notification to the user |
55
+ | `await_response` | Wait for a user response to an approval request |
56
+ | `get_pending_commands` | Retrieve commands sent by the user via SMS/email |
57
+ | `register_agent` | Self-register a new agent (if no API key configured) |
58
+ | `get_account_status` | Check quota usage and account status |
59
+
60
+ ## Environment Variables
61
+
62
+ | Variable | Required | Description |
63
+ |----------|----------|-------------|
64
+ | `RELAYRAIL_API_KEY` | Yes | Your agent's API key from relayrail.dev |
65
+ | `RELAYRAIL_API_URL` | No | Override API endpoint (default: https://www.relayrail.dev/api/mcp) |
66
+ | `RELAYRAIL_DEBUG` | No | Enable debug logging |
67
+
68
+ ## Documentation
69
+
70
+ Full documentation available at [relayrail.dev/docs](https://relayrail.dev/docs)
71
+
72
+ ## License
73
+
74
+ MIT
@@ -0,0 +1,507 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { Agent, User, TypedSupabaseClient } from '@relayrail/database';
3
+ import { Tier } from '@relayrail/shared';
4
+
5
+ /**
6
+ * RelayRail MCP Server Types
7
+ */
8
+
9
+ /**
10
+ * Server configuration options
11
+ */
12
+ interface ServerConfig {
13
+ /** Supabase URL */
14
+ supabaseUrl: string;
15
+ /** Supabase service role key (bypasses RLS) */
16
+ supabaseServiceRoleKey: string;
17
+ /** Resend API key for email */
18
+ resendApiKey?: string;
19
+ /** Telnyx API key for SMS */
20
+ telnyxApiKey?: string;
21
+ /** Telnyx phone number for sending SMS */
22
+ telnyxPhoneNumber?: string;
23
+ /** Base URL for response pages */
24
+ baseUrl: string;
25
+ }
26
+ /**
27
+ * Context passed to tool handlers
28
+ */
29
+ interface ToolContext {
30
+ /** The authenticated agent making the request */
31
+ agent: Agent;
32
+ /** The user who owns this agent */
33
+ user: User;
34
+ }
35
+ /**
36
+ * Auth error codes for specific failure reasons
37
+ */
38
+ type AuthErrorCode = 'INVALID_FORMAT' | 'KEY_TRUNCATED' | 'KEY_NOT_FOUND' | 'KEY_INACTIVE' | 'HASH_MISMATCH' | 'USER_NOT_FOUND';
39
+ /**
40
+ * Detailed auth error with code, message, and hint
41
+ */
42
+ interface AuthError {
43
+ /** Error code for programmatic handling */
44
+ code: AuthErrorCode;
45
+ /** Human-readable error message */
46
+ message: string;
47
+ /** Actionable hint for the user */
48
+ hint: string;
49
+ }
50
+ /**
51
+ * Result of API key authentication
52
+ */
53
+ interface AuthResult {
54
+ success: boolean;
55
+ agent?: Agent;
56
+ user?: User;
57
+ /** Simple error string for backwards compatibility */
58
+ error?: string;
59
+ /** Detailed error information */
60
+ authError?: AuthError;
61
+ }
62
+ /**
63
+ * User context included in tool responses
64
+ */
65
+ interface UserContext {
66
+ /** User's email address */
67
+ email: string;
68
+ /** User's subscription tier */
69
+ tier: 'free' | 'pro' | 'team';
70
+ /** Preferred notification channel */
71
+ preferred_channel: 'email' | 'sms';
72
+ }
73
+ /**
74
+ * Quota information included in tool responses
75
+ */
76
+ interface QuotaInfo$1 {
77
+ /** Number of messages used this period */
78
+ used: number;
79
+ /** Included limit for the tier */
80
+ limit: number;
81
+ /** Remaining in included quota */
82
+ remaining: number;
83
+ /** Whether this message was an overage */
84
+ overage?: boolean;
85
+ /** Total overage messages this period */
86
+ overage_count?: number;
87
+ /** Overage rate in dollars */
88
+ overage_rate?: number;
89
+ /** Human-readable message */
90
+ message?: string;
91
+ }
92
+ /**
93
+ * Quota error when limits are exceeded and overage is disabled
94
+ */
95
+ interface QuotaError$1 {
96
+ /** Always true when this object is present */
97
+ exceeded: true;
98
+ /** Current usage count */
99
+ current: number;
100
+ /** Included limit */
101
+ limit: number;
102
+ /** User's tier */
103
+ tier: 'free' | 'pro' | 'team';
104
+ /** Whether overage is disabled by user */
105
+ overage_disabled?: boolean;
106
+ /** Human-readable error message */
107
+ message: string;
108
+ /** URL to enable overage charges */
109
+ enable_overage_url?: string;
110
+ /** URL to upgrade tier */
111
+ upgradeUrl: string;
112
+ }
113
+ /**
114
+ * Approval request parameters
115
+ */
116
+ interface ApprovalRequestParams {
117
+ /** Message to send to the user */
118
+ message: string;
119
+ /** Optional list of response options */
120
+ options?: string[];
121
+ /** Additional context data */
122
+ context?: Record<string, unknown>;
123
+ /** Timeout in minutes (default: 60, max: 1440) */
124
+ timeout_minutes?: number;
125
+ /** Severity level */
126
+ severity?: 'info' | 'warning' | 'critical';
127
+ }
128
+ /**
129
+ * Notification parameters
130
+ */
131
+ interface NotificationParams {
132
+ /** Message to send to the user */
133
+ message: string;
134
+ /** Additional context data */
135
+ context?: Record<string, unknown>;
136
+ /** Severity level */
137
+ severity?: 'info' | 'warning' | 'critical';
138
+ }
139
+ /**
140
+ * Await response parameters
141
+ */
142
+ interface AwaitResponseParams {
143
+ /** Request ID to wait for */
144
+ request_id: string;
145
+ /** Timeout in seconds to wait for response */
146
+ timeout_seconds?: number;
147
+ }
148
+ /**
149
+ * Get pending commands parameters
150
+ */
151
+ interface GetPendingCommandsParams {
152
+ /** Maximum number of commands to return */
153
+ limit?: number;
154
+ }
155
+ /**
156
+ * Tool response for request_approval
157
+ */
158
+ interface ApprovalResponse {
159
+ /** Request ID for tracking */
160
+ request_id: string;
161
+ /** Current status */
162
+ status: 'pending' | 'approved' | 'rejected' | 'expired' | 'blocked';
163
+ /** User's response if available */
164
+ response?: {
165
+ choice?: string;
166
+ message?: string;
167
+ };
168
+ /** When the request expires */
169
+ expires_at: string;
170
+ /** User context for the request */
171
+ user?: UserContext;
172
+ /** Quota information */
173
+ quota?: QuotaInfo$1;
174
+ /** Quota error if blocked */
175
+ quota_error?: QuotaError$1;
176
+ }
177
+ /**
178
+ * Tool response for send_notification
179
+ */
180
+ interface NotificationResponse {
181
+ /** Request ID for tracking */
182
+ request_id: string;
183
+ /** Delivery status */
184
+ status: 'pending' | 'delivered' | 'failed' | 'blocked';
185
+ /** Channel used */
186
+ channel: 'email' | 'sms';
187
+ /** User context for the request */
188
+ user?: UserContext;
189
+ /** Quota information */
190
+ quota?: QuotaInfo$1;
191
+ /** Quota error if blocked */
192
+ quota_error?: QuotaError$1;
193
+ }
194
+ /**
195
+ * Tool response for await_response
196
+ */
197
+ interface AwaitResult {
198
+ /** Request ID */
199
+ request_id: string;
200
+ /** Current status */
201
+ status: 'pending' | 'approved' | 'rejected' | 'expired';
202
+ /** User's response if available */
203
+ response?: {
204
+ choice?: string;
205
+ message?: string;
206
+ };
207
+ /** Whether the wait timed out */
208
+ timed_out: boolean;
209
+ }
210
+ /**
211
+ * Command from user
212
+ */
213
+ interface UserCommand {
214
+ /** Request ID */
215
+ request_id: string;
216
+ /** Command message from user */
217
+ message: string;
218
+ /** When the command was received */
219
+ received_at: string;
220
+ /** Channel the command came from */
221
+ channel: 'email' | 'sms';
222
+ }
223
+
224
+ /**
225
+ * RelayRail MCP Server
226
+ * Core server implementation using Model Context Protocol SDK
227
+ */
228
+
229
+ declare const VERSION = "0.1.0";
230
+ /**
231
+ * RelayRail MCP Server
232
+ */
233
+ declare class RelayRailServer {
234
+ private server;
235
+ private supabase;
236
+ private config;
237
+ private context;
238
+ private router;
239
+ constructor(config: ServerConfig);
240
+ /**
241
+ * Register all MCP tools
242
+ */
243
+ private registerTools;
244
+ /**
245
+ * Authenticate with an API key and set the context
246
+ */
247
+ authenticate(apiKey: string): Promise<boolean>;
248
+ /**
249
+ * Get the current authentication context
250
+ */
251
+ getContext(): ToolContext | null;
252
+ /**
253
+ * Get the underlying MCP server instance
254
+ */
255
+ getMcpServer(): McpServer;
256
+ /**
257
+ * Get the Supabase client
258
+ */
259
+ getSupabase(): TypedSupabaseClient;
260
+ /**
261
+ * Get the server configuration
262
+ */
263
+ getConfig(): ServerConfig;
264
+ /**
265
+ * Start the server with stdio transport
266
+ */
267
+ start(): Promise<void>;
268
+ }
269
+ /**
270
+ * Create a new RelayRail server instance
271
+ */
272
+ declare function createServer(config: ServerConfig): RelayRailServer;
273
+
274
+ /**
275
+ * RelayRail MCP Server Authentication
276
+ * API key validation and agent lookup
277
+ */
278
+
279
+ /**
280
+ * Hash an API key for storage/comparison
281
+ */
282
+ declare function hashApiKey(apiKey: string): string;
283
+ /**
284
+ * Extract the prefix from an API key for lookup
285
+ */
286
+ declare function getApiKeyPrefix(apiKey: string): string;
287
+ /**
288
+ * Validate an API key and return the associated agent and user
289
+ */
290
+ declare function authenticateApiKey(supabase: TypedSupabaseClient, apiKey: string): Promise<AuthResult>;
291
+ /**
292
+ * Generate a new API key for an agent
293
+ */
294
+ declare function generateApiKey(): {
295
+ key: string;
296
+ prefix: string;
297
+ hash: string;
298
+ };
299
+
300
+ /**
301
+ * Email service using Resend
302
+ * Handles sending approval requests and notifications via email
303
+ */
304
+
305
+ interface EmailConfig {
306
+ resendApiKey: string;
307
+ fromEmail: string;
308
+ fromName: string;
309
+ baseUrl: string;
310
+ /** Inbound domain for email replies (e.g., in.relayrail.dev) */
311
+ inboundDomain?: string;
312
+ }
313
+
314
+ /**
315
+ * SMS service using Telnyx
316
+ * Handles sending approval requests and notifications via SMS
317
+ */
318
+
319
+ interface SmsConfig {
320
+ apiKey: string;
321
+ fromNumber: string;
322
+ baseUrl: string;
323
+ }
324
+
325
+ /**
326
+ * Request Router
327
+ * Routes requests to appropriate channels (email/SMS) based on user preferences
328
+ * Includes quota enforcement and overage billing
329
+ */
330
+
331
+ interface RouterConfig {
332
+ email: EmailConfig;
333
+ sms?: SmsConfig;
334
+ baseUrl: string;
335
+ }
336
+ interface RouteApprovalParams {
337
+ requestId: string;
338
+ responseToken: string;
339
+ message: string;
340
+ options?: string[];
341
+ expiresAt: string;
342
+ severity?: 'info' | 'warning' | 'critical';
343
+ }
344
+ interface RouteNotificationParams {
345
+ requestId: string;
346
+ message: string;
347
+ severity?: 'info' | 'warning' | 'critical';
348
+ }
349
+ interface QuotaInfo {
350
+ used: number;
351
+ limit: number;
352
+ remaining: number;
353
+ overage?: boolean;
354
+ overage_count?: number;
355
+ overage_rate?: number;
356
+ message?: string;
357
+ }
358
+ interface QuotaError {
359
+ exceeded: true;
360
+ current: number;
361
+ limit: number;
362
+ tier: Tier;
363
+ overage_disabled?: boolean;
364
+ message: string;
365
+ enable_overage_url?: string;
366
+ upgradeUrl: string;
367
+ }
368
+ interface RouteResult {
369
+ success: boolean;
370
+ channel: 'email' | 'sms';
371
+ /** Channel the user requested (may differ from channel used) */
372
+ channel_requested?: 'email' | 'sms';
373
+ /** Reason for fallback if channel differs from requested */
374
+ fallback_reason?: string;
375
+ messageId?: string;
376
+ error?: string;
377
+ /** Quota information if send was successful */
378
+ quota?: QuotaInfo;
379
+ /** Quota error if send was blocked */
380
+ quotaError?: QuotaError;
381
+ }
382
+ /**
383
+ * Request router that sends requests via the appropriate channel
384
+ * with quota enforcement and overage billing
385
+ */
386
+ declare class RequestRouter {
387
+ private emailService;
388
+ private smsService?;
389
+ private usageService;
390
+ private smsEnabled;
391
+ private baseUrl;
392
+ constructor(config: RouterConfig, supabase: TypedSupabaseClient);
393
+ /**
394
+ * Route an approval request to the user
395
+ */
396
+ routeApproval(params: RouteApprovalParams, user: User, agent: Agent): Promise<RouteResult>;
397
+ /**
398
+ * Route a notification to the user
399
+ */
400
+ routeNotification(params: RouteNotificationParams, user: User, agent: Agent): Promise<RouteResult>;
401
+ /**
402
+ * Select the best available channel with fallback logic
403
+ * Returns the channel to use and a reason if fallback occurred
404
+ */
405
+ private selectChannel;
406
+ /**
407
+ * Create quota info object for successful sends
408
+ */
409
+ private createQuotaInfo;
410
+ /**
411
+ * Create quota error result when send is blocked
412
+ */
413
+ private createQuotaErrorResult;
414
+ }
415
+
416
+ /**
417
+ * HTTP Transport for MCP Server
418
+ * Handles MCP protocol messages over HTTP POST
419
+ */
420
+
421
+ interface MCPRequest {
422
+ jsonrpc: '2.0';
423
+ id: string | number;
424
+ method: string;
425
+ params?: Record<string, unknown>;
426
+ }
427
+ interface MCPResponse {
428
+ jsonrpc: '2.0';
429
+ id: string | number;
430
+ result?: unknown;
431
+ error?: {
432
+ code: number;
433
+ message: string;
434
+ data?: unknown;
435
+ };
436
+ }
437
+ interface MCPNotification {
438
+ jsonrpc: '2.0';
439
+ method: string;
440
+ params?: Record<string, unknown>;
441
+ }
442
+ declare const MCP_ERRORS: {
443
+ PARSE_ERROR: number;
444
+ INVALID_REQUEST: number;
445
+ METHOD_NOT_FOUND: number;
446
+ INVALID_PARAMS: number;
447
+ INTERNAL_ERROR: number;
448
+ NOT_AUTHENTICATED: number;
449
+ RATE_LIMITED: number;
450
+ };
451
+ interface HTTPTransportConfig {
452
+ supabase: TypedSupabaseClient;
453
+ serverConfig: ServerConfig;
454
+ router?: RequestRouter;
455
+ }
456
+ /**
457
+ * HTTP Transport handler for MCP protocol
458
+ */
459
+ declare class HTTPTransport {
460
+ private supabase;
461
+ private config;
462
+ private router;
463
+ private sessions;
464
+ private sessionTTL;
465
+ constructor(transportConfig: HTTPTransportConfig);
466
+ /**
467
+ * Handle an incoming HTTP request
468
+ */
469
+ handleRequest(body: MCPRequest | MCPRequest[], apiKey?: string, sessionId?: string): Promise<{
470
+ response: MCPResponse | MCPResponse[];
471
+ newSessionId?: string;
472
+ }>;
473
+ /**
474
+ * Handle a single MCP request
475
+ */
476
+ private handleSingleRequest;
477
+ /**
478
+ * Handle initialize request
479
+ */
480
+ private handleInitialize;
481
+ /**
482
+ * Handle tools/list request
483
+ */
484
+ private handleListTools;
485
+ /**
486
+ * Handle tools/call request
487
+ */
488
+ private handleToolCall;
489
+ /**
490
+ * Create a success response
491
+ */
492
+ private successResponse;
493
+ /**
494
+ * Create an error response
495
+ */
496
+ private errorResponse;
497
+ /**
498
+ * Clean up expired sessions
499
+ */
500
+ private cleanupSessions;
501
+ }
502
+ /**
503
+ * Create an HTTP transport instance
504
+ */
505
+ declare function createHTTPTransport(config: HTTPTransportConfig): HTTPTransport;
506
+
507
+ export { type ApprovalRequestParams, type ApprovalResponse, type AuthResult, type AwaitResponseParams, type AwaitResult, type GetPendingCommandsParams, HTTPTransport, type HTTPTransportConfig, type MCPNotification, type MCPRequest, type MCPResponse, MCP_ERRORS, type NotificationParams, type NotificationResponse, RelayRailServer, type ServerConfig, type ToolContext, type UserCommand, VERSION, authenticateApiKey, createHTTPTransport, createServer, generateApiKey, getApiKeyPrefix, hashApiKey };