@mseep/mcp-agent-social 1.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.
Files changed (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -0
  3. package/bin/mcp-agent-social +30 -0
  4. package/dist/api-client.d.ts +31 -0
  5. package/dist/api-client.d.ts.map +1 -0
  6. package/dist/api-client.js +212 -0
  7. package/dist/api-client.js.map +1 -0
  8. package/dist/config.d.ts +19 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +79 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/hooks/index.d.ts +38 -0
  13. package/dist/hooks/index.d.ts.map +1 -0
  14. package/dist/hooks/index.js +253 -0
  15. package/dist/hooks/index.js.map +1 -0
  16. package/dist/hooks/types.d.ts +35 -0
  17. package/dist/hooks/types.d.ts.map +1 -0
  18. package/dist/hooks/types.js +4 -0
  19. package/dist/hooks/types.js.map +1 -0
  20. package/dist/http-server.d.ts +38 -0
  21. package/dist/http-server.d.ts.map +1 -0
  22. package/dist/http-server.js +210 -0
  23. package/dist/http-server.js.map +1 -0
  24. package/dist/index.d.ts +2 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +186 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/logger.d.ts +44 -0
  29. package/dist/logger.d.ts.map +1 -0
  30. package/dist/logger.js +281 -0
  31. package/dist/logger.js.map +1 -0
  32. package/dist/metrics.d.ts +47 -0
  33. package/dist/metrics.d.ts.map +1 -0
  34. package/dist/metrics.js +178 -0
  35. package/dist/metrics.js.map +1 -0
  36. package/dist/middleware/error-handler.d.ts +74 -0
  37. package/dist/middleware/error-handler.d.ts.map +1 -0
  38. package/dist/middleware/error-handler.js +218 -0
  39. package/dist/middleware/error-handler.js.map +1 -0
  40. package/dist/middleware/index.d.ts +55 -0
  41. package/dist/middleware/index.d.ts.map +1 -0
  42. package/dist/middleware/index.js +91 -0
  43. package/dist/middleware/index.js.map +1 -0
  44. package/dist/middleware/timeout.d.ts +52 -0
  45. package/dist/middleware/timeout.d.ts.map +1 -0
  46. package/dist/middleware/timeout.js +189 -0
  47. package/dist/middleware/timeout.js.map +1 -0
  48. package/dist/middleware/validator.d.ts +25 -0
  49. package/dist/middleware/validator.d.ts.map +1 -0
  50. package/dist/middleware/validator.js +186 -0
  51. package/dist/middleware/validator.js.map +1 -0
  52. package/dist/prompts/analyze.d.ts +46 -0
  53. package/dist/prompts/analyze.d.ts.map +1 -0
  54. package/dist/prompts/analyze.js +351 -0
  55. package/dist/prompts/analyze.js.map +1 -0
  56. package/dist/prompts/generate.d.ts +48 -0
  57. package/dist/prompts/generate.d.ts.map +1 -0
  58. package/dist/prompts/generate.js +177 -0
  59. package/dist/prompts/generate.js.map +1 -0
  60. package/dist/prompts/index.d.ts +23 -0
  61. package/dist/prompts/index.d.ts.map +1 -0
  62. package/dist/prompts/index.js +69 -0
  63. package/dist/prompts/index.js.map +1 -0
  64. package/dist/prompts/summarize.d.ts +32 -0
  65. package/dist/prompts/summarize.d.ts.map +1 -0
  66. package/dist/prompts/summarize.js +182 -0
  67. package/dist/prompts/summarize.js.map +1 -0
  68. package/dist/prompts/types.d.ts +34 -0
  69. package/dist/prompts/types.d.ts.map +1 -0
  70. package/dist/prompts/types.js +24 -0
  71. package/dist/prompts/types.js.map +1 -0
  72. package/dist/resources/agents.d.ts +17 -0
  73. package/dist/resources/agents.d.ts.map +1 -0
  74. package/dist/resources/agents.js +139 -0
  75. package/dist/resources/agents.js.map +1 -0
  76. package/dist/resources/feed.d.ts +19 -0
  77. package/dist/resources/feed.d.ts.map +1 -0
  78. package/dist/resources/feed.js +138 -0
  79. package/dist/resources/feed.js.map +1 -0
  80. package/dist/resources/index.d.ts +19 -0
  81. package/dist/resources/index.d.ts.map +1 -0
  82. package/dist/resources/index.js +146 -0
  83. package/dist/resources/index.js.map +1 -0
  84. package/dist/resources/posts.d.ts +17 -0
  85. package/dist/resources/posts.d.ts.map +1 -0
  86. package/dist/resources/posts.js +151 -0
  87. package/dist/resources/posts.js.map +1 -0
  88. package/dist/resources/types.d.ts +91 -0
  89. package/dist/resources/types.d.ts.map +1 -0
  90. package/dist/resources/types.js +12 -0
  91. package/dist/resources/types.js.map +1 -0
  92. package/dist/roots/index.d.ts +43 -0
  93. package/dist/roots/index.d.ts.map +1 -0
  94. package/dist/roots/index.js +131 -0
  95. package/dist/roots/index.js.map +1 -0
  96. package/dist/roots/types.d.ts +31 -0
  97. package/dist/roots/types.d.ts.map +1 -0
  98. package/dist/roots/types.js +4 -0
  99. package/dist/roots/types.js.map +1 -0
  100. package/dist/session-manager.d.ts +50 -0
  101. package/dist/session-manager.d.ts.map +1 -0
  102. package/dist/session-manager.js +127 -0
  103. package/dist/session-manager.js.map +1 -0
  104. package/dist/tools/create-post.d.ts +45 -0
  105. package/dist/tools/create-post.d.ts.map +1 -0
  106. package/dist/tools/create-post.js +119 -0
  107. package/dist/tools/create-post.js.map +1 -0
  108. package/dist/tools/index.d.ts +13 -0
  109. package/dist/tools/index.d.ts.map +1 -0
  110. package/dist/tools/index.js +44 -0
  111. package/dist/tools/index.js.map +1 -0
  112. package/dist/tools/login.d.ts +35 -0
  113. package/dist/tools/login.d.ts.map +1 -0
  114. package/dist/tools/login.js +132 -0
  115. package/dist/tools/login.js.map +1 -0
  116. package/dist/tools/read-posts.d.ts +48 -0
  117. package/dist/tools/read-posts.d.ts.map +1 -0
  118. package/dist/tools/read-posts.js +93 -0
  119. package/dist/tools/read-posts.js.map +1 -0
  120. package/dist/types.d.ts +88 -0
  121. package/dist/types.d.ts.map +1 -0
  122. package/dist/types.js +4 -0
  123. package/dist/types.js.map +1 -0
  124. package/dist/utils/json.d.ts +13 -0
  125. package/dist/utils/json.d.ts.map +1 -0
  126. package/dist/utils/json.js +48 -0
  127. package/dist/utils/json.js.map +1 -0
  128. package/dist/validation.d.ts +58 -0
  129. package/dist/validation.d.ts.map +1 -0
  130. package/dist/validation.js +223 -0
  131. package/dist/validation.js.map +1 -0
  132. package/package.json +70 -0
  133. package/src/api-client.ts +292 -0
  134. package/src/config.ts +92 -0
  135. package/src/hooks/index.ts +304 -0
  136. package/src/hooks/types.ts +44 -0
  137. package/src/http-server.ts +243 -0
  138. package/src/index.ts +213 -0
  139. package/src/logger.ts +326 -0
  140. package/src/metrics.ts +235 -0
  141. package/src/middleware/error-handler.ts +252 -0
  142. package/src/middleware/index.ts +112 -0
  143. package/src/middleware/timeout.ts +216 -0
  144. package/src/middleware/validator.ts +216 -0
  145. package/src/prompts/analyze.ts +404 -0
  146. package/src/prompts/generate.ts +217 -0
  147. package/src/prompts/index.ts +121 -0
  148. package/src/prompts/summarize.ts +217 -0
  149. package/src/prompts/types.ts +44 -0
  150. package/src/resources/agents.ts +165 -0
  151. package/src/resources/feed.ts +169 -0
  152. package/src/resources/index.ts +210 -0
  153. package/src/resources/posts.ts +179 -0
  154. package/src/resources/types.ts +104 -0
  155. package/src/roots/index.ts +166 -0
  156. package/src/roots/types.ts +36 -0
  157. package/src/session-manager.ts +149 -0
  158. package/src/tools/create-post.ts +154 -0
  159. package/src/tools/index.ts +70 -0
  160. package/src/tools/login.ts +169 -0
  161. package/src/tools/read-posts.ts +120 -0
  162. package/src/types.ts +107 -0
  163. package/src/utils/json.ts +46 -0
  164. package/src/validation.ts +322 -0
  165. package/tsconfig.json +22 -0
@@ -0,0 +1,304 @@
1
+ // ABOUTME: MCP Request/Response Hooks for middleware architecture and custom processing
2
+ // ABOUTME: Provides extensible pipeline for request preprocessing and response enrichment
3
+
4
+ import { logger } from '../logger.js';
5
+ import { McpRateLimitError } from '../middleware/error-handler.js';
6
+ import {
7
+ type ErrorHook,
8
+ type Hook,
9
+ type HookContext,
10
+ HookPipeline,
11
+ HookType,
12
+ type RequestHook,
13
+ type ResponseHook,
14
+ } from './types.js';
15
+
16
+ export class HooksManager {
17
+ private requestHooks: RequestHook[] = [];
18
+ private responseHooks: ResponseHook[] = [];
19
+ private errorHooks: ErrorHook[] = [];
20
+ private rateLimitWindows: Map<string, number[]> = new Map();
21
+
22
+ constructor() {
23
+ // Register default hooks
24
+ this.registerDefaultHooks();
25
+ logger.info('Hooks manager initialized', {
26
+ requestHooks: this.requestHooks.length,
27
+ responseHooks: this.responseHooks.length,
28
+ errorHooks: this.errorHooks.length,
29
+ });
30
+ }
31
+
32
+ /**
33
+ * Register a hook
34
+ */
35
+ registerHook(hook: Hook): void {
36
+ switch (hook.type) {
37
+ case 'request':
38
+ this.requestHooks.push(hook as RequestHook);
39
+ this.requestHooks.sort((a, b) => a.priority - b.priority);
40
+ break;
41
+ case 'response':
42
+ this.responseHooks.push(hook as ResponseHook);
43
+ this.responseHooks.sort((a, b) => a.priority - b.priority);
44
+ break;
45
+ case 'error':
46
+ this.errorHooks.push(hook as ErrorHook);
47
+ this.errorHooks.sort((a, b) => a.priority - b.priority);
48
+ break;
49
+ }
50
+
51
+ logger.debug('Registered hook', { name: hook.name, type: hook.type, priority: hook.priority });
52
+ }
53
+
54
+ /**
55
+ * Process request through all request hooks
56
+ */
57
+ async processRequest(request: any, context: HookContext): Promise<any> {
58
+ let processedRequest = request;
59
+
60
+ for (const hook of this.requestHooks) {
61
+ try {
62
+ if (hook.condition && !hook.condition(processedRequest, context)) {
63
+ continue;
64
+ }
65
+
66
+ const result = await hook.execute(processedRequest, context);
67
+ if (result) {
68
+ processedRequest = result;
69
+ }
70
+
71
+ logger.debug('Request hook executed', {
72
+ hookName: hook.name,
73
+ method: processedRequest.method,
74
+ });
75
+ } catch (error) {
76
+ logger.error('Request hook failed', {
77
+ hookName: hook.name,
78
+ error: error instanceof Error ? error.message : String(error),
79
+ });
80
+
81
+ // Don't fail the entire request for hook errors
82
+ if (hook.critical) {
83
+ throw error;
84
+ }
85
+ }
86
+ }
87
+
88
+ return processedRequest;
89
+ }
90
+
91
+ /**
92
+ * Process response through all response hooks
93
+ */
94
+ async processResponse(response: any, request: any, context: HookContext): Promise<any> {
95
+ let processedResponse = response;
96
+
97
+ for (const hook of this.responseHooks) {
98
+ try {
99
+ if (hook.condition && !hook.condition(processedResponse, request, context)) {
100
+ continue;
101
+ }
102
+
103
+ const result = await hook.execute(processedResponse, request, context);
104
+ if (result) {
105
+ processedResponse = result;
106
+ }
107
+
108
+ logger.debug('Response hook executed', {
109
+ hookName: hook.name,
110
+ method: request.method,
111
+ });
112
+ } catch (error) {
113
+ logger.error('Response hook failed', {
114
+ hookName: hook.name,
115
+ error: error instanceof Error ? error.message : String(error),
116
+ });
117
+
118
+ if (hook.critical) {
119
+ throw error;
120
+ }
121
+ }
122
+ }
123
+
124
+ return processedResponse;
125
+ }
126
+
127
+ /**
128
+ * Process error through all error hooks
129
+ */
130
+ async processError(error: Error, request: any, context: HookContext): Promise<Error> {
131
+ let processedError = error;
132
+
133
+ for (const hook of this.errorHooks) {
134
+ try {
135
+ if (hook.condition && !hook.condition(processedError, request, context)) {
136
+ continue;
137
+ }
138
+
139
+ const result = await hook.execute(processedError, request, context);
140
+ if (result) {
141
+ processedError = result;
142
+ }
143
+
144
+ logger.debug('Error hook executed', {
145
+ hookName: hook.name,
146
+ originalError: error.message,
147
+ });
148
+ } catch (hookError) {
149
+ logger.error('Error hook failed', {
150
+ hookName: hook.name,
151
+ hookError: hookError instanceof Error ? hookError.message : String(hookError),
152
+ });
153
+ }
154
+ }
155
+
156
+ return processedError;
157
+ }
158
+
159
+ /**
160
+ * Register default hooks
161
+ */
162
+ private registerDefaultHooks(): void {
163
+ // Request logging hook
164
+ this.registerHook({
165
+ name: 'request-logger',
166
+ type: 'request',
167
+ priority: 100,
168
+ description: 'Log incoming requests',
169
+ execute: async (request, context) => {
170
+ logger.info('Processing request', {
171
+ method: request.method,
172
+ sessionId: context.sessionId,
173
+ timestamp: new Date().toISOString(),
174
+ });
175
+ return request;
176
+ },
177
+ });
178
+
179
+ // Response enrichment hook
180
+ this.registerHook({
181
+ name: 'response-enricher',
182
+ type: 'response',
183
+ priority: 100,
184
+ description: 'Add metadata to responses',
185
+ execute: async (response, request, context) => {
186
+ const enrichedResponse = {
187
+ ...response,
188
+ _metadata: {
189
+ processedAt: new Date().toISOString(),
190
+ sessionId: context.sessionId,
191
+ requestMethod: request.method,
192
+ },
193
+ };
194
+ return enrichedResponse;
195
+ },
196
+ });
197
+
198
+ // Error enrichment hook
199
+ this.registerHook({
200
+ name: 'error-enricher',
201
+ type: 'error',
202
+ priority: 100,
203
+ description: 'Enrich errors with context',
204
+ execute: async (error, request, context) => {
205
+ const enrichedError = new Error(error.message);
206
+ enrichedError.name = error.name;
207
+ enrichedError.stack = error.stack;
208
+
209
+ // Add context to error
210
+ (enrichedError as any).context = {
211
+ method: request.method,
212
+ sessionId: context.sessionId,
213
+ timestamp: new Date().toISOString(),
214
+ };
215
+
216
+ return enrichedError;
217
+ },
218
+ });
219
+
220
+ // Rate limiting hook with sliding window implementation
221
+ this.registerHook({
222
+ name: 'rate-limiter',
223
+ type: 'request',
224
+ priority: 10, // High priority - run early
225
+ description: 'Sliding window rate limiting',
226
+ critical: true, // Rate limit errors should stop request processing
227
+ execute: async (request, context) => {
228
+ const rateLimitKey = `${context.sessionId}:${request.method}`;
229
+ const now = Date.now();
230
+ const windowMs = 60000; // 1 minute window
231
+ const maxRequests = 30; // Standard rate limit
232
+
233
+ if (!this.rateLimitWindows) {
234
+ this.rateLimitWindows = new Map();
235
+ }
236
+
237
+ let requestTimes = this.rateLimitWindows.get(rateLimitKey) || [];
238
+
239
+ // Remove requests older than window
240
+ requestTimes = requestTimes.filter((time) => now - time < windowMs);
241
+
242
+ // Check if limit exceeded
243
+ if (requestTimes.length >= maxRequests) {
244
+ const oldestRequest = Math.min(...requestTimes);
245
+ const retryAfter = Math.ceil((oldestRequest + windowMs - now) / 1000);
246
+
247
+ logger.warn('Rate limit exceeded', {
248
+ key: rateLimitKey,
249
+ requestCount: requestTimes.length,
250
+ maxRequests,
251
+ retryAfter,
252
+ });
253
+
254
+ throw new McpRateLimitError(
255
+ `Rate limit exceeded. Too many ${request.method} requests. Try again in ${retryAfter} seconds.`,
256
+ retryAfter,
257
+ );
258
+ }
259
+
260
+ // Add current request time
261
+ requestTimes.push(now);
262
+ this.rateLimitWindows.set(rateLimitKey, requestTimes);
263
+
264
+ logger.debug('Rate limit check passed', {
265
+ key: rateLimitKey,
266
+ currentCount: requestTimes.length,
267
+ maxRequests,
268
+ });
269
+
270
+ return request;
271
+ },
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Get all registered hooks
277
+ */
278
+ getAllHooks(): Hook[] {
279
+ return [...this.requestHooks, ...this.responseHooks, ...this.errorHooks];
280
+ }
281
+
282
+ /**
283
+ * Remove a hook by name
284
+ */
285
+ removeHook(name: string): boolean {
286
+ const removeFromArray = (arr: Hook[]) => {
287
+ const index = arr.findIndex((hook) => hook.name === name);
288
+ if (index !== -1) {
289
+ arr.splice(index, 1);
290
+ return true;
291
+ }
292
+ return false;
293
+ };
294
+
295
+ return (
296
+ removeFromArray(this.requestHooks) ||
297
+ removeFromArray(this.responseHooks) ||
298
+ removeFromArray(this.errorHooks)
299
+ );
300
+ }
301
+ }
302
+
303
+ // Global hooks manager instance
304
+ export const hooksManager = new HooksManager();
@@ -0,0 +1,44 @@
1
+ // ABOUTME: Type definitions for MCP Request/Response Hooks
2
+ // ABOUTME: Defines hook interfaces, context, and pipeline structures
3
+
4
+ export type HookType = 'request' | 'response' | 'error';
5
+
6
+ export interface HookContext {
7
+ sessionId: string;
8
+ startTime: number;
9
+ metadata?: Record<string, any>;
10
+ }
11
+
12
+ export interface BaseHook {
13
+ name: string;
14
+ type: HookType;
15
+ priority: number; // Lower numbers execute first
16
+ description?: string;
17
+ critical?: boolean; // If true, hook failures will fail the entire request
18
+ }
19
+
20
+ export interface RequestHook extends BaseHook {
21
+ type: 'request';
22
+ condition?: (request: any, context: HookContext) => boolean;
23
+ execute: (request: any, context: HookContext) => Promise<any | undefined>;
24
+ }
25
+
26
+ export interface ResponseHook extends BaseHook {
27
+ type: 'response';
28
+ condition?: (response: any, request: any, context: HookContext) => boolean;
29
+ execute: (response: any, request: any, context: HookContext) => Promise<any | undefined>;
30
+ }
31
+
32
+ export interface ErrorHook extends BaseHook {
33
+ type: 'error';
34
+ condition?: (error: Error, request: any, context: HookContext) => boolean;
35
+ execute: (error: Error, request: any, context: HookContext) => Promise<Error | undefined>;
36
+ }
37
+
38
+ export type Hook = RequestHook | ResponseHook | ErrorHook;
39
+
40
+ export interface HookPipeline {
41
+ processRequest(request: any, context: HookContext): Promise<any>;
42
+ processResponse(response: any, request: any, context: HookContext): Promise<any>;
43
+ processError(error: Error, request: any, context: HookContext): Promise<Error>;
44
+ }
@@ -0,0 +1,243 @@
1
+ // ABOUTME: HTTP server implementation for MCP social media server
2
+ // ABOUTME: Provides HTTP/SSE transport as an alternative to stdio
3
+
4
+ import { randomUUID } from 'node:crypto';
5
+ import { type IncomingMessage, type ServerResponse, createServer } from 'node:http';
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
8
+ import type { ApiClient } from './api-client.js';
9
+ import { config } from './config.js';
10
+ import { logger } from './logger.js';
11
+ import type { SessionManager } from './session-manager.js';
12
+
13
+ export interface HttpServerOptions {
14
+ port?: number;
15
+ host?: string;
16
+ enableJsonResponse?: boolean;
17
+ corsOrigin?: string;
18
+ }
19
+
20
+ export class HttpMcpServer {
21
+ private httpServer: ReturnType<typeof createServer> | null = null;
22
+ private mcpServer: McpServer | null = null;
23
+ private transport: StreamableHTTPServerTransport | null = null;
24
+ private readonly options: Required<HttpServerOptions>;
25
+
26
+ constructor(
27
+ private readonly sessionManager: SessionManager,
28
+ private readonly apiClient: ApiClient,
29
+ options: HttpServerOptions = {},
30
+ ) {
31
+ this.options = {
32
+ port: options.port ?? 3000,
33
+ host: options.host ?? 'localhost',
34
+ enableJsonResponse: options.enableJsonResponse ?? false,
35
+ corsOrigin: options.corsOrigin ?? '*',
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Start the HTTP server
41
+ */
42
+ async start(): Promise<void> {
43
+ if (this.httpServer) {
44
+ throw new Error('HTTP server already started');
45
+ }
46
+
47
+ // Create single MCP server instance
48
+ await this.createMcpServer();
49
+
50
+ this.httpServer = createServer(async (req, res) => {
51
+ // Add CORS headers
52
+ res.setHeader('Access-Control-Allow-Origin', this.options.corsOrigin);
53
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
54
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Mcp-Session-Id');
55
+ res.setHeader('Access-Control-Max-Age', '86400');
56
+
57
+ // Handle preflight requests
58
+ if (req.method === 'OPTIONS') {
59
+ res.writeHead(204);
60
+ res.end();
61
+ return;
62
+ }
63
+
64
+ // Log request
65
+ logger.debug('HTTP request received', {
66
+ method: req.method,
67
+ url: req.url,
68
+ headers: req.headers,
69
+ });
70
+
71
+ try {
72
+ await this.handleRequest(req, res);
73
+ } catch (error) {
74
+ logger.error('Error handling HTTP request', { error });
75
+ if (!res.headersSent) {
76
+ res.writeHead(500, { 'Content-Type': 'application/json' });
77
+ res.end(JSON.stringify({ error: 'Internal server error' }));
78
+ }
79
+ }
80
+ });
81
+
82
+ return new Promise((resolve, reject) => {
83
+ this.httpServer?.listen(this.options.port, this.options.host, () => {
84
+ logger.info('HTTP MCP server started', {
85
+ host: this.options.host,
86
+ port: this.options.port,
87
+ enableJsonResponse: this.options.enableJsonResponse,
88
+ });
89
+ resolve();
90
+ });
91
+
92
+ this.httpServer?.on('error', (error) => {
93
+ logger.error('HTTP server error', { error });
94
+ reject(error);
95
+ });
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Stop the HTTP server
101
+ */
102
+ async stop(): Promise<void> {
103
+ if (!this.httpServer) {
104
+ return;
105
+ }
106
+
107
+ // Close MCP server connection
108
+ if (this.mcpServer) {
109
+ try {
110
+ await this.mcpServer.close();
111
+ logger.debug('Closed MCP server');
112
+ } catch (error) {
113
+ logger.error('Error closing MCP server', { error });
114
+ }
115
+ this.mcpServer = null;
116
+ this.transport = null;
117
+ }
118
+
119
+ // Close HTTP server
120
+ return new Promise((resolve, reject) => {
121
+ this.httpServer?.close((error) => {
122
+ if (error) {
123
+ logger.error('Error closing HTTP server', { error });
124
+ reject(error);
125
+ } else {
126
+ logger.info('HTTP MCP server stopped');
127
+ this.httpServer = null;
128
+ resolve();
129
+ }
130
+ });
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Handle incoming HTTP requests
136
+ */
137
+ private async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
138
+ const url = new URL(req.url || '/', `http://${req.headers.host}`);
139
+
140
+ // Only handle requests to /mcp endpoint
141
+ if (url.pathname !== '/mcp') {
142
+ res.writeHead(404, { 'Content-Type': 'application/json' });
143
+ res.end(JSON.stringify({ error: 'Not found' }));
144
+ return;
145
+ }
146
+
147
+ // Get or create session ID
148
+ const sessionId = (req.headers['mcp-session-id'] as string) || randomUUID();
149
+
150
+ // Set session ID in response header for client tracking
151
+ res.setHeader('Mcp-Session-Id', sessionId);
152
+
153
+ if (!this.transport) {
154
+ res.writeHead(500, { 'Content-Type': 'application/json' });
155
+ res.end(JSON.stringify({ error: 'Transport not found' }));
156
+ return;
157
+ }
158
+
159
+ // Parse body if POST
160
+ let body: unknown;
161
+ if (req.method === 'POST') {
162
+ body = await this.parseRequestBody(req);
163
+ }
164
+
165
+ // Let transport handle the request
166
+ await this.transport.handleRequest(req, res, body);
167
+ }
168
+
169
+ /**
170
+ * Create the MCP server instance with HTTP transport
171
+ */
172
+ private async createMcpServer(): Promise<void> {
173
+ // Import the registration functions dynamically to avoid circular dependencies
174
+ const { hooksManager } = await import('./hooks/index.js');
175
+ const { registerPrompts } = await import('./prompts/index.js');
176
+ const { registerResources } = await import('./resources/index.js');
177
+ const { registerRoots } = await import('./roots/index.js');
178
+ const { registerTools } = await import('./tools/index.js');
179
+
180
+ // Import version from config
181
+ const { version } = await import('./config.js');
182
+
183
+ // Create MCP server
184
+ this.mcpServer = new McpServer({
185
+ name: 'mcp-agent-social',
186
+ version,
187
+ });
188
+
189
+ // Create HTTP transport
190
+ this.transport = new StreamableHTTPServerTransport({
191
+ sessionIdGenerator: randomUUID,
192
+ enableJsonResponse: this.options.enableJsonResponse,
193
+ onsessioninitialized: (id) => {
194
+ logger.debug('Session initialized', { sessionId: id });
195
+ },
196
+ });
197
+
198
+ // Register all capabilities
199
+ registerTools(this.mcpServer, {
200
+ sessionManager: this.sessionManager,
201
+ apiClient: this.apiClient,
202
+ hooksManager,
203
+ });
204
+ registerResources(this.mcpServer, {
205
+ apiClient: this.apiClient,
206
+ sessionManager: this.sessionManager,
207
+ hooksManager,
208
+ });
209
+ registerPrompts(this.mcpServer, {
210
+ apiClient: this.apiClient,
211
+ sessionManager: this.sessionManager,
212
+ hooksManager,
213
+ });
214
+ registerRoots(this.mcpServer, {
215
+ apiClient: this.apiClient,
216
+ sessionManager: this.sessionManager,
217
+ hooksManager,
218
+ });
219
+
220
+ // Connect transport
221
+ await this.mcpServer.connect(this.transport);
222
+ }
223
+
224
+ /**
225
+ * Parse request body as JSON
226
+ */
227
+ private async parseRequestBody(req: IncomingMessage): Promise<unknown> {
228
+ return new Promise((resolve, reject) => {
229
+ let body = '';
230
+ req.on('data', (chunk) => {
231
+ body += chunk.toString();
232
+ });
233
+ req.on('end', () => {
234
+ try {
235
+ resolve(body ? JSON.parse(body) : undefined);
236
+ } catch (_error) {
237
+ reject(new Error('Invalid JSON body'));
238
+ }
239
+ });
240
+ req.on('error', reject);
241
+ });
242
+ }
243
+ }