@relayrail/server 0.1.5 → 0.1.7

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 CHANGED
@@ -6,7 +6,7 @@ RelayRail MCP Server - SMS and email connectivity for AI agents via Model Contex
6
6
 
7
7
  ```bash
8
8
  # Use with npx (recommended)
9
- npx @relayrail/server start
9
+ npx @relayrail/server
10
10
 
11
11
  # Or install globally
12
12
  npm install -g @relayrail/server
@@ -23,21 +23,21 @@ Sign up at [relayrail.dev](https://relayrail.dev) and create an agent to get you
23
23
  **Claude Code (CLI):**
24
24
 
25
25
  ```bash
26
- claude mcp add --transport stdio relayrail \
27
- --env RELAYRAIL_API_KEY=rr_your_api_key \
28
- -- npx @relayrail/server start
26
+ claude mcp add relayrail \
27
+ -e RELAYRAIL_API_KEY=rr_your_api_key \
28
+ -- npx @relayrail/server
29
29
  ```
30
30
 
31
31
  **Claude Desktop:**
32
32
 
33
- Add to `~/.config/claude/claude_desktop_config.json`:
33
+ Add to your config file (`~/.config/claude/claude_desktop_config.json` on macOS/Linux or `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
34
34
 
35
35
  ```json
36
36
  {
37
37
  "mcpServers": {
38
38
  "relayrail": {
39
39
  "command": "npx",
40
- "args": ["@relayrail/server", "start"],
40
+ "args": ["@relayrail/server"],
41
41
  "env": {
42
42
  "RELAYRAIL_API_KEY": "rr_your_api_key"
43
43
  }
@@ -46,24 +46,57 @@ Add to `~/.config/claude/claude_desktop_config.json`:
46
46
  }
47
47
  ```
48
48
 
49
+ **Cursor / Cline:**
50
+
51
+ Same JSON format as above in your MCP settings.
52
+
49
53
  ## Available Tools
50
54
 
51
55
  | Tool | Description |
52
56
  |------|-------------|
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 |
57
+ | `request_approval` | Request user approval with custom options (Yes/No or custom choices) |
58
+ | `send_notification` | Send a one-way notification to the user (no response expected) |
59
+ | `await_response` | Wait for a user response to a previous approval request |
56
60
  | `get_pending_commands` | Retrieve commands sent by the user via SMS/email |
57
61
  | `register_agent` | Self-register a new agent (if no API key configured) |
58
62
  | `get_account_status` | Check quota usage and account status |
59
63
 
64
+ ### Tool Parameters
65
+
66
+ **`request_approval`**
67
+ - `message` (required): The approval request message
68
+ - `options`: Custom response options (default: `["Yes", "No"]`)
69
+ - `severity`: `"info"` | `"warning"` | `"critical"` - affects notification priority
70
+ - `timeout_minutes`: How long to wait for response (default: 60, max: 1440)
71
+ - `context`: Additional context data (object)
72
+
73
+ **`send_notification`**
74
+ - `message` (required): The notification message
75
+ - `severity`: `"info"` | `"warning"` | `"critical"`
76
+ - `context`: Additional context data (object)
77
+
78
+ **`await_response`**
79
+ - `request_id` (required): The request_id from a previous `request_approval` call
80
+ - `timeout_seconds`: How long to wait (default: 30, max: 300)
81
+
60
82
  ## Environment Variables
61
83
 
62
84
  | Variable | Required | Description |
63
85
  |----------|----------|-------------|
64
86
  | `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 |
87
+ | `RELAYRAIL_BASE_URL` | No | Override API endpoint (default: https://www.relayrail.dev) |
88
+ | `RELAYRAIL_DEBUG` | No | Enable debug logging (set to `true`) |
89
+
90
+ > **Note:** You only need `RELAYRAIL_API_KEY`. The server automatically routes messages through the RelayRail API - no local Telnyx or Resend credentials required.
91
+
92
+ ## How It Works
93
+
94
+ 1. Your agent calls a tool like `request_approval`
95
+ 2. RelayRail sends an SMS or email to the user (based on their preferences)
96
+ 3. The user responds via the link or by replying directly
97
+ 4. Your agent retrieves the response with `await_response`
98
+
99
+ All message delivery is handled by the RelayRail service - you don't need to configure any SMS or email providers.
67
100
 
68
101
  ## Documentation
69
102
 
package/dist/cli.js CHANGED
@@ -147,7 +147,7 @@ function getApiKeyPrefix(apiKey) {
147
147
  if (!apiKey.startsWith(API_KEY_PREFIX)) {
148
148
  return "";
149
149
  }
150
- return apiKey.substring(0, API_KEY_PREFIX.length + 8);
150
+ return apiKey.substring(0, API_KEY_PREFIX.length + 7);
151
151
  }
152
152
  async function authenticateApiKey(supabase, apiKey) {
153
153
  if (!apiKey || !apiKey.startsWith(API_KEY_PREFIX)) {
@@ -1576,16 +1576,22 @@ function createProxyRouter(config) {
1576
1576
  var VERSION = "0.1.0";
1577
1577
  var RelayRailServer = class {
1578
1578
  server;
1579
- supabase;
1579
+ supabase = null;
1580
1580
  config;
1581
1581
  context = null;
1582
1582
  router = null;
1583
1583
  constructor(config) {
1584
1584
  this.config = config;
1585
- this.supabase = createServiceClient(config.supabaseUrl, config.supabaseServiceRoleKey);
1585
+ const hasSupabaseCredentials = config.supabaseServiceRoleKey && config.supabaseServiceRoleKey.length > 0;
1586
+ if (hasSupabaseCredentials) {
1587
+ this.supabase = createServiceClient(config.supabaseUrl, config.supabaseServiceRoleKey);
1588
+ }
1586
1589
  const hasEmail = !!config.resendApiKey;
1587
1590
  const hasSms = !!(config.telnyxApiKey && config.telnyxPhoneNumber);
1588
1591
  if (hasEmail || hasSms) {
1592
+ if (!this.supabase) {
1593
+ throw new Error("Supabase credentials required when using local email/SMS configuration");
1594
+ }
1589
1595
  this.router = createRouter(
1590
1596
  {
1591
1597
  baseUrl: config.baseUrl,
@@ -1605,7 +1611,6 @@ var RelayRailServer = class {
1605
1611
  this.supabase
1606
1612
  );
1607
1613
  } else if (config.apiKey) {
1608
- console.log("[RelayRail] Using proxy router for message delivery");
1609
1614
  this.router = createProxyRouter({
1610
1615
  apiUrl: config.baseUrl,
1611
1616
  apiKey: config.apiKey
@@ -1617,6 +1622,26 @@ var RelayRailServer = class {
1617
1622
  });
1618
1623
  this.registerTools();
1619
1624
  }
1625
+ /**
1626
+ * Check if we're in proxy mode (no direct Supabase access)
1627
+ */
1628
+ isProxyMode() {
1629
+ return !this.supabase && !!this.config.apiKey;
1630
+ }
1631
+ /**
1632
+ * Make a proxy API call
1633
+ */
1634
+ async proxyApiCall(endpoint, body) {
1635
+ const response = await fetch(`${this.config.baseUrl}/api/mcp/${endpoint}`, {
1636
+ method: "POST",
1637
+ headers: {
1638
+ "Content-Type": "application/json",
1639
+ "Authorization": `Bearer ${this.config.apiKey}`
1640
+ },
1641
+ body: JSON.stringify(body)
1642
+ });
1643
+ return response.json();
1644
+ }
1620
1645
  /**
1621
1646
  * Register all MCP tools
1622
1647
  */
@@ -1638,6 +1663,12 @@ var RelayRailServer = class {
1638
1663
  };
1639
1664
  }
1640
1665
  try {
1666
+ if (this.isProxyMode()) {
1667
+ const result2 = await this.proxyApiCall("request-approval", params);
1668
+ return {
1669
+ content: [{ type: "text", text: JSON.stringify(result2) }]
1670
+ };
1671
+ }
1641
1672
  const result = await requestApproval(
1642
1673
  params,
1643
1674
  {
@@ -1681,6 +1712,12 @@ var RelayRailServer = class {
1681
1712
  };
1682
1713
  }
1683
1714
  try {
1715
+ if (this.isProxyMode()) {
1716
+ const result2 = await this.proxyApiCall("send-notification", params);
1717
+ return {
1718
+ content: [{ type: "text", text: JSON.stringify(result2) }]
1719
+ };
1720
+ }
1684
1721
  const result = await sendNotification(
1685
1722
  params,
1686
1723
  {
@@ -1722,6 +1759,12 @@ var RelayRailServer = class {
1722
1759
  };
1723
1760
  }
1724
1761
  try {
1762
+ if (this.isProxyMode()) {
1763
+ const result2 = await this.proxyApiCall("await-response", params);
1764
+ return {
1765
+ content: [{ type: "text", text: JSON.stringify(result2) }]
1766
+ };
1767
+ }
1725
1768
  const result = await awaitResponse(
1726
1769
  params,
1727
1770
  {
@@ -1875,6 +1918,12 @@ var RelayRailServer = class {
1875
1918
  getContext() {
1876
1919
  return this.context;
1877
1920
  }
1921
+ /**
1922
+ * Set authentication context directly (from pre-validated API key)
1923
+ */
1924
+ setAuthContext(context) {
1925
+ this.context = context;
1926
+ }
1878
1927
  /**
1879
1928
  * Get the underlying MCP server instance
1880
1929
  */
@@ -1882,7 +1931,7 @@ var RelayRailServer = class {
1882
1931
  return this.server;
1883
1932
  }
1884
1933
  /**
1885
- * Get the Supabase client
1934
+ * Get the Supabase client (may be null in proxy mode)
1886
1935
  */
1887
1936
  getSupabase() {
1888
1937
  return this.supabase;
@@ -1906,6 +1955,23 @@ function createServer(config) {
1906
1955
  }
1907
1956
 
1908
1957
  // src/cli.ts
1958
+ async function validateApiKey(apiKey, baseUrl) {
1959
+ try {
1960
+ const response = await fetch(`${baseUrl}/api/auth/validate`, {
1961
+ method: "POST",
1962
+ headers: { "Content-Type": "application/json" },
1963
+ body: JSON.stringify({ api_key: apiKey })
1964
+ });
1965
+ const data = await response.json();
1966
+ return data;
1967
+ } catch (error) {
1968
+ return {
1969
+ valid: false,
1970
+ error: `Failed to connect to RelayRail API: ${error instanceof Error ? error.message : "Unknown error"}`,
1971
+ hint: "Check your internet connection and try again."
1972
+ };
1973
+ }
1974
+ }
1909
1975
  async function main() {
1910
1976
  const apiKey = process.env.RELAYRAIL_API_KEY;
1911
1977
  if (!apiKey) {
@@ -1927,25 +1993,69 @@ async function main() {
1927
1993
  }, null, 2));
1928
1994
  process.exit(1);
1929
1995
  }
1996
+ const baseUrl = process.env.RELAYRAIL_BASE_URL || "https://www.relayrail.dev";
1997
+ const validation = await validateApiKey(apiKey, baseUrl);
1998
+ if (!validation.valid) {
1999
+ console.error(`Error: ${validation.error || "Invalid API key"}`);
2000
+ if (validation.hint) {
2001
+ console.error(`Hint: ${validation.hint}`);
2002
+ }
2003
+ console.error("");
2004
+ console.error("Get a new API key from https://relayrail.dev/dashboard/agents");
2005
+ process.exit(1);
2006
+ }
1930
2007
  const config = {
1931
2008
  supabaseUrl: process.env.SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL || "https://lcmdokppykqmigqcwnol.supabase.co",
1932
- supabaseServiceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY || "sb_secret_cj-1Y1KxjlapuwyXvfSKLA_xBJ0oS8i",
1933
- baseUrl: process.env.RELAYRAIL_BASE_URL || "https://www.relayrail.dev",
2009
+ supabaseServiceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY || "",
2010
+ baseUrl,
1934
2011
  resendApiKey: process.env.RESEND_API_KEY,
1935
2012
  telnyxApiKey: process.env.TELNYX_API_KEY,
1936
2013
  telnyxPhoneNumber: process.env.TELNYX_PHONE_NUMBER,
1937
- apiKey
2014
+ apiKey,
1938
2015
  // Used for proxy routing when local email/SMS credentials unavailable
2016
+ // Pre-validated context from hosted endpoint
2017
+ validatedAgent: validation.agent,
2018
+ validatedUser: validation.user
1939
2019
  };
1940
2020
  try {
1941
2021
  const server = createServer(config);
1942
- const authenticated = await server.authenticate(apiKey);
1943
- if (!authenticated) {
1944
- console.error("Error: Invalid API key");
1945
- console.error("");
1946
- console.error("Please check your RELAYRAIL_API_KEY and try again.");
1947
- console.error("Get a new API key from https://relayrail.dev/dashboard/agents");
1948
- process.exit(1);
2022
+ if (validation.agent && validation.user) {
2023
+ const agent = {
2024
+ id: validation.agent.id,
2025
+ name: validation.agent.name,
2026
+ user_id: validation.agent.user_id,
2027
+ is_active: validation.agent.is_active,
2028
+ created_at: validation.agent.created_at,
2029
+ // These fields are not needed for tool operations but required by type
2030
+ api_key_hash: "",
2031
+ api_key_prefix: "",
2032
+ last_seen_at: null,
2033
+ metadata: {}
2034
+ };
2035
+ const user = {
2036
+ id: validation.user.id,
2037
+ email: validation.user.email,
2038
+ phone: validation.user.phone,
2039
+ tier: validation.user.tier,
2040
+ notification_preferences: validation.user.notification_preferences,
2041
+ allow_email_overage: validation.user.allow_email_overage,
2042
+ allow_sms_overage: validation.user.allow_sms_overage,
2043
+ // These fields are not needed for tool operations but required by type
2044
+ stripe_customer_id: null,
2045
+ email_verified: true,
2046
+ created_at: (/* @__PURE__ */ new Date()).toISOString(),
2047
+ updated_at: (/* @__PURE__ */ new Date()).toISOString()
2048
+ };
2049
+ server.setAuthContext({ agent, user });
2050
+ } else {
2051
+ const authenticated = await server.authenticate(apiKey);
2052
+ if (!authenticated) {
2053
+ console.error("Error: Invalid API key");
2054
+ console.error("");
2055
+ console.error("Please check your RELAYRAIL_API_KEY and try again.");
2056
+ console.error("Get a new API key from https://relayrail.dev/dashboard/agents");
2057
+ process.exit(1);
2058
+ }
1949
2059
  }
1950
2060
  await server.start();
1951
2061
  } catch (error) {
package/dist/index.d.ts CHANGED
@@ -239,6 +239,14 @@ declare class RelayRailServer {
239
239
  private context;
240
240
  private router;
241
241
  constructor(config: ServerConfig);
242
+ /**
243
+ * Check if we're in proxy mode (no direct Supabase access)
244
+ */
245
+ private isProxyMode;
246
+ /**
247
+ * Make a proxy API call
248
+ */
249
+ private proxyApiCall;
242
250
  /**
243
251
  * Register all MCP tools
244
252
  */
@@ -251,14 +259,18 @@ declare class RelayRailServer {
251
259
  * Get the current authentication context
252
260
  */
253
261
  getContext(): ToolContext | null;
262
+ /**
263
+ * Set authentication context directly (from pre-validated API key)
264
+ */
265
+ setAuthContext(context: ToolContext): void;
254
266
  /**
255
267
  * Get the underlying MCP server instance
256
268
  */
257
269
  getMcpServer(): McpServer;
258
270
  /**
259
- * Get the Supabase client
271
+ * Get the Supabase client (may be null in proxy mode)
260
272
  */
261
- getSupabase(): TypedSupabaseClient;
273
+ getSupabase(): TypedSupabaseClient | null;
262
274
  /**
263
275
  * Get the server configuration
264
276
  */
package/dist/index.js CHANGED
@@ -145,7 +145,7 @@ function getApiKeyPrefix(apiKey) {
145
145
  if (!apiKey.startsWith(API_KEY_PREFIX)) {
146
146
  return "";
147
147
  }
148
- return apiKey.substring(0, API_KEY_PREFIX.length + 8);
148
+ return apiKey.substring(0, API_KEY_PREFIX.length + 7);
149
149
  }
150
150
  async function authenticateApiKey(supabase, apiKey) {
151
151
  if (!apiKey || !apiKey.startsWith(API_KEY_PREFIX)) {
@@ -1574,16 +1574,22 @@ function createProxyRouter(config) {
1574
1574
  var VERSION = "0.1.0";
1575
1575
  var RelayRailServer = class {
1576
1576
  server;
1577
- supabase;
1577
+ supabase = null;
1578
1578
  config;
1579
1579
  context = null;
1580
1580
  router = null;
1581
1581
  constructor(config) {
1582
1582
  this.config = config;
1583
- this.supabase = createServiceClient(config.supabaseUrl, config.supabaseServiceRoleKey);
1583
+ const hasSupabaseCredentials = config.supabaseServiceRoleKey && config.supabaseServiceRoleKey.length > 0;
1584
+ if (hasSupabaseCredentials) {
1585
+ this.supabase = createServiceClient(config.supabaseUrl, config.supabaseServiceRoleKey);
1586
+ }
1584
1587
  const hasEmail = !!config.resendApiKey;
1585
1588
  const hasSms = !!(config.telnyxApiKey && config.telnyxPhoneNumber);
1586
1589
  if (hasEmail || hasSms) {
1590
+ if (!this.supabase) {
1591
+ throw new Error("Supabase credentials required when using local email/SMS configuration");
1592
+ }
1587
1593
  this.router = createRouter(
1588
1594
  {
1589
1595
  baseUrl: config.baseUrl,
@@ -1603,7 +1609,6 @@ var RelayRailServer = class {
1603
1609
  this.supabase
1604
1610
  );
1605
1611
  } else if (config.apiKey) {
1606
- console.log("[RelayRail] Using proxy router for message delivery");
1607
1612
  this.router = createProxyRouter({
1608
1613
  apiUrl: config.baseUrl,
1609
1614
  apiKey: config.apiKey
@@ -1615,6 +1620,26 @@ var RelayRailServer = class {
1615
1620
  });
1616
1621
  this.registerTools();
1617
1622
  }
1623
+ /**
1624
+ * Check if we're in proxy mode (no direct Supabase access)
1625
+ */
1626
+ isProxyMode() {
1627
+ return !this.supabase && !!this.config.apiKey;
1628
+ }
1629
+ /**
1630
+ * Make a proxy API call
1631
+ */
1632
+ async proxyApiCall(endpoint, body) {
1633
+ const response = await fetch(`${this.config.baseUrl}/api/mcp/${endpoint}`, {
1634
+ method: "POST",
1635
+ headers: {
1636
+ "Content-Type": "application/json",
1637
+ "Authorization": `Bearer ${this.config.apiKey}`
1638
+ },
1639
+ body: JSON.stringify(body)
1640
+ });
1641
+ return response.json();
1642
+ }
1618
1643
  /**
1619
1644
  * Register all MCP tools
1620
1645
  */
@@ -1636,6 +1661,12 @@ var RelayRailServer = class {
1636
1661
  };
1637
1662
  }
1638
1663
  try {
1664
+ if (this.isProxyMode()) {
1665
+ const result2 = await this.proxyApiCall("request-approval", params);
1666
+ return {
1667
+ content: [{ type: "text", text: JSON.stringify(result2) }]
1668
+ };
1669
+ }
1639
1670
  const result = await requestApproval(
1640
1671
  params,
1641
1672
  {
@@ -1679,6 +1710,12 @@ var RelayRailServer = class {
1679
1710
  };
1680
1711
  }
1681
1712
  try {
1713
+ if (this.isProxyMode()) {
1714
+ const result2 = await this.proxyApiCall("send-notification", params);
1715
+ return {
1716
+ content: [{ type: "text", text: JSON.stringify(result2) }]
1717
+ };
1718
+ }
1682
1719
  const result = await sendNotification(
1683
1720
  params,
1684
1721
  {
@@ -1720,6 +1757,12 @@ var RelayRailServer = class {
1720
1757
  };
1721
1758
  }
1722
1759
  try {
1760
+ if (this.isProxyMode()) {
1761
+ const result2 = await this.proxyApiCall("await-response", params);
1762
+ return {
1763
+ content: [{ type: "text", text: JSON.stringify(result2) }]
1764
+ };
1765
+ }
1723
1766
  const result = await awaitResponse(
1724
1767
  params,
1725
1768
  {
@@ -1873,6 +1916,12 @@ var RelayRailServer = class {
1873
1916
  getContext() {
1874
1917
  return this.context;
1875
1918
  }
1919
+ /**
1920
+ * Set authentication context directly (from pre-validated API key)
1921
+ */
1922
+ setAuthContext(context) {
1923
+ this.context = context;
1924
+ }
1876
1925
  /**
1877
1926
  * Get the underlying MCP server instance
1878
1927
  */
@@ -1880,7 +1929,7 @@ var RelayRailServer = class {
1880
1929
  return this.server;
1881
1930
  }
1882
1931
  /**
1883
- * Get the Supabase client
1932
+ * Get the Supabase client (may be null in proxy mode)
1884
1933
  */
1885
1934
  getSupabase() {
1886
1935
  return this.supabase;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@relayrail/server",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "RelayRail MCP Server - SMS/Email connectivity for AI agents",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",