Skip to content

Conversation

@poad
Copy link
Owner

@poad poad commented Oct 9, 2025

概要

コネクションハンドリングを修正

変更点

追加・変更・削除したファイル (リポジトリルートからの相対パス) 変更内容 事由
platform/lambda/index.ts https://github.com/mhart/mcp-hono-stateless/blob/main/src/index.ts#L119-L158 を参考にコネクションハンドリングを修正 リソース管理に問題がありそうだったため
platform/package.json fetch-to-node を追加 レスポンス返却完了のタイミングでリソースクローズするため

関連Issue

確認事項

  • (Typescriptの場合) pnpm audit --fix で脆弱性を修正済みか?
  • (Typescriptの場合) pnpm lint-fix でコードスタイルは修正済みか?
  • (Markdownの場合)markdownlint-2 で Markdown の lint は修正済みか?

特記事項

@amazon-q-developer
Copy link

Code review in progress. Analyzing for code quality issues and best practices. Detailed findings will be posted upon completion.

Using Amazon Q Developer for GitHub

Amazon Q Developer1 is an AI-powered assistant that integrates directly into your GitHub workflow, enhancing your development process with intelligent features for code development, review, and transformation.

Slash Commands

Command Description
/q <message> Chat with the agent to ask questions or request revisions
/q review Requests an Amazon Q powered code review
/q help Displays usage information

Features

Agentic Chat
Enables interactive conversation with Amazon Q to ask questions about the pull request or request specific revisions. Use /q <message> in comment threads or the review body to engage with the agent directly.

Code Review
Analyzes pull requests for code quality, potential issues, and security concerns. Provides feedback and suggested fixes. Automatically triggered on new or reopened PRs (can be disabled for AWS registered installations), or manually with /q review slash command in a comment.

Customization

You can create project-specific rules for Amazon Q Developer to follow:

  1. Create a .amazonq/rules folder in your project root.
  2. Add Markdown files in this folder to define rules (e.g., cdk-rules.md).
  3. Write detailed prompts in these files, such as coding standards or best practices.
  4. Amazon Q Developer will automatically use these rules when generating code or providing assistance.

Example rule:

All Amazon S3 buckets must have encryption enabled, enforce SSL, and block public access.
All Amazon DynamoDB Streams tables must have encryption enabled.
All Amazon SNS topics must have encryption enabled and enforce SSL.
All Amazon SNS queues must enforce SSL.

Feedback

To provide feedback on Amazon Q Developer, create an issue in the Amazon Q Developer public repository.

For more detailed information, visit the Amazon Q for GitHub documentation.

Footnotes

  1. Amazon Q Developer uses generative AI. You may need to verify generated code before using it in your environment. See the AWS Responsible AI Policy.

Copy link

@amazon-q-developer amazon-q-developer bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

レビュー概要

このPRは、MCPサーバーのコネクションハンドリングを改善する目的で実装されていますが、重大なリソース管理の問題があります。

🚨 重要な問題

  1. リソースリーク: res.on('close') イベントに依存したリソース管理は、Lambda環境では信頼性に欠けます
  2. エラーハンドリングの劣化: 元のコードにあった堅牢なエラーハンドリングとクリーンアップロジックが失われています

📋 主な修正要求

  1. 必須: try-finally パターンを使用した確実なリソースクリーンアップの実装
  2. 必須: Promise.allSettled を使用した安全なクローズ処理の復活
  3. 推奨: ログ出力の一貫性確保(console.loglogger.trace
  4. 推奨: 新しい依存関係 fetch-to-node のセキュリティ監査

💡 提案

元のコードの堅牢なリソース管理パターンを維持しつつ、新しいトランスポート実装を統合することを推奨します。現在の実装は、参考にしたコードがサーバー環境向けであるのに対し、Lambda環境では異なる考慮が必要です。

コードの品質と安全性を確保するため、上記の修正を適用してから再レビューをお願いします。

const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // セッションIDを生成しない(ステートレスモード)
enableJsonResponse: true,
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 リソースリーク: res.on('close') イベントハンドラーによるリソース管理に重大な問題があります。Lambda環境では、レスポンスの close イベントが適切に発火しない可能性があり、その結果 transport.close()server.close() が呼ばれずにリソースリークが発生する恐れがあります1

元のコードの try-finally パターンの方が確実にリソースクリーンアップを保証できます。現在の実装では、Lambda関数の実行が完了してもMCPサーバーとトランスポートが適切にクローズされない可能性があります。

Footnotes

  1. Lambda環境では、レスポンスストリームの動作が通常のNode.jsサーバーと異なる場合があります

});
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // セッションIDを生成しない(ステートレスモード)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ログレベルの不整合: console.log を使用していますが、他の箇所では logger インスタンスを使用しています。一貫性を保つため、ここでも logger.trace または logger.debug を使用することを推奨します。

Comment on lines 58 to +70

const closeResources = async (server: McpServer, transport: StreamableHTTPTransport) => {
// 両方のクローズを確実に実行(片方が失敗してももう片方を実行)
const closeResults = await Promise.allSettled([
transport.close(),
server.close(),
]);

// クローズエラーをログ出力
closeResults.forEach((result, index) => {
if (result.status === 'rejected') {
const resourceName = index === 0 ? 'transport' : 'server';
const error = result.reason;
const errorDetails = error instanceof Error
? { message: error.message, stack: error.stack }
: error;
logger.error(`Error closing ${resourceName}:`, { error: errorDetails });
}
});
};

// ルートを設定
app.post('/mcp', async (c) => {
const { req, res } = toReqRes(c.req.raw);
const server = createMcpServer();
const transport = new StreamableHTTPTransport({
sessionIdGenerator: undefined, // セッションIDを生成しない(ステートレスモード)
enableJsonResponse: true,
});
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // セッションIDを生成しない(ステートレスモード)
enableJsonResponse: true,
});
await server.connect(transport);
try {
logger.trace('MCP リクエストを受信');
return await transport.handleRequest(c);
} catch (error) {
return handleError(c, error, 'MCP リクエスト処理中のエラー:');
} finally {
await closeResources(server, transport);
}
logger.trace('MCP リクエストを受信');
await transport.handleRequest(req, res, await c.req.json());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

エラーハンドリングの改善が必要: 現在の実装では、transport.handleRequest() でエラーが発生した場合のリソースクリーンアップが不十分です。以下の改善を提案します:

  1. try-catch-finally パターンを使用してリソースの確実なクリーンアップを保証
  2. transport.close()server.close() の呼び出し時のエラーハンドリング
  3. 元のコードにあった Promise.allSettled を使用した安全なクリーンアップロジックの復活
Suggested change
const closeResources = async (server: McpServer, transport: StreamableHTTPTransport) => {
// 両方のクローズを確実に実行(片方が失敗してももう片方を実行)
const closeResults = await Promise.allSettled([
transport.close(),
server.close(),
]);
// クローズエラーをログ出力
closeResults.forEach((result, index) => {
if (result.status === 'rejected') {
const resourceName = index === 0 ? 'transport' : 'server';
const error = result.reason;
const errorDetails = error instanceof Error
? { message: error.message, stack: error.stack }
: error;
logger.error(`Error closing ${resourceName}:`, { error: errorDetails });
}
});
};
// ルートを設定
app.post('/mcp', async (c) => {
const { req, res } = toReqRes(c.req.raw);
const server = createMcpServer();
const transport = new StreamableHTTPTransport({
sessionIdGenerator: undefined, // セッションIDを生成しない(ステートレスモード)
enableJsonResponse: true,
});
try {
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // セッションIDを生成しない(ステートレスモード)
enableJsonResponse: true,
});
await server.connect(transport);
try {
logger.trace('MCP リクエストを受信');
return await transport.handleRequest(c);
} catch (error) {
return handleError(c, error, 'MCP リクエスト処理中のエラー:');
} finally {
await closeResources(server, transport);
}
logger.trace('MCP リクエストを受信');
await transport.handleRequest(req, res, await c.req.json());
const { req, res } = toReqRes(c.req.raw);
const server = createMcpServer();
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // セッションIDを生成しない(ステートレスモード)
enableJsonResponse: true,
});
try {
await server.connect(transport);
logger.trace('MCP リクエストを受信');
await transport.handleRequest(req, res, await c.req.json());
return toFetchResponse(res);
} catch (error) {
return handleError(c, error, 'MCP 接続中のエラー:');
} finally {
// 両方のクローズを確実に実行(片方が失敗してももう片方を実行)
const closeResults = await Promise.allSettled([
transport.close(),
server.close(),
]);
// クローズエラーをログ出力
closeResults.forEach((result, index) => {
if (result.status === 'rejected') {
const resourceName = index === 0 ? 'transport' : 'server';
const error = result.reason;
const errorDetails = error instanceof Error
? { message: error.message, stack: error.stack }
: error;
logger.error(`Error closing ${resourceName}:`, { error: errorDetails });
}
});
}

});
};

// ルートを設定

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

トランスポートインスタンス化のタイミング: トランスポートを try ブロック内で作成することで、接続エラー時にもトランスポートのクリーンアップが必要になります。元のコードのように、トランスポートを事前に作成し、接続エラー時は適切にクリーンアップする方が安全です。

);
};

const closeResources = async (server: McpServer, transport: StreamableHTTPTransport) => {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

削除された重要な機能: closeResources 関数は、リソースの安全なクリーンアップを担保する重要な機能でした。この関数が提供していた以下の機能が失われています:

  1. Promise.allSettled による両方のリソースの確実なクリーンアップ
  2. 個別のクローズエラーのログ出力
  3. 一方のクローズが失敗しても他方を実行する堅牢性

この機能を復活させることを強く推奨します。

@poad poad closed this Oct 9, 2025
@poad poad marked this pull request as ready for review October 9, 2025 10:47
@poad poad deleted the fix/mcp-connection-close branch October 9, 2025 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants