Skip to content

Commit 76ae79d

Browse files
committed
feat(all): warn about HTTP framework dependencies during GitHub deployment validation
RuntimeDetector now checks for HTTP framework dependencies (express, fastify, hono, koa for Node.js; flask, fastapi, uvicorn, starlette, django for Python) and returns non-blocking warnings in the validation response. The frontend shows a yellow warning banner in the validation step when these frameworks are detected, since GitHub deployments only support stdio transport and HTTP/SSE servers will fail at startup.
1 parent fbf1f51 commit 76ae79d

File tree

9 files changed

+101
-6
lines changed

9 files changed

+101
-6
lines changed

services/backend/api-spec.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15298,6 +15298,12 @@
1529815298
},
1529915299
"commit_sha": {
1530015300
"type": "string"
15301+
},
15302+
"warnings": {
15303+
"type": "array",
15304+
"items": {
15305+
"type": "string"
15306+
}
1530115307
}
1530215308
},
1530315309
"required": [

services/backend/api-spec.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10598,6 +10598,10 @@ paths:
1059810598
type: string
1059910599
commit_sha:
1060010600
type: string
10601+
warnings:
10602+
type: array
10603+
items:
10604+
type: string
1060110605
required:
1060210606
- runtime
1060310607
- mcp_sdk

services/backend/src/lib/deployment/runtime-detector.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface RuntimeDetectionResult {
2727
description?: string;
2828
license?: string;
2929
};
30+
warnings?: string[];
3031
}
3132

3233
/**
@@ -89,6 +90,20 @@ export class RuntimeDetector {
8990
const mcpSdk = packageJson.dependencies?.['@modelcontextprotocol/sdk'] ||
9091
packageJson.devDependencies?.['@modelcontextprotocol/sdk'];
9192

93+
// Detect HTTP framework dependencies that suggest non-stdio transport
94+
const HTTP_FRAMEWORKS_NODE = ['express', 'fastify', 'hono', 'koa', '@hono/node-server'];
95+
const deps = packageJson.dependencies || {};
96+
const detectedFrameworks = HTTP_FRAMEWORKS_NODE.filter(fw => fw in deps);
97+
98+
const warnings: string[] = [];
99+
if (detectedFrameworks.length > 0) {
100+
warnings.push(
101+
`Detected HTTP framework dependencies: ${detectedFrameworks.join(', ')}. ` +
102+
`GitHub deployments only support stdio-based MCP servers. ` +
103+
`If this server uses HTTP/SSE transport instead of stdio, deployment will fail.`
104+
);
105+
}
106+
92107
return {
93108
runtime: 'node',
94109
mcp_sdk: {
@@ -98,7 +113,8 @@ export class RuntimeDetector {
98113
runtime: 'node'
99114
},
100115
scripts: packageJson.scripts || {},
101-
packageJson
116+
packageJson,
117+
...(warnings.length > 0 && { warnings })
102118
};
103119
} catch {
104120
return null; // package.json not found
@@ -116,6 +132,8 @@ export class RuntimeDetector {
116132
): Promise<RuntimeDetectionResult | null> {
117133
// Try requirements.txt first
118134
let mcpSdkInfo: McpSdkInfo | null = null;
135+
const HTTP_FRAMEWORKS_PYTHON = ['flask', 'fastapi', 'uvicorn', 'starlette', 'django'];
136+
const detectedPythonFrameworks: string[] = [];
119137

120138
try {
121139
const { data: file } = await octokit.repos.getContent({
@@ -160,6 +178,17 @@ export class RuntimeDetector {
160178
runtime: 'python'
161179
};
162180
}
181+
182+
// Detect HTTP framework dependencies
183+
for (const fw of HTTP_FRAMEWORKS_PYTHON) {
184+
const hasFramework = requirements.some(line => {
185+
const trimmed = line.trim().toLowerCase();
186+
return trimmed === fw || trimmed.startsWith(`${fw}==`) || trimmed.startsWith(`${fw}>=`) || trimmed.startsWith(`${fw}<=`) || trimmed.startsWith(`${fw}[`);
187+
});
188+
if (hasFramework) {
189+
detectedPythonFrameworks.push(fw);
190+
}
191+
}
163192
}
164193
} catch {
165194
// Continue to pyproject.toml
@@ -208,6 +237,15 @@ export class RuntimeDetector {
208237
}
209238
}
210239

240+
// Detect HTTP framework dependencies in pyproject.toml
241+
if (detectedPythonFrameworks.length === 0) {
242+
for (const fw of HTTP_FRAMEWORKS_PYTHON) {
243+
if (new RegExp(`["']${fw}["']`).test(content) || new RegExp(`["']${fw}[><=\\[]`).test(content)) {
244+
detectedPythonFrameworks.push(fw);
245+
}
246+
}
247+
}
248+
211249
// Extract project metadata from [project] section (always do this)
212250
const nameMatch = content.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
213251
const versionProjMatch = content.match(/^\s*version\s*=\s*["']([^"']+)["']/m);
@@ -228,10 +266,19 @@ export class RuntimeDetector {
228266

229267
// Return if we found MCP SDK (from either requirements.txt or pyproject.toml)
230268
if (mcpSdkInfo) {
269+
const warnings: string[] = [];
270+
if (detectedPythonFrameworks.length > 0) {
271+
warnings.push(
272+
`Detected HTTP framework dependencies: ${detectedPythonFrameworks.join(', ')}. ` +
273+
`GitHub deployments only support stdio-based MCP servers. ` +
274+
`If this server uses HTTP/SSE transport instead of stdio, deployment will fail.`
275+
);
276+
}
231277
return {
232278
runtime: 'python',
233279
mcp_sdk: mcpSdkInfo,
234-
pyprojectToml
280+
pyprojectToml,
281+
...(warnings.length > 0 && { warnings })
235282
};
236283
}
237284
}
@@ -241,9 +288,18 @@ export class RuntimeDetector {
241288

242289
// If we found MCP SDK in requirements.txt but no pyproject.toml, return without metadata
243290
if (mcpSdkInfo) {
291+
const warnings: string[] = [];
292+
if (detectedPythonFrameworks.length > 0) {
293+
warnings.push(
294+
`Detected HTTP framework dependencies: ${detectedPythonFrameworks.join(', ')}. ` +
295+
`GitHub deployments only support stdio-based MCP servers. ` +
296+
`If this server uses HTTP/SSE transport instead of stdio, deployment will fail.`
297+
);
298+
}
244299
return {
245300
runtime: 'python',
246-
mcp_sdk: mcpSdkInfo
301+
mcp_sdk: mcpSdkInfo,
302+
...(warnings.length > 0 && { warnings })
247303
};
248304
}
249305

services/backend/src/routes/teams/deploy/validate.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ const VALIDATE_SUCCESS_RESPONSE_SCHEMA = {
6363
type: 'object',
6464
additionalProperties: { type: 'string' }
6565
},
66-
commit_sha: { type: 'string' }
66+
commit_sha: { type: 'string' },
67+
warnings: {
68+
type: 'array',
69+
items: { type: 'string' }
70+
}
6771
},
6872
required: ['runtime', 'mcp_sdk', 'commit_sha']
6973
}

services/backend/src/services/deploymentValidationService.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export interface ValidationMetadata {
2828

2929
// Git metadata
3030
commit_sha: string;
31+
32+
// Non-blocking warnings (e.g., HTTP framework detected)
33+
warnings?: string[];
3134
}
3235

3336
export interface ValidationResult {
@@ -320,7 +323,8 @@ export class DeploymentValidationService {
320323
runtime: runtimeResult.runtime,
321324
mcp_sdk: runtimeResult.mcp_sdk,
322325
scripts: runtimeResult.scripts,
323-
commit_sha: commitSha
326+
commit_sha: commitSha,
327+
...(runtimeResult.warnings?.length && { warnings: runtimeResult.warnings })
324328
}
325329
};
326330
}

services/frontend/src/components/deploy/steps/ValidatingDeploymentStep.vue

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { computed } from 'vue'
33
import { useI18n } from 'vue-i18n'
44
import { Spinner } from '@/components/ui/spinner'
55
import { Empty, EmptyHeader, EmptyMedia, EmptyTitle, EmptyDescription } from '@/components/ui/empty'
6-
import { AlertCircle } from 'lucide-vue-next'
6+
import { AlertCircle, AlertTriangle } from 'lucide-vue-next'
77
88
interface Props {
99
isLoading: boolean
@@ -24,6 +24,7 @@ interface Props {
2424
start?: string
2525
[key: string]: string | undefined
2626
}
27+
warnings?: string[]
2728
} | null
2829
}
2930
@@ -214,6 +215,23 @@ function getRuntimeLabel(runtime: string) {
214215
</div>
215216
</li>
216217
</ul>
218+
219+
<!-- Transport Warning -->
220+
<div v-if="metadata.warnings?.length" class="mt-4 rounded-md bg-yellow-50 p-4 dark:bg-yellow-950/30">
221+
<div class="flex">
222+
<div class="flex-shrink-0">
223+
<AlertTriangle class="h-5 w-5 text-yellow-400" />
224+
</div>
225+
<div class="ml-3">
226+
<h3 class="text-sm font-medium text-yellow-800 dark:text-yellow-200">
227+
{{ t('deployments.wizard.validating.success.warningTitle') }}
228+
</h3>
229+
<div class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">
230+
<p v-for="warning in metadata.warnings" :key="warning">{{ warning }}</p>
231+
</div>
232+
</div>
233+
</div>
234+
</div>
217235
</div>
218236
</div>
219237
</div>

services/frontend/src/i18n/locales/en/deployments.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ export default {
126126
buildCommands: 'Build Commands',
127127
name: 'Name',
128128
noBuildScripts: 'No build or start scripts detected',
129+
warningTitle: 'Warning',
129130
},
130131
},
131132

services/frontend/src/services/deploymentService.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export class DeploymentService {
177177
[key: string]: string | undefined
178178
}
179179
commit_sha: string
180+
warnings?: string[]
180181
}
181182
error?: string
182183
step?: string

services/frontend/src/views/deploy/create.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ interface ValidationMetadata {
5050
[key: string]: string | undefined
5151
}
5252
commit_sha: string
53+
warnings?: string[]
5354
}
5455
5556
const formData = ref({

0 commit comments

Comments
 (0)