api_client.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. import json
  2. import uuid
  3. import urllib.request
  4. class JsonTransport:
  5. def post_json(self, url, payload, headers, timeout):
  6. request = urllib.request.Request(
  7. url=url,
  8. data=json.dumps(payload).encode('utf-8'),
  9. headers=dict({'Content-Type': 'application/json'}, **headers),
  10. method='POST',
  11. )
  12. with urllib.request.urlopen(request, timeout=timeout) as response:
  13. return json.loads(response.read().decode('utf-8-sig'))
  14. def post_multipart(self, url, fields, files, headers, timeout):
  15. boundary = '----McpFormBoundary{0}'.format(uuid.uuid4().hex)
  16. body = bytearray()
  17. for name, value in (fields or {}).items():
  18. body.extend(('--{0}\r\n'.format(boundary)).encode('utf-8'))
  19. body.extend(
  20. 'Content-Disposition: form-data; name="{0}"\r\n\r\n'.format(
  21. name
  22. ).encode('utf-8')
  23. )
  24. body.extend(str(value).encode('utf-8'))
  25. body.extend(b'\r\n')
  26. for name, spec in (files or {}).items():
  27. filename, file_bytes, content_type = spec
  28. safe_name = str(filename or 'file').replace('"', '')
  29. body.extend(('--{0}\r\n'.format(boundary)).encode('utf-8'))
  30. body.extend(
  31. (
  32. 'Content-Disposition: form-data; name="{0}"; '
  33. 'filename="{1}"\r\n'
  34. ).format(name, safe_name).encode('utf-8')
  35. )
  36. body.extend(
  37. 'Content-Type: {0}\r\n\r\n'.format(
  38. content_type or 'application/octet-stream'
  39. ).encode('utf-8')
  40. )
  41. body.extend(file_bytes if isinstance(file_bytes, (bytes, bytearray)) else b'')
  42. body.extend(b'\r\n')
  43. body.extend(('--{0}--\r\n'.format(boundary)).encode('utf-8'))
  44. request_headers = dict(headers or {})
  45. request_headers['Content-Type'] = (
  46. 'multipart/form-data; boundary={0}'.format(boundary)
  47. )
  48. request = urllib.request.Request(
  49. url=url,
  50. data=bytes(body),
  51. headers=request_headers,
  52. method='POST',
  53. )
  54. with urllib.request.urlopen(request, timeout=timeout) as response:
  55. return json.loads(response.read().decode('utf-8-sig'))
  56. class ApiClient:
  57. def __init__(self, base_url, token_store, transport=None, timeout=10):
  58. self.base_url = (base_url or '').rstrip('/')
  59. self.token_store = token_store
  60. self.transport = transport or JsonTransport()
  61. self.timeout = int(timeout)
  62. def call_tool(self, tool_code, route_path, payload, request_id):
  63. token = self.token_store.require_token()
  64. url = self.base_url + '/' + route_path.lstrip('/')
  65. headers = {
  66. 'Authorization': 'Bearer {0}'.format(token),
  67. 'X-MCP-Tool-Code': tool_code,
  68. 'X-Request-Id': request_id,
  69. }
  70. return self.transport.post_json(url, payload, headers, self.timeout)
  71. def list_enabled_tools(self, request_id=''):
  72. token = self.token_store.require_token()
  73. url = self.base_url + '/mcp/tools/listEnabledTools'
  74. headers = {
  75. 'Authorization': 'Bearer {0}'.format(token),
  76. 'X-Request-Id': str(request_id or '').strip(),
  77. }
  78. return self.transport.post_json(url, {}, headers, self.timeout)
  79. def upload_headhaul_document(
  80. self,
  81. upload_token,
  82. filename,
  83. file_bytes,
  84. content_type='',
  85. request_id='',
  86. ):
  87. token = self.token_store.require_token()
  88. url = self.base_url + '/mcp/tools/uploadHeadhaulDocument'
  89. headers = {
  90. 'Authorization': 'Bearer {0}'.format(token),
  91. 'X-MCP-Tool-Code': 'prepare_headhaul_document_upload',
  92. 'X-Request-Id': str(request_id or '').strip(),
  93. }
  94. return self.transport.post_multipart(
  95. url,
  96. {'upload_token': upload_token},
  97. {
  98. 'file': (
  99. filename,
  100. file_bytes,
  101. content_type or 'application/octet-stream',
  102. ),
  103. },
  104. headers,
  105. max(self.timeout, 120),
  106. )