server.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. from __future__ import annotations
  2. import os
  3. from typing import Any
  4. from fastmcp import FastMCP
  5. from .auth import load_projects_config
  6. from .config_api import (
  7. list_device_types as api_list_device_types,
  8. list_locations as api_list_locations,
  9. list_meter_types as api_list_meter_types,
  10. list_system_tree as api_list_system_tree,
  11. list_systems as api_list_systems,
  12. search_devices as api_search_devices,
  13. search_meters as api_search_meters,
  14. search_points as api_search_points,
  15. )
  16. mcp = FastMCP("m2")
  17. @mcp.tool(
  18. name="project.list",
  19. title="Project List",
  20. description="List enabled projects configured in sys_config for m2 tools. 在执行其他工具前,询问用户要查询哪一个项目,根据用户的选择查询对应项目。",
  21. tags={"project", "list"},
  22. )
  23. def project_list() -> dict[str, Any]:
  24. projects = load_projects_config()
  25. result: list[dict[str, Any]] = []
  26. for item in projects:
  27. if not item["enabled"]:
  28. continue
  29. result.append(
  30. {
  31. "project_key": item["project_key"],
  32. "project_name": item["project_name"],
  33. }
  34. )
  35. result.sort(key=lambda item: item["project_key"])
  36. return {
  37. "projects": result,
  38. "total": len(result),
  39. }
  40. def _append_next_page_hint(payload: Any, page_num: int) -> Any:
  41. if not isinstance(payload, dict):
  42. return payload
  43. data = payload.get("data")
  44. if not isinstance(data, dict):
  45. return payload
  46. total_page = data.get("total_page")
  47. if isinstance(total_page, int) and total_page > page_num:
  48. payload.setdefault(
  49. "mcp_note",
  50. f"Current result is page {page_num}. If the target was not found, continue with page_num={page_num + 1}.",
  51. )
  52. return payload
  53. @mcp.tool()
  54. def list_locations(project_key: str, keyword: str = "", page_size: int = 100, page_num: int = 1) -> Any:
  55. """List location data from the config API."""
  56. payload = api_list_locations(project_key, keyword=keyword, page_size=page_size, page_num=page_num)
  57. return _append_next_page_hint(payload, page_num)
  58. @mcp.tool()
  59. def list_system_tree(project_key: str) -> Any:
  60. """Get the full system tree from the config API."""
  61. return api_list_system_tree(project_key)
  62. @mcp.tool()
  63. def list_systems(
  64. project_key: str,
  65. page_size: int = 100,
  66. page_num: int = 1,
  67. system_type_id: int = 0,
  68. show_below: bool = True,
  69. ) -> Any:
  70. """List systems by system type."""
  71. payload = api_list_systems(
  72. project_key,
  73. page_size=page_size,
  74. page_num=page_num,
  75. system_type_id=system_type_id,
  76. show_below=show_below,
  77. )
  78. return _append_next_page_hint(payload, page_num)
  79. @mcp.tool()
  80. def list_device_types(project_key: str) -> Any:
  81. """List all device types."""
  82. return api_list_device_types(project_key)
  83. @mcp.tool()
  84. def list_meter_types(project_key: str) -> Any:
  85. """List all meter types."""
  86. return api_list_meter_types(project_key)
  87. @mcp.tool()
  88. def search_devices(
  89. project_key: str,
  90. page_size: int = 100,
  91. page_num: int = 1,
  92. keyword: str = "",
  93. location_id: int = 0,
  94. show_below: bool = True,
  95. system_ids: list[int] | None = None,
  96. device_type_ids: list[int] | None = None,
  97. ) -> Any:
  98. """Search devices with id-based filters."""
  99. payload = api_search_devices(
  100. project_key,
  101. page_size=page_size,
  102. page_num=page_num,
  103. keyword=keyword,
  104. location_id=location_id,
  105. show_below=show_below,
  106. system_ids=system_ids,
  107. device_type_ids=device_type_ids,
  108. )
  109. return _append_next_page_hint(payload, page_num)
  110. @mcp.tool()
  111. def search_meters(
  112. project_key: str,
  113. page_size: int = 100,
  114. page_num: int = 1,
  115. keyword: str = "",
  116. location_id: int = 0,
  117. show_below: bool = True,
  118. meter_type_id: int = 0,
  119. measurement_location_ids: list[int] | None = None,
  120. measurement_system_ids: list[int] | None = None,
  121. measurement_device_type_ids: list[int] | None = None,
  122. status: int | None = None,
  123. ) -> Any:
  124. """Search meters with id-based filters."""
  125. payload = api_search_meters(
  126. project_key,
  127. page_size=page_size,
  128. page_num=page_num,
  129. keyword=keyword,
  130. location_id=location_id,
  131. show_below=show_below,
  132. meter_type_id=meter_type_id,
  133. measurement_location_ids=measurement_location_ids,
  134. measurement_system_ids=measurement_system_ids,
  135. measurement_device_type_ids=measurement_device_type_ids,
  136. status=status,
  137. )
  138. return _append_next_page_hint(payload, page_num)
  139. @mcp.tool()
  140. def search_points(project_key: str, id: int, page_size: int = 100, page_num: int = 1) -> Any:
  141. """Search points under a meter by meter id."""
  142. payload = api_search_points(project_key, id=id, page_size=page_size, page_num=page_num)
  143. return _append_next_page_hint(payload, page_num)
  144. def main() -> None:
  145. host = os.getenv("MCP_HOST", "0.0.0.0").strip() or "0.0.0.0"
  146. port = int(os.getenv("MCP_PORT", "8500"))
  147. path = os.getenv("MCP_PATH", "/mcp").strip() or "/mcp"
  148. mcp.run(transport="http", host=host, port=port, path=path)
  149. if __name__ == "__main__":
  150. main()