Skip to content

Commit 015197c

Browse files
author
Nicola Spieser
committed
feat: tool outputSchema, resource/template annotations, structuredContent — bump to v0.31.0
- Add outputSchema support on MCPTool with automatic structuredContent generation - Add MCPResourceAnnotations (audience, priority) for resources and resource templates - Builder-style API: setOutputSchema(), setAudience(), setPriority() - structuredContent auto-generated for tools with outputSchema when handler returns valid JSON - 27 new tests covering all new features — total: 1064 tests
1 parent e7be61b commit 015197c

14 files changed

Lines changed: 553 additions & 9 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@ test/native/test_session
2121
test/native/test_content_transport
2222
test/native/test_advanced
2323
test/native/test_integration
24+
test/native/test_output_annotations

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.31.0] — 2026-02-21
6+
7+
### Added
8+
- **Tool `outputSchema`**: Optional JSON Schema for structured tool output per MCP 2025-03-26 spec. Tools with `outputSchema` automatically include `structuredContent` in their response when the handler returns valid JSON.
9+
- **Resource annotations**: `audience` and `priority` hints on `MCPResource` for guiding client/model behavior. Builder-style API: `res.setAudience("user").setPriority(0.8f)`.
10+
- **Resource template annotations**: Same annotation support on `MCPResourceTemplate`.
11+
- **`MCPResourceAnnotations` struct**: Reusable annotations with `setAudience()`, `setPriority()`, and `toJson()`.
12+
- **27 new tests** covering outputSchema serialization, structuredContent generation (simple + rich handlers, arrays, error cases, non-JSON), resource/template annotation serialization, builder chains, and default state. Total: **1064 tests**.
13+
14+
### Changed
15+
- `MCPTool::toJson()` now emits `outputSchema` when set.
16+
- `MCPResource::toJson()` and `MCPResourceTemplate::toJson()` now emit `annotations` when set.
17+
- Tool call dispatch (`_handleToolsCall`) auto-generates `structuredContent` from handler output for tools with `outputSchema`.
18+
519
## [0.29.1] - 2026-02-21
620

721
### Features

docs/API.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,35 @@ mcpd implements MCP specification 2025-03-26 via Streamable HTTP transport.
290290
| `resources/subscribe` | Subscribe to change notifications for a resource URI |
291291
| `resources/unsubscribe` | Unsubscribe from resource change notifications |
292292

293+
### Tool Output Schema & Structured Content
294+
295+
Tools can declare an `outputSchema` (JSON Schema) describing their structured output:
296+
297+
```cpp
298+
MCPTool tool("get_reading", "Get sensor reading", inputSchema, handler);
299+
tool.setOutputSchema(R"({"type":"object","properties":{"temp":{"type":"number"}}})");
300+
mcp.addTool(tool);
301+
```
302+
303+
When a tool with `outputSchema` returns valid JSON, the response automatically includes both `content` (text) and `structuredContent` (parsed JSON), per MCP 2025-03-26 spec.
304+
305+
### Resource & Template Annotations
306+
307+
Resources and resource templates support `audience` and `priority` annotations:
308+
309+
```cpp
310+
MCPResource res("file://log", "Log", "System log", "text/plain", handler);
311+
res.setAudience("user").setPriority(0.8f);
312+
mcp.addResource(res);
313+
314+
MCPResourceTemplate tmpl("sensor://{id}", "Sensor", "Desc", "text/plain", handler);
315+
tmpl.setAudience("assistant").setPriority(0.5f);
316+
mcp.addResourceTemplate(tmpl);
317+
```
318+
319+
- `audience`: `"user"` or `"assistant"` — hints who the content is primarily for
320+
- `priority`: `0.0` to `1.0` — relative importance hint for ordering/filtering
321+
293322
### Session Management
294323

295324
- Session ID sent via `Mcp-Session-Id` header

library.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mcpd",
3-
"version": "0.27.5",
3+
"version": "0.31.0",
44
"description": "MCP Server SDK for Microcontrollers — Expose ESP32/RP2040/STM32 hardware as AI-accessible tools via Model Context Protocol",
55
"keywords": [
66
"mcp",

library.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name=mcpd
2-
version=0.27.5
2+
version=0.31.0
33
author=Nicola Spieser
44
maintainer=Nicola Spieser <redbasecap-buiss@users.noreply.github.com>
55
sentence=MCP Server SDK for Microcontrollers

src/MCPResource.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,27 @@ namespace mcpd {
1717
*/
1818
using MCPResourceHandler = std::function<String()>;
1919

20+
/**
21+
* Resource annotations per MCP 2025-03-26 spec.
22+
* Describes the intended audience and priority of a resource.
23+
*/
24+
struct MCPResourceAnnotations {
25+
String audience; // "user", "assistant", or empty (both)
26+
float priority = -1; // 0.0 to 1.0, -1 = unset
27+
bool hasAnnotations = false;
28+
29+
MCPResourceAnnotations& setAudience(const char* a) { audience = a; hasAnnotations = true; return *this; }
30+
MCPResourceAnnotations& setPriority(float p) { priority = p; hasAnnotations = true; return *this; }
31+
32+
void toJson(JsonObject& obj) const {
33+
if (!audience.isEmpty()) {
34+
JsonArray arr = obj["audience"].to<JsonArray>();
35+
arr.add(audience);
36+
}
37+
if (priority >= 0.0f) obj["priority"] = priority;
38+
}
39+
};
40+
2041
/**
2142
* Represents a single MCP resource.
2243
*/
@@ -26,6 +47,7 @@ struct MCPResource {
2647
String description;
2748
String mimeType;
2849
MCPResourceHandler handler;
50+
MCPResourceAnnotations annotations;
2951

3052
MCPResource() = default;
3153

@@ -35,6 +57,25 @@ struct MCPResource {
3557
: uri(uri), name(name), description(description),
3658
mimeType(mimeType), handler(handler) {}
3759

60+
/** Builder-style: set annotations */
61+
MCPResource& annotate(const MCPResourceAnnotations& ann) {
62+
annotations = ann;
63+
annotations.hasAnnotations = true;
64+
return *this;
65+
}
66+
67+
/** Convenience: set audience hint */
68+
MCPResource& setAudience(const char* audience) {
69+
annotations.setAudience(audience);
70+
return *this;
71+
}
72+
73+
/** Convenience: set priority hint (0.0–1.0) */
74+
MCPResource& setPriority(float priority) {
75+
annotations.setPriority(priority);
76+
return *this;
77+
}
78+
3879
/**
3980
* Serialize for resources/list response.
4081
*/
@@ -43,6 +84,10 @@ struct MCPResource {
4384
obj["name"] = name;
4485
obj["description"] = description;
4586
obj["mimeType"] = mimeType;
87+
if (annotations.hasAnnotations) {
88+
JsonObject ann = obj["annotations"].to<JsonObject>();
89+
annotations.toJson(ann);
90+
}
4691
}
4792
};
4893

src/MCPResourceTemplate.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#include <map>
1717
#include <vector>
1818

19+
#include "MCPResource.h" // For MCPResourceAnnotations
20+
1921
namespace mcpd {
2022

2123
/**
@@ -36,6 +38,7 @@ struct MCPResourceTemplate {
3638
String description;
3739
String mimeType;
3840
MCPResourceTemplateHandler handler;
41+
MCPResourceAnnotations annotations;
3942

4043
MCPResourceTemplate() = default;
4144

@@ -45,6 +48,25 @@ struct MCPResourceTemplate {
4548
: uriTemplate(uriTemplate), name(name), description(description),
4649
mimeType(mimeType), handler(handler) {}
4750

51+
/** Builder-style: set annotations */
52+
MCPResourceTemplate& annotate(const MCPResourceAnnotations& ann) {
53+
annotations = ann;
54+
annotations.hasAnnotations = true;
55+
return *this;
56+
}
57+
58+
/** Convenience: set audience hint */
59+
MCPResourceTemplate& setAudience(const char* audience) {
60+
annotations.setAudience(audience);
61+
return *this;
62+
}
63+
64+
/** Convenience: set priority hint (0.0–1.0) */
65+
MCPResourceTemplate& setPriority(float priority) {
66+
annotations.setPriority(priority);
67+
return *this;
68+
}
69+
4870
/**
4971
* Serialize for resources/templates/list response.
5072
*/
@@ -53,6 +75,10 @@ struct MCPResourceTemplate {
5375
obj["name"] = name;
5476
obj["description"] = description;
5577
obj["mimeType"] = mimeType;
78+
if (annotations.hasAnnotations) {
79+
JsonObject ann = obj["annotations"].to<JsonObject>();
80+
annotations.toJson(ann);
81+
}
5682
}
5783

5884
/**

src/MCPTool.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ struct MCPToolAnnotations {
5050
struct MCPTool {
5151
String name;
5252
String description;
53-
String inputSchemaJson; // JSON Schema as string
53+
String inputSchemaJson; // JSON Schema as string
54+
String outputSchemaJson; // Optional: JSON Schema for structured output
5455
MCPToolHandler handler;
5556
MCPToolAnnotations annotations;
5657

@@ -92,6 +93,12 @@ struct MCPTool {
9293
return *this;
9394
}
9495

96+
/** Set output schema (builder-style). Enables structured content in tool results. */
97+
MCPTool& setOutputSchema(const char* schemaJson) {
98+
outputSchemaJson = schemaJson;
99+
return *this;
100+
}
101+
95102
/** Convenience: mark as local-only (openWorldHint=false) */
96103
MCPTool& markLocalOnly() {
97104
annotations.openWorldHint = false;
@@ -112,6 +119,13 @@ struct MCPTool {
112119
deserializeJson(schemaDoc, inputSchemaJson);
113120
obj["inputSchema"] = schemaDoc.as<JsonVariant>();
114121

122+
// Include output schema if set
123+
if (!outputSchemaJson.isEmpty()) {
124+
JsonDocument outDoc;
125+
deserializeJson(outDoc, outputSchemaJson);
126+
obj["outputSchema"] = outDoc.as<JsonVariant>();
127+
}
128+
115129
// Include annotations if set
116130
if (annotations.hasAnnotations) {
117131
JsonObject ann = obj["annotations"].to<JsonObject>();

src/mcpd.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,17 @@ String Server::_handleToolsCall(JsonVariant params, JsonVariant id) {
782782
JsonDocument result;
783783
JsonObject resultObj = result.to<JsonObject>();
784784
toolResult.toJson(resultObj);
785+
786+
// If tool has outputSchema and first content is text, include structuredContent
787+
if (!tool.outputSchemaJson.isEmpty() && !toolResult.isError &&
788+
!toolResult.content.empty() && toolResult.content[0].type == MCPContent::TEXT) {
789+
JsonDocument structured;
790+
DeserializationError err = deserializeJson(structured, toolResult.content[0].text);
791+
if (!err) {
792+
resultObj["structuredContent"] = structured.as<JsonVariant>();
793+
}
794+
}
795+
785796
serializeJson(result, resultStr);
786797
if (toolResult.isError) callIsError = true;
787798
} else {
@@ -802,6 +813,16 @@ String Server::_handleToolsCall(JsonVariant params, JsonVariant id) {
802813
if (callIsError) {
803814
result["isError"] = true;
804815
}
816+
817+
// If tool has outputSchema, include structuredContent
818+
if (!tool.outputSchemaJson.isEmpty() && !callIsError) {
819+
JsonDocument structured;
820+
DeserializationError err = deserializeJson(structured, handlerResult);
821+
if (!err) {
822+
result["structuredContent"] = structured.as<JsonVariant>();
823+
}
824+
}
825+
805826
serializeJson(result, resultStr);
806827
}
807828

src/mcpd.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
#include "MCPTransportBLE.h"
4545
#endif
4646

47-
#define MCPD_VERSION "0.30.0"
47+
#define MCPD_VERSION "0.31.0"
4848
#define MCPD_MCP_PROTOCOL_VERSION "2025-03-26"
4949

5050
namespace mcpd {

0 commit comments

Comments
 (0)