@sashido/mcp-server 0.0.0-stage → 0.2.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.
Files changed (191) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -2
  3. package/dist/access.d.ts +47 -0
  4. package/dist/access.d.ts.map +1 -0
  5. package/dist/access.js +156 -0
  6. package/dist/access.js.map +1 -0
  7. package/dist/advice/format.d.ts +22 -0
  8. package/dist/advice/format.d.ts.map +1 -0
  9. package/dist/advice/format.js +70 -0
  10. package/dist/advice/format.js.map +1 -0
  11. package/dist/advice/schemaChecks.d.ts +17 -0
  12. package/dist/advice/schemaChecks.d.ts.map +1 -0
  13. package/dist/advice/schemaChecks.js +64 -0
  14. package/dist/advice/schemaChecks.js.map +1 -0
  15. package/dist/audit.d.ts +32 -0
  16. package/dist/audit.d.ts.map +1 -0
  17. package/dist/audit.js +41 -0
  18. package/dist/audit.js.map +1 -0
  19. package/dist/auth.d.ts +17 -0
  20. package/dist/auth.d.ts.map +1 -0
  21. package/dist/auth.js +72 -0
  22. package/dist/auth.js.map +1 -0
  23. package/dist/bin/login.d.ts +3 -0
  24. package/dist/bin/login.d.ts.map +1 -0
  25. package/dist/bin/login.js +16 -0
  26. package/dist/bin/login.js.map +1 -0
  27. package/dist/bin/mcp-server.d.ts +3 -0
  28. package/dist/bin/mcp-server.d.ts.map +1 -0
  29. package/dist/bin/mcp-server.js +27 -0
  30. package/dist/bin/mcp-server.js.map +1 -0
  31. package/dist/client/parse.d.ts +219 -0
  32. package/dist/client/parse.d.ts.map +1 -0
  33. package/dist/client/parse.js +414 -0
  34. package/dist/client/parse.js.map +1 -0
  35. package/dist/client/sashido.d.ts +213 -0
  36. package/dist/client/sashido.d.ts.map +1 -0
  37. package/dist/client/sashido.js +289 -0
  38. package/dist/client/sashido.js.map +1 -0
  39. package/dist/config.d.ts +27 -0
  40. package/dist/config.d.ts.map +1 -0
  41. package/dist/config.js +70 -0
  42. package/dist/config.js.map +1 -0
  43. package/dist/context.d.ts +33 -0
  44. package/dist/context.d.ts.map +1 -0
  45. package/dist/context.js +132 -0
  46. package/dist/context.js.map +1 -0
  47. package/dist/index.d.ts +10 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +8 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/server.d.ts +13 -0
  52. package/dist/server.d.ts.map +1 -0
  53. package/dist/server.js +60 -0
  54. package/dist/server.js.map +1 -0
  55. package/dist/session/redis-client.d.ts +23 -0
  56. package/dist/session/redis-client.d.ts.map +1 -0
  57. package/dist/session/redis-client.js +136 -0
  58. package/dist/session/redis-client.js.map +1 -0
  59. package/dist/session/session-manager.d.ts +6 -0
  60. package/dist/session/session-manager.d.ts.map +1 -0
  61. package/dist/session/session-manager.js +68 -0
  62. package/dist/session/session-manager.js.map +1 -0
  63. package/dist/session/sse-pubsub.d.ts +23 -0
  64. package/dist/session/sse-pubsub.d.ts.map +1 -0
  65. package/dist/session/sse-pubsub.js +121 -0
  66. package/dist/session/sse-pubsub.js.map +1 -0
  67. package/dist/session/types.d.ts +43 -0
  68. package/dist/session/types.d.ts.map +1 -0
  69. package/dist/session/types.js +2 -0
  70. package/dist/session/types.js.map +1 -0
  71. package/dist/tools/advisor/index.d.ts +3 -0
  72. package/dist/tools/advisor/index.d.ts.map +1 -0
  73. package/dist/tools/advisor/index.js +149 -0
  74. package/dist/tools/advisor/index.js.map +1 -0
  75. package/dist/tools/apps/index.d.ts +3 -0
  76. package/dist/tools/apps/index.d.ts.map +1 -0
  77. package/dist/tools/apps/index.js +113 -0
  78. package/dist/tools/apps/index.js.map +1 -0
  79. package/dist/tools/audit/index.d.ts +6 -0
  80. package/dist/tools/audit/index.d.ts.map +1 -0
  81. package/dist/tools/audit/index.js +69 -0
  82. package/dist/tools/audit/index.js.map +1 -0
  83. package/dist/tools/builds/index.d.ts +3 -0
  84. package/dist/tools/builds/index.d.ts.map +1 -0
  85. package/dist/tools/builds/index.js +85 -0
  86. package/dist/tools/builds/index.js.map +1 -0
  87. package/dist/tools/classes/index.d.ts +3 -0
  88. package/dist/tools/classes/index.d.ts.map +1 -0
  89. package/dist/tools/classes/index.js +223 -0
  90. package/dist/tools/classes/index.js.map +1 -0
  91. package/dist/tools/cloud-functions/index.d.ts +3 -0
  92. package/dist/tools/cloud-functions/index.d.ts.map +1 -0
  93. package/dist/tools/cloud-functions/index.js +154 -0
  94. package/dist/tools/cloud-functions/index.js.map +1 -0
  95. package/dist/tools/deployments/index.d.ts +3 -0
  96. package/dist/tools/deployments/index.d.ts.map +1 -0
  97. package/dist/tools/deployments/index.js +124 -0
  98. package/dist/tools/deployments/index.js.map +1 -0
  99. package/dist/tools/engines/index.d.ts +3 -0
  100. package/dist/tools/engines/index.d.ts.map +1 -0
  101. package/dist/tools/engines/index.js +68 -0
  102. package/dist/tools/engines/index.js.map +1 -0
  103. package/dist/tools/env/index.d.ts +3 -0
  104. package/dist/tools/env/index.d.ts.map +1 -0
  105. package/dist/tools/env/index.js +116 -0
  106. package/dist/tools/env/index.js.map +1 -0
  107. package/dist/tools/files/index.d.ts +3 -0
  108. package/dist/tools/files/index.d.ts.map +1 -0
  109. package/dist/tools/files/index.js +132 -0
  110. package/dist/tools/files/index.js.map +1 -0
  111. package/dist/tools/health/index.d.ts +3 -0
  112. package/dist/tools/health/index.d.ts.map +1 -0
  113. package/dist/tools/health/index.js +47 -0
  114. package/dist/tools/health/index.js.map +1 -0
  115. package/dist/tools/infrastructure/deployments.d.ts +23 -0
  116. package/dist/tools/infrastructure/deployments.d.ts.map +1 -0
  117. package/dist/tools/infrastructure/deployments.js +76 -0
  118. package/dist/tools/infrastructure/deployments.js.map +1 -0
  119. package/dist/tools/infrastructure/index.d.ts +9 -0
  120. package/dist/tools/infrastructure/index.d.ts.map +1 -0
  121. package/dist/tools/infrastructure/index.js +164 -0
  122. package/dist/tools/infrastructure/index.js.map +1 -0
  123. package/dist/tools/jobs/index.d.ts +3 -0
  124. package/dist/tools/jobs/index.d.ts.map +1 -0
  125. package/dist/tools/jobs/index.js +338 -0
  126. package/dist/tools/jobs/index.js.map +1 -0
  127. package/dist/tools/logs/index.d.ts +3 -0
  128. package/dist/tools/logs/index.d.ts.map +1 -0
  129. package/dist/tools/logs/index.js +133 -0
  130. package/dist/tools/logs/index.js.map +1 -0
  131. package/dist/tools/objects/index.d.ts +3 -0
  132. package/dist/tools/objects/index.d.ts.map +1 -0
  133. package/dist/tools/objects/index.js +185 -0
  134. package/dist/tools/objects/index.js.map +1 -0
  135. package/dist/tools/push/index.d.ts +3 -0
  136. package/dist/tools/push/index.d.ts.map +1 -0
  137. package/dist/tools/push/index.js +434 -0
  138. package/dist/tools/push/index.js.map +1 -0
  139. package/dist/tools/types/index.d.ts +3 -0
  140. package/dist/tools/types/index.d.ts.map +1 -0
  141. package/dist/tools/types/index.js +45 -0
  142. package/dist/tools/types/index.js.map +1 -0
  143. package/dist/tools/webhooks/index.d.ts +3 -0
  144. package/dist/tools/webhooks/index.d.ts.map +1 -0
  145. package/dist/tools/webhooks/index.js +443 -0
  146. package/dist/tools/webhooks/index.js.map +1 -0
  147. package/dist/transports/http.d.ts +4 -0
  148. package/dist/transports/http.d.ts.map +1 -0
  149. package/dist/transports/http.js +241 -0
  150. package/dist/transports/http.js.map +1 -0
  151. package/dist/transports/stdio.d.ts +7 -0
  152. package/dist/transports/stdio.d.ts.map +1 -0
  153. package/dist/transports/stdio.js +29 -0
  154. package/dist/transports/stdio.js.map +1 -0
  155. package/dist/typegen/dart.d.ts +3 -0
  156. package/dist/typegen/dart.d.ts.map +1 -0
  157. package/dist/typegen/dart.js +84 -0
  158. package/dist/typegen/dart.js.map +1 -0
  159. package/dist/typegen/kotlin.d.ts +3 -0
  160. package/dist/typegen/kotlin.d.ts.map +1 -0
  161. package/dist/typegen/kotlin.js +84 -0
  162. package/dist/typegen/kotlin.js.map +1 -0
  163. package/dist/typegen/model.d.ts +37 -0
  164. package/dist/typegen/model.d.ts.map +1 -0
  165. package/dist/typegen/model.js +82 -0
  166. package/dist/typegen/model.js.map +1 -0
  167. package/dist/typegen/swift.d.ts +3 -0
  168. package/dist/typegen/swift.d.ts.map +1 -0
  169. package/dist/typegen/swift.js +105 -0
  170. package/dist/typegen/swift.js.map +1 -0
  171. package/dist/typegen/typescript.d.ts +3 -0
  172. package/dist/typegen/typescript.d.ts.map +1 -0
  173. package/dist/typegen/typescript.js +73 -0
  174. package/dist/typegen/typescript.js.map +1 -0
  175. package/dist/upgrade/rules.d.ts +27 -0
  176. package/dist/upgrade/rules.d.ts.map +1 -0
  177. package/dist/upgrade/rules.js +133 -0
  178. package/dist/upgrade/rules.js.map +1 -0
  179. package/dist/upgrade/scan.d.ts +18 -0
  180. package/dist/upgrade/scan.d.ts.map +1 -0
  181. package/dist/upgrade/scan.js +38 -0
  182. package/dist/upgrade/scan.js.map +1 -0
  183. package/dist/upgrade/versions.d.ts +8 -0
  184. package/dist/upgrade/versions.d.ts.map +1 -0
  185. package/dist/upgrade/versions.js +25 -0
  186. package/dist/upgrade/versions.js.map +1 -0
  187. package/dist/version.d.ts +3 -0
  188. package/dist/version.d.ts.map +1 -0
  189. package/dist/version.js +3 -0
  190. package/dist/version.js.map +1 -0
  191. package/package.json +68 -4
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAEpE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAc,MAAM,cAAc,CAAC;AAoBnE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC;AAEvC,2EAA2E;AAC3E,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,KAAK,IAAI,CAmBtF,CAAC;AAEF;;;GAGG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,WAAW,EAAE,UAAU,GAAE,MAAM,EAAO,GAAG,SAAS,CAYnF;AAED,OAAO,EAAE,SAAS,EAAE,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,60 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { appConfig } from './config.js';
3
+ import { toolServer } from './context.js';
4
+ import { registerAppTools } from './tools/apps/index.js';
5
+ import { registerBuildTools } from './tools/builds/index.js';
6
+ import { registerDeploymentTools } from './tools/deployments/index.js';
7
+ import { registerEnvTools } from './tools/env/index.js';
8
+ import { registerEngineTools } from './tools/engines/index.js';
9
+ import { registerHealthTools } from './tools/health/index.js';
10
+ import { registerJobTools } from './tools/jobs/index.js';
11
+ import { registerLogTools } from './tools/logs/index.js';
12
+ import { registerClassTools } from './tools/classes/index.js';
13
+ import { registerObjectTools } from './tools/objects/index.js';
14
+ import { registerFileTools } from './tools/files/index.js';
15
+ import { registerCloudFunctionTools } from './tools/cloud-functions/index.js';
16
+ import { registerPushTools } from './tools/push/index.js';
17
+ import { registerWebhookTools } from './tools/webhooks/index.js';
18
+ import { registerInfrastructureTools } from './tools/infrastructure/index.js';
19
+ import { registerAuditTools } from './tools/audit/index.js';
20
+ import { registerTypeTools } from './tools/types/index.js';
21
+ import { registerAdvisorTools } from './tools/advisor/index.js';
22
+ import { SERVER_NAME, SERVER_VERSION } from './version.js';
23
+ export { SERVER_NAME, SERVER_VERSION };
24
+ /** Tool groups (the `features` option), each the folder under src/tools */
25
+ export const TOOL_GROUPS = {
26
+ apps: registerAppTools,
27
+ builds: registerBuildTools,
28
+ deployments: registerDeploymentTools,
29
+ infrastructure: registerInfrastructureTools,
30
+ audit: registerAuditTools,
31
+ types: registerTypeTools,
32
+ advisor: registerAdvisorTools,
33
+ env: registerEnvTools,
34
+ engines: registerEngineTools,
35
+ health: registerHealthTools,
36
+ classes: registerClassTools,
37
+ objects: registerObjectTools,
38
+ files: registerFileTools,
39
+ 'cloud-functions': registerCloudFunctionTools,
40
+ push: registerPushTools,
41
+ jobs: registerJobTools,
42
+ logs: registerLogTools,
43
+ webhooks: registerWebhookTools,
44
+ };
45
+ /**
46
+ * An MCP server for one access token: only the tools its scopes, read-only
47
+ * mode, app limit and feature groups allow are registered.
48
+ */
49
+ export function createServer(ctx, registered = []) {
50
+ const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION });
51
+ for (const [group, register] of Object.entries(TOOL_GROUPS)) {
52
+ register(toolServer(server, group, ctx.access, registered, ctx), ctx);
53
+ }
54
+ if (appConfig.logLevel === 'debug') {
55
+ console.error(`[MCP] Server created with ${registered.length} tools`);
56
+ }
57
+ return server;
58
+ }
59
+ export { McpServer };
60
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { RedisClientType } from "redis";
2
+ /**
3
+ * Get or create the Redis client singleton.
4
+ * Returns null if REDIS_URL is not configured or connection fails.
5
+ */
6
+ export declare function getRedisClient(): Promise<RedisClientType | null>;
7
+ /**
8
+ * Check if Redis is configured
9
+ */
10
+ export declare function isRedisConfigured(): boolean;
11
+ /**
12
+ * Check if Redis is connected and healthy
13
+ */
14
+ export declare function isRedisConnected(): boolean;
15
+ /**
16
+ * Ping Redis to check health
17
+ */
18
+ export declare function pingRedis(): Promise<boolean>;
19
+ /**
20
+ * Gracefully close Redis connection
21
+ */
22
+ export declare function closeRedisConnection(): Promise<void>;
23
+ //# sourceMappingURL=redis-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-client.d.ts","sourceRoot":"","sources":["../../src/session/redis-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,eAAe,EAAE,MAAM,OAAO,CAAC;AAUtD;;;GAGG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAoBtE;AA4ED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C;AAED;;GAEG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC,CASlD;AAED;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAc1D"}
@@ -0,0 +1,136 @@
1
+ import { createClient } from "redis";
2
+ import { appConfig } from "../config.js";
3
+ let redisClient = null;
4
+ let connectionPromise = null;
5
+ let isConnecting = false;
6
+ let connectionAttempts = 0;
7
+ const MAX_RETRY_ATTEMPTS = 5;
8
+ const RETRY_DELAY_MS = 2000;
9
+ /**
10
+ * Get or create the Redis client singleton.
11
+ * Returns null if REDIS_URL is not configured or connection fails.
12
+ */
13
+ export async function getRedisClient() {
14
+ if (!appConfig.redis.url) {
15
+ return null;
16
+ }
17
+ // Return existing healthy connection
18
+ if (redisClient?.isOpen) {
19
+ return redisClient;
20
+ }
21
+ // If already connecting, wait for that attempt
22
+ if (connectionPromise) {
23
+ return connectionPromise;
24
+ }
25
+ // Start new connection attempt
26
+ connectionPromise = connectRedis();
27
+ const result = await connectionPromise;
28
+ connectionPromise = null;
29
+ return result;
30
+ }
31
+ async function connectRedis() {
32
+ if (isConnecting)
33
+ return null;
34
+ isConnecting = true;
35
+ try {
36
+ const client = createClient({
37
+ url: appConfig.redis.url,
38
+ password: appConfig.redis.password,
39
+ database: appConfig.redis.db,
40
+ socket: {
41
+ connectTimeout: 5000,
42
+ reconnectStrategy: (retries) => {
43
+ if (retries >= MAX_RETRY_ATTEMPTS) {
44
+ console.error(`[Redis] Max reconnection attempts (${MAX_RETRY_ATTEMPTS}) reached`);
45
+ return new Error("Max reconnection attempts reached");
46
+ }
47
+ const delay = Math.min(retries * 500, 3000);
48
+ console.error(`[Redis] Reconnecting in ${delay}ms (attempt ${retries + 1}/${MAX_RETRY_ATTEMPTS})`);
49
+ return delay;
50
+ },
51
+ },
52
+ });
53
+ client.on("error", (err) => {
54
+ console.error("[Redis] Error:", err.message);
55
+ });
56
+ client.on("reconnecting", () => {
57
+ console.error("[Redis] Reconnecting...");
58
+ });
59
+ client.on("ready", () => {
60
+ connectionAttempts = 0;
61
+ console.error("[Redis] Connection ready");
62
+ });
63
+ client.on("end", () => {
64
+ console.error("[Redis] Connection ended");
65
+ redisClient = null;
66
+ });
67
+ await client.connect();
68
+ console.error("[Redis] Connected to", appConfig.redis.url);
69
+ redisClient = client;
70
+ connectionAttempts = 0;
71
+ return redisClient;
72
+ }
73
+ catch (err) {
74
+ connectionAttempts++;
75
+ const message = err instanceof Error ? err.message : "Unknown error";
76
+ console.error(`[Redis] Connection failed (attempt ${connectionAttempts}): ${message}`);
77
+ if (connectionAttempts < MAX_RETRY_ATTEMPTS) {
78
+ console.error(`[Redis] Will retry in ${RETRY_DELAY_MS}ms...`);
79
+ await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
80
+ isConnecting = false;
81
+ return connectRedis();
82
+ }
83
+ console.error("[Redis] Giving up after max retries, falling back to in-memory");
84
+ return null;
85
+ }
86
+ finally {
87
+ isConnecting = false;
88
+ }
89
+ }
90
+ /**
91
+ * Check if Redis is configured
92
+ */
93
+ export function isRedisConfigured() {
94
+ return !!appConfig.redis.url;
95
+ }
96
+ /**
97
+ * Check if Redis is connected and healthy
98
+ */
99
+ export function isRedisConnected() {
100
+ return redisClient?.isOpen ?? false;
101
+ }
102
+ /**
103
+ * Ping Redis to check health
104
+ */
105
+ export async function pingRedis() {
106
+ try {
107
+ const client = await getRedisClient();
108
+ if (!client)
109
+ return false;
110
+ const result = await client.ping();
111
+ return result === "PONG";
112
+ }
113
+ catch {
114
+ return false;
115
+ }
116
+ }
117
+ /**
118
+ * Gracefully close Redis connection
119
+ */
120
+ export async function closeRedisConnection() {
121
+ if (redisClient?.isOpen) {
122
+ try {
123
+ await redisClient.quit();
124
+ console.error("[Redis] Connection closed gracefully");
125
+ }
126
+ catch (err) {
127
+ console.error("[Redis] Error closing connection:", err);
128
+ // Force disconnect if graceful quit fails
129
+ redisClient.disconnect();
130
+ }
131
+ }
132
+ redisClient = null;
133
+ connectionPromise = null;
134
+ connectionAttempts = 0;
135
+ }
136
+ //# sourceMappingURL=redis-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-client.js","sourceRoot":"","sources":["../../src/session/redis-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { SessionMetadata } from "./types.js";
2
+ export declare function registerSession(sessionId: string, tokenHash: string): Promise<void>;
3
+ /** The session if it exists, refreshing its expiry; null otherwise. */
4
+ export declare function touchSession(sessionId: string): Promise<SessionMetadata | null>;
5
+ export declare function deleteSession(sessionId: string): Promise<void>;
6
+ //# sourceMappingURL=session-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.d.ts","sourceRoot":"","sources":["../../src/session/session-manager.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAqBlD,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,uEAAuE;AACvE,wBAAsB,YAAY,CAChC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAyBjC;AAED,wBAAsB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASpE"}
@@ -0,0 +1,68 @@
1
+ import { getRedisClient, isRedisConfigured } from "./redis-client.js";
2
+ import { appConfig } from "../config.js";
3
+ // SSE sessions only: Streamable HTTP is stateless. A session remembers the
4
+ // hash of the token that opened it, so messages posted to it must come with
5
+ // the same token, on whichever instance they arrive.
6
+ const KEY_PREFIX = "mcp:";
7
+ const ttlMs = () => appConfig.redis.sessionTtlSeconds * 1000;
8
+ const inMemorySessions = new Map();
9
+ // in-memory sessions expire like the Redis ones
10
+ setInterval(() => {
11
+ const now = Date.now();
12
+ inMemorySessions.forEach((session, id) => {
13
+ if (now - session.lastAccessedAt > ttlMs()) {
14
+ inMemorySessions.delete(id);
15
+ }
16
+ });
17
+ }, 60 * 1000).unref();
18
+ export async function registerSession(sessionId, tokenHash) {
19
+ const metadata = {
20
+ sessionId,
21
+ createdAt: Date.now(),
22
+ lastAccessedAt: Date.now(),
23
+ transportType: "sse",
24
+ tokenHash,
25
+ };
26
+ if (isRedisConfigured()) {
27
+ const redis = await getRedisClient();
28
+ if (redis) {
29
+ await redis.setEx(`${KEY_PREFIX}session:${sessionId}`, appConfig.redis.sessionTtlSeconds, JSON.stringify(metadata));
30
+ return;
31
+ }
32
+ }
33
+ inMemorySessions.set(sessionId, metadata);
34
+ }
35
+ /** The session if it exists, refreshing its expiry; null otherwise. */
36
+ export async function touchSession(sessionId) {
37
+ if (isRedisConfigured()) {
38
+ const redis = await getRedisClient();
39
+ if (redis) {
40
+ const key = `${KEY_PREFIX}session:${sessionId}`;
41
+ const data = await redis.get(key);
42
+ if (!data)
43
+ return null;
44
+ const metadata = JSON.parse(data);
45
+ metadata.lastAccessedAt = Date.now();
46
+ await redis.setEx(key, appConfig.redis.sessionTtlSeconds, JSON.stringify(metadata));
47
+ return metadata;
48
+ }
49
+ }
50
+ const session = inMemorySessions.get(sessionId);
51
+ if (!session || Date.now() - session.lastAccessedAt > ttlMs()) {
52
+ inMemorySessions.delete(sessionId);
53
+ return null;
54
+ }
55
+ session.lastAccessedAt = Date.now();
56
+ return session;
57
+ }
58
+ export async function deleteSession(sessionId) {
59
+ if (isRedisConfigured()) {
60
+ const redis = await getRedisClient();
61
+ if (redis) {
62
+ await redis.del(`${KEY_PREFIX}session:${sessionId}`);
63
+ return;
64
+ }
65
+ }
66
+ inMemorySessions.delete(sessionId);
67
+ }
68
+ //# sourceMappingURL=session-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-manager.js","sourceRoot":"","sources":["../../src/session/session-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ type MessageHandler = (message: string) => void;
2
+ /**
3
+ * Subscribe to messages for a session
4
+ */
5
+ export declare function subscribeToSession(sessionId: string, handler: MessageHandler): Promise<void>;
6
+ /**
7
+ * Unsubscribe from a session
8
+ */
9
+ export declare function unsubscribeFromSession(sessionId: string): Promise<void>;
10
+ /**
11
+ * Publish a message to a session (can be on any server)
12
+ */
13
+ export declare function publishToSession(sessionId: string, message: string): Promise<boolean>;
14
+ /**
15
+ * Check if a session has a local subscriber (SSE connection on this server)
16
+ */
17
+ export declare function hasLocalSubscriber(sessionId: string): boolean;
18
+ /**
19
+ * Close pub/sub connections
20
+ */
21
+ export declare function closePubSub(): Promise<void>;
22
+ export {};
23
+ //# sourceMappingURL=sse-pubsub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-pubsub.d.ts","sourceRoot":"","sources":["../../src/session/sse-pubsub.ts"],"names":[],"mappings":"AAIA,KAAK,cAAc,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;AAyDhD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAaf;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ7E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,OAAO,CAAC,CAqBlB;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAYjD"}
@@ -0,0 +1,121 @@
1
+ import { createClient } from "redis";
2
+ import { appConfig } from "../config.js";
3
+ import { isRedisConfigured } from "./redis-client.js";
4
+ // Separate client for pub/sub (required by Redis - can't use same connection)
5
+ let subscriberClient = null;
6
+ let publisherClient = null;
7
+ // Local subscriptions: sessionId -> handler
8
+ const localSubscriptions = new Map();
9
+ // In-memory fallback for non-Redis mode
10
+ const inMemoryQueues = new Map();
11
+ const CHANNEL_PREFIX = "mcp:sse:";
12
+ /**
13
+ * Initialize pub/sub clients
14
+ */
15
+ async function ensureClients() {
16
+ if (!isRedisConfigured() || !appConfig.redis.url) {
17
+ return false;
18
+ }
19
+ if (subscriberClient?.isOpen && publisherClient?.isOpen) {
20
+ return true;
21
+ }
22
+ try {
23
+ const config = {
24
+ url: appConfig.redis.url,
25
+ password: appConfig.redis.password,
26
+ database: appConfig.redis.db,
27
+ };
28
+ if (!subscriberClient?.isOpen) {
29
+ subscriberClient = createClient(config);
30
+ subscriberClient.on("error", (err) => {
31
+ console.error("[SSE PubSub] Subscriber error:", err.message);
32
+ });
33
+ await subscriberClient.connect();
34
+ }
35
+ if (!publisherClient?.isOpen) {
36
+ publisherClient = createClient(config);
37
+ publisherClient.on("error", (err) => {
38
+ console.error("[SSE PubSub] Publisher error:", err.message);
39
+ });
40
+ await publisherClient.connect();
41
+ }
42
+ console.error("[SSE PubSub] Connected");
43
+ return true;
44
+ }
45
+ catch (err) {
46
+ console.error("[SSE PubSub] Connection failed:", err);
47
+ return false;
48
+ }
49
+ }
50
+ /**
51
+ * Subscribe to messages for a session
52
+ */
53
+ export async function subscribeToSession(sessionId, handler) {
54
+ localSubscriptions.set(sessionId, handler);
55
+ const redisAvailable = await ensureClients();
56
+ if (redisAvailable && subscriberClient) {
57
+ const channel = `${CHANNEL_PREFIX}${sessionId}`;
58
+ await subscriberClient.subscribe(channel, (message) => {
59
+ const localHandler = localSubscriptions.get(sessionId);
60
+ if (localHandler) {
61
+ localHandler(message);
62
+ }
63
+ });
64
+ }
65
+ }
66
+ /**
67
+ * Unsubscribe from a session
68
+ */
69
+ export async function unsubscribeFromSession(sessionId) {
70
+ localSubscriptions.delete(sessionId);
71
+ inMemoryQueues.delete(sessionId);
72
+ if (subscriberClient?.isOpen) {
73
+ const channel = `${CHANNEL_PREFIX}${sessionId}`;
74
+ await subscriberClient.unsubscribe(channel).catch(() => { });
75
+ }
76
+ }
77
+ /**
78
+ * Publish a message to a session (can be on any server)
79
+ */
80
+ export async function publishToSession(sessionId, message) {
81
+ // Try local first (same server optimization)
82
+ const localHandler = localSubscriptions.get(sessionId);
83
+ if (localHandler) {
84
+ localHandler(message);
85
+ return true;
86
+ }
87
+ // Try Redis pub/sub
88
+ const redisAvailable = await ensureClients();
89
+ if (redisAvailable && publisherClient) {
90
+ const channel = `${CHANNEL_PREFIX}${sessionId}`;
91
+ const receivers = await publisherClient.publish(channel, message);
92
+ return receivers > 0;
93
+ }
94
+ // Fallback: queue in memory (only works for same server)
95
+ const queue = inMemoryQueues.get(sessionId) ?? [];
96
+ queue.push(message);
97
+ inMemoryQueues.set(sessionId, queue);
98
+ return false;
99
+ }
100
+ /**
101
+ * Check if a session has a local subscriber (SSE connection on this server)
102
+ */
103
+ export function hasLocalSubscriber(sessionId) {
104
+ return localSubscriptions.has(sessionId);
105
+ }
106
+ /**
107
+ * Close pub/sub connections
108
+ */
109
+ export async function closePubSub() {
110
+ localSubscriptions.clear();
111
+ inMemoryQueues.clear();
112
+ if (subscriberClient?.isOpen) {
113
+ await subscriberClient.quit().catch(() => { });
114
+ subscriberClient = null;
115
+ }
116
+ if (publisherClient?.isOpen) {
117
+ await publisherClient.quit().catch(() => { });
118
+ publisherClient = null;
119
+ }
120
+ }
121
+ //# sourceMappingURL=sse-pubsub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sse-pubsub.js","sourceRoot":"","sources":["../../src/session/sse-pubsub.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
2
+ export type StreamId = string;
3
+ export type EventId = string;
4
+ /**
5
+ * Stored event structure in Redis
6
+ */
7
+ export interface StoredEvent {
8
+ eventId: EventId;
9
+ streamId: StreamId;
10
+ message: JSONRPCMessage;
11
+ timestamp: number;
12
+ }
13
+ /**
14
+ * Session metadata stored in Redis
15
+ */
16
+ export interface SessionMetadata {
17
+ sessionId: string;
18
+ createdAt: number;
19
+ lastAccessedAt: number;
20
+ transportType: "sse";
21
+ /** sha256 of the access token that opened the session */
22
+ tokenHash: string;
23
+ }
24
+ /**
25
+ * EventStore interface from MCP SDK (re-exported for convenience)
26
+ */
27
+ export interface EventStore {
28
+ /**
29
+ * Stores an event for later retrieval
30
+ */
31
+ storeEvent(streamId: StreamId, message: JSONRPCMessage): Promise<EventId>;
32
+ /**
33
+ * Get the stream ID associated with a given event ID
34
+ */
35
+ getStreamIdForEventId?(eventId: EventId): Promise<StreamId | undefined>;
36
+ /**
37
+ * Replay events after a given event ID
38
+ */
39
+ replayEventsAfter(lastEventId: EventId, callbacks: {
40
+ send: (eventId: EventId, message: JSONRPCMessage) => Promise<void>;
41
+ }): Promise<StreamId>;
42
+ }
43
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/session/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC;AAC9B,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAE7B;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,KAAK,CAAC;IACrB,yDAAyD;IACzD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE1E;;OAEG;IACH,qBAAqB,CAAC,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAAC,CAAC;IAExE;;OAEG;IACH,iBAAiB,CACf,WAAW,EAAE,OAAO,EACpB,SAAS,EAAE;QACT,IAAI,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACpE,GACA,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/session/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import { ToolContext, ToolServer } from "../../context.js";
2
+ export declare function registerAdvisorTools(server: ToolServer, ctx: ToolContext): void;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/tools/advisor/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAmD3D,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,EAAE,WAAW,GAAG,IAAI,CA+H/E"}