agent-planner-mcp 0.3.1 → 0.5.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.
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Session Manager for MCP HTTP Server
3
+ *
4
+ * Manages client sessions for the Streamable HTTP transport.
5
+ * Each session tracks:
6
+ * - Unique session ID (Mcp-Session-Id header)
7
+ * - Initialization state
8
+ * - Client capabilities
9
+ * - Creation and last activity timestamps
10
+ */
11
+
12
+ const { randomUUID } = require('crypto');
13
+
14
+ class SessionManager {
15
+ constructor(options = {}) {
16
+ // In-memory session storage
17
+ this.sessions = new Map();
18
+
19
+ // Configuration
20
+ this.sessionTimeout = options.sessionTimeout || 30 * 60 * 1000; // 30 minutes default
21
+ this.cleanupInterval = options.cleanupInterval || 5 * 60 * 1000; // 5 minutes default
22
+
23
+ // Start periodic cleanup
24
+ this.startCleanup();
25
+
26
+ console.error('SessionManager initialized');
27
+ }
28
+
29
+ /**
30
+ * Create a new session
31
+ * @returns {string} Session ID
32
+ */
33
+ createSession() {
34
+ const sessionId = randomUUID();
35
+
36
+ const session = {
37
+ id: sessionId,
38
+ initialized: false,
39
+ clientCapabilities: null,
40
+ createdAt: Date.now(),
41
+ lastActivityAt: Date.now()
42
+ };
43
+
44
+ this.sessions.set(sessionId, session);
45
+
46
+ console.error(`Session created: ${sessionId}`);
47
+ return sessionId;
48
+ }
49
+
50
+ /**
51
+ * Get a session by ID
52
+ * @param {string} sessionId - Session ID
53
+ * @returns {Object|null} Session object or null if not found
54
+ */
55
+ getSession(sessionId) {
56
+ if (!sessionId) {
57
+ return null;
58
+ }
59
+
60
+ const session = this.sessions.get(sessionId);
61
+
62
+ if (!session) {
63
+ return null;
64
+ }
65
+
66
+ // Update last activity
67
+ session.lastActivityAt = Date.now();
68
+
69
+ return session;
70
+ }
71
+
72
+ /**
73
+ * Mark a session as initialized
74
+ * @param {string} sessionId - Session ID
75
+ * @param {Object} clientCapabilities - Client capabilities from initialize request
76
+ */
77
+ initializeSession(sessionId, clientCapabilities) {
78
+ const session = this.sessions.get(sessionId);
79
+
80
+ if (!session) {
81
+ throw new Error(`Session not found: ${sessionId}`);
82
+ }
83
+
84
+ session.initialized = true;
85
+ session.clientCapabilities = clientCapabilities;
86
+ session.lastActivityAt = Date.now();
87
+
88
+ console.error(`Session initialized: ${sessionId}`);
89
+ }
90
+
91
+ /**
92
+ * Store a per-session API client
93
+ * @param {string} sessionId - Session ID
94
+ * @param {Object} apiClient - API client instance bound to user's token
95
+ */
96
+ setApiClient(sessionId, apiClient) {
97
+ const session = this.sessions.get(sessionId);
98
+ if (session) {
99
+ session.apiClient = apiClient;
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Get the per-session API client
105
+ * @param {string} sessionId - Session ID
106
+ * @returns {Object|null} API client or null
107
+ */
108
+ getApiClient(sessionId) {
109
+ const session = this.sessions.get(sessionId);
110
+ return session?.apiClient || null;
111
+ }
112
+
113
+ /**
114
+ * Check if a session is initialized
115
+ * @param {string} sessionId - Session ID
116
+ * @returns {boolean} True if initialized
117
+ */
118
+ isInitialized(sessionId) {
119
+ const session = this.sessions.get(sessionId);
120
+ return session && session.initialized;
121
+ }
122
+
123
+ /**
124
+ * Delete a session
125
+ * @param {string} sessionId - Session ID
126
+ * @returns {boolean} True if session was deleted
127
+ */
128
+ deleteSession(sessionId) {
129
+ const deleted = this.sessions.delete(sessionId);
130
+
131
+ if (deleted) {
132
+ console.error(`Session deleted: ${sessionId}`);
133
+ }
134
+
135
+ return deleted;
136
+ }
137
+
138
+ /**
139
+ * Clean up expired sessions
140
+ */
141
+ cleanupExpiredSessions() {
142
+ const now = Date.now();
143
+ let cleanedCount = 0;
144
+
145
+ for (const [sessionId, session] of this.sessions.entries()) {
146
+ const age = now - session.lastActivityAt;
147
+
148
+ if (age > this.sessionTimeout) {
149
+ this.sessions.delete(sessionId);
150
+ cleanedCount++;
151
+ console.error(`Session expired: ${sessionId} (inactive for ${Math.round(age / 1000)}s)`);
152
+ }
153
+ }
154
+
155
+ if (cleanedCount > 0) {
156
+ console.error(`Cleaned up ${cleanedCount} expired sessions`);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Start periodic cleanup of expired sessions
162
+ */
163
+ startCleanup() {
164
+ this.cleanupTimer = setInterval(() => {
165
+ this.cleanupExpiredSessions();
166
+ }, this.cleanupInterval);
167
+
168
+ // Prevent the timer from keeping the process alive
169
+ this.cleanupTimer.unref();
170
+ }
171
+
172
+ /**
173
+ * Stop periodic cleanup
174
+ */
175
+ stopCleanup() {
176
+ if (this.cleanupTimer) {
177
+ clearInterval(this.cleanupTimer);
178
+ this.cleanupTimer = null;
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Get session statistics
184
+ * @returns {Object} Statistics object
185
+ */
186
+ getStats() {
187
+ const now = Date.now();
188
+ const stats = {
189
+ total: this.sessions.size,
190
+ initialized: 0,
191
+ uninitialized: 0,
192
+ sessions: []
193
+ };
194
+
195
+ for (const [sessionId, session] of this.sessions.entries()) {
196
+ if (session.initialized) {
197
+ stats.initialized++;
198
+ } else {
199
+ stats.uninitialized++;
200
+ }
201
+
202
+ stats.sessions.push({
203
+ id: sessionId,
204
+ initialized: session.initialized,
205
+ age: Math.round((now - session.createdAt) / 1000),
206
+ idleTime: Math.round((now - session.lastActivityAt) / 1000)
207
+ });
208
+ }
209
+
210
+ return stats;
211
+ }
212
+
213
+ /**
214
+ * Destroy the session manager
215
+ */
216
+ destroy() {
217
+ this.stopCleanup();
218
+ this.sessions.clear();
219
+ console.error('SessionManager destroyed');
220
+ }
221
+ }
222
+
223
+ module.exports = { SessionManager };
package/src/setup.js CHANGED
@@ -124,7 +124,7 @@ function createEnvFile(config) {
124
124
  API_URL=${config.apiUrl}
125
125
  USER_API_TOKEN=${config.token}
126
126
  MCP_SERVER_NAME=planning-system
127
- MCP_SERVER_VERSION=0.2.0
127
+ MCP_SERVER_VERSION=${require('../package.json').version}
128
128
  NODE_ENV=production
129
129
  `;
130
130
 
@@ -136,7 +136,7 @@ async function globalSearch(query) {
136
136
  }
137
137
 
138
138
  // Check if response has categorized results (plans, nodes, etc.)
139
- const categories = ['plans', 'nodes', 'comments', 'logs', 'artifacts'];
139
+ const categories = ['plans', 'nodes', 'comments', 'logs'];
140
140
  const allResults = [];
141
141
 
142
142
  categories.forEach(category => {
@@ -156,9 +156,11 @@ async function globalSearch(query) {
156
156
  return allResults;
157
157
  }
158
158
 
159
- // Generic handler for any object with arrays
160
- Object.keys(response).forEach(key => {
161
- if (Array.isArray(response[key]) && key !== 'results') {
159
+ // Generic handler for any object with arrays - log warning about unexpected format
160
+ const unknownKeys = Object.keys(response).filter(key => Array.isArray(response[key]) && key !== 'results');
161
+ if (unknownKeys.length > 0) {
162
+ console.error(`[search-wrapper] Warning: Unexpected response format with keys: ${unknownKeys.join(', ')}. Normalizing results.`);
163
+ unknownKeys.forEach(key => {
162
164
  response[key].forEach(item => {
163
165
  allResults.push({
164
166
  ...item,
@@ -167,8 +169,12 @@ async function globalSearch(query) {
167
169
  source: 'global_search'
168
170
  });
169
171
  });
170
- }
171
- });
172
+ });
173
+ }
174
+
175
+ if (allResults.length === 0 && Object.keys(response).length > 0) {
176
+ console.error(`[search-wrapper] Warning: Could not extract results from response. Keys: ${Object.keys(response).join(', ')}`);
177
+ }
172
178
 
173
179
  return allResults;
174
180
  } catch (error) {