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.
- fc_client_docker-0.9.2/PKG-INFO +71 -0
- fc_client_docker-0.9.2/fc_client_docker.egg-info/PKG-INFO +71 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker.egg-info/SOURCES.txt +1 -0
- fc_client_docker-0.9.2/fc_common/VERSION +1 -0
- fc_client_docker-0.9.2/fc_mcp/README.md +196 -0
- fc_client_docker-0.9.2/fc_mcp/mcp_server.py +274 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/api_svr.py +1 -174
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/setup.py +42 -5
- fc_client_docker-0.9.0/PKG-INFO +0 -18
- fc_client_docker-0.9.0/fc_client_docker.egg-info/PKG-INFO +0 -18
- fc_client_docker-0.9.0/fc_common/VERSION +0 -1
- fc_client_docker-0.9.0/fc_mcp/mcp_server.py +0 -64
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.QualityGate +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.github/workflows/main.yml +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.gitignore +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.pylintrc +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.python-version +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/.readthedocs.yaml +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/LICENSE +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/README.rst +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/SCR-fc.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/black.sh +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/docker.sh +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/package.sh +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/publish_docker.sh +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/pylint.sh +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/pytest.sh +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/ci/setup.sh +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/Makefile +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/conf.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/configuration.rst +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/css/css-style.css +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/architecture.svg +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/era.svg +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/fc.png +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/flowchart.svg +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/images/principle.svg +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/index.rst +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/make.bat +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/overview.rst +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/quickstart.rst +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/support.rst +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/doc/usage.rst +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_client/Dockerfile +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_client/fc-agent +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_client/fc-client +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_guarder/Dockerfile +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_guarder/docker-compose.yaml +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_guarder/entrypoint.sh +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_guarder/sample.env +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_server/Dockerfile +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_server/docker-compose.yaml +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/docker/fc_server/entrypoint.sh +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_agent/agent.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client/client.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client/sample_client.env +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_daemon/client_daemon.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker/fc_client_docker +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker.egg-info/dependency_links.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker.egg-info/entry_points.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_client_docker.egg-info/top_level.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/config.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/etcd.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/logger.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_common/version.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_guarder/guarder.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_base.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/external/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/external/uboot_mcp.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/external/uuu_bcu_mcp.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/internal/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_mcp/mcp_plugins/internal/fc_mcp.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/config/sample_cfg.yaml +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/config/sample_lavacli.yaml +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/config/sample_resource.yaml +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/config.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/coordinator.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/decorators.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/core/plugin.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/management/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/management/cmd_online_lava_devices.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/management/common.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/labgrid.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/lava.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/utils/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/utils/labgrid.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/plugins/utils/lava.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/server.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server/tools/cache_helper.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server_daemon/__init__.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/fc_server_daemon/server_daemon.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/agent-requirements.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/ci-requirements.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/client-requirements.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/doc-requirements.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements/server-requirements.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/requirements.txt +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/setup.cfg +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/conftest.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_client/test_client.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_guarder/conftest.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_guarder/test_guarder.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/config/cfg.yaml +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/conftest.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/test_config.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/test_coordinator.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/test_misc.py +0 -0
- {fc_client_docker-0.9.0 → fc_client_docker-0.9.2}/tests/fc_server/test_plugin_labgrid.py +0 -0
- {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
|
+
|
|
@@ -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()
|