Skip to content

Commit 9daca9b

Browse files
authored
Merge pull request #2 from WATonomous/eddy/tests_and_basic_config_loading
adding testing lib and config parsing
2 parents bce73cb + c639e77 commit 9daca9b

File tree

34 files changed

+2832
-112
lines changed

34 files changed

+2832
-112
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ CLAUDE.md
1515
.vscode/browse.vc.db
1616
.vscode/browse.vc.db-shm
1717
.vscode/browse.vc.db-wal
18+
19+
# Python Artifacts
20+
__pycache__/

robot_mcp_common_msg_plugins/robot_common_msg_plugins/COLCON_IGNORE

Whitespace-only changes.

robot_mcp_common_msg_plugins/robot_mcp_geometry_msg_plugins/COLCON_IGNORE

Whitespace-only changes.

robot_mcp_common_msg_plugins/robot_mcp_nav_msg_plugins/COLCON_IGNORE

Whitespace-only changes.

robot_mcp_common_msg_plugins/robot_mcp_sensor_msg_plugins/COLCON_IGNORE

Whitespace-only changes.

robot_mcp_common_msg_plugins/robot_mcp_std_msg_plugins/COLCON_IGNORE

Whitespace-only changes.

robot_mcp_server/CMakeLists.txt

Lines changed: 70 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -35,42 +35,79 @@ find_package(nlohmann_json REQUIRED)
3535

3636
set(include_dir ${CMAKE_CURRENT_SOURCE_DIR}/include)
3737

38-
# Server library (to be populated with source files in later phases)
39-
# add_library(${PROJECT_NAME} SHARED
40-
# # src files will go here
41-
# )
42-
# target_include_directories(${PROJECT_NAME} PUBLIC
43-
# $<BUILD_INTERFACE:${include_dir}>
44-
# $<INSTALL_INTERFACE:include>
45-
# )
46-
# target_link_libraries(${PROJECT_NAME}
47-
# PUBLIC
48-
# robot_mcp_msg_pluginlib::robot_mcp_msg_pluginlib
49-
# PRIVATE
50-
# rclcpp::rclcpp
51-
# rclcpp_lifecycle::rclcpp_lifecycle
52-
# rclcpp_action::rclcpp_action
53-
# rclcpp_components::rclcpp_components
54-
# ${std_msgs_TARGETS}
55-
# pluginlib::pluginlib
56-
# nlohmann_json::nlohmann_json
57-
# )
58-
#
59-
# install(TARGETS ${PROJECT_NAME}
60-
# EXPORT ${PROJECT_NAME}Targets
61-
# ARCHIVE DESTINATION lib
62-
# LIBRARY DESTINATION lib
63-
# RUNTIME DESTINATION bin
64-
# )
65-
#
66-
# install(EXPORT ${PROJECT_NAME}Targets
67-
# NAMESPACE ${PROJECT_NAME}::
68-
# DESTINATION share/${PROJECT_NAME}/cmake
69-
# )
38+
# Server library
39+
add_library(${PROJECT_NAME} SHARED
40+
src/robot_mcp_server_node.cpp
41+
src/mcp_config/config_parser.cpp
42+
)
43+
target_include_directories(${PROJECT_NAME} PUBLIC
44+
$<BUILD_INTERFACE:${include_dir}>
45+
$<INSTALL_INTERFACE:include>
46+
)
47+
target_link_libraries(${PROJECT_NAME}
48+
PUBLIC
49+
robot_mcp_msg_pluginlib::robot_mcp_msg_pluginlib
50+
PRIVATE
51+
rclcpp::rclcpp
52+
rclcpp_lifecycle::rclcpp_lifecycle
53+
rclcpp_action::rclcpp_action
54+
rclcpp_components::component
55+
${std_msgs_TARGETS}
56+
pluginlib::pluginlib
57+
nlohmann_json::nlohmann_json
58+
)
59+
60+
# Register as a rclcpp_components component
61+
rclcpp_components_register_node(${PROJECT_NAME}
62+
PLUGIN "robot_mcp::MCPServerNode"
63+
EXECUTABLE ${PROJECT_NAME}_node
64+
)
65+
66+
install(TARGETS ${PROJECT_NAME}
67+
EXPORT ${PROJECT_NAME}Targets
68+
ARCHIVE DESTINATION lib
69+
LIBRARY DESTINATION lib
70+
RUNTIME DESTINATION bin
71+
)
72+
73+
install(EXPORT ${PROJECT_NAME}Targets
74+
NAMESPACE ${PROJECT_NAME}::
75+
DESTINATION share/${PROJECT_NAME}/cmake
76+
)
7077

7178
install(DIRECTORY include/
7279
DESTINATION include
7380
)
7481

75-
# ament_export_targets(${PROJECT_NAME}Targets HAS_LIBRARY_TARGET)
82+
# Install test configuration files
83+
install(DIRECTORY test_mcp_config/
84+
DESTINATION share/${PROJECT_NAME}/test_mcp_config
85+
)
86+
87+
if(BUILD_TESTING)
88+
# Find robot_mcp_test package
89+
find_package(robot_mcp_test REQUIRED)
90+
find_package(launch_testing_ament_cmake REQUIRED)
91+
92+
# Include the custom test function
93+
include(${robot_mcp_test_DIR}/add_robot_mcp_test.cmake)
94+
95+
# Add lifecycle tests
96+
add_robot_mcp_test(test_lifecycle
97+
test/test_lifecycle.cpp
98+
LIBRARIES
99+
${PROJECT_NAME}
100+
)
101+
102+
# Add configuration parser launch tests
103+
add_launch_test(
104+
test_mcp_config/test_complete_config.py
105+
)
106+
107+
add_launch_test(
108+
test_mcp_config/test_minimal_config.py
109+
)
110+
endif()
111+
112+
ament_export_targets(${PROJECT_NAME}Targets HAS_LIBRARY_TARGET)
76113
ament_package()
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright (c) 2025-present WATonomous. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Copyright 2025 WATonomous
16+
//
17+
// Licensed under the Apache License, Version 2.0 (the "License");
18+
// you may not use this file except in compliance with the License.
19+
// You may obtain a copy of the License at
20+
//
21+
// http://www.apache.org/licenses/LICENSE-2.0
22+
//
23+
// Unless required by applicable law or agreed to in writing, software
24+
// distributed under the License is distributed on an "AS IS" BASIS,
25+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26+
// See the License for the specific language governing permissions and
27+
// limitations under the License.
28+
29+
#ifndef ROBOT_MCP_CONFIG_PARSER_HPP_
30+
#define ROBOT_MCP_CONFIG_PARSER_HPP_
31+
32+
#include <map>
33+
#include <memory>
34+
#include <string>
35+
#include <vector>
36+
37+
#include "rclcpp/rclcpp.hpp"
38+
#include "rclcpp_lifecycle/lifecycle_node.hpp"
39+
#include "robot_mcp_server/mcp_config/config_types.hpp"
40+
41+
namespace robot_mcp::config
42+
{
43+
44+
/**
45+
* @brief Exception thrown when configuration parsing fails
46+
*/
47+
class ConfigParseException : public std::runtime_error
48+
{
49+
public:
50+
explicit ConfigParseException(const std::string & message)
51+
: std::runtime_error(message)
52+
{}
53+
};
54+
55+
/**
56+
* @brief Parser for MCP server configuration from ROS2 parameters
57+
*
58+
* This class reads ROS2 parameters and constructs a MCPServerConfig object.
59+
* It handles parameter validation and provides detailed error messages.
60+
*/
61+
class ConfigParser
62+
{
63+
public:
64+
/**
65+
* @brief Parse configuration from a ROS2 node's parameters
66+
*
67+
* @param node ROS2 lifecycle node to read parameters from
68+
* @return Parsed configuration
69+
* @throws ConfigParseException if parsing fails
70+
*/
71+
static MCPServerConfig parse(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
72+
73+
/**
74+
* @brief Validate a configuration object
75+
*
76+
* Checks for:
77+
* - Duplicate names
78+
* - Invalid port numbers
79+
* - Missing required fields
80+
* - Invalid resource group references
81+
*
82+
* @param config Configuration to validate
83+
* @throws ConfigParseException if validation fails
84+
*/
85+
static void validate(const MCPServerConfig & config);
86+
87+
private:
88+
/**
89+
* @brief Parse server configuration
90+
*/
91+
static ServerConfig parseServerConfig(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
92+
93+
/**
94+
* @brief Parse topic configurations
95+
*/
96+
static std::vector<TopicConfig> parseTopics(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
97+
98+
/**
99+
* @brief Parse service configurations
100+
*/
101+
static std::vector<ServiceConfig> parseServices(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
102+
103+
/**
104+
* @brief Parse action configurations
105+
*/
106+
static std::vector<ActionConfig> parseActions(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
107+
108+
/**
109+
* @brief Parse resource group configurations
110+
*/
111+
static std::map<std::string, ResourceGroupConfig> parseResourceGroups(
112+
rclcpp_lifecycle::LifecycleNode::SharedPtr node);
113+
114+
/**
115+
* @brief Parse conflict resolution string to enum
116+
*/
117+
static ConflictResolution parseConflictResolution(const std::string & str);
118+
};
119+
120+
} // namespace robot_mcp::config
121+
122+
#endif // ROBOT_MCP_CONFIG_PARSER_HPP_
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright (c) 2025-present WATonomous. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
// Copyright 2025 WATonomous
16+
//
17+
// Licensed under the Apache License, Version 2.0 (the "License");
18+
// you may not use this file except in compliance with the License.
19+
// You may obtain a copy of the License at
20+
//
21+
// http://www.apache.org/licenses/LICENSE-2.0
22+
//
23+
// Unless required by applicable law or agreed to in writing, software
24+
// distributed under the License is distributed on an "AS IS" BASIS,
25+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26+
// See the License for the specific language governing permissions and
27+
// limitations under the License.
28+
29+
#ifndef ROBOT_MCP_CONFIG_TYPES_HPP_
30+
#define ROBOT_MCP_CONFIG_TYPES_HPP_
31+
32+
#include <map>
33+
#include <optional>
34+
#include <string>
35+
#include <vector>
36+
37+
namespace robot_mcp::config
38+
{
39+
40+
/**
41+
* @brief Conflict resolution strategy for resource groups
42+
*/
43+
enum class ConflictResolution
44+
{
45+
ERROR, ///< Return error if conflict occurs
46+
CANCEL, ///< Cancel existing operation and start new one
47+
QUEUE ///< Queue new operation until current one completes
48+
};
49+
50+
/**
51+
* @brief Configuration for HTTP server
52+
*/
53+
struct ServerConfig
54+
{
55+
std::string host{"0.0.0.0"}; ///< Server host address
56+
int port{8080}; ///< Server port number
57+
std::optional<std::string> api_key; ///< Optional API key for authentication
58+
int max_connections{100}; ///< Maximum concurrent connections
59+
int timeout_ms{30000}; ///< Request timeout in milliseconds
60+
bool enable_cors{true}; ///< Enable CORS headers
61+
};
62+
63+
/**
64+
* @brief Configuration for a topic (publish/subscribe)
65+
*/
66+
struct TopicConfig
67+
{
68+
std::string name; ///< Friendly name for MCP
69+
std::string topic; ///< ROS2 topic name
70+
std::string msg_type; ///< ROS2 message type (e.g., "std_msgs/msg/String")
71+
std::string plugin; ///< Plugin class name
72+
bool subscribe{true}; ///< Subscribe and cache messages
73+
bool publish{true}; ///< Allow publishing to this topic
74+
std::vector<std::string> resource_groups; ///< Resource groups this topic belongs to
75+
};
76+
77+
/**
78+
* @brief Configuration for a service (call/response)
79+
*/
80+
struct ServiceConfig
81+
{
82+
std::string name; ///< Friendly name for MCP
83+
std::string service; ///< ROS2 service name
84+
std::string srv_type; ///< ROS2 service type
85+
std::string plugin; ///< Plugin class name
86+
int timeout_ms{5000}; ///< Service call timeout
87+
std::vector<std::string> resource_groups; ///< Resource groups this service belongs to
88+
};
89+
90+
/**
91+
* @brief Configuration for an action (goal/feedback/result)
92+
*/
93+
struct ActionConfig
94+
{
95+
std::string name; ///< Friendly name for MCP
96+
std::string action; ///< ROS2 action name
97+
std::string action_type; ///< ROS2 action type
98+
std::string plugin; ///< Plugin class name
99+
int timeout_ms{0}; ///< Action timeout (0 = no timeout)
100+
bool cancellable{true}; ///< Whether action can be cancelled
101+
std::vector<std::string> resource_groups; ///< Resource groups this action belongs to
102+
};
103+
104+
/**
105+
* @brief Configuration for a resource group
106+
*
107+
* Resource groups prevent conflicting operations from running concurrently.
108+
* For example, multiple navigation commands shouldn't run simultaneously.
109+
*/
110+
struct ResourceGroupConfig
111+
{
112+
std::string name; ///< Resource group name
113+
int max_concurrent{1}; ///< Maximum concurrent operations
114+
bool interruptible{true}; ///< Whether operations can be cancelled
115+
ConflictResolution conflict_resolution{ConflictResolution::ERROR}; ///< Default conflict handling
116+
};
117+
118+
/**
119+
* @brief Complete MCP server configuration
120+
*/
121+
struct MCPServerConfig
122+
{
123+
ServerConfig server; ///< HTTP server configuration
124+
std::vector<TopicConfig> topics; ///< Topics to expose
125+
std::vector<ServiceConfig> services; ///< Services to expose
126+
std::vector<ActionConfig> actions; ///< Actions to expose
127+
std::map<std::string, ResourceGroupConfig> resource_groups; ///< Resource group definitions
128+
};
129+
130+
} // namespace robot_mcp::config
131+
132+
#endif // ROBOT_MCP_CONFIG_TYPES_HPP_

0 commit comments

Comments
 (0)