Fix: Add shutdown handler for proper connection cleanup
Added GraphitiService.shutdown() and QueueService.shutdown() methods, and registered shutdown handler in main MCP server lifecycle to ensure proper cleanup of Neo4j connections on server exit. Changes: - Added GraphitiService.shutdown() method to close Neo4j driver - Added QueueService.shutdown() method to log worker cancellation - Wrapped run_mcp_server() in try/finally block to call shutdown - Updated dependency to graphiti-core-varming>=0.23.2 (connection pool config) This prevents "Response write failure" errors during server shutdown by ensuring all database connections are properly closed before exit. Version: graphiti-mcp-varming 1.0.6 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a74bdf8321
commit
0e52b339b2
3 changed files with 91 additions and 41 deletions
|
|
@ -10,7 +10,7 @@ allow-direct-references = true
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "graphiti-mcp-varming"
|
name = "graphiti-mcp-varming"
|
||||||
version = "1.0.5"
|
version = "1.0.6"
|
||||||
description = "Graphiti MCP Server - Enhanced fork with additional tools by Varming"
|
description = "Graphiti MCP Server - Enhanced fork with additional tools by Varming"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10,<4"
|
requires-python = ">=3.10,<4"
|
||||||
|
|
@ -31,7 +31,7 @@ classifiers = [
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mcp>=1.21.0",
|
"mcp>=1.21.0",
|
||||||
"openai>=1.91.0",
|
"openai>=1.91.0",
|
||||||
"graphiti-core-varming>=0.23.1", # Varming fork with database parameter fix (0.23.1+varming.1)
|
"graphiti-core-varming>=0.23.2", # Varming fork with connection pool config (0.23.2)
|
||||||
"pydantic-settings>=2.0.0",
|
"pydantic-settings>=2.0.0",
|
||||||
"pyyaml>=6.0",
|
"pyyaml>=6.0",
|
||||||
]
|
]
|
||||||
|
|
@ -46,7 +46,7 @@ Issues = "https://github.com/Varming73/graphiti/issues"
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
# FalkorDB support (Neo4j is included in graphiti-core-varming by default)
|
# FalkorDB support (Neo4j is included in graphiti-core-varming by default)
|
||||||
falkordb = ["graphiti-core-varming[falkordb]>=0.23.1"]
|
falkordb = ["graphiti-core-varming[falkordb]>=0.23.2"]
|
||||||
|
|
||||||
# Azure support
|
# Azure support
|
||||||
azure = [
|
azure = [
|
||||||
|
|
@ -82,7 +82,7 @@ all = [
|
||||||
]
|
]
|
||||||
|
|
||||||
dev = [
|
dev = [
|
||||||
"graphiti-core-varming>=0.23.1",
|
"graphiti-core-varming>=0.23.2",
|
||||||
"httpx>=0.28.1",
|
"httpx>=0.28.1",
|
||||||
"mcp>=1.21.0",
|
"mcp>=1.21.0",
|
||||||
"pyright>=1.1.404",
|
"pyright>=1.1.404",
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,29 @@ class GraphitiService:
|
||||||
logger.error(f'Failed to initialize Graphiti client: {e}')
|
logger.error(f'Failed to initialize Graphiti client: {e}')
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
async def shutdown(self) -> None:
|
||||||
|
"""Clean shutdown of Graphiti service.
|
||||||
|
|
||||||
|
Closes database connections and cancels queue workers.
|
||||||
|
"""
|
||||||
|
logger.info('Shutting down Graphiti service...')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Cancel all queue workers
|
||||||
|
if self.queue_service:
|
||||||
|
await self.queue_service.shutdown()
|
||||||
|
|
||||||
|
# Close Graphiti client (which closes the driver)
|
||||||
|
if self.client and self.client.driver:
|
||||||
|
logger.info('Closing Neo4j driver...')
|
||||||
|
await self.client.driver.close()
|
||||||
|
logger.info('Neo4j driver closed successfully')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Error during shutdown: {e}')
|
||||||
|
finally:
|
||||||
|
logger.info('Graphiti service shutdown complete')
|
||||||
|
|
||||||
async def get_client(self) -> Graphiti:
|
async def get_client(self) -> Graphiti:
|
||||||
"""Get the Graphiti client, initializing if necessary."""
|
"""Get the Graphiti client, initializing if necessary."""
|
||||||
if self.client is None:
|
if self.client is None:
|
||||||
|
|
@ -1614,7 +1637,10 @@ async def initialize_server() -> ServerConfig:
|
||||||
|
|
||||||
|
|
||||||
async def run_mcp_server():
|
async def run_mcp_server():
|
||||||
"""Run the MCP server in the current event loop."""
|
"""Run the MCP server with proper lifecycle management."""
|
||||||
|
global graphiti_service
|
||||||
|
|
||||||
|
try:
|
||||||
# Initialize the server
|
# Initialize the server
|
||||||
mcp_config = await initialize_server()
|
mcp_config = await initialize_server()
|
||||||
|
|
||||||
|
|
@ -1655,6 +1681,11 @@ async def run_mcp_server():
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'Unsupported transport: {mcp_config.transport}. Use "sse" or "stdio"')
|
raise ValueError(f'Unsupported transport: {mcp_config.transport}. Use "sse" or "stdio"')
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# Always clean up on exit
|
||||||
|
if graphiti_service:
|
||||||
|
await graphiti_service.shutdown()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Main function to run the Graphiti MCP server."""
|
"""Main function to run the Graphiti MCP server."""
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,25 @@ class QueueService:
|
||||||
self._graphiti_client = graphiti_client
|
self._graphiti_client = graphiti_client
|
||||||
logger.info('Queue service initialized with graphiti client')
|
logger.info('Queue service initialized with graphiti client')
|
||||||
|
|
||||||
|
async def shutdown(self) -> None:
|
||||||
|
"""Cancel all queue workers and wait for completion."""
|
||||||
|
if not self._queue_workers:
|
||||||
|
logger.info('No queue workers to shut down')
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info(f'Shutting down {len(self._queue_workers)} queue workers...')
|
||||||
|
|
||||||
|
# Cancel all worker tasks if we have task references
|
||||||
|
# Note: Currently we don't store task references, so workers will be
|
||||||
|
# cancelled when the event loop shuts down. This is a placeholder for
|
||||||
|
# Phase 4 enhancement where we'll add task tracking.
|
||||||
|
|
||||||
|
for group_id, is_running in list(self._queue_workers.items()):
|
||||||
|
if is_running:
|
||||||
|
logger.info(f'Queue worker for {group_id} will be cancelled on shutdown')
|
||||||
|
|
||||||
|
logger.info('Queue service shutdown complete')
|
||||||
|
|
||||||
async def add_episode(
|
async def add_episode(
|
||||||
self,
|
self,
|
||||||
group_id: str,
|
group_id: str,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue