Skip to content

Commit 6c476f3

Browse files
committed
adding testing lib and config parsing
1 parent bce73cb commit 6c476f3

29 files changed

+2740
-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_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: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright 2025 WATonomous
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+
#ifndef ROBOT_MCP_CONFIG_PARSER_HPP_
16+
#define ROBOT_MCP_CONFIG_PARSER_HPP_
17+
18+
#include <memory>
19+
#include <string>
20+
21+
#include "rclcpp/rclcpp.hpp"
22+
#include "rclcpp_lifecycle/lifecycle_node.hpp"
23+
#include "robot_mcp_server/mcp_config/config_types.hpp"
24+
25+
namespace robot_mcp::config
26+
{
27+
28+
/**
29+
* @brief Exception thrown when configuration parsing fails
30+
*/
31+
class ConfigParseException : public std::runtime_error
32+
{
33+
public:
34+
explicit ConfigParseException(const std::string & message)
35+
: std::runtime_error(message) {}
36+
};
37+
38+
/**
39+
* @brief Parser for MCP server configuration from ROS2 parameters
40+
*
41+
* This class reads ROS2 parameters and constructs a MCPServerConfig object.
42+
* It handles parameter validation and provides detailed error messages.
43+
*/
44+
class ConfigParser
45+
{
46+
public:
47+
/**
48+
* @brief Parse configuration from a ROS2 node's parameters
49+
*
50+
* @param node ROS2 lifecycle node to read parameters from
51+
* @return Parsed configuration
52+
* @throws ConfigParseException if parsing fails
53+
*/
54+
static MCPServerConfig parse(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
55+
56+
/**
57+
* @brief Validate a configuration object
58+
*
59+
* Checks for:
60+
* - Duplicate names
61+
* - Invalid port numbers
62+
* - Missing required fields
63+
* - Invalid resource group references
64+
*
65+
* @param config Configuration to validate
66+
* @throws ConfigParseException if validation fails
67+
*/
68+
static void validate(const MCPServerConfig & config);
69+
70+
private:
71+
/**
72+
* @brief Parse server configuration
73+
*/
74+
static ServerConfig parseServerConfig(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
75+
76+
/**
77+
* @brief Parse topic configurations
78+
*/
79+
static std::vector<TopicConfig> parseTopics(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
80+
81+
/**
82+
* @brief Parse service configurations
83+
*/
84+
static std::vector<ServiceConfig> parseServices(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
85+
86+
/**
87+
* @brief Parse action configurations
88+
*/
89+
static std::vector<ActionConfig> parseActions(rclcpp_lifecycle::LifecycleNode::SharedPtr node);
90+
91+
/**
92+
* @brief Parse resource group configurations
93+
*/
94+
static std::map<std::string, ResourceGroupConfig> parseResourceGroups(
95+
rclcpp_lifecycle::LifecycleNode::SharedPtr node);
96+
97+
/**
98+
* @brief Parse conflict resolution string to enum
99+
*/
100+
static ConflictResolution parseConflictResolution(const std::string & str);
101+
};
102+
103+
} // namespace robot_mcp::config
104+
105+
#endif // ROBOT_MCP_CONFIG_PARSER_HPP_
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2025 WATonomous
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+
#ifndef ROBOT_MCP_CONFIG_TYPES_HPP_
16+
#define ROBOT_MCP_CONFIG_TYPES_HPP_
17+
18+
#include <string>
19+
#include <vector>
20+
#include <optional>
21+
#include <map>
22+
23+
namespace robot_mcp::config
24+
{
25+
26+
/**
27+
* @brief Conflict resolution strategy for resource groups
28+
*/
29+
enum class ConflictResolution
30+
{
31+
ERROR, ///< Return error if conflict occurs
32+
CANCEL, ///< Cancel existing operation and start new one
33+
QUEUE ///< Queue new operation until current one completes
34+
};
35+
36+
/**
37+
* @brief Configuration for HTTP server
38+
*/
39+
struct ServerConfig
40+
{
41+
std::string host{"0.0.0.0"}; ///< Server host address
42+
int port{8080}; ///< Server port number
43+
std::optional<std::string> api_key; ///< Optional API key for authentication
44+
int max_connections{100}; ///< Maximum concurrent connections
45+
int timeout_ms{30000}; ///< Request timeout in milliseconds
46+
bool enable_cors{true}; ///< Enable CORS headers
47+
};
48+
49+
/**
50+
* @brief Configuration for a topic (publish/subscribe)
51+
*/
52+
struct TopicConfig
53+
{
54+
std::string name; ///< Friendly name for MCP
55+
std::string topic; ///< ROS2 topic name
56+
std::string msg_type; ///< ROS2 message type (e.g., "std_msgs/msg/String")
57+
std::string plugin; ///< Plugin class name
58+
bool subscribe{true}; ///< Subscribe and cache messages
59+
bool publish{true}; ///< Allow publishing to this topic
60+
std::vector<std::string> resource_groups; ///< Resource groups this topic belongs to
61+
};
62+
63+
/**
64+
* @brief Configuration for a service (call/response)
65+
*/
66+
struct ServiceConfig
67+
{
68+
std::string name; ///< Friendly name for MCP
69+
std::string service; ///< ROS2 service name
70+
std::string srv_type; ///< ROS2 service type
71+
std::string plugin; ///< Plugin class name
72+
int timeout_ms{5000}; ///< Service call timeout
73+
std::vector<std::string> resource_groups; ///< Resource groups this service belongs to
74+
};
75+
76+
/**
77+
* @brief Configuration for an action (goal/feedback/result)
78+
*/
79+
struct ActionConfig
80+
{
81+
std::string name; ///< Friendly name for MCP
82+
std::string action; ///< ROS2 action name
83+
std::string action_type; ///< ROS2 action type
84+
std::string plugin; ///< Plugin class name
85+
int timeout_ms{0}; ///< Action timeout (0 = no timeout)
86+
bool cancellable{true}; ///< Whether action can be cancelled
87+
std::vector<std::string> resource_groups; ///< Resource groups this action belongs to
88+
};
89+
90+
/**
91+
* @brief Configuration for a resource group
92+
*
93+
* Resource groups prevent conflicting operations from running concurrently.
94+
* For example, multiple navigation commands shouldn't run simultaneously.
95+
*/
96+
struct ResourceGroupConfig
97+
{
98+
std::string name; ///< Resource group name
99+
int max_concurrent{1}; ///< Maximum concurrent operations
100+
bool interruptible{true}; ///< Whether operations can be cancelled
101+
ConflictResolution conflict_resolution{ConflictResolution::ERROR}; ///< Default conflict handling
102+
};
103+
104+
/**
105+
* @brief Complete MCP server configuration
106+
*/
107+
struct MCPServerConfig
108+
{
109+
ServerConfig server; ///< HTTP server configuration
110+
std::vector<TopicConfig> topics; ///< Topics to expose
111+
std::vector<ServiceConfig> services; ///< Services to expose
112+
std::vector<ActionConfig> actions; ///< Actions to expose
113+
std::map<std::string, ResourceGroupConfig> resource_groups; ///< Resource group definitions
114+
};
115+
116+
} // namespace robot_mcp::config
117+
118+
#endif // ROBOT_MCP_CONFIG_TYPES_HPP_

0 commit comments

Comments
 (0)