fc-client-docker 0.9.0__tar.gz → 0.9.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. fc_client_docker-0.9.2/PKG-INFO +71 -0
  2. fc_client_docker-0.9.2/fc_client_docker.egg-info/PKG-INFO +71 -0
  3. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker.egg-info/SOURCES.txt +1 -0
  4. fc_client_docker-0.9.2/fc_common/VERSION +1 -0
  5. fc_client_docker-0.9.2/fc_mcp/README.md +196 -0
  6. fc_client_docker-0.9.2/fc_mcp/mcp_server.py +274 -0
  7. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/api_svr.py +1 -174
  8. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/setup.py +42 -5
  9. fc_client_docker-0.9.0/PKG-INFO +0 -18
  10. fc_client_docker-0.9.0/fc_client_docker.egg-info/PKG-INFO +0 -18
  11. fc_client_docker-0.9.0/fc_common/VERSION +0 -1
  12. fc_client_docker-0.9.0/fc_mcp/mcp_server.py +0 -64
  13. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.QualityGate +0 -0
  14. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.github/workflows/main.yml +0 -0
  15. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.gitignore +0 -0
  16. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.pylintrc +0 -0
  17. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.python-version +0 -0
  18. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.readthedocs.yaml +0 -0
  19. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/LICENSE +0 -0
  20. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/README.rst +0 -0
  21. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/SCR-fc.txt +0 -0
  22. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/black.sh +0 -0
  23. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/docker.sh +0 -0
  24. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/package.sh +0 -0
  25. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/publish_docker.sh +0 -0
  26. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/pylint.sh +0 -0
  27. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/pytest.sh +0 -0
  28. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/setup.sh +0 -0
  29. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/Makefile +0 -0
  30. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/conf.py +0 -0
  31. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/configuration.rst +0 -0
  32. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/css/css-style.css +0 -0
  33. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/architecture.svg +0 -0
  34. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/era.svg +0 -0
  35. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/fc.png +0 -0
  36. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/flowchart.svg +0 -0
  37. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/principle.svg +0 -0
  38. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/index.rst +0 -0
  39. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/make.bat +0 -0
  40. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/overview.rst +0 -0
  41. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/quickstart.rst +0 -0
  42. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/support.rst +0 -0
  43. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/usage.rst +0 -0
  44. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_client/Dockerfile +0 -0
  45. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_client/fc-agent +0 -0
  46. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_client/fc-client +0 -0
  47. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_guarder/Dockerfile +0 -0
  48. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_guarder/docker-compose.yaml +0 -0
  49. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_guarder/entrypoint.sh +0 -0
  50. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_guarder/sample.env +0 -0
  51. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_server/Dockerfile +0 -0
  52. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_server/docker-compose.yaml +0 -0
  53. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_server/entrypoint.sh +0 -0
  54. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_agent/agent.py +0 -0
  55. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client/client.py +0 -0
  56. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client/sample_client.env +0 -0
  57. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_daemon/client_daemon.py +0 -0
  58. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker/__init__.py +0 -0
  59. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker/fc_client_docker +0 -0
  60. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker.egg-info/dependency_links.txt +0 -0
  61. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker.egg-info/entry_points.txt +0 -0
  62. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker.egg-info/top_level.txt +0 -0
  63. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/__init__.py +0 -0
  64. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/config.py +0 -0
  65. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/etcd.py +0 -0
  66. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/logger.py +0 -0
  67. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/version.py +0 -0
  68. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_guarder/guarder.py +0 -0
  69. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/__init__.py +0 -0
  70. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_base.py +0 -0
  71. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/__init__.py +0 -0
  72. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/external/__init__.py +0 -0
  73. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/external/uboot_mcp.py +0 -0
  74. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/external/uuu_bcu_mcp.py +0 -0
  75. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/internal/__init__.py +0 -0
  76. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/internal/fc_mcp.py +0 -0
  77. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/__init__.py +0 -0
  78. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/config/sample_cfg.yaml +0 -0
  79. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/config/sample_lavacli.yaml +0 -0
  80. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/config/sample_resource.yaml +0 -0
  81. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/__init__.py +0 -0
  82. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/config.py +0 -0
  83. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/coordinator.py +0 -0
  84. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/decorators.py +0 -0
  85. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/plugin.py +0 -0
  86. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/management/__init__.py +0 -0
  87. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/management/cmd_online_lava_devices.py +0 -0
  88. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/management/common.py +0 -0
  89. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/__init__.py +0 -0
  90. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/labgrid.py +0 -0
  91. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/lava.py +0 -0
  92. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/utils/__init__.py +0 -0
  93. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/utils/labgrid.py +0 -0
  94. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/utils/lava.py +0 -0
  95. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/server.py +0 -0
  96. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/tools/cache_helper.py +0 -0
  97. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server_daemon/__init__.py +0 -0
  98. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server_daemon/server_daemon.py +0 -0
  99. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/agent-requirements.txt +0 -0
  100. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/ci-requirements.txt +0 -0
  101. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/client-requirements.txt +0 -0
  102. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/doc-requirements.txt +0 -0
  103. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/server-requirements.txt +0 -0
  104. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements.txt +0 -0
  105. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/setup.cfg +0 -0
  106. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/conftest.py +0 -0
  107. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_client/test_client.py +0 -0
  108. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_guarder/conftest.py +0 -0
  109. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_guarder/test_guarder.py +0 -0
  110. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/config/cfg.yaml +0 -0
  111. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/conftest.py +0 -0
  112. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/test_config.py +0 -0
  113. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/test_coordinator.py +0 -0
  114. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/test_misc.py +0 -0
  115. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/test_plugin_labgrid.py +0 -0
  116. {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/test_plugin_lava.py +0 -0
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.1
2
+ Name: fc_client_docker
3
+ Version: 0.9.2
4
+ Summary: Dockerized wrapper for the FC client
5
+ Home-page: https://github.com/NXP/fc
6
+ Author: Larry Shen
7
+ Author-email: larry.shen@nxp.com
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Requires-Python: >=3
19
+ Description-Content-Type: text/x-rst
20
+ License-File: LICENSE
21
+
22
+ What is FC?
23
+ -----------
24
+
25
+ FC (Framework Coordinator), is an open source coordinator service among different embeded frameworks.
26
+ It's built using the same principles as `Mesos <http://mesos.apache.org/>`_, provides different frameworks (e.g., LAVA, Labgrid) with API's for
27
+ resource management and scheduling across entire board farms.
28
+
29
+ Background
30
+ ----------
31
+
32
+ There are many different systems in embedded test community, see `this <https://elinux.org/Test_Systems>`_. They are good at different scenarios, e.g.
33
+
34
+ * **Test automation:**
35
+
36
+ LAVA, Fuego, autotest, avocado, TI VATF, U-Boot test tool, CI-RT R4D, Baylibe Lab in a Box......
37
+
38
+ * **Development automation:**
39
+
40
+ Labgrid......
41
+
42
+ Let's take a look at why FC is useful by going back in time.
43
+
44
+ .. image:: doc/images/era.svg
45
+
46
+ * **Traditional deployment era:** Early on, as nearly all above systems require dedicated control of the board resources, organizations have to
47
+ afford multiple series of hardware to meet the requirements of different frameworks, which leads to resource waste when leverage different systems.
48
+ * **FC deployment era:** As a solution, FC was introduced. It allows different frameworks could share same series of hardware, move resources between
49
+ systems seamlessly. Different systems will continue work without aware existence of other systems. So, FC could be treat as "scheduler's scheduler".
50
+ The initial idea comes from `Jan's proposal to move boards between LAVA and labgrid on ATS2019 <https://elinux.org/Automated_Testing_Summit_2019>`_,
51
+ we realized the FC based on that thought (Thanks community to inspire the idea).
52
+
53
+ Compared to other resource management system, it has low invasion to frameworks. This means: unlike mesos which you should write your mesos scheduler
54
+ for your framework & inject it into your framework code, FC won't change your framework code, it tries to adapt to your framework.
55
+ But still, your framework need next two features:
56
+
57
+ * Your framework needs to have a job queue which FC could monitor.
58
+ * Your framework needs to have ways to let FC control resource's availability, temporary connect/disconnect from your framework.
59
+
60
+ Supported frameworks
61
+ --------------------
62
+
63
+ FC is designed as a plugin system to support different frameworks, it currently supports:
64
+
65
+ * **LAVA:** use it for test automation
66
+ * **Labgrid:** use it for development automation
67
+
68
+ But, it not limits to above two, you could write your own plugins to support other framework.
69
+
70
+ For more detail, move your steps to `FC home page <https://fc.readthedocs.org/>`_.
71
+
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.1
2
+ Name: fc_client_docker
3
+ Version: 0.9.2
4
+ Summary: Dockerized wrapper for the FC client
5
+ Home-page: https://github.com/NXP/fc
6
+ Author: Larry Shen
7
+ Author-email: larry.shen@nxp.com
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Requires-Python: >=3
19
+ Description-Content-Type: text/x-rst
20
+ License-File: LICENSE
21
+
22
+ What is FC?
23
+ -----------
24
+
25
+ FC (Framework Coordinator), is an open source coordinator service among different embeded frameworks.
26
+ It's built using the same principles as `Mesos <http://mesos.apache.org/>`_, provides different frameworks (e.g., LAVA, Labgrid) with API's for
27
+ resource management and scheduling across entire board farms.
28
+
29
+ Background
30
+ ----------
31
+
32
+ There are many different systems in embedded test community, see `this <https://elinux.org/Test_Systems>`_. They are good at different scenarios, e.g.
33
+
34
+ * **Test automation:**
35
+
36
+ LAVA, Fuego, autotest, avocado, TI VATF, U-Boot test tool, CI-RT R4D, Baylibe Lab in a Box......
37
+
38
+ * **Development automation:**
39
+
40
+ Labgrid......
41
+
42
+ Let's take a look at why FC is useful by going back in time.
43
+
44
+ .. image:: doc/images/era.svg
45
+
46
+ * **Traditional deployment era:** Early on, as nearly all above systems require dedicated control of the board resources, organizations have to
47
+ afford multiple series of hardware to meet the requirements of different frameworks, which leads to resource waste when leverage different systems.
48
+ * **FC deployment era:** As a solution, FC was introduced. It allows different frameworks could share same series of hardware, move resources between
49
+ systems seamlessly. Different systems will continue work without aware existence of other systems. So, FC could be treat as "scheduler's scheduler".
50
+ The initial idea comes from `Jan's proposal to move boards between LAVA and labgrid on ATS2019 <https://elinux.org/Automated_Testing_Summit_2019>`_,
51
+ we realized the FC based on that thought (Thanks community to inspire the idea).
52
+
53
+ Compared to other resource management system, it has low invasion to frameworks. This means: unlike mesos which you should write your mesos scheduler
54
+ for your framework & inject it into your framework code, FC won't change your framework code, it tries to adapt to your framework.
55
+ But still, your framework need next two features:
56
+
57
+ * Your framework needs to have a job queue which FC could monitor.
58
+ * Your framework needs to have ways to let FC control resource's availability, temporary connect/disconnect from your framework.
59
+
60
+ Supported frameworks
61
+ --------------------
62
+
63
+ FC is designed as a plugin system to support different frameworks, it currently supports:
64
+
65
+ * **LAVA:** use it for test automation
66
+ * **Labgrid:** use it for development automation
67
+
68
+ But, it not limits to above two, you could write your own plugins to support other framework.
69
+
70
+ For more detail, move your steps to `FC home page <https://fc.readthedocs.org/>`_.
71
+
@@ -60,6 +60,7 @@ fc_common/etcd.py
60
60
  fc_common/logger.py
61
61
  fc_common/version.py
62
62
  fc_guarder/guarder.py
63
+ fc_mcp/README.md
63
64
  fc_mcp/__init__.py
64
65
  fc_mcp/mcp_base.py
65
66
  fc_mcp/mcp_server.py
@@ -0,0 +1 @@
1
+ 0.9.2
@@ -0,0 +1,196 @@
1
+ # FC MCP Server
2
+
3
+ An [MCP](https://modelcontextprotocol.io/) server, built on
4
+ [FastMCP](https://github.com/jlowin/fastmcp), that exposes FC
5
+ operations as MCP tools. Under the hood every tool shells out to `fc-client`,
6
+ so an AI agent can lock/unlock resources, query status, run console/ssh/power
7
+ commands, and drive image deploy/flash workflows.
8
+
9
+ ## Architecture
10
+
11
+ The server is plugin based. `mcp_server.py` creates the FastMCP instance, loads
12
+ the internal plugin, then auto-discovers external plugins.
13
+
14
+ ```
15
+ fc_mcp/
16
+ mcp_server.py # entry point (fc-mcp), transport handling
17
+ mcp_base.py # MCPPlugin base class + shared helpers
18
+ mcp_plugins/
19
+ internal/fc_mcp.py # core fc-client operations (always loaded)
20
+ external/
21
+ uuu_bcu_mcp.py # UUU/BCU flashing tools (sample external plugin)
22
+ uboot_mcp.py # U-Boot / predeploy config tools (sample)
23
+ ```
24
+
25
+ Any `*_mcp.py` file placed under `mcp_plugins/external/` that exposes a
26
+ `Plugin` class is discovered and loaded automatically at startup.
27
+
28
+ ## Installation
29
+
30
+ From the repository root:
31
+
32
+ ```bash
33
+ python setup.py install fc-mcp
34
+ ```
35
+
36
+
37
+ This installs the `fc-mcp` console entry point. Requires Python >= 3.10 and
38
+ `fastmcp >= 2.3` (needed for the Streamable HTTP transport).
39
+
40
+ ## Starting the server
41
+
42
+ The server supports two transports. **stdio** is the default and is what a
43
+ local client (for example `fc-agent`, which launches the server as a
44
+ subprocess) uses. **Streamable HTTP** runs the server as a standalone HTTP
45
+ service so remote / third-party MCP clients can connect.
46
+
47
+ ### stdio (default)
48
+
49
+ ```bash
50
+ fc-mcp
51
+ ```
52
+
53
+ The client is responsible for spawning the process and talking JSON-RPC over
54
+ stdin/stdout.
55
+
56
+ ### Streamable HTTP
57
+
58
+ ```bash
59
+ # Local only (default host 127.0.0.1, port 8000, path /mcp)
60
+ fc-mcp --transport http
61
+
62
+ # Expose to the network
63
+ fc-mcp --transport http --host 0.0.0.0 --port 8000 --path /mcp
64
+ ```
65
+
66
+ Clients then connect to `http://<host>:<port><path>`, e.g.
67
+ `http://127.0.0.1:8000/mcp`.
68
+
69
+ ### Options
70
+
71
+ Every flag has a matching environment variable, so the server can be configured
72
+ in containers/services without CLI arguments. CLI flags take precedence.
73
+
74
+ | Flag | Environment variable | Default | Description |
75
+ | ------------- | -------------------- | ----------- | -------------------------------------------------- |
76
+ | `--transport` | `FC_MCP_TRANSPORT` | `stdio` | Transport mode: `stdio` or `http`. |
77
+ | `--host` | `FC_MCP_HOST` | `127.0.0.1` | Bind host (HTTP transport only). |
78
+ | `--port` | `FC_MCP_PORT` | `8000` | Bind port (HTTP transport only). |
79
+ | `--path` | `FC_MCP_PATH` | `/mcp` | URL path the MCP endpoint is served on (HTTP only).|
80
+ | _(none)_ | `FC_MCP_AUTH_TOKEN` | _(unset)_ | Static Bearer token for HTTP transport (env only). |
81
+
82
+ Example using environment variables:
83
+
84
+ ```bash
85
+ export FC_MCP_TRANSPORT=http
86
+ export FC_MCP_HOST=0.0.0.0
87
+ export FC_MCP_PORT=9000
88
+ fc-mcp
89
+ # -> http://0.0.0.0:9000/mcp
90
+ ```
91
+
92
+ ## Authentication (HTTP transport)
93
+
94
+ By default the HTTP endpoint is **unauthenticated** — anyone who can reach
95
+ `http://<host>:<port><path>` can invoke every tool. When exposing the server on
96
+ a network (`--host 0.0.0.0`), enable static Bearer-token auth by setting the
97
+ `FC_MCP_AUTH_TOKEN` environment variable:
98
+
99
+ ```bash
100
+ export FC_MCP_TRANSPORT=http
101
+ export FC_MCP_HOST=0.0.0.0
102
+ export FC_MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
103
+ fc-mcp
104
+ ```
105
+
106
+ Behavior:
107
+
108
+ - If `FC_MCP_AUTH_TOKEN` is unset or empty, the server runs without auth
109
+ (backward compatible).
110
+ - If set, every request must include `Authorization: Bearer <token>`;
111
+ otherwise the server responds with `401 Unauthorized`.
112
+ - The token is only enforced for HTTP transport. stdio transport ignores it.
113
+
114
+ The token is intentionally read **only from an environment variable**, not from
115
+ a CLI flag: command-line arguments are visible to other local users via `ps` /
116
+ `/proc/<pid>/cmdline` (and leak into shell history), whereas
117
+ `/proc/<pid>/environ` is readable only by the process owner and root.
118
+
119
+ Client config with a Bearer token:
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "fc": {
125
+ "url": "http://127.0.0.1:8000/mcp",
126
+ "headers": {
127
+ "Authorization": "Bearer <token>"
128
+ }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ ## Connecting a third-party MCP client
135
+
136
+ For HTTP transport, point the client at the server URL. A typical MCP client
137
+ config entry looks like:
138
+
139
+ ```json
140
+ {
141
+ "mcpServers": {
142
+ "fc": {
143
+ "url": "http://127.0.0.1:8000/mcp"
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ For stdio transport, configure the client to launch the process instead:
150
+
151
+ ```json
152
+ {
153
+ "mcpServers": {
154
+ "fc": {
155
+ "command": "fc-mcp"
156
+ }
157
+ }
158
+ }
159
+ ```
160
+
161
+ ## Available tools
162
+
163
+ Grouped by purpose (see the plugin sources for full signatures):
164
+
165
+ - **Resource management** (`mcp_plugins/internal/fc_mcp.py`):
166
+ `lock`, `unlock`, `status`, `cluster_info`, `all_locks`,
167
+ `advanced_features`, `get_console_names`, `set_comment`.
168
+ - **Command execution** (`mcp_plugins/internal/fc_mcp.py`):
169
+ `get_fc_command` (build a command string), `fc_command` (run or return
170
+ ssh/console/power/scp/rsync commands).
171
+ - **Deploy / flash** (`mcp_plugins/external/uuu_bcu_mcp.py`):
172
+ `boot_mode_switch`, `flash_usb_boot_nexus`, `flash_usb_boot_uri`,
173
+ `flash_non_usb_boot_uri`.
174
+ - **U-Boot / predeploy** (`mcp_plugins/external/uboot_mcp.py`):
175
+ `get_board_server_ip`, `get_uboot_config_uri`, `get_uboot_config_nexus`,
176
+ `list_available_boards`.
177
+
178
+ ## Extending with plugins
179
+
180
+ Add a new `*_mcp.py` file under `mcp_plugins/external/` that defines a `Plugin`
181
+ class subclassing `MCPPlugin` and implementing `register_tools()`:
182
+
183
+ ```python
184
+ from fc_mcp.mcp_base import MCPPlugin
185
+
186
+
187
+ class Plugin(MCPPlugin):
188
+ def register_tools(self):
189
+ @self.mcp.tool()
190
+ def my_tool(resource_id: str) -> dict:
191
+ """Describe what the tool does for the agent."""
192
+ ...
193
+ return {"success": True}
194
+ ```
195
+
196
+ The file is picked up automatically the next time the server starts.
@@ -0,0 +1,274 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ #
4
+ # Copyright 2025-2026 NXP
5
+ #
6
+ # SPDX-License-Identifier: MIT
7
+
8
+ """
9
+ FastMCP Server for FC Agent
10
+ Provides tools to interact with fc clusters by calling fc-client.
11
+ """
12
+
13
+ import argparse
14
+ import importlib
15
+ import logging
16
+ import os
17
+ import signal
18
+ import sys
19
+ import threading
20
+ import time
21
+
22
+ from fastmcp import FastMCP
23
+ from fastmcp.server.auth.providers.jwt import StaticTokenVerifier
24
+ from fastmcp.server.middleware import Middleware, MiddlewareContext
25
+
26
+ from fc_common.logger import Logger
27
+ from fc_mcp.mcp_base import MCPPlugin
28
+ from fc_mcp.mcp_plugins.internal.fc_mcp import Plugin
29
+
30
+ LOGGER = logging.getLogger("fc_mcp")
31
+
32
+
33
+ class IdleMonitor(Middleware):
34
+ """Track MCP request activity and shut the server down after being idle.
35
+
36
+ A FastMCP middleware refreshes the last-activity timestamp on every
37
+ incoming MCP message. A background daemon thread periodically checks the
38
+ elapsed idle time and, once it exceeds the configured timeout, records a
39
+ log entry and sends SIGTERM to the current process so uvicorn performs a
40
+ graceful shutdown.
41
+
42
+ This is only meaningful for the HTTP transport, where requests exist.
43
+ """
44
+
45
+ def __init__(self, timeout_seconds, check_interval=300):
46
+ self._timeout = timeout_seconds
47
+ # Poll at most every check_interval seconds, but never longer than the timeout itself.
48
+ self._check_interval = min(check_interval, timeout_seconds)
49
+ self._last_activity = time.monotonic()
50
+ self._lock = threading.Lock()
51
+
52
+ def _touch(self):
53
+ with self._lock:
54
+ self._last_activity = time.monotonic()
55
+
56
+ async def on_message(self, context: MiddlewareContext, call_next):
57
+ self._touch()
58
+ return await call_next(context)
59
+
60
+ def start(self):
61
+ """Launch the background idle-watchdog thread (daemon)."""
62
+ thread = threading.Thread(
63
+ target=self._watch, name="fc-mcp-idle-monitor", daemon=True
64
+ )
65
+ thread.start()
66
+
67
+ def _watch(self):
68
+ while True:
69
+ time.sleep(self._check_interval)
70
+ with self._lock:
71
+ idle = time.monotonic() - self._last_activity
72
+ if idle >= self._timeout:
73
+ LOGGER.info(
74
+ "FC MCP HTTP server idle for %.0f seconds "
75
+ "(timeout %d seconds), shutting down gracefully.",
76
+ idle,
77
+ self._timeout,
78
+ )
79
+ # Trigger uvicorn's graceful shutdown via its signal handler.
80
+ os.kill(os.getpid(), signal.SIGTERM)
81
+ return
82
+
83
+
84
+ def load_external_plugins():
85
+ """Automatically discover and load all plugins from the external folder"""
86
+ external_dir = os.path.join(os.path.dirname(__file__), "mcp_plugins", "external")
87
+
88
+ if not os.path.exists(external_dir):
89
+ MCPPlugin.load_plugins(mcp)
90
+ return
91
+
92
+ # Find all *_mcp.py files
93
+ for filename in os.listdir(external_dir):
94
+ if filename.endswith("_mcp.py") and not filename.startswith("__"):
95
+ module_name = filename[:-3]
96
+ try:
97
+ full_module_name = f"fc_mcp.mcp_plugins.external.{module_name}"
98
+ module = importlib.import_module(full_module_name)
99
+
100
+ if hasattr(module, "Plugin"):
101
+ plugin_class = getattr(module, "Plugin")
102
+ plugin_class(mcp)
103
+ print(f"Loaded external plugin: {module_name}")
104
+ else:
105
+ print(f"Warning: No Plugin class found in {module_name}")
106
+
107
+ except Exception as exce: # pylint: disable=broad-except
108
+ print(f"Failed to load plugin {module_name}: {exce}")
109
+
110
+
111
+ # Initialize the FastMCP server
112
+ mcp = FastMCP("FC MCP Server")
113
+
114
+ # Load internal plugins
115
+ Plugin(mcp)
116
+
117
+ # Load external plugins
118
+ load_external_plugins()
119
+
120
+
121
+ def _default_port():
122
+ """Read FC_MCP_PORT from env, falling back to 8000 if unset or invalid."""
123
+ raw = os.environ.get("FC_MCP_PORT", "8000")
124
+ try:
125
+ return int(raw)
126
+ except (TypeError, ValueError):
127
+ print(
128
+ f"Warning: invalid FC_MCP_PORT '{raw}', falling back to 8000",
129
+ file=sys.stderr,
130
+ )
131
+ return 8000
132
+
133
+
134
+ def _default_idle_timeout():
135
+ """Read FC_MCP_IDLE_TIMEOUT from env, falling back to 3600 seconds."""
136
+ raw = os.environ.get("FC_MCP_IDLE_TIMEOUT", "3600")
137
+ try:
138
+ return int(raw)
139
+ except (TypeError, ValueError):
140
+ print(
141
+ f"Warning: invalid FC_MCP_IDLE_TIMEOUT '{raw}', falling back to 3600",
142
+ file=sys.stderr,
143
+ )
144
+ return 3600
145
+
146
+
147
+ def parse_args():
148
+ """Parse command line arguments for transport selection.
149
+
150
+ Environment variables provide the defaults so the server can be
151
+ configured in containerized/service deployments without CLI flags:
152
+ FC_MCP_TRANSPORT, FC_MCP_HOST, FC_MCP_PORT, FC_MCP_PATH, FC_MCP_IDLE_TIMEOUT
153
+ """
154
+ parser = argparse.ArgumentParser(
155
+ prog="fc-mcp",
156
+ description="FC MCP Server (supports stdio and streamable HTTP transports).",
157
+ )
158
+ parser.add_argument(
159
+ "--transport",
160
+ choices=["stdio", "http"],
161
+ default=os.environ.get("FC_MCP_TRANSPORT", "stdio"),
162
+ help="Transport mode: 'stdio' (default) or 'http' (streamable HTTP).",
163
+ )
164
+ parser.add_argument(
165
+ "--host",
166
+ default=os.environ.get("FC_MCP_HOST", "127.0.0.1"),
167
+ help="Host to bind when using HTTP transport (default: 127.0.0.1).",
168
+ )
169
+ parser.add_argument(
170
+ "--port",
171
+ type=int,
172
+ default=_default_port(),
173
+ help="Port to bind when using HTTP transport (default: 8000).",
174
+ )
175
+ parser.add_argument(
176
+ "--path",
177
+ default=os.environ.get("FC_MCP_PATH", "/mcp"),
178
+ help="URL path to serve MCP on when using HTTP transport (default: /mcp).",
179
+ )
180
+ parser.add_argument(
181
+ "--idle-timeout",
182
+ type=int,
183
+ default=_default_idle_timeout(),
184
+ help=(
185
+ "Idle timeout in seconds for HTTP transport. If no MCP request is "
186
+ "received within this window, the server shuts down gracefully. "
187
+ "Set to 0 to disable (default: 3600). Env: FC_MCP_IDLE_TIMEOUT."
188
+ ),
189
+ )
190
+ return parser.parse_args()
191
+
192
+
193
+ def _configure_http_auth():
194
+ """Enable static Bearer-token auth for HTTP transport when configured.
195
+
196
+ The token is read from the FC_MCP_AUTH_TOKEN environment variable only.
197
+ Passing secrets via environment variables avoids exposing them through the
198
+ process command line (visible via ps / /proc/<pid>/cmdline to other users).
199
+
200
+ Behavior:
201
+ - If FC_MCP_AUTH_TOKEN is unset or empty, HTTP transport runs without
202
+ authentication (backward compatible).
203
+ - If set, clients must send 'Authorization: Bearer <token>' or requests
204
+ are rejected with HTTP 401.
205
+
206
+ The token is only enforced for HTTP transport; stdio transport ignores it.
207
+ """
208
+ token = os.environ.get("FC_MCP_AUTH_TOKEN", "").strip()
209
+ if not token:
210
+ print(
211
+ "FC MCP HTTP transport: no FC_MCP_AUTH_TOKEN set, "
212
+ "running WITHOUT authentication.",
213
+ file=sys.stderr,
214
+ )
215
+ return
216
+
217
+ mcp.auth = StaticTokenVerifier(
218
+ tokens={
219
+ token: {
220
+ "client_id": "fc-mcp",
221
+ "scopes": [],
222
+ }
223
+ }
224
+ )
225
+ print(
226
+ "FC MCP HTTP transport: Bearer token authentication ENABLED.",
227
+ file=sys.stderr,
228
+ )
229
+
230
+
231
+ def main():
232
+ args = parse_args()
233
+
234
+ if args.transport == "http":
235
+ # Streamable HTTP transport for remote/third-party MCP clients.
236
+ # Clients connect to http://<host>:<port><path>, e.g. http://127.0.0.1:8000/mcp
237
+ Logger.init("fc_mcp", log_name="mcp_server.log")
238
+ _configure_http_auth()
239
+
240
+ idle_monitor = None
241
+ if args.idle_timeout > 0:
242
+ idle_monitor = IdleMonitor(args.idle_timeout)
243
+ mcp.add_middleware(idle_monitor)
244
+ LOGGER.info(
245
+ "FC MCP HTTP server starting on %s:%d%s " "(idle timeout %d seconds).",
246
+ args.host,
247
+ args.port,
248
+ args.path,
249
+ args.idle_timeout,
250
+ )
251
+ idle_monitor.start()
252
+ else:
253
+ LOGGER.info(
254
+ "FC MCP HTTP server starting on %s:%d%s (idle timeout disabled).",
255
+ args.host,
256
+ args.port,
257
+ args.path,
258
+ )
259
+
260
+ mcp.run(
261
+ transport="http",
262
+ host=args.host,
263
+ port=args.port,
264
+ path=args.path,
265
+ show_banner=False,
266
+ )
267
+ LOGGER.info("FC MCP HTTP server stopped.")
268
+ else:
269
+ # Default stdio transport (used by fc-agent subprocess launch).
270
+ mcp.run(show_banner=False)
271
+
272
+
273
+ if __name__ == "__main__":
274
+ main()