@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
package/src/index.ts ADDED
@@ -0,0 +1,213 @@
1
+ // ABOUTME: Main entry point for the MCP Agent Social Media Server
2
+ // ABOUTME: Initializes and starts the MCP server with social media tools
3
+
4
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
+ import { ApiClient } from './api-client.js';
7
+ import { ENV_KEYS, config, validateConfig, version } from './config.js';
8
+ import { hooksManager } from './hooks/index.js';
9
+ import { HttpMcpServer } from './http-server.js';
10
+ import { logger } from './logger.js';
11
+ import { metrics } from './metrics.js';
12
+ import { registerPrompts } from './prompts/index.js';
13
+ import { registerResources } from './resources/index.js';
14
+ import { registerRoots } from './roots/index.js';
15
+ import { SessionManager } from './session-manager.js';
16
+ import { registerTools } from './tools/index.js';
17
+
18
+ // Initialize shared components
19
+ const sessionManager = new SessionManager();
20
+ const apiClient = new ApiClient();
21
+
22
+ // Server instances
23
+ let mcpServer: McpServer | null = null;
24
+ let httpServer: HttpMcpServer | null = null;
25
+
26
+ // Store cleanup interval globally for shutdown
27
+ let cleanupInterval: ReturnType<typeof setInterval> | null = null;
28
+ let keepAliveInterval: ReturnType<typeof setInterval> | null = null;
29
+
30
+ async function main() {
31
+ try {
32
+ // Log startup information
33
+ logger.info('MCP Server starting', {
34
+ nodeVersion: process.version,
35
+ platform: process.platform,
36
+ pid: process.pid,
37
+ logLevel: process.env[ENV_KEYS.LOG_LEVEL] || 'INFO',
38
+ });
39
+
40
+ validateConfig();
41
+
42
+ // Log configuration (without sensitive data)
43
+ logger.info(`Starting MCP server for team: ${config.teamName}`);
44
+ logger.debug('Configuration loaded', {
45
+ baseUrl: config.socialApiBaseUrl,
46
+ teamName: config.teamName,
47
+ timeout: config.apiTimeout,
48
+ logLevel: config.logLevel,
49
+ });
50
+
51
+ // Determine transport mode
52
+ const transportMode = process.env[ENV_KEYS.MCP_TRANSPORT] || 'stdio';
53
+
54
+ if (transportMode === 'http') {
55
+ // HTTP mode
56
+ const httpPort = Number.parseInt(process.env[ENV_KEYS.MCP_HTTP_PORT] || '3000', 10);
57
+ const httpHost = process.env[ENV_KEYS.MCP_HTTP_HOST] || 'localhost';
58
+
59
+ logger.info('Starting in HTTP mode', { port: httpPort, host: httpHost });
60
+
61
+ httpServer = new HttpMcpServer(sessionManager, apiClient, {
62
+ port: httpPort,
63
+ host: httpHost,
64
+ enableJsonResponse: process.env[ENV_KEYS.MCP_ENABLE_JSON] === 'true',
65
+ corsOrigin: process.env[ENV_KEYS.MCP_CORS_ORIGIN] || '*',
66
+ });
67
+
68
+ await httpServer.start();
69
+ } else {
70
+ // Stdio mode (default)
71
+ logger.info('Starting in stdio mode');
72
+
73
+ mcpServer = new McpServer({
74
+ name: 'mcp-agent-social',
75
+ version,
76
+ });
77
+
78
+ const transport = new StdioServerTransport();
79
+
80
+ // Register all capabilities
81
+ registerTools(mcpServer, { sessionManager, apiClient, hooksManager });
82
+ registerResources(mcpServer, { apiClient, sessionManager, hooksManager });
83
+ registerPrompts(mcpServer, { apiClient, sessionManager, hooksManager });
84
+ registerRoots(mcpServer, { apiClient, sessionManager, hooksManager });
85
+
86
+ // Connect to transport
87
+ logger.debug('Connecting server to transport');
88
+ await mcpServer.connect(transport);
89
+ logger.info('Server connected successfully');
90
+ }
91
+
92
+ // The transport itself doesn't expose error handlers, but we can handle stdio events
93
+ process.stdin.on('error', (error) => {
94
+ logger.error('Stdin error', { error: error.message, stack: error.stack });
95
+ shutdown('STDIN_ERROR');
96
+ });
97
+
98
+ process.stdout.on('error', (error) => {
99
+ logger.error('Stdout error', { error: error.message, stack: error.stack });
100
+ shutdown('STDOUT_ERROR');
101
+ });
102
+
103
+ process.stdin.on('close', () => {
104
+ logger.warn('Stdin closed unexpectedly');
105
+ shutdown('STDIN_CLOSE');
106
+ });
107
+
108
+ process.stdin.on('end', () => {
109
+ logger.warn('Stdin ended');
110
+ shutdown('STDIN_END');
111
+ });
112
+ logger.info('MCP Agent Social Server running', {
113
+ toolsCount: 3,
114
+ resourcesCount: 6,
115
+ promptsCount: 8,
116
+ rootsEnabled: true,
117
+ hooksCount: hooksManager.getAllHooks().length,
118
+ transport: 'stdio',
119
+ });
120
+
121
+ // Set up graceful shutdown
122
+ process.on('SIGINT', () => shutdown('SIGINT'));
123
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
124
+
125
+ // Handle uncaught errors to prevent sudden crashes
126
+ process.on('uncaughtException', (error) => {
127
+ // Prevent infinite loops when logging EPIPE errors
128
+ if (error instanceof Error && 'code' in error && error.code === 'EPIPE') {
129
+ // Don't try to log EPIPE errors - just shutdown silently
130
+ shutdown('UNCAUGHT_EXCEPTION');
131
+ return;
132
+ }
133
+ logger.error('Uncaught exception', { error: error.message, stack: error.stack });
134
+ shutdown('UNCAUGHT_EXCEPTION');
135
+ });
136
+
137
+ process.on('unhandledRejection', (reason, promise) => {
138
+ // Prevent infinite loops when rejection is due to EPIPE errors
139
+ if (reason instanceof Error && 'code' in reason && reason.code === 'EPIPE') {
140
+ // Don't try to log EPIPE-related rejections - just shutdown silently
141
+ shutdown('UNHANDLED_REJECTION');
142
+ return;
143
+ }
144
+ logger.error('Unhandled rejection', { reason, promise });
145
+ shutdown('UNHANDLED_REJECTION');
146
+ });
147
+
148
+ // Set up periodic session cleanup (every 30 minutes)
149
+ cleanupInterval = setInterval(async () => {
150
+ const removed = await sessionManager.cleanupOldSessions(3600000); // 1 hour
151
+ if (removed > 0) {
152
+ logger.info(`Cleaned up ${removed} old sessions`);
153
+ }
154
+ }, 1800000); // 30 minutes
155
+
156
+ // Set up keepalive to prevent connection timeout
157
+ keepAliveInterval = setInterval(() => {
158
+ logger.debug('Keepalive ping', { uptime: process.uptime() });
159
+ }, 30000); // Every 30 seconds
160
+ } catch (error) {
161
+ logger.error('Failed to start server', {
162
+ error: error instanceof Error ? error.message : String(error),
163
+ stack: error instanceof Error ? error.stack : undefined,
164
+ });
165
+ process.exit(1);
166
+ }
167
+ }
168
+
169
+ async function shutdown(signal: string) {
170
+ logger.serverShutdown(signal);
171
+ logger.warn(`Received ${signal}, shutting down gracefully...`);
172
+
173
+ // Clear intervals
174
+ if (cleanupInterval) {
175
+ clearInterval(cleanupInterval);
176
+ }
177
+ if (keepAliveInterval) {
178
+ clearInterval(keepAliveInterval);
179
+ }
180
+
181
+ // Shutdown metrics collector
182
+ metrics.shutdown();
183
+
184
+ // Clean up sessions
185
+ const sessionCount = sessionManager.getSessionCount();
186
+ if (sessionCount > 0) {
187
+ logger.info(`Cleaning up ${sessionCount} active sessions...`);
188
+ await sessionManager.clearAllSessions();
189
+ }
190
+
191
+ // Close server
192
+ try {
193
+ if (httpServer) {
194
+ await httpServer.stop();
195
+ logger.info('HTTP server closed successfully');
196
+ } else if (mcpServer) {
197
+ await mcpServer.close();
198
+ logger.info('MCP server closed successfully');
199
+ }
200
+ } catch (error) {
201
+ logger.error('Error closing server', { error });
202
+ }
203
+
204
+ // Exit with appropriate code
205
+ const exitCode = signal === 'UNCAUGHT_EXCEPTION' || signal === 'UNHANDLED_REJECTION' ? 1 : 0;
206
+ process.exit(exitCode);
207
+ }
208
+
209
+ main().catch((error) => {
210
+ // Write to stderr to avoid polluting JSON-RPC stream in stdio mode
211
+ process.stderr.write(`Unhandled error: ${error}\n`);
212
+ process.exit(1);
213
+ });
package/src/logger.ts ADDED
@@ -0,0 +1,326 @@
1
+ // ABOUTME: Enhanced logging utility for the MCP Agent Social Media Server
2
+ // ABOUTME: Provides structured logging with levels, context, and performance tracking
3
+
4
+ import { appendFileSync, existsSync, writeFileSync } from 'node:fs';
5
+ import { mkdirSync } from 'node:fs';
6
+ import { basename, dirname } from 'node:path';
7
+ import { ENV_KEYS } from './config.js';
8
+
9
+ export enum LogLevel {
10
+ SILENT = -1,
11
+ ERROR = 0,
12
+ WARN = 1,
13
+ INFO = 2,
14
+ DEBUG = 3,
15
+ }
16
+
17
+ export interface LogContext {
18
+ tool?: string;
19
+ sessionId?: string;
20
+ agentName?: string;
21
+ requestId?: string;
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ export class Logger {
26
+ private static instance: Logger;
27
+ private logLevel: LogLevel;
28
+ private startTime: number;
29
+ private isStdioMode: boolean;
30
+ private logFile: string | null;
31
+ private instanceId: string;
32
+
33
+ private constructor() {
34
+ this.logLevel = this.parseLogLevel(process.env[ENV_KEYS.LOG_LEVEL] || 'INFO');
35
+ this.startTime = Date.now();
36
+ this.isStdioMode = process.env[ENV_KEYS.MCP_TRANSPORT] !== 'http';
37
+ this.logFile = process.env.LOG_FILE || null;
38
+
39
+ // Create instance identifier from current working directory + process ID
40
+ const dirName = basename(process.cwd()) || 'unknown';
41
+ this.instanceId = `${dirName}:${process.pid}`;
42
+
43
+ // Initialize log file if specified
44
+ if (this.logFile) {
45
+ try {
46
+ // Ensure directory exists
47
+ const logDir = dirname(this.logFile);
48
+ if (!existsSync(logDir)) {
49
+ mkdirSync(logDir, { recursive: true });
50
+ }
51
+
52
+ // Write startup banner to log file
53
+ const banner = `\n=== MCP Agent Social Server [${this.instanceId}] Started at ${new Date().toISOString()} ===\n`;
54
+ if (existsSync(this.logFile)) {
55
+ appendFileSync(this.logFile, banner);
56
+ } else {
57
+ writeFileSync(this.logFile, banner);
58
+ }
59
+ } catch (error) {
60
+ // If file logging fails, continue without it but log to stderr
61
+ process.stderr.write(`Failed to initialize log file ${this.logFile}: ${error}\n`);
62
+ this.logFile = null;
63
+ }
64
+ }
65
+ }
66
+
67
+ static getInstance(): Logger {
68
+ if (!Logger.instance) {
69
+ Logger.instance = new Logger();
70
+ }
71
+ return Logger.instance;
72
+ }
73
+
74
+ private parseLogLevel(level: string): LogLevel {
75
+ switch (level.toUpperCase()) {
76
+ case 'SILENT':
77
+ return LogLevel.SILENT;
78
+ case 'ERROR':
79
+ return LogLevel.ERROR;
80
+ case 'WARN':
81
+ return LogLevel.WARN;
82
+ case 'INFO':
83
+ return LogLevel.INFO;
84
+ case 'DEBUG':
85
+ return LogLevel.DEBUG;
86
+ default:
87
+ return LogLevel.INFO;
88
+ }
89
+ }
90
+
91
+ private formatMessage(level: string, message: string, context?: LogContext): string {
92
+ const timestamp = new Date().toISOString();
93
+ const uptime = Math.floor((Date.now() - this.startTime) / 1000);
94
+ let contextStr = '';
95
+
96
+ if (context) {
97
+ try {
98
+ contextStr = ` ${JSON.stringify(context)}`;
99
+ } catch (error) {
100
+ // Handle circular references or other JSON serialization errors
101
+ contextStr = ` ${JSON.stringify(
102
+ {
103
+ ...context,
104
+ _jsonError: error instanceof Error ? error.message : 'Unknown JSON error',
105
+ },
106
+ (_key, value) => {
107
+ if (typeof value === 'object' && value !== null) {
108
+ // Simple circular reference detection
109
+ if (
110
+ typeof value.toString === 'function' &&
111
+ value.toString().includes('[object Object]')
112
+ ) {
113
+ return '[Object]';
114
+ }
115
+ }
116
+ return value;
117
+ },
118
+ )}`;
119
+ }
120
+ }
121
+
122
+ return `[${timestamp}] [${level}] [${this.instanceId}] [uptime:${uptime}s] ${message}${contextStr}`;
123
+ }
124
+
125
+ private log(level: LogLevel, levelStr: string, message: string, context?: LogContext): void {
126
+ if (level <= this.logLevel) {
127
+ const formattedMessage = this.formatMessage(levelStr, message, context);
128
+
129
+ // Always write to file if configured
130
+ if (this.logFile) {
131
+ try {
132
+ appendFileSync(this.logFile, `${formattedMessage}\n`);
133
+ } catch (error) {
134
+ // If file logging fails, try stderr but don't create infinite loops
135
+ try {
136
+ process.stderr.write(`File logging failed: ${error}\n`);
137
+ } catch (_stderrError) {
138
+ // If both file and stderr fail, silently continue - avoid infinite loops
139
+ // This prevents EPIPE cascades when stdio is completely broken
140
+ }
141
+ }
142
+ }
143
+
144
+ try {
145
+ if (this.isStdioMode) {
146
+ // In stdio mode, write to stderr to avoid polluting JSON-RPC stream
147
+ process.stderr.write(`${formattedMessage}\n`);
148
+ } else {
149
+ // In HTTP mode, use console logging
150
+ if (level === LogLevel.ERROR) {
151
+ console.error(formattedMessage);
152
+ } else {
153
+ console.log(formattedMessage);
154
+ }
155
+ }
156
+ } catch (error) {
157
+ // Completely ignore EPIPE errors to prevent infinite loops
158
+ // These happen when stdout/stderr are closed (e.g., when Claude Desktop disconnects)
159
+ if (error instanceof Error && 'code' in error && error.code === 'EPIPE') {
160
+ // Silent fail on EPIPE - don't try to log this error as it creates infinite loops
161
+ return;
162
+ }
163
+
164
+ // Only rethrow non-EPIPE errors, but also protect against infinite loops
165
+ if (this.logFile) {
166
+ try {
167
+ appendFileSync(this.logFile, `Logger stdio error: ${error}\n`);
168
+ } catch (_fileError) {
169
+ // If both stdio and file fail, we're in a bad state - just return
170
+ return;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ error(message: string, context?: LogContext): void {
178
+ this.log(LogLevel.ERROR, 'ERROR', message, context);
179
+ }
180
+
181
+ warn(message: string, context?: LogContext): void {
182
+ this.log(LogLevel.WARN, 'WARN', message, context);
183
+ }
184
+
185
+ info(message: string, context?: LogContext): void {
186
+ this.log(LogLevel.INFO, 'INFO', message, context);
187
+ }
188
+
189
+ debug(message: string, context?: LogContext): void {
190
+ this.log(LogLevel.DEBUG, 'DEBUG', message, context);
191
+ }
192
+
193
+ // Tool-specific logging helpers
194
+ toolStart(toolName: string, args: unknown, context?: LogContext): void {
195
+ this.info(`Tool ${toolName} started`, {
196
+ tool: toolName,
197
+ args: args,
198
+ ...context,
199
+ });
200
+ }
201
+
202
+ toolSuccess(toolName: string, duration: number, context?: LogContext): void {
203
+ this.info(`Tool ${toolName} completed`, {
204
+ tool: toolName,
205
+ duration: `${duration}ms`,
206
+ status: 'success',
207
+ ...context,
208
+ });
209
+ }
210
+
211
+ toolError(toolName: string, error: Error, duration: number, context?: LogContext): void {
212
+ this.error(`Tool ${toolName} failed`, {
213
+ tool: toolName,
214
+ duration: `${duration}ms`,
215
+ status: 'error',
216
+ error: error.message,
217
+ stack: error.stack,
218
+ ...context,
219
+ });
220
+ }
221
+
222
+ // Session-specific logging
223
+ sessionCreated(sessionId: string, agentName: string): void {
224
+ this.info('Session created', { sessionId, agentName, event: 'session_created' });
225
+ }
226
+
227
+ sessionDeleted(sessionId: string, agentName?: string): void {
228
+ this.info('Session deleted', { sessionId, agentName, event: 'session_deleted' });
229
+ }
230
+
231
+ sessionValidationFailed(sessionId: string, reason: string): void {
232
+ this.warn('Session validation failed', {
233
+ sessionId,
234
+ reason,
235
+ event: 'session_validation_failed',
236
+ });
237
+ }
238
+
239
+ // API-specific logging
240
+ apiRequest(method: string, url: string, context?: LogContext): void {
241
+ this.debug(`API request: ${method} ${url}`, {
242
+ method,
243
+ url,
244
+ event: 'api_request',
245
+ ...context,
246
+ });
247
+ }
248
+
249
+ apiResponse(
250
+ method: string,
251
+ url: string,
252
+ status: number,
253
+ duration: number,
254
+ context?: LogContext,
255
+ ): void {
256
+ const logMethod = status >= 400 ? this.warn.bind(this) : this.debug.bind(this);
257
+
258
+ logMethod(`API response: ${method} ${url} - ${status}`, {
259
+ method,
260
+ url,
261
+ status,
262
+ duration: `${duration}ms`,
263
+ event: 'api_response',
264
+ ...context,
265
+ });
266
+ }
267
+
268
+ apiError(method: string, url: string, error: Error, context?: LogContext): void {
269
+ this.error(`API error: ${method} ${url}`, {
270
+ method,
271
+ url,
272
+ error: error.message,
273
+ event: 'api_error',
274
+ ...context,
275
+ });
276
+ }
277
+
278
+ // Performance logging
279
+ performance(operation: string, duration: number, context?: LogContext): void {
280
+ const logMethod = duration > 1000 ? this.warn.bind(this) : this.info.bind(this);
281
+
282
+ logMethod(`Performance: ${operation}`, {
283
+ operation,
284
+ duration: `${duration}ms`,
285
+ slow: duration > 1000,
286
+ ...context,
287
+ });
288
+ }
289
+
290
+ // Shutdown/death logging
291
+ serverShutdown(reason: string, context?: LogContext): void {
292
+ const shutdownMessage = `=== SERVER SHUTDOWN: ${reason} at ${new Date().toISOString()} ===`;
293
+
294
+ // Always write shutdown to file if configured, even if log level is low
295
+ if (this.logFile) {
296
+ try {
297
+ appendFileSync(this.logFile, `${shutdownMessage}\n`);
298
+ if (context) {
299
+ appendFileSync(this.logFile, `Context: ${JSON.stringify(context)}\n`);
300
+ }
301
+ appendFileSync(
302
+ this.logFile,
303
+ `Uptime: ${Math.floor((Date.now() - this.startTime) / 1000)}s\n\n`,
304
+ );
305
+ } catch (error) {
306
+ // Even if file logging fails, try to write to stderr but avoid infinite loops
307
+ try {
308
+ process.stderr.write(`File logging failed during shutdown: ${error}\n`);
309
+ } catch (_stderrError) {
310
+ // If both fail during shutdown, we can't do much - just continue
311
+ }
312
+ }
313
+ }
314
+
315
+ // Also log normally
316
+ this.error(`Server shutting down: ${reason}`, {
317
+ reason,
318
+ uptime: Math.floor((Date.now() - this.startTime) / 1000),
319
+ timestamp: new Date().toISOString(),
320
+ ...context,
321
+ });
322
+ }
323
+ }
324
+
325
+ // Export singleton instance
326
+ export const logger = Logger.getInstance();