ESP Service
简介
ESP Service 是面向 ESP-IDF 的三层服务基础设施。服务基类提供统一的生命周期状态机与事件发布。服务管理器在此之上提供运行时注册、批量启停与工具调用。可选的 MCP(Model Context Protocol)服务器再把已注册服务的工具,通过多种传输方式暴露给大模型或智能体。按键、Wi-Fi、命令行、OTA 等业务服务均基于该基类实现。
功能清单
生命周期状态机:
UNINITIALIZED→INITIALIZED→RUNNING⇄PAUSED,所有状态切换在调用方任务上下文中同步执行基于 vtable(
esp_service_ops_t)的子类化机制,派生服务只需实现所需的生命周期回调每个服务实例绑定一个 ADF Event Hub,通过
esp_service_publish_event()/esp_service_event_subscribe()发布订阅事件低功耗钩子
on_lowpower_enter/on_lowpower_exit,不引起状态切换esp_service_manager支持运行时注册/注销、按名称或类别查找、批量start_all/stop_all服务注册时可附带 JSON 格式的工具描述,由管理器自动解析并支持
esp_service_manager_invoke_tool()调用可选 MCP 服务器实现 MCP 2024-11-05 协议(
tools/list、tools/call、notifications/tools/list_changed),支持 HTTP、SSE、WebSocket、UART、STDIO、SDIO 六种传输方式服务管理器与 MCP 服务器内部均由互斥锁保护,可在多任务环境下并发调用
技术拆解
三层模型
ESP Service 分为三层,彼此独立,可按需选用:esp_service_t 是最小可用单元,只用它就能获得生命周期管理和事件发布能力;esp_service_manager_t 在多个服务实例之上提供统一注册与查找;MCP 服务器再挂载到管理器上,把工具调用暴露给外部 Agent。
classDiagram
direction TB
class esp_service_t {
+state
+esp_service_start()
+esp_service_publish_event()
}
class esp_service_manager_t {
+esp_service_manager_register()
+esp_service_manager_invoke_tool()
}
class esp_service_mcp_server_t {
+tools/list
+tools/call
}
esp_service_manager_t "1" o-- "0..*" esp_service_t
esp_service_mcp_server_t --> esp_service_manager_t
只使用基类即可实现一个服务;引入管理器用于多服务编排;MCP 服务器是否挂载完全可选,三者不强制绑定使用。
生命周期与子类化
派生服务把 esp_service_t 作为结构体的第一个成员嵌入,填充 esp_service_ops_t 中需要的回调,再调用 esp_service_init() 完成初始化。状态机由基类维护,四个生命周期 API(esp_service_start()、esp_service_stop()、esp_service_pause()、esp_service_resume())均在调用方任务上下文中同步调用对应的 ops 回调,服务如果需要长时间运行的后台任务,应在 on_start 内自行创建并立即返回。
typedef struct {
esp_service_t base; /* 必须是第一个成员 */
/* ... 派生字段 ... */
} my_service_t;
static esp_err_t my_on_start(esp_service_t *base)
{
my_service_t *svc = (my_service_t *)base;
/* 创建后台任务、使能硬件等 */
return ESP_OK;
}
static const esp_service_ops_t s_my_ops = {
.on_start = my_on_start,
};
esp_service_config_t cfg = { .name = "my_service" };
esp_service_init(&svc->base, &cfg, &s_my_ops);
基类在每次状态切换成功后自动发布 ESP_SERVICE_EVENT_STATE_CHANGED 事件(ID 为 UINT16_MAX - 1),派生服务定义自己的事件枚举时不能使用该值或通配符 UINT16_MAX。领域事件(如 OTA 进度、按键动作)通过同一个事件总线发布,发布与订阅方式见 ADF Event Hub。
备注
低功耗钩子由 esp_service_lowpower_enter() / esp_service_lowpower_exit() 直接调用,不经过状态机,适合用来挂起无线电、LED 等外围资源。
服务管理器
esp_service_manager_t 维护一份服务注册表,每个条目通过 esp_service_registration_t 描述:必填的 service 实例、可选的分类字符串 category(供 find_by_category 查询)、以及一对可选的 tool_desc / tool_invoke。两者都设置时,管理器会解析 tool_desc 中的 JSON 工具描述数组,并把 esp_service_manager_invoke_tool() 的调用路由到 tool_invoke 回调;两者都为空则仅做生命周期管理。
esp_service_manager_t *mgr;
esp_service_manager_create(NULL, &mgr);
esp_service_manager_register(mgr, &(esp_service_registration_t){
.service = (esp_service_t *)my_service,
.category = "audio",
});
esp_service_manager_start_all(mgr);
工具描述是一段 JSON 数组,每项包含 name、description 和 inputSchema:
[
{
"name": "player_service_play",
"description": "Start audio playback",
"inputSchema": { "type": "object", "properties": {} }
}
]
CLI Service 用管理器实现它的 svc / tool 命令,具体用法见 ESP CLI Service。
MCP 服务器(可选)
启用 CONFIG_ESP_MCP_ENABLE 后可以创建 MCP 服务器,把管理器上注册的工具通过 JSON-RPC 2.0 暴露给外部 Agent。服务器本身与传输方式解耦,通过 esp_service_manager_as_tool_provider() 把管理器包装为工具来源,再传入一个具体的传输实例。
flowchart TD
LLM["LLM / AI Agent"] --> MCP[MCP 服务器]
MCP --> MGR[服务管理器]
MGR --> S1[服务 A]
MGR --> S2[服务 B]
支持的传输方式各自对应一个独立的 Kconfig 选项:HTTP(POST /mcp)、SSE 流式、WebSocket、UART、STDIO、SDIO,均以 esp_service_mcp_trans_t 为统一接口,可按目标设备的连接方式任选其一或多个。
esp_service_mcp_trans_t *transport = NULL;
esp_service_mcp_trans_http_create(&http_cfg, &transport);
esp_service_mcp_server_config_t cfg = ESP_SERVICE_MCP_SERVER_CONFIG_DEFAULT();
esp_service_manager_as_tool_provider(mgr, &cfg.tool_provider);
cfg.transport = transport;
esp_service_mcp_server_t *server = NULL;
esp_service_mcp_server_create(&cfg, &server);
esp_service_mcp_server_start(server);
应用示例
components/esp_service/examples/mock_services/演示服务管理器与全部 MCP 传输方式的组合,附带主机侧 Python 测试脚本。services_hub 演示 Wi-Fi Service、OTA Service、CLI Service、Button Service 多个服务组合、并集成
esp_board_manager的生产风格用法。
FAQ
Q1:一个服务必须注册到 esp_service_manager 才能使用吗?
不需要。esp_service_t 自身即可完成初始化、启停和事件发布,管理器只在需要跨服务编排或 MCP 工具调用时才有必要引入。
Q2:MCP 服务器支持同时开启多种传输方式吗?
每个 esp_service_mcp_server_t 实例绑定一个传输实例;需要多种传输方式同时对外提供服务时,创建多个服务器实例并共享同一个工具来源即可。
API 参考
Header File
Functions
-
esp_err_t esp_service_init(esp_service_t *service, const esp_service_config_t *config, const esp_service_ops_t *ops)
Initialize service base.
- 参数:
service – [in] Service instance (caller allocates)
config – [in] Configuration
ops – [in] Lifecycle operations (may be NULL)
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG service or config is NULL.
ESP_ERR_NO_MEM Insufficient memory.
other Error from ops->on_init.
-
esp_err_t esp_service_deinit(esp_service_t *service)
Deinitialize service base.
- 参数:
service – [in] Service instance
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Invalid argument
-
esp_err_t esp_service_start(esp_service_t *service)
Start service (INITIALIZED -> RUNNING)
Calls ops->on_start() synchronously in the caller's task context. If the service needs background work it should create its own task inside on_start and return immediately.
- 参数:
service – [in] Service instance
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Invalid argument
ESP_ERR_INVALID_STATE Not in INITIALIZED state (e.g. already RUNNING)
other Error returned by ops->on_start
-
esp_err_t esp_service_stop(esp_service_t *service)
Stop service (any -> INITIALIZED)
- 参数:
service – [in] Service instance
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Invalid argument
-
esp_err_t esp_service_pause(esp_service_t *service)
Pause service (RUNNING -> PAUSED)
- 参数:
service – [in] Service instance
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG service is NULL.
ESP_ERR_INVALID_STATE Not in RUNNING state.
other Error from ops->on_pause.
-
esp_err_t esp_service_resume(esp_service_t *service)
Resume service (PAUSED -> RUNNING)
- 参数:
service – [in] Service instance
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG service is NULL.
ESP_ERR_INVALID_STATE Not in PAUSED state.
other Error from ops->on_resume.
-
esp_err_t esp_service_get_state(const esp_service_t *service, esp_service_state_t *out_state)
Get current service state.
- 参数:
service – [in] Service instance
out_state – [out] Output: current state
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Invalid argument
-
esp_err_t esp_service_is_running(const esp_service_t *service, bool *out_running)
Check if service is running.
- 参数:
service – [in] Service instance
out_running – [out] Output: true if state is RUNNING
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Invalid argument
-
esp_err_t esp_service_publish_event(esp_service_t *service, uint16_t event_id, const void *payload, uint16_t payload_len, adf_event_payload_release_cb_t release_cb, void *release_ctx)
Publish a domain event through the service-bound Event Hub.
备注
Payload ownership contract (stricter than adf_event_hub_publish()):
If release_cb is non-NULL, it is invoked EXACTLY ONCE for every invocation of this function, regardless of the return code (ESP_OK, INVALID_ARG, INVALID_STATE, NOT_FOUND, NO_MEM).
The caller MUST NOT free or otherwise access payload after this function returns; doing so results in a double-free / UAF.
release_cb may fire synchronously inside this call (callback-mode delivery or early error path), so any fields the caller needs to log/use must be captured into local variables BEFORE the call.
If release_cb is NULL, the caller keeps payload ownership on all paths (typical for stack/static/long-lived payloads).
- 参数:
service – [in] Service instance
event_id – [in] Domain-local event id (must be > 0)
payload – [in] Event payload pointer (may be NULL)
payload_len – [in] Payload length in bytes
release_cb – [in] Optional payload release callback; when non-NULL, ownership of payload is transferred on every path
release_ctx – [in] Optional release callback context
- 返回:
ESP_OK Event published.
ESP_ERR_INVALID_ARG service is NULL or event_id is zero.
ESP_ERR_INVALID_STATE No event hub bound.
ESP_ERR_NOT_FOUND Hub is not a registered domain.
ESP_ERR_NO_MEM Internal envelope allocation failed.
-
esp_err_t esp_service_event_subscribe(esp_service_t *service, const adf_event_subscribe_info_t *info)
Subscribe to events on a domain through the service-bound Event Hub.
备注
Wraps adf_event_hub_subscribe(). At least one delivery target (target_queue or handler) must be set in info.
- 参数:
service – [in] Service instance (must have a bound event hub)
info – [in] Subscription parameters
- 返回:
ESP_OK Subscriber registered.
ESP_ERR_INVALID_ARG service or info is NULL.
ESP_ERR_INVALID_STATE No event hub bound.
ESP_ERR_NO_MEM Subscriber storage allocation failed.
-
esp_err_t esp_service_event_unsubscribe(esp_service_t *service, const char *domain, uint16_t event_id)
Unsubscribe from events on a domain.
- 参数:
service – [in] Service instance (must have a bound event hub)
domain – [in] Domain to unsubscribe from; NULL = service’s own domain
event_id – [in] Event ID filter; ADF_EVENT_ANY_ID removes all
- 返回:
ESP_OK At least one subscriber removed.
ESP_ERR_INVALID_ARG service is NULL.
ESP_ERR_INVALID_STATE No event hub bound.
ESP_ERR_NOT_FOUND Domain not registered or no matching subscriber.
-
esp_err_t esp_service_event_delivery_done(esp_service_t *service, adf_event_delivery_t *delivery)
Release a queue-mode delivery reference.
备注
Must be called exactly once per received adf_event_delivery_t.
- 参数:
service – [in] Service instance (must have a bound event hub)
delivery – [in] Delivery item from the subscriber queue
- 返回:
ESP_OK Reference released.
ESP_ERR_INVALID_ARG service or delivery is NULL.
ESP_ERR_INVALID_STATE No event hub bound.
-
esp_err_t esp_service_get_event_hub(const esp_service_t *service, adf_event_hub_t *out_hub)
Get the event hub handle bound to a service.
- 参数:
service – [in] Service instance
out_hub – [out] Output: event hub handle (NULL if not bound)
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG service or out_hub is NULL.
-
esp_err_t esp_service_get_last_error(const esp_service_t *service, esp_err_t *out_err)
Get last error recorded by the base layer (e.g. on_start failed)
Returns the error code from the most recent failed lifecycle call. ESP_OK if no error has occurred.
- 参数:
service – [in] Service instance
out_err – [out] Output: last error code (ESP_OK if none)
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Invalid argument
-
esp_err_t esp_service_lowpower_enter(esp_service_t *service)
Notify service to enter low-power mode.
Calls ops->on_lowpower_enter if provided. This is a direct callback invocation — it does not change the service lifecycle state. Typical use: suspend radio, turn off LED, reduce clock, etc.- 参数:
service – [in] Service instance
- 返回:
ESP_OK On success or on_lowpower_enter is NULL
ESP_ERR_INVALID_ARG service is NULL
other Error returned by on_lowpower_enter
-
esp_err_t esp_service_lowpower_exit(esp_service_t *service)
Notify service to exit low-power mode.
Calls ops->on_lowpower_exit if provided. This is a direct callback invocation — it does not change the service lifecycle state.- 参数:
service – [in] Service instance
- 返回:
ESP_OK On success or on_lowpower_exit is NULL
ESP_ERR_INVALID_ARG service is NULL
other Error returned by on_lowpower_exit
-
esp_err_t esp_service_get_name(const esp_service_t *service, const char **out_name)
Get service name.
- 参数:
service – [in] Service instance
out_name – [out] Output: service name pointer
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Invalid argument
-
esp_err_t esp_service_get_user_data(const esp_service_t *service, void **out_data)
Get user data.
- 参数:
service – [in] Service instance
out_data – [out] Output: user data pointer
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Invalid argument
-
esp_err_t esp_service_set_user_data(esp_service_t *service, void *user_data)
Replace the user-data pointer held by the base layer.
Typical use: a subclass clears `user_data` in `on_deinit` after freeing its private context. Does not free the previous pointer.- 参数:
service – [in] Service instance
user_data – [in] New value (may be NULL)
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG service is NULL
-
esp_err_t esp_service_set_event_hub(esp_service_t *service, adf_event_hub_t hub)
Replace the Event Hub handle bound to the service.
Assigns the internal hub pointer only. The caller owns hub lifetime: destroy or retain any previous handle (e.g. via `esp_service_get_event_hub`) before calling, as this function does not call `adf_event_hub_destroy`.- 参数:
service – [in] Service instance
hub – [in] Hub handle to store (may be NULL)
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG service is NULL
-
esp_err_t esp_service_state_to_str(esp_service_state_t state, const char **out_str)
Convert state to string.
- 参数:
state – [in] Service state
out_str – [out] Output: state name string
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Invalid argument or state
-
esp_err_t esp_service_get_event_name(const esp_service_t *service, uint16_t event_id, const char **out_name)
Get human-readable name for a domain event ID.
备注
The base STATE_CHANGED event is handled by the core layer. Domain-specific events are delegated to ops->event_to_name if provided.
- 参数:
service – [in] Service instance
event_id – [in] Event ID to look up
out_name – [out] Output: event name string (may be NULL if unknown)
- 返回:
ESP_OK Name found
ESP_ERR_INVALID_ARG Invalid argument
ESP_ERR_NOT_FOUND No name mapping for this event ID
Structures
-
struct esp_service_state_changed_payload_t
Payload for ESP_SERVICE_EVENT_STATE_CHANGED.
Public Members
-
esp_service_state_t old_state
Previous state
-
esp_service_state_t new_state
New state
-
esp_service_state_t old_state
-
struct esp_service_config_t
Service base configuration.
-
struct esp_service_ops
Lifecycle operations (virtual methods for derived services)
These are called by the base class at appropriate lifecycle points. Derived services implement these to add custom behavior. All methods are optional (NULL = no-op).
Public Members
-
esp_err_t (*on_init)(esp_service_t *service, const esp_service_config_t *config)
Called during init
-
esp_err_t (*on_deinit)(esp_service_t *service)
Called during deinit
-
esp_err_t (*on_start)(esp_service_t *service)
Called when starting
-
esp_err_t (*on_stop)(esp_service_t *service)
Called when stopping
-
esp_err_t (*on_pause)(esp_service_t *service)
Called when pausing
-
esp_err_t (*on_resume)(esp_service_t *service)
Called when resuming
-
esp_err_t (*on_lowpower_enter)(esp_service_t *service)
Low-power enter hook; suspend resources
-
esp_err_t (*on_lowpower_exit)(esp_service_t *service)
Low-power exit hook; restore resources
-
const char *(*event_to_name)(uint16_t event_id)
Map event_id to name; NULL if unknown
-
esp_err_t (*on_init)(esp_service_t *service, const esp_service_config_t *config)
-
struct esp_service
Service base structure.
Derived services embed this as the first member:
typedef struct { esp_service_t base; // Must be first // ... derived fields } my_service_t;
Public Members
-
const char *name
Service name (owned copy); also used as event hub domain
-
esp_service_state_t state
Current state; atomic — safe to read from any task without a lock
-
const esp_service_ops_t *ops
Lifecycle operations vtable
-
void *user_data
User context (from config, for service logic)
-
esp_err_t last_err
Last error recorded by the base layer
-
adf_event_hub_t event_hub
Optional Event Hub handle (from adf_event_hub_create)
-
const char *name
Macros
-
ESP_SERVICE_EVENT_STATE_CHANGED
Service base module.
This is the base class for all services. It provides: - Lifecycle operations via esp_service_ops_t (virtual methods) - State machine: UNINITIALIZED -> INITIALIZED -> RUNNING <-> PAUSED - All lifecycle API calls (start/stop/pause/resume) execute synchronously in the caller's task context, invoking the corresponding vtable op. Services that need a persistent background task create and manage it themselves inside on_init / on_start / on_stop. - Low-power hooks: optional on_lowpower_enter / on_lowpower_exit ops; invoked by esp_service_lowpower_enter() / esp_service_lowpower_exit(), which are simple direct calls — no state change, no SLEEPING state. - Event hub integration: optional adf_event_hub for publish-subscribe events - All functions return esp_err_t, results passed via output parameters Subclass domain events (e.g. player track end, OTA progress) are not part of the base; each subclass defines its own event enum, payload, and set_event_cb. See docs/SUBCLASS_EVENT_DESIGN.md for the pattern.Event IDs published by the service base layer.
State transition; payload: esp_service_state_changed_payload_tThese are published automatically when a service has a bound event hub. Subscribers use these IDs in adf_event_subscribe_info_t::event_id. Base-layer IDs occupy the top of the uint16_t range so that the low range [1 .. UINT16_MAX-2] is freely available for domain-specific events. ADF_EVENT_ANY_ID (UINT16_MAX) is the wildcard; derived services MUST NOT use UINT16_MAX or UINT16_MAX-1.
-
ESP_SERVICE_BASE(service)
Get base service.
-
ESP_SERVICE_CONFIG_DEFAULT()
Default configuration macro.
Type Definitions
-
typedef struct esp_service esp_service_t
-
typedef struct esp_service_ops esp_service_ops_t
Lifecycle operations (virtual methods for derived services)
These are called by the base class at appropriate lifecycle points. Derived services implement these to add custom behavior. All methods are optional (NULL = no-op).
Enumerations
-
enum esp_service_state_t
Service state enumeration.
Values:
-
enumerator ESP_SERVICE_STATE_UNINITIALIZED
Not initialized
-
enumerator ESP_SERVICE_STATE_INITIALIZED
Initialized, ready to start
-
enumerator ESP_SERVICE_STATE_RUNNING
Running normally
-
enumerator ESP_SERVICE_STATE_PAUSED
Paused, can resume
-
enumerator ESP_SERVICE_STATE_STOPPING
Stopping (internal)
-
enumerator ESP_SERVICE_STATE_ERROR
Error state
-
enumerator ESP_SERVICE_STATE_MAX
Sentinel, do not use
-
enumerator ESP_SERVICE_STATE_UNINITIALIZED
Header File
Functions
-
esp_err_t esp_service_manager_create(const esp_service_manager_config_t *config, esp_service_manager_t **out_mgr)
Create service manager.
- 参数:
config – [in] Configuration (NULL for defaults)
out_mgr – [out] Output: manager instance
- 返回:
ESP_OK On success
ESP_ERR_NO_MEM Allocation failed
ESP_ERR_INVALID_ARG out_mgr is NULL
-
esp_err_t esp_service_manager_destroy(esp_service_manager_t *mgr)
Destroy service manager.
- 参数:
mgr – [in] Manager instance
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG mgr is NULL.
-
esp_err_t esp_service_manager_register(esp_service_manager_t *mgr, const esp_service_registration_t *reg)
Register a service.
Automatically parses tool description from service->tool_desc and registers all capabilities as invocable tools.
- 参数:
mgr – [in] Manager instance
reg – [in] Registration info
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG Parameters invalid
ESP_ERR_NO_MEM Registry full
ESP_FAIL JSON parsing failed
-
esp_err_t esp_service_manager_unregister(esp_service_manager_t *mgr, esp_service_t *service)
Unregister a service.
- 参数:
mgr – [in] Manager instance
service – [in] Service to unregister
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG mgr or service is NULL.
ESP_ERR_NOT_FOUND Service not registered.
ESP_ERR_TIMEOUT Mutex acquisition timed out.
-
esp_err_t esp_service_manager_find_by_name(esp_service_manager_t *mgr, const char *name, esp_service_t **out_service)
Find service by name.
- 参数:
mgr – [in] Manager instance
name – [in] Service name
out_service – [out] Output: service instance
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG mgr, name, or out_service is NULL.
ESP_ERR_NOT_FOUND Service not found.
ESP_ERR_TIMEOUT Mutex acquisition timed out.
-
esp_err_t esp_service_manager_find_by_category(esp_service_manager_t *mgr, const char *category, uint16_t index, esp_service_t **out_service)
Find service by category.
- 参数:
mgr – [in] Manager instance
category – [in] Category string
index – [in] Index in category (0-based)
out_service – [out] Output: service instance
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG mgr, category, or out_service is NULL.
ESP_ERR_NOT_FOUND Service not found.
ESP_ERR_TIMEOUT Mutex acquisition timed out.
-
esp_err_t esp_service_manager_get_count(esp_service_manager_t *mgr, uint16_t *out_count)
Get number of registered services.
- 参数:
mgr – [in] Manager instance
out_count – [out] Output: service count
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG mgr or out_count is NULL.
ESP_ERR_TIMEOUT Mutex acquisition timed out.
-
esp_err_t esp_service_manager_start_all(esp_service_manager_t *mgr)
Start all registered services.
- 参数:
mgr – [in] Manager instance
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG mgr is NULL.
ESP_ERR_TIMEOUT Mutex acquisition timed out.
-
esp_err_t esp_service_manager_stop_all(esp_service_manager_t *mgr)
Stop all registered services.
- 参数:
mgr – [in] Manager instance
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG mgr is NULL.
ESP_ERR_TIMEOUT Mutex acquisition timed out.
-
esp_err_t esp_service_manager_get_tools(esp_service_manager_t *mgr, const esp_service_tool_t **out_tools, uint16_t max_tools, uint16_t *out_count)
Get all available tools.
- 参数:
mgr – [in] Manager instance
out_tools – [out] Output: array of tool pointers
max_tools – [in] Size of out_tools array
out_count – [out] Output: actual tool count
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG mgr, out_tools, or out_count is NULL.
ESP_ERR_TIMEOUT Mutex acquisition timed out.
-
esp_err_t esp_service_manager_clone_tools(esp_service_manager_t *mgr, esp_service_tool_t **out_tools, uint16_t *out_count)
Get a deep-copied snapshot of all registered tools.
Unlike esp_service_manager_get_tools(), which returns raw pointers into manager-owned storage that are invalidated on unregister, this function allocates a caller-owned array whose name/description/ input_schema strings are duplicated under the manager mutex, so the snapshot stays valid across concurrent service unregistration. Free the result with esp_service_manager_free_cloned_tools().
- 参数:
mgr – [in] Manager instance
out_tools – [out] Receives a newly allocated array (NULL when count==0)
out_count – [out] Receives the number of entries in *out_tools
- 返回:
ESP_OK On success (including the empty-registry case).
ESP_ERR_INVALID_ARG mgr, out_tools, or out_count is NULL.
ESP_ERR_TIMEOUT Mutex acquisition timed out.
ESP_ERR_NO_MEM Allocation failed.
-
void esp_service_manager_free_cloned_tools(esp_service_tool_t *tools, uint16_t count)
Free an array returned by esp_service_manager_clone_tools()
- 参数:
tools – [in] Array to free (may be NULL)
count – [in] Number of entries in the array
-
esp_err_t esp_service_manager_invoke_tool(esp_service_manager_t *mgr, const char *tool_name, const char *args_json, char *result, size_t result_size)
Invoke a tool.
The manager mutex is released before the handler runs, so long-running handlers do not block other manager APIs.
备注
The caller must not unregister the owning service while a tool on it is being invoked; the handler receives a shallow snapshot whose string fields are owned by the service entry.
- 参数:
mgr – [in] Manager instance
tool_name – [in] Tool name
args_json – [in] Arguments (JSON string)
result – [out] Buffer for result (JSON string)
result_size – [in] Size of result buffer
- 返回:
ESP_OK On success.
ESP_ERR_INVALID_ARG mgr, tool_name, or result is NULL.
ESP_ERR_NOT_FOUND Tool not found.
ESP_ERR_NOT_SUPPORTED Tool has no invocation handler.
ESP_ERR_TIMEOUT Mutex acquisition timed out.
Other Error from tool invocation handler.
-
esp_err_t esp_service_manager_as_tool_provider(esp_service_manager_t *mgr, esp_service_mcp_tool_provider_t *out_provider)
Wrap a service manager instance as an esp_service_mcp_tool_provider_t.
Populates out_provider with callbacks that delegate to the given manager. No allocation is performed; out_provider may live on the stack or inside a config struct.
- 参数:
mgr – [in] Service manager instance
out_provider – [out] Provider struct to populate
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG mgr or out_provider is NULL
Structures
-
struct esp_service_registration_t
Service registration info.
tool_desc and tool_invoke are both optional: - Both NULL → service is registered for lifecycle management only; no tool discovery or dispatch is set up. - Both set → manager parses tool_desc and routes invoke_tool() calls through tool_invoke; enables MCP tool support.Public Members
-
esp_service_t *service
Service instance
-
const char *category
Service category (e.g., “audio”, “display”)
-
uint32_t flags
Combination of ESP_SERVICE_REG_FLAG_xxx (0 = default)
-
const char *tool_desc
MCP tool description JSON array; NULL = no tool support
-
esp_service_tool_invoke_fn_t tool_invoke
Tool invocation handler; NULL = no tool support
-
esp_service_t *service
-
struct esp_service_manager_config_t
Service manager configuration.
Macros
-
ESP_SERVICE_REG_FLAG_SKIP_BATCH_START
Registration flags (per-service batch control)
Skip this service in start_all
-
ESP_SERVICE_REG_FLAG_SKIP_BATCH_STOP
Skip this service in stop_all
-
ESP_SERVICE_MANAGER_CONFIG_DEFAULT()
Default configuration.
Type Definitions
-
typedef struct esp_service_manager esp_service_manager_t
Service Manager - Dynamic service registry and lifecycle management.
Features: - Dynamic service registration/unregistration - Automatic capability discovery from service JSON schemas - Service lifecycle management (init/start/stop/deinit) - Query services by name or category - Thread-safe operations
-
typedef esp_err_t (*esp_service_tool_invoke_fn_t)(esp_service_t *service, const esp_service_tool_t *tool, const char *args, char *result, size_t result_size)
Tool invocation callback — translate a JSON-RPC tool call into a C API call.
- Param service:
[in] Service instance (cast to concrete type inside)
- Param tool:
[in] Tool metadata (name, description, input_schema)
- Param args:
[in] JSON string with tool arguments (may be NULL or “{}”)
- Param result:
[out] Buffer to write JSON result string into
- Param result_size:
[in] Size of result buffer
- Return:
ESP_OK On success
ESP_ERR_NOT_SUPPORTED Unknown tool name
ESP_ERR_INVALID_ARG Missing or invalid argument
Header File
Functions
-
esp_err_t esp_service_mcp_server_create(const esp_service_mcp_server_config_t *config, esp_service_mcp_server_t **out_srv)
Create MCP server.
Binds to the given transport by setting a request handler callback. The server does NOT take ownership of the transport; the caller is responsible for destroying it after the server is destroyed.
- 参数:
config – [in] Configuration (tool_provider callbacks and transport are required)
out_srv – [out] Output: server instance
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG config, tool_provider callbacks, or transport is NULL
ESP_ERR_NO_MEM Allocation failed
-
esp_err_t esp_service_mcp_server_destroy(esp_service_mcp_server_t *srv)
Destroy MCP server.
Stops the server if still running. Does NOT destroy the transport.
- 参数:
srv – [in] Server instance
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG If srv is NULL
-
esp_err_t esp_service_mcp_server_start(esp_service_mcp_server_t *srv)
Start MCP server.
Starts the bound transport, which begins accepting client connections/data.
- 参数:
srv – [in] Server instance
- 返回:
ESP_OK On success (including when the server is already running)
ESP_ERR_INVALID_ARG If srv is NULL
Other Error code returned by the bound transport’s start routine
-
esp_err_t esp_service_mcp_server_stop(esp_service_mcp_server_t *srv)
Stop MCP server.
Stops the bound transport.
- 参数:
srv – [in] Server instance
- 返回:
ESP_OK On success (including when the server is not running)
ESP_ERR_INVALID_ARG If srv is NULL
-
esp_err_t esp_service_mcp_server_handle_request(esp_service_mcp_server_t *srv, const char *request, esp_service_mcp_response_t *response)
Handle MCP request (synchronous, public API)
Parses a JSON-RPC request and dispatches to the appropriate handler. Protocol-level failures (parse error, unknown method, invalid params) are reported in the returned response structure, not via the return code.
- 参数:
srv – [in] Server instance
request – [in] JSON-RPC request string
response – [out] Output: response structure (caller must call esp_service_mcp_response_free)
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG If srv, request or response is NULL
-
esp_err_t esp_service_mcp_server_notify(esp_service_mcp_server_t *srv, const char *method, const char *params)
Send notification to all connected clients.
Broadcasts a JSON-RPC notification via the bound transport.
- 参数:
srv – [in] Server instance
method – [in] Notification method (e.g., “notifications/tools/list_changed”)
params – [in] Notification params (JSON string, can be NULL)
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG If srv or method is NULL
ESP_ERR_NO_MEM If notification serialization fails
Other Error code returned by the bound transport’s broadcast routine
-
esp_err_t esp_service_mcp_server_get_capabilities(esp_service_mcp_server_t *srv, esp_service_mcp_server_capabilities_t *out_caps)
Get server capabilities.
- 参数:
srv – [in] Server instance
out_caps – [out] Output: capabilities
- 返回:
ESP_OK On success
ESP_ERR_INVALID_ARG If srv or out_caps is NULL
-
void esp_service_mcp_response_free(esp_service_mcp_response_t *response)
Free response structure.
- 参数:
response – [in] Response to free
-
char *esp_service_mcp_build_response(const char *id, const char *result)
Helper: Build JSON-RPC success response.
- 参数:
id – [in] Request ID (JSON string or number)
result – [in] Result object (JSON string)
- 返回:
Non-NULL Heap-allocated JSON-RPC response string; caller must free()
NULL On allocation failure (OOM)
-
char *esp_service_mcp_build_error(const char *id, int code, const char *message)
Helper: Build JSON-RPC error response.
- 参数:
id – [in] Request ID (can be NULL)
code – [in] Error code
message – [in] Error message
- 返回:
Non-NULL Heap-allocated JSON-RPC error response string; caller must free()
NULL On allocation failure (OOM)
-
char *esp_service_mcp_build_tools_list(const esp_service_tool_t **tools, uint16_t count)
Helper: Build tools/list response.
- 参数:
tools – [in] Array of tools
count – [in] Tool count
- 返回:
Non-NULL Heap-allocated JSON array string; caller must free()
NULL On allocation failure (OOM)
Structures
-
struct esp_service_tool
MCP tool descriptor.
Populated at registration time by parsing the tool_desc JSON. Fields are read-only from the MCP server's perspective.
-
struct esp_service_mcp_tool_provider_t
Abstract tool provider interface.
Decouples esp_service_mcp_server from any concrete tool registry. Implement these two callbacks to expose tools to the MCP server. Use esp_service_manager_as_tool_provider() to wrap an esp_service_manager_t instance.
Public Members
-
esp_err_t (*get_tools)(void *ctx, const esp_service_tool_t **out_tools, uint16_t max_tools, uint16_t *out_count)
Enumerate all available tools.
- Param ctx:
[in] Opaque context
- Param out_tools:
[out] Array to fill with tool pointers
- Param max_tools:
[in] Capacity of out_tools
- Param out_count:
[out] Number of tools written
- Return:
ESP_OK On success
-
esp_err_t (*invoke_tool)(void *ctx, const char *tool_name, const char *args_json, char *result, size_t result_size)
Invoke a named tool.
- Param ctx:
[in] Opaque context
- Param tool_name:
[in] Tool name
- Param args_json:
[in] JSON-encoded arguments (may be NULL or “{}”)
- Param result:
[out] Buffer for JSON result string
- Param result_size:
[in] Size of result buffer
- Return:
ESP_OK On success
ESP_ERR_NOT_FOUND If the tool name is unknown
-
void *ctx
Opaque context passed to both callbacks
-
esp_err_t (*get_tools)(void *ctx, const esp_service_tool_t **out_tools, uint16_t max_tools, uint16_t *out_count)
-
struct esp_service_mcp_response_t
MCP request handler result.
-
struct esp_service_mcp_server_config_t
MCP server configuration.
No transport-specific fields. All transport configuration is handled by the concrete transport's own create function.
Public Members
-
esp_service_mcp_tool_provider_t tool_provider
Tool source (esp_service_manager_as_tool_provider())
-
esp_service_mcp_trans_t *transport
Abstract transport instance (required)
-
const char *server_name
Server name for MCP initialize (default: “esp-mcp-server”)
-
const char *server_version
Server version for MCP initialize (default: “1.0.0”)
-
size_t max_request_size
Max request size in bytes (default: 4096)
-
size_t max_response_size
Max tool result + response size in bytes (default: 4096)
-
uint16_t max_tools
Max tools for tools/list (default: 64)
-
uint32_t timeout_ms
Request timeout (default: 5000)
-
esp_service_mcp_tool_provider_t tool_provider
-
struct esp_service_mcp_server_capabilities_t
MCP server capabilities.
Macros
-
ESP_SERVICE_MCP_SERVER_CONFIG_DEFAULT()
Default server configuration.
Type Definitions
-
typedef struct esp_service_mcp_server esp_service_mcp_server_t
MCP Server - Exposes services as Model Context Protocol tools.
Implements MCP specification 2024-11-05: - tools/list: List available tools - tools/call: Invoke tools - notifications/tools/list_changed: Notify tool list changes Architecture: - Transport-agnostic: receives a pre-created esp_service_mcp_trans_t* and communicates through the abstract transport interface. - Tool-source-agnostic: receives an esp_service_mcp_tool_provider_t that abstracts the underlying tool registry; use esp_service_manager_as_tool_provider() to wrap an esp_service_manager_t, or supply custom callbacks. - JSON-RPC 2.0 protocol handling - Request/response validation Typical usage:
// 1. Create a concrete transport esp_service_mcp_trans_t *transport = NULL; esp_service_mcp_trans_http_create(&http_cfg, &transport); // 2. Wrap the service manager as a tool provider esp_service_mcp_server_config_t cfg = ESP_SERVICE_MCP_SERVER_CONFIG_DEFAULT(); esp_service_manager_as_tool_provider(mgr, &cfg.tool_provider); cfg.transport = transport; esp_service_mcp_server_create(&cfg, &server); // 3. Start (starts both server logic and transport) esp_service_mcp_server_start(server); // 4. Cleanup esp_service_mcp_server_stop(server); esp_service_mcp_server_destroy(server); esp_service_mcp_trans_destroy(transport);
-
typedef struct esp_service_tool esp_service_tool_t