Skip to content

Commit 7e5edfa

Browse files
committed
mcp startup issues due to stdio
1 parent aa10260 commit 7e5edfa

15 files changed

Lines changed: 1312 additions & 19 deletions

debug-mcp.js

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
const { spawn } = require('child_process');
2+
const path = require('path');
3+
4+
// Simple debug script to test MCP communication
5+
async function testMCPCommunication() {
6+
console.log('Starting MCP server debug test...');
7+
8+
const serverPath = path.join(__dirname, 'build/index.js');
9+
10+
const serverProcess = spawn('node', [serverPath], {
11+
env: {
12+
...process.env,
13+
GITHUB_TOKEN: 'test-token',
14+
GITHUB_OWNER: 'test-owner',
15+
GITHUB_REPO: 'test-repo'
16+
},
17+
stdio: ['pipe', 'pipe', 'pipe']
18+
});
19+
20+
let stdoutData = '';
21+
let stderrData = '';
22+
let responseReceived = false;
23+
24+
serverProcess.stdout.on('data', (data) => {
25+
const chunk = data.toString();
26+
stdoutData += chunk;
27+
console.log('STDOUT RECEIVED:', JSON.stringify(chunk));
28+
29+
// Check if we got a JSON response
30+
try {
31+
const lines = chunk.split('\n').filter(line => line.trim());
32+
for (const line of lines) {
33+
if (line.trim()) {
34+
const parsed = JSON.parse(line);
35+
console.log('VALID JSON RESPONSE:', parsed);
36+
responseReceived = true;
37+
}
38+
}
39+
} catch (e) {
40+
console.log('Non-JSON on stdout:', chunk);
41+
}
42+
});
43+
44+
serverProcess.stderr.on('data', (data) => {
45+
const chunk = data.toString();
46+
stderrData += chunk;
47+
console.log('STDERR RECEIVED:', JSON.stringify(chunk));
48+
});
49+
50+
serverProcess.on('error', (error) => {
51+
console.error('Process error:', error);
52+
});
53+
54+
serverProcess.on('exit', (code, signal) => {
55+
console.log('Process exited with code:', code, 'signal:', signal);
56+
});
57+
58+
// Wait for server startup
59+
console.log('Waiting for server startup...');
60+
await new Promise(resolve => setTimeout(resolve, 3000));
61+
62+
console.log('Sending initialize request...');
63+
const initRequest = {
64+
jsonrpc: "2.0",
65+
id: 1,
66+
method: "initialize",
67+
params: {
68+
protocolVersion: "2024-11-05",
69+
capabilities: {},
70+
clientInfo: {
71+
name: "debug-client",
72+
version: "1.0.0"
73+
}
74+
}
75+
};
76+
77+
serverProcess.stdin.write(JSON.stringify(initRequest) + '\n');
78+
console.log('Sent:', JSON.stringify(initRequest));
79+
80+
// Wait for response
81+
await new Promise(resolve => setTimeout(resolve, 2000));
82+
83+
console.log('\n=== RESULTS ===');
84+
console.log('Response received:', responseReceived);
85+
console.log('Stdout length:', stdoutData.length);
86+
console.log('Stderr length:', stderrData.length);
87+
console.log('Full stdout:', JSON.stringify(stdoutData));
88+
console.log('Full stderr (truncated):', JSON.stringify(stderrData.substring(0, 500)));
89+
90+
serverProcess.kill('SIGTERM');
91+
}
92+
93+
testMCPCommunication().catch(console.error);

debug-simple.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
const { spawn } = require('child_process');
2+
const path = require('path');
3+
4+
// Simpler test with better error handling
5+
async function testServerBasic() {
6+
console.log('Testing basic MCP server communication...');
7+
8+
const serverPath = path.join(__dirname, 'build/index.js');
9+
10+
const serverProcess = spawn('node', [serverPath], {
11+
env: {
12+
...process.env,
13+
GITHUB_TOKEN: 'test-token',
14+
GITHUB_OWNER: 'test-owner',
15+
GITHUB_REPO: 'test-repo'
16+
},
17+
stdio: ['pipe', 'pipe', 'pipe']
18+
});
19+
20+
let hasResponded = false;
21+
let responseData = '';
22+
23+
serverProcess.stdout.on('data', (data) => {
24+
const chunk = data.toString();
25+
responseData += chunk;
26+
console.log('Got stdout:', JSON.stringify(chunk));
27+
hasResponded = true;
28+
});
29+
30+
serverProcess.stderr.on('data', (data) => {
31+
console.log('Got stderr:', data.toString().substring(0, 200) + '...');
32+
});
33+
34+
serverProcess.on('error', (error) => {
35+
console.error('Process error:', error);
36+
});
37+
38+
serverProcess.on('exit', (code, signal) => {
39+
console.log('Process exited:', code, signal);
40+
});
41+
42+
// Wait for server to be ready
43+
console.log('Waiting for server startup...');
44+
await new Promise(resolve => setTimeout(resolve, 5000));
45+
46+
// Send MCP request
47+
console.log('Sending MCP request...');
48+
const request = JSON.stringify({
49+
jsonrpc: "2.0",
50+
id: 1,
51+
method: "initialize",
52+
params: {
53+
protocolVersion: "2024-11-05",
54+
capabilities: {},
55+
clientInfo: {
56+
name: "test-client",
57+
version: "1.0.0"
58+
}
59+
}
60+
}) + '\n';
61+
62+
console.log('Request:', request);
63+
serverProcess.stdin.write(request);
64+
65+
// Wait for response
66+
console.log('Waiting for response...');
67+
for (let i = 0; i < 20; i++) { // Wait up to 10 seconds
68+
if (hasResponded) break;
69+
await new Promise(resolve => setTimeout(resolve, 500));
70+
console.log(`Waiting... ${i + 1}/20`);
71+
}
72+
73+
console.log('Has responded:', hasResponded);
74+
console.log('Response data:', responseData);
75+
76+
// Cleanup
77+
serverProcess.kill();
78+
79+
return hasResponded;
80+
}
81+
82+
testServerBasic()
83+
.then(result => {
84+
console.log('Test result:', result ? 'SUCCESS' : 'FAILED');
85+
process.exit(result ? 0 : 1);
86+
})
87+
.catch(error => {
88+
console.error('Test error:', error);
89+
process.exit(1);
90+
});
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# Bidirectional Synchronization: MCP GitHub Project Manager
2+
3+
This document details the bidirectional synchronization mechanism between the MCP GitHub Project Manager and GitHub's API.
4+
5+
## 1. Real-time Task Update Flow
6+
7+
The system uses a combination of webhooks and periodic synchronization to maintain real-time updates:
8+
9+
```typescript
10+
private async handleProjectsV2ItemEvent(webhook: WebhookEvent): Promise<ResourceEvent[]> {
11+
const { action, projects_v2_item: item } = webhook.payload;
12+
13+
const events: ResourceEvent[] = [];
14+
15+
// Handle the item itself (could be an issue or pull request)
16+
if (item.content_type === 'Issue' && item.content_node_id) {
17+
const eventType = this.mapActionToEventType(action);
18+
if (eventType) {
19+
events.push({
20+
id: `${webhook.id}-issue-${item.content_node_id}`,
21+
type: eventType,
22+
resourceType: ResourceType.ISSUE,
23+
resourceId: item.content_node_id,
24+
// ...other properties
25+
});
26+
}
27+
}
28+
29+
return events;
30+
}
31+
```
32+
33+
The flow works as follows:
34+
1. GitHub sends webhook events when tasks (issues/PRs) are updated
35+
2. The `GitHubWebhookHandler` processes these events and converts them to standardized `ResourceEvent` objects
36+
3. These events are then propagated through the system to update local state
37+
4. For outgoing changes, the MCP server uses GitHub's GraphQL API to push updates
38+
39+
## 2. Detecting Human Team Member Changes
40+
41+
Changes made by team members in GitHub Projects are detected through:
42+
43+
```typescript
44+
private async syncResourceTypeWithMetadata(
45+
type: ResourceType,
46+
metadata: SyncMetadata[]
47+
): Promise<{ synced: number; skipped: number }> {
48+
// Check which resources need syncing
49+
const resourcesNeedingSync = await this.checkResourceChanges(metadata);
50+
51+
this.logger.info(`${type}: ${resourcesNeedingSync.length} resources need syncing out of ${metadata.length} total`);
52+
53+
// Sync resources that need updating
54+
for (const resourceId of resourcesNeedingSync) {
55+
try {
56+
await this.syncSingleResource(type, resourceId);
57+
synced++;
58+
} catch (error) {
59+
this.logger.warn(`Failed to sync ${type} ${resourceId}:`, error);
60+
// Continue with other resources
61+
}
62+
}
63+
// ...
64+
}
65+
```
66+
67+
The system:
68+
1. Maintains metadata about each resource including last modified timestamps
69+
2. Periodically checks for changes using the `checkResourceChanges` method
70+
3. Uses ETag/version tracking to detect changes efficiently
71+
4. Synchronizes only resources that have changed since last sync
72+
73+
## 3. Progress Tracking and Status Updates
74+
75+
When developers update task status in GitHub, the system:
76+
77+
1. Receives webhook notifications for status changes
78+
2. Updates the local cache with new status information
79+
3. Propagates these changes to any subscribed clients
80+
4. Updates progress metrics based on task status changes
81+
82+
The implementation leverages the event system to track these changes in real-time.
83+
84+
## 4. Components for Event Handling and Synchronization
85+
86+
The key components responsible for handling events and synchronization are:
87+
88+
1. **GitHubWebhookHandler**: Processes incoming webhook events from GitHub
89+
- Converts GitHub-specific events to standardized resource events
90+
- Handles different event types (projects, issues, PRs, etc.)
91+
92+
2. **GitHubStateSyncService**: Manages state synchronization
93+
- Performs initial sync on startup
94+
- Handles periodic syncs for specific resource types
95+
- Maintains metadata for efficient change detection
96+
97+
3. **ResourceCache**: Stores synchronized resources
98+
- Provides type-safe access to resources
99+
- Tracks resource metadata for change detection
100+
101+
4. **EventSubscriptionManager**: Manages client subscriptions to events
102+
- Allows filtering by resource type and event type
103+
- Delivers events to subscribed clients
104+
105+
Conflict resolution uses optimistic locking with version checks:
106+
107+
```typescript
108+
// Example field update with retry
109+
async function updateFieldValue(
110+
projectId: string,
111+
itemId: string,
112+
fieldId: string,
113+
value: any
114+
): Promise<void> {
115+
await retryManager.executeWithRetry(async () => {
116+
const lock = await lockManager.acquireLock(itemId);
117+
try {
118+
await fieldManager.validateValue(fieldId, value);
119+
await projectRepo.updateFieldValue(projectId, itemId, fieldId, value);
120+
} finally {
121+
await lockManager.releaseLock(itemId, lock.id);
122+
}
123+
});
124+
}
125+
```
126+
127+
## 5. Recalibrating Task Estimates and Project Timelines
128+
129+
The system recalibrates task estimates and project timelines through:
130+
131+
1. **AI-powered analysis**: The system includes AI tools that can analyze task complexity and adjust estimates
132+
2. **Event-based triggers**: When tasks are updated, the system can trigger re-estimation
133+
3. **Progressive response**: Updates are streamed to clients as they occur
134+
135+
```
136+
# 3. Get next task recommendations
137+
get_next_task({
138+
"sprintCapacity": 40,
139+
"teamSkills": ["react", "node.js", "typescript", "python"],
140+
"maxComplexity": 7,
141+
"includeAnalysis": true,
142+
"excludeBlocked": true
143+
})
144+
```
145+
146+
The AI features can analyze task progress and provide updated recommendations as the project evolves.
147+
148+
## Architecture Diagram
149+
150+
```
151+
┌─────────────────┐ ┌─────────────────┐
152+
│ GitHub API │◄────────┤ Webhook Handler │
153+
│ (Projects v2) │ └────────┬────────┘
154+
└────────┬────────┘ │
155+
│ ▼
156+
│ ┌─────────────────┐
157+
│ │ Event System │
158+
│ │ - Subscription │
159+
│ │ - Event Store │
160+
│ └────────┬────────┘
161+
│ │
162+
▼ ▼
163+
┌─────────────────┐ ┌─────────────────┐
164+
│ GitHub Repos │◄────────┤ Resource Cache │
165+
│ - Project │ │ - Type Safety │
166+
│ - Issue │ │ - Metadata │
167+
│ - Milestone │ └────────┬────────┘
168+
└────────┬────────┘ │
169+
│ ▼
170+
│ ┌─────────────────┐
171+
│ │ Sync Service │
172+
▼ │ - Change Detect │
173+
┌─────────────────┐ │ - Versioning │
174+
│ MCP Layer │◄────────┤ - Persistence │
175+
└─────────────────┘ └─────────────────┘
176+
```
177+
178+
This bidirectional synchronization architecture ensures that changes flow seamlessly between GitHub and the MCP system, maintaining consistency while providing real-time updates to all stakeholders.

jest.config.cjs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@ module.exports = {
1111
'ts-jest',
1212
{
1313
tsconfig: {
14-
module: 'commonjs',
14+
module: 'esnext',
15+
target: 'es2022',
16+
moduleResolution: 'node'
1517
},
16-
useESM: false,
18+
useESM: true,
1719
},
1820
],
1921
},
@@ -22,6 +24,7 @@ module.exports = {
2224
testMatch: [
2325
'**/__tests__/**/*.test.ts',
2426
'**/__tests__/**/*.spec.ts',
27+
'**/__tests__/**/*.e2e.ts',
2528
'**/tests/**/*.test.ts',
2629
'**/tests/**/*.spec.ts',
2730
],

jest.e2e.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const config = {
55
preset: 'ts-jest/presets/default-esm',
66
testEnvironment: 'node',
77
testMatch: ['**/*.e2e.ts'],
8-
setupFiles: ['<rootDir>/src/__tests__/e2e/setup.ts'],
8+
setupFilesAfterEnv: ['<rootDir>/src/__tests__/e2e/setup.ts'],
99
transformIgnorePatterns: [
1010
'node_modules/(?!(@octokit)/)',
1111
],

0 commit comments

Comments
 (0)