mcp_protocol.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import json
  2. import sys
  3. class McpProtocolHandler:
  4. protocol_version = '2025-06-18'
  5. server_name = 'fms-mcp-gateway'
  6. server_version = '0.1.0'
  7. def __init__(self, gateway_app):
  8. self.gateway_app = gateway_app
  9. self.initialized = False
  10. def handle_message(self, message):
  11. if not isinstance(message, dict):
  12. return self._error_response(None, -32600, 'Invalid Request')
  13. if 'id' in message:
  14. return self.handle_request(message)
  15. method = str(message.get('method') or '').strip()
  16. if method == 'notifications/initialized':
  17. self.initialized = True
  18. return None
  19. return None
  20. def handle_request(self, request):
  21. request_id = request.get('id')
  22. method = str(request.get('method') or '').strip()
  23. try:
  24. if method == 'initialize':
  25. self.initialized = True
  26. return self._success_response(
  27. request_id,
  28. {
  29. 'protocolVersion': self.protocol_version,
  30. 'capabilities': {
  31. 'tools': {
  32. 'listChanged': False,
  33. }
  34. },
  35. 'serverInfo': {
  36. 'name': self.server_name,
  37. 'version': self.server_version,
  38. },
  39. },
  40. )
  41. if method == 'tools/list':
  42. tools = [self._normalize_tool(tool) for tool in self.gateway_app.list_tools()]
  43. return self._success_response(request_id, {'tools': tools})
  44. if method == 'tools/call':
  45. params = request.get('params') or {}
  46. tool_name = params.get('name')
  47. arguments = params.get('arguments') or {}
  48. tool_result = self.gateway_app.call_tool(tool_name, arguments)
  49. structured_content = tool_result.get('data') or {}
  50. return self._success_response(
  51. request_id,
  52. {
  53. 'content': [
  54. {
  55. 'type': 'text',
  56. 'text': self._render_text(structured_content),
  57. }
  58. ],
  59. 'structuredContent': structured_content,
  60. 'isError': False,
  61. },
  62. )
  63. return self._error_response(request_id, -32601, 'Method not found: {0}'.format(method or '<empty>'))
  64. except Exception as exc:
  65. if method == 'tools/call':
  66. return self._success_response(
  67. request_id,
  68. {
  69. 'content': [
  70. {
  71. 'type': 'text',
  72. 'text': str(exc),
  73. }
  74. ],
  75. 'isError': True,
  76. },
  77. )
  78. return self._error_response(request_id, -32000, str(exc))
  79. def run_stdio(self, stdin=None, stdout=None):
  80. stdin = stdin or sys.stdin
  81. stdout = stdout or sys.stdout
  82. for raw_line in stdin:
  83. line = str(raw_line).strip()
  84. if not line:
  85. continue
  86. try:
  87. message = json.loads(line)
  88. except ValueError:
  89. response = self._error_response(None, -32700, 'Parse error')
  90. else:
  91. response = self.handle_message(message)
  92. if response is None:
  93. continue
  94. stdout.write(json.dumps(response, ensure_ascii=False) + '\n')
  95. if hasattr(stdout, 'flush'):
  96. stdout.flush()
  97. return 0
  98. def _normalize_tool(self, tool):
  99. normalized = dict(tool)
  100. if 'input_schema' in normalized:
  101. normalized['inputSchema'] = normalized.pop('input_schema')
  102. return normalized
  103. @staticmethod
  104. def _render_text(structured_content):
  105. if not structured_content:
  106. return 'ok'
  107. return json.dumps(structured_content, ensure_ascii=False)
  108. @staticmethod
  109. def _success_response(request_id, result):
  110. return {
  111. 'jsonrpc': '2.0',
  112. 'id': request_id,
  113. 'result': result,
  114. }
  115. @staticmethod
  116. def _error_response(request_id, code, message):
  117. return {
  118. 'jsonrpc': '2.0',
  119. 'id': request_id,
  120. 'error': {
  121. 'code': code,
  122. 'message': message,
  123. },
  124. }