@mcp-ts/sdk 1.0.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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +297 -0
  3. package/dist/adapters/agui-adapter.d.mts +119 -0
  4. package/dist/adapters/agui-adapter.d.ts +119 -0
  5. package/dist/adapters/agui-adapter.js +109 -0
  6. package/dist/adapters/agui-adapter.js.map +1 -0
  7. package/dist/adapters/agui-adapter.mjs +107 -0
  8. package/dist/adapters/agui-adapter.mjs.map +1 -0
  9. package/dist/adapters/agui-middleware.d.mts +171 -0
  10. package/dist/adapters/agui-middleware.d.ts +171 -0
  11. package/dist/adapters/agui-middleware.js +429 -0
  12. package/dist/adapters/agui-middleware.js.map +1 -0
  13. package/dist/adapters/agui-middleware.mjs +417 -0
  14. package/dist/adapters/agui-middleware.mjs.map +1 -0
  15. package/dist/adapters/ai-adapter.d.mts +38 -0
  16. package/dist/adapters/ai-adapter.d.ts +38 -0
  17. package/dist/adapters/ai-adapter.js +82 -0
  18. package/dist/adapters/ai-adapter.js.map +1 -0
  19. package/dist/adapters/ai-adapter.mjs +80 -0
  20. package/dist/adapters/ai-adapter.mjs.map +1 -0
  21. package/dist/adapters/langchain-adapter.d.mts +46 -0
  22. package/dist/adapters/langchain-adapter.d.ts +46 -0
  23. package/dist/adapters/langchain-adapter.js +102 -0
  24. package/dist/adapters/langchain-adapter.js.map +1 -0
  25. package/dist/adapters/langchain-adapter.mjs +100 -0
  26. package/dist/adapters/langchain-adapter.mjs.map +1 -0
  27. package/dist/adapters/mastra-adapter.d.mts +49 -0
  28. package/dist/adapters/mastra-adapter.d.ts +49 -0
  29. package/dist/adapters/mastra-adapter.js +95 -0
  30. package/dist/adapters/mastra-adapter.js.map +1 -0
  31. package/dist/adapters/mastra-adapter.mjs +93 -0
  32. package/dist/adapters/mastra-adapter.mjs.map +1 -0
  33. package/dist/client/index.d.mts +119 -0
  34. package/dist/client/index.d.ts +119 -0
  35. package/dist/client/index.js +225 -0
  36. package/dist/client/index.js.map +1 -0
  37. package/dist/client/index.mjs +223 -0
  38. package/dist/client/index.mjs.map +1 -0
  39. package/dist/client/react.d.mts +151 -0
  40. package/dist/client/react.d.ts +151 -0
  41. package/dist/client/react.js +492 -0
  42. package/dist/client/react.js.map +1 -0
  43. package/dist/client/react.mjs +489 -0
  44. package/dist/client/react.mjs.map +1 -0
  45. package/dist/client/vue.d.mts +157 -0
  46. package/dist/client/vue.d.ts +157 -0
  47. package/dist/client/vue.js +474 -0
  48. package/dist/client/vue.js.map +1 -0
  49. package/dist/client/vue.mjs +471 -0
  50. package/dist/client/vue.mjs.map +1 -0
  51. package/dist/events-BP6WyRNh.d.mts +110 -0
  52. package/dist/events-BP6WyRNh.d.ts +110 -0
  53. package/dist/index.d.mts +10 -0
  54. package/dist/index.d.ts +10 -0
  55. package/dist/index.js +2784 -0
  56. package/dist/index.js.map +1 -0
  57. package/dist/index.mjs +2723 -0
  58. package/dist/index.mjs.map +1 -0
  59. package/dist/multi-session-client-BOFgPypS.d.ts +389 -0
  60. package/dist/multi-session-client-DMF3ED2O.d.mts +389 -0
  61. package/dist/server/index.d.mts +269 -0
  62. package/dist/server/index.d.ts +269 -0
  63. package/dist/server/index.js +2444 -0
  64. package/dist/server/index.js.map +1 -0
  65. package/dist/server/index.mjs +2414 -0
  66. package/dist/server/index.mjs.map +1 -0
  67. package/dist/shared/index.d.mts +24 -0
  68. package/dist/shared/index.d.ts +24 -0
  69. package/dist/shared/index.js +223 -0
  70. package/dist/shared/index.js.map +1 -0
  71. package/dist/shared/index.mjs +190 -0
  72. package/dist/shared/index.mjs.map +1 -0
  73. package/dist/types-SbDlA2VX.d.mts +153 -0
  74. package/dist/types-SbDlA2VX.d.ts +153 -0
  75. package/dist/utils-0qmYrqoa.d.mts +92 -0
  76. package/dist/utils-0qmYrqoa.d.ts +92 -0
  77. package/package.json +165 -0
  78. package/src/adapters/agui-adapter.ts +210 -0
  79. package/src/adapters/agui-middleware.ts +512 -0
  80. package/src/adapters/ai-adapter.ts +115 -0
  81. package/src/adapters/langchain-adapter.ts +127 -0
  82. package/src/adapters/mastra-adapter.ts +126 -0
  83. package/src/client/core/sse-client.ts +340 -0
  84. package/src/client/index.ts +26 -0
  85. package/src/client/react/index.ts +10 -0
  86. package/src/client/react/useMcp.ts +558 -0
  87. package/src/client/vue/index.ts +10 -0
  88. package/src/client/vue/useMcp.ts +542 -0
  89. package/src/index.ts +11 -0
  90. package/src/server/handlers/nextjs-handler.ts +216 -0
  91. package/src/server/handlers/sse-handler.ts +699 -0
  92. package/src/server/index.ts +57 -0
  93. package/src/server/mcp/multi-session-client.ts +132 -0
  94. package/src/server/mcp/oauth-client.ts +1168 -0
  95. package/src/server/mcp/storage-oauth-provider.ts +239 -0
  96. package/src/server/storage/file-backend.ts +169 -0
  97. package/src/server/storage/index.ts +115 -0
  98. package/src/server/storage/memory-backend.ts +132 -0
  99. package/src/server/storage/redis-backend.ts +210 -0
  100. package/src/server/storage/redis.ts +160 -0
  101. package/src/server/storage/types.ts +109 -0
  102. package/src/shared/constants.ts +29 -0
  103. package/src/shared/errors.ts +133 -0
  104. package/src/shared/events.ts +166 -0
  105. package/src/shared/index.ts +70 -0
  106. package/src/shared/types.ts +274 -0
  107. package/src/shared/utils.ts +16 -0
@@ -0,0 +1,210 @@
1
+
2
+ import type { Redis } from 'ioredis';
3
+ import { customAlphabet } from 'nanoid';
4
+ import { StorageBackend, SessionData, SetClientOptions } from './types';
5
+ import { SESSION_TTL_SECONDS } from '../../shared/constants.js';
6
+
7
+ /** first char: letters only (required by OpenAI) */
8
+ const firstChar = customAlphabet(
9
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
10
+ 1
11
+ );
12
+
13
+ /** remaining chars: alphanumeric */
14
+ const rest = customAlphabet(
15
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
16
+ 11
17
+ );
18
+
19
+ /**
20
+ * Redis implementation of StorageBackend
21
+ */
22
+ export class RedisStorageBackend implements StorageBackend {
23
+ private readonly DEFAULT_TTL = SESSION_TTL_SECONDS;
24
+ private readonly KEY_PREFIX = 'mcp:session:';
25
+
26
+ constructor(private redis: Redis) { }
27
+
28
+ /**
29
+ * Generates Redis key for a specific session
30
+ * @private
31
+ */
32
+ private getSessionKey(identity: string, sessionId: string): string {
33
+ return `${this.KEY_PREFIX}${identity}:${sessionId}`;
34
+ }
35
+
36
+ /**
37
+ * Generates Redis key for tracking all sessions for an identity
38
+ * @private
39
+ */
40
+ private getIdentityKey(identity: string): string {
41
+ return `mcp:identity:${identity}:sessions`;
42
+ }
43
+
44
+ generateSessionId(): string {
45
+ return firstChar() + rest();
46
+ }
47
+
48
+ async createSession(session: SessionData, ttl?: number): Promise<void> {
49
+ const { sessionId, identity } = session;
50
+ if (!sessionId || !identity) throw new Error('identity and sessionId required');
51
+
52
+ const sessionKey = this.getSessionKey(identity, sessionId);
53
+ const identityKey = this.getIdentityKey(identity);
54
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
55
+
56
+ /** ioredis syntax: set(key, val, 'EX', ttl, 'NX') */
57
+ const result = await this.redis.set(
58
+ sessionKey,
59
+ JSON.stringify(session),
60
+ 'EX',
61
+ effectiveTtl,
62
+ 'NX'
63
+ );
64
+
65
+ if (result !== 'OK') {
66
+ throw new Error(`Session ${sessionId} already exists`);
67
+ }
68
+
69
+ await this.redis.sadd(identityKey, sessionId);
70
+ }
71
+ async updateSession(identity: string, sessionId: string, data: Partial<SessionData>, ttl?: number): Promise<void> {
72
+ const sessionKey = this.getSessionKey(identity, sessionId);
73
+ const effectiveTtl = ttl ?? this.DEFAULT_TTL;
74
+
75
+ /** Lua script for atomic parsing, merging, and saving */
76
+ const script = `
77
+ local currentStr = redis.call("GET", KEYS[1])
78
+ if not currentStr then
79
+ return 0
80
+ end
81
+
82
+ local current = cjson.decode(currentStr)
83
+ local updates = cjson.decode(ARGV[1])
84
+
85
+ for k,v in pairs(updates) do
86
+ current[k] = v
87
+ end
88
+
89
+ redis.call("SET", KEYS[1], cjson.encode(current), "EX", ARGV[2])
90
+ return 1
91
+ `;
92
+
93
+ const result = await this.redis.eval(
94
+ script,
95
+ 1,
96
+ sessionKey,
97
+ JSON.stringify(data),
98
+ effectiveTtl
99
+ );
100
+
101
+ if (result === 0) {
102
+ throw new Error(`Session ${sessionId} not found for identity ${identity}`);
103
+ }
104
+ }
105
+
106
+ async getSession(identity: string, sessionId: string): Promise<SessionData | null> {
107
+ try {
108
+ const sessionKey = this.getSessionKey(identity, sessionId);
109
+ const sessionDataStr = await this.redis.get(sessionKey);
110
+
111
+ if (!sessionDataStr) {
112
+ return null;
113
+ }
114
+
115
+ const sessionData: SessionData = JSON.parse(sessionDataStr);
116
+ return sessionData;
117
+ } catch (error) {
118
+ console.error('[RedisStorage] Failed to get session:', error);
119
+ return null;
120
+ }
121
+ }
122
+
123
+ async getIdentityMcpSessions(identity: string): Promise<string[]> {
124
+ const identityKey = this.getIdentityKey(identity);
125
+ try {
126
+ return await this.redis.smembers(identityKey);
127
+ } catch (error) {
128
+ console.error(`[RedisStorage] Failed to get sessions for ${identity}:`, error);
129
+ return [];
130
+ }
131
+ }
132
+
133
+ async getIdentitySessionsData(identity: string): Promise<SessionData[]> {
134
+ try {
135
+ const sessionIds = await this.redis.smembers(this.getIdentityKey(identity));
136
+ if (sessionIds.length === 0) return [];
137
+
138
+ const results = await Promise.all(
139
+ sessionIds.map(async (sessionId) => {
140
+ const data = await this.redis.get(this.getSessionKey(identity, sessionId));
141
+ return data ? (JSON.parse(data) as SessionData) : null;
142
+ })
143
+ );
144
+
145
+ return results.filter((session): session is SessionData => session !== null);
146
+ } catch (error) {
147
+ console.error(`[RedisStorage] Failed to get session data for ${identity}:`, error);
148
+ return [];
149
+ }
150
+ }
151
+
152
+ async removeSession(identity: string, sessionId: string): Promise<void> {
153
+ try {
154
+ const sessionKey = this.getSessionKey(identity, sessionId);
155
+ const identityKey = this.getIdentityKey(identity);
156
+
157
+ await this.redis.srem(identityKey, sessionId);
158
+ await this.redis.del(sessionKey);
159
+ } catch (error) {
160
+ console.error('[RedisStorage] Failed to remove session:', error);
161
+ }
162
+ }
163
+
164
+ async getAllSessionIds(): Promise<string[]> {
165
+ try {
166
+ const pattern = `${this.KEY_PREFIX}*`;
167
+ const keys = await this.redis.keys(pattern);
168
+ return keys.map((key) => key.replace(this.KEY_PREFIX, ''));
169
+ } catch (error) {
170
+ console.error('[RedisStorage] Failed to get all sessions:', error);
171
+ return [];
172
+ }
173
+ }
174
+
175
+ async clearAll(): Promise<void> {
176
+ try {
177
+ const pattern = `${this.KEY_PREFIX}*`;
178
+ const keys = await this.redis.keys(pattern);
179
+ if (keys.length > 0) {
180
+ await this.redis.del(...keys);
181
+ }
182
+ } catch (error) {
183
+ console.error('[RedisStorage] Failed to clear sessions:', error);
184
+ }
185
+ }
186
+
187
+ async cleanupExpiredSessions(): Promise<void> {
188
+ try {
189
+ const pattern = `${this.KEY_PREFIX}*`;
190
+ const keys = await this.redis.keys(pattern);
191
+
192
+ for (const key of keys) {
193
+ const ttl = await this.redis.ttl(key);
194
+ if (ttl <= 0) {
195
+ await this.redis.del(key);
196
+ }
197
+ }
198
+ } catch (error) {
199
+ console.error('[RedisStorage] Failed to cleanup expired sessions:', error);
200
+ }
201
+ }
202
+
203
+ async disconnect(): Promise<void> {
204
+ try {
205
+ await this.redis.quit();
206
+ } catch (error) {
207
+ console.error('[RedisStorage] Failed to disconnect:', error);
208
+ }
209
+ }
210
+ }
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Redis connection management with dependency injection support
3
+ * Allows configuration and testing without environment variables
4
+ */
5
+ import type { Redis } from 'ioredis';
6
+
7
+ export interface RedisConfig {
8
+ /**
9
+ * Redis connection URL (defaults to REDIS_URL env var)
10
+ */
11
+ url?: string;
12
+
13
+ /**
14
+ * Enable lazy connection (default: true)
15
+ */
16
+ lazyConnect?: boolean;
17
+
18
+ /**
19
+ * Maximum retries per request (default: 1)
20
+ */
21
+ maxRetriesPerRequest?: number;
22
+
23
+ /**
24
+ * Enable verbose logging (default: false)
25
+ */
26
+ verbose?: boolean;
27
+ /**
28
+ * @internal For testing only - bypass ioredis import
29
+ */
30
+ RedisConstructor?: any;
31
+ }
32
+
33
+ declare global {
34
+ // eslint-disable-next-line no-var
35
+ var __redis: Redis | undefined;
36
+ var __redisConfig: RedisConfig | undefined;
37
+ }
38
+
39
+ let redisInstance: Redis | null = null;
40
+
41
+ /**
42
+ * Initialize Redis with custom configuration
43
+ * Call this before any Redis operations if you need custom config
44
+ * @param config - Redis configuration options
45
+ */
46
+ export async function initRedis(config: RedisConfig): Promise<Redis> {
47
+ if (redisInstance) {
48
+ // Already initialized, return existing instance
49
+ return redisInstance;
50
+ }
51
+
52
+ const url = config.url ?? process.env.REDIS_URL;
53
+
54
+ if (!url) {
55
+ throw new Error(
56
+ 'Redis URL is required. Set REDIS_URL environment variable or pass url in config.'
57
+ );
58
+ }
59
+
60
+ let Redis: typeof import('ioredis').Redis;
61
+ if (config.RedisConstructor) {
62
+ Redis = config.RedisConstructor;
63
+ } else {
64
+ try {
65
+ const ioredis = await import('ioredis');
66
+ Redis = ioredis.Redis;
67
+ } catch (error) {
68
+ throw new Error(
69
+ 'ioredis is not installed. Install it with:\n' +
70
+ ' npm install ioredis\n\n' +
71
+ 'Or use a different storage backend:\n' +
72
+ ' MCP_TS_STORAGE_TYPE=memory (for development)\n' +
73
+ ' MCP_TS_STORAGE_TYPE=file (for local persistence)'
74
+ );
75
+ }
76
+ }
77
+
78
+ redisInstance = new Redis(url, {
79
+ lazyConnect: config.lazyConnect ?? true,
80
+ maxRetriesPerRequest: config.maxRetriesPerRequest ?? 1,
81
+ });
82
+
83
+ if (config.verbose !== false) {
84
+ redisInstance.on('ready', () => {
85
+ console.log('✅ Redis connected');
86
+ });
87
+
88
+ redisInstance.on('error', (err) => {
89
+ console.error('❌ Redis error:', err.message);
90
+ });
91
+
92
+ redisInstance.on('reconnecting', () => {
93
+ console.log('🔄 Redis reconnecting...');
94
+ });
95
+ }
96
+
97
+ // Store globally for hot reloading scenarios
98
+ global.__redis = redisInstance;
99
+ global.__redisConfig = config;
100
+
101
+ return redisInstance;
102
+ }
103
+
104
+ /**
105
+ * Get the Redis instance
106
+ * Automatically initializes with default config if not already initialized
107
+ */
108
+ export async function getRedis(): Promise<Redis> {
109
+ if (redisInstance) {
110
+ return redisInstance;
111
+ }
112
+
113
+ // Check for existing global instance (hot reload scenario)
114
+ if (global.__redis) {
115
+ redisInstance = global.__redis;
116
+ return redisInstance;
117
+ }
118
+
119
+ // Initialize with default config
120
+ return await initRedis({});
121
+ }
122
+
123
+ /**
124
+ * Set a custom Redis instance (useful for testing with mocks)
125
+ * @param instance - Redis instance or mock
126
+ */
127
+ export function setRedisInstance(instance: Redis): void {
128
+ redisInstance = instance;
129
+ global.__redis = instance;
130
+ }
131
+
132
+ /**
133
+ * Close Redis connection and clear instance
134
+ */
135
+ export async function closeRedis(): Promise<void> {
136
+ if (redisInstance) {
137
+ await redisInstance.quit();
138
+ redisInstance = null;
139
+ global.__redis = undefined;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Default Redis export for backward compatibility
145
+ * Will auto-initialize on first access
146
+ * Note: This is a lazy proxy that initializes Redis on first method call
147
+ */
148
+ export const redis = new Proxy({} as Redis, {
149
+ get(_target, prop) {
150
+ // Return a function that handles async initialization
151
+ return async (...args: any[]) => {
152
+ const instance = await getRedis();
153
+ const value = (instance as any)[prop];
154
+ if (typeof value === 'function') {
155
+ return value.apply(instance, args);
156
+ }
157
+ return value;
158
+ };
159
+ },
160
+ });
@@ -0,0 +1,109 @@
1
+
2
+ import type { MCPClient } from '../mcp/oauth-client.js';
3
+ import type {
4
+ OAuthTokens,
5
+ OAuthClientInformationMixed,
6
+ } from '@modelcontextprotocol/sdk/shared/auth.js';
7
+
8
+ export interface SessionData {
9
+ sessionId: string;
10
+ serverId?: string; // Database server ID for mapping
11
+ serverName?: string;
12
+ serverUrl: string;
13
+ transportType: 'sse' | 'streamable_http';
14
+ callbackUrl: string;
15
+ createdAt: number;
16
+ identity?: string;
17
+ headers?: Record<string, string>;
18
+ // OAuth data (consolidated)
19
+ clientInformation?: OAuthClientInformationMixed;
20
+ tokens?: OAuthTokens;
21
+ codeVerifier?: string;
22
+ clientId?: string;
23
+ }
24
+
25
+ export interface SetClientOptions {
26
+ sessionId: string;
27
+ serverId?: string; // Database server ID
28
+ serverName?: string; // Human-readable server name
29
+ client?: MCPClient;
30
+ serverUrl?: string;
31
+ callbackUrl?: string;
32
+ transportType?: 'sse' | 'streamable_http';
33
+ identity?: string;
34
+ headers?: Record<string, string>;
35
+ }
36
+
37
+ /**
38
+ * Interface for MCP Session Storage Backends
39
+ */
40
+ export interface StorageBackend {
41
+ /**
42
+ * Optional initialization (e.g., database connection)
43
+ */
44
+ init?(): Promise<void>;
45
+
46
+ /**
47
+ * Generates a unique session ID
48
+ */
49
+ generateSessionId(): string;
50
+
51
+ /**
52
+ * Stores or updates a session
53
+ */
54
+ /**
55
+ * Creates a new session. Throws if session already exists.
56
+ * @param session - Session data to create
57
+ * @param ttl - Optional TTL in seconds (defaults to backend's default)
58
+ */
59
+ createSession(session: SessionData, ttl?: number): Promise<void>;
60
+
61
+ /**
62
+ * Updates an existing session with partial data. Throws if session does not exist.
63
+ * @param identity - User identity
64
+ * @param sessionId - Session identifier
65
+ * @param data - Partial session data to update
66
+ * @param ttl - Optional TTL in seconds (defaults to backend's default)
67
+ */
68
+ updateSession(identity: string, sessionId: string, data: Partial<SessionData>, ttl?: number): Promise<void>;
69
+
70
+ /**
71
+ * Retrieves a session
72
+ */
73
+ getSession(identity: string, sessionId: string): Promise<SessionData | null>;
74
+
75
+ /**
76
+ * Gets full session data for all of an identity's sessions
77
+ */
78
+ getIdentitySessionsData(identity: string): Promise<SessionData[]>;
79
+
80
+ /**
81
+ * Removes a session
82
+ */
83
+ removeSession(identity: string, sessionId: string): Promise<void>;
84
+
85
+ /**
86
+ * Gets all sessions IDs of an identity
87
+ */
88
+ getIdentityMcpSessions(identity: string): Promise<string[]>;
89
+
90
+ /**
91
+ * Gets all session IDs across all users (Admin)
92
+ */
93
+ getAllSessionIds(): Promise<string[]>;
94
+
95
+ /**
96
+ * Clears all sessions (Admin)
97
+ */
98
+ clearAll(): Promise<void>;
99
+
100
+ /**
101
+ * Clean up expired sessions
102
+ */
103
+ cleanupExpiredSessions(): Promise<void>;
104
+
105
+ /**
106
+ * Disconnect from storage backend
107
+ */
108
+ disconnect(): Promise<void>;
109
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Centralized constants for MCP Redis library
3
+ * Eliminates magic numbers and enables consistent configuration
4
+ */
5
+
6
+ // Redis TTL and Session Management
7
+ export const SESSION_TTL_SECONDS = 43200; // 12 hours
8
+ export const STATE_EXPIRATION_MS = 10 * 60 * 1000; // 10 minutes for OAuth state
9
+
10
+ // Heartbeat and Connection
11
+ export const DEFAULT_HEARTBEAT_INTERVAL_MS = 30000; // 30 seconds
12
+
13
+ // Redis Key Prefixes
14
+ export const REDIS_KEY_PREFIX = 'mcp:session:';
15
+
16
+ // Token Management
17
+ export const TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000; // 5 minute buffer before expiry
18
+
19
+ // Client Information
20
+ export const DEFAULT_CLIENT_NAME = 'MCP Assistant';
21
+ export const DEFAULT_CLIENT_URI = 'https://mcp-assistant.in';
22
+ export const DEFAULT_LOGO_URI = 'https://mcp-assistant.in/logo.png';
23
+ export const DEFAULT_POLICY_URI = 'https://mcp-assistant.in/privacy';
24
+ export const SOFTWARE_ID = '@mcp-ts';
25
+ export const SOFTWARE_VERSION = '1.0.0-beta.5';
26
+
27
+ // MCP Client Configuration
28
+ export const MCP_CLIENT_NAME = 'mcp-ts-oauth-client';
29
+ export const MCP_CLIENT_VERSION = '2.0';
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Standardized error classes for MCP Redis library
3
+ * Provides consistent error handling across the codebase
4
+ */
5
+
6
+ /**
7
+ * Base error class for all MCP-related errors
8
+ */
9
+ export class McpError extends Error {
10
+ constructor(
11
+ public readonly code: string,
12
+ message: string,
13
+ public readonly cause?: Error
14
+ ) {
15
+ super(message);
16
+ this.name = 'McpError';
17
+ // Maintain proper prototype chain for instanceof checks
18
+ Object.setPrototypeOf(this, new.target.prototype);
19
+ }
20
+
21
+ toJSON() {
22
+ return {
23
+ name: this.name,
24
+ code: this.code,
25
+ message: this.message,
26
+ ...(this.cause ? { cause: this.cause.message } : {}),
27
+ };
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Thrown when OAuth authorization is required
33
+ */
34
+ export class UnauthorizedError extends McpError {
35
+ constructor(message: string = 'OAuth authorization required', cause?: Error) {
36
+ super('UNAUTHORIZED', message, cause);
37
+ this.name = 'UnauthorizedError';
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Thrown when connection to MCP server fails
43
+ */
44
+ export class ConnectionError extends McpError {
45
+ constructor(message: string, cause?: Error) {
46
+ super('CONNECTION_ERROR', message, cause);
47
+ this.name = 'ConnectionError';
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Thrown when session is not found or expired
53
+ */
54
+ export class SessionNotFoundError extends McpError {
55
+ constructor(sessionId: string, cause?: Error) {
56
+ super('SESSION_NOT_FOUND', `Session not found: ${sessionId}`, cause);
57
+ this.name = 'SessionNotFoundError';
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Thrown when session validation fails
63
+ */
64
+ export class SessionValidationError extends McpError {
65
+ constructor(message: string, cause?: Error) {
66
+ super('SESSION_VALIDATION_ERROR', message, cause);
67
+ this.name = 'SessionValidationError';
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Thrown when authentication fails
73
+ */
74
+ export class AuthenticationError extends McpError {
75
+ constructor(message: string, cause?: Error) {
76
+ super('AUTH_ERROR', message, cause);
77
+ this.name = 'AuthenticationError';
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Thrown when OAuth state validation fails
83
+ */
84
+ export class InvalidStateError extends McpError {
85
+ constructor(message: string = 'Invalid OAuth state', cause?: Error) {
86
+ super('INVALID_STATE', message, cause);
87
+ this.name = 'InvalidStateError';
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Thrown when client is not connected
93
+ */
94
+ export class NotConnectedError extends McpError {
95
+ constructor(message: string = 'Not connected to server', cause?: Error) {
96
+ super('NOT_CONNECTED', message, cause);
97
+ this.name = 'NotConnectedError';
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Thrown when required configuration is missing
103
+ */
104
+ export class ConfigurationError extends McpError {
105
+ constructor(message: string, cause?: Error) {
106
+ super('CONFIGURATION_ERROR', message, cause);
107
+ this.name = 'ConfigurationError';
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Thrown when tool execution fails
113
+ */
114
+ export class ToolExecutionError extends McpError {
115
+ constructor(toolName: string, message: string, cause?: Error) {
116
+ super('TOOL_EXECUTION_ERROR', `Tool '${toolName}' failed: ${message}`, cause);
117
+ this.name = 'ToolExecutionError';
118
+ }
119
+ }
120
+
121
+ /**
122
+ * RPC error codes for SSE communication
123
+ */
124
+ export const RpcErrorCodes = {
125
+ EXECUTION_ERROR: 'EXECUTION_ERROR',
126
+ MISSING_IDENTITY: 'MISSING_IDENTITY',
127
+ UNAUTHORIZED: 'UNAUTHORIZED',
128
+ NO_CONNECTION: 'NO_CONNECTION',
129
+ UNKNOWN_METHOD: 'UNKNOWN_METHOD',
130
+ INVALID_PARAMS: 'INVALID_PARAMS',
131
+ } as const;
132
+
133
+ export type RpcErrorCode = typeof RpcErrorCodes[keyof typeof RpcErrorCodes];