Skip to content

Commit 8cd6e55

Browse files
author
Nicola Spieser
committed
feat: upgrade to MCP 2025-11-25 — icons, title, ResourceLink, Resource.size — bump to v0.32.0
New MCP 2025-11-25 spec features: - Icons on Server, Tool, Resource, ResourceTemplate, Prompt - title field on Tool, Resource, ResourceTemplate, Prompt - ResourceLink content type for tool results - Resource.size field (content size in bytes) - Annotations.lastModified field (ISO 8601 timestamp) - Implementation.description and websiteUrl in server info - Protocol version negotiation (backwards compatible with 2025-03-26) Add 41 new tests (1105 total), all passing.
1 parent 015197c commit 8cd6e55

17 files changed

Lines changed: 713 additions & 23 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,4 @@ test/native/test_content_transport
2222
test/native/test_advanced
2323
test/native/test_integration
2424
test/native/test_output_annotations
25+
test/native/test_*

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
1717
[![PlatformIO](https://img.shields.io/badge/PlatformIO-compatible-orange.svg)](https://platformio.org)
1818
[![Arduino](https://img.shields.io/badge/Arduino-compatible-teal.svg)](https://www.arduino.cc)
19-
[![MCP](https://img.shields.io/badge/MCP-2025--03--26-purple.svg)](https://modelcontextprotocol.io)
19+
[![MCP](https://img.shields.io/badge/MCP-2025--11--25-purple.svg)](https://modelcontextprotocol.io)
2020
[![Works with Claude Desktop](https://img.shields.io/badge/Works%20with-Claude%20Desktop-cc785c.svg)](https://claude.ai/download)
2121

2222
</div>
@@ -32,8 +32,8 @@
3232
| Feature | mcpd | ESP32MCPServer | esp-mcp |
3333
|---|:---:|:---:|:---:|
3434
| Runs on the MCU ||| ❌ CLI tool |
35-
| MCP spec compliant | ✅ 2025-03-26 | ❌ custom WS ||
36-
| Actually compiles |1037 tests | ❌ self-described | N/A |
35+
| MCP spec compliant | ✅ 2025-11-25 | ❌ custom WS ||
36+
| Actually compiles |1105 tests | ❌ self-described | N/A |
3737
| Streamable HTTP + SSE ||||
3838
| WebSocket transport ||||
3939
| Claude Desktop bridge ||||
@@ -47,6 +47,8 @@
4747
| Server Instructions (LLM guidance) ||||
4848
| Runtime Tool Enable/Disable ||||
4949
| Structured Content (text, image, resource) ||||
50+
| Icons (server, tools, resources, prompts) ||||
51+
| ResourceLink content type ||||
5052
| Progress Notifications ||||
5153
| Request Cancellation ||||
5254
| Prompts support ||||
@@ -324,7 +326,7 @@ mcp.enableTool("maintenance_tool"); // Visible again
324326

325327
### 📋 Server Instructions
326328

327-
Guide the LLM's behavior with your device using server instructions (MCP 2025-03-26):
329+
Guide the LLM's behavior with your device using server instructions (MCP 2025-11-25):
328330

329331
```cpp
330332
mcp.setInstructions(
@@ -422,7 +424,7 @@ For full API documentation, see [docs/API.md](docs/API.md).
422424
423425
## MCP Compliance
424426
425-
Implements [MCP specification 2025-03-26](https://modelcontextprotocol.io/specification/2025-03-26):
427+
Implements [MCP specification 2025-11-25](https://modelcontextprotocol.io/specification/2025-11-25):
426428
427429
- ✅ JSON-RPC 2.0 message format
428430
- ✅ `initialize` / `initialized` lifecycle

src/MCPContent.h

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,16 @@ namespace mcpd {
2424
* A single content item in a tool result.
2525
*/
2626
struct MCPContent {
27-
enum Type { TEXT, IMAGE, AUDIO, RESOURCE };
27+
enum Type { TEXT, IMAGE, AUDIO, RESOURCE, RESOURCE_LINK };
2828

2929
Type type;
3030
String text; // For TEXT: the text content
31-
String data; // For IMAGE: base64-encoded image data
32-
String mimeType; // For IMAGE or RESOURCE
33-
String uri; // For RESOURCE: resource URI
31+
String data; // For IMAGE/AUDIO: base64-encoded data
32+
String mimeType; // For IMAGE, AUDIO, RESOURCE, RESOURCE_LINK
33+
String uri; // For RESOURCE/RESOURCE_LINK: resource URI
3434
String blob; // For RESOURCE: base64-encoded binary (alternative to text)
35+
String name; // For RESOURCE_LINK: resource name
36+
String description; // For RESOURCE_LINK: resource description
3537

3638
// Factory: text content
3739
static MCPContent makeText(const String& text) {
@@ -81,6 +83,19 @@ struct MCPContent {
8183
return c;
8284
}
8385

86+
// Factory: resource link (MCP 2025-11-25)
87+
static MCPContent makeResourceLink(const String& uri, const String& name,
88+
const String& mimeType = "",
89+
const String& description = "") {
90+
MCPContent c;
91+
c.type = RESOURCE_LINK;
92+
c.uri = uri;
93+
c.name = name;
94+
c.mimeType = mimeType;
95+
c.description = description;
96+
return c;
97+
}
98+
8499
void toJson(JsonObject obj) const {
85100
switch (type) {
86101
case TEXT:
@@ -109,6 +124,14 @@ struct MCPContent {
109124
}
110125
break;
111126
}
127+
case RESOURCE_LINK: {
128+
obj["type"] = "resource_link";
129+
obj["uri"] = uri;
130+
obj["name"] = name;
131+
if (!mimeType.isEmpty()) obj["mimeType"] = mimeType;
132+
if (!description.isEmpty()) obj["description"] = description;
133+
break;
134+
}
112135
}
113136
}
114137
};

src/MCPIcon.h

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* mcpd — MCP Icon definition
3+
*
4+
* Icons per MCP 2025-11-25 spec. Displayable in client UIs.
5+
* Supports PNG, JPEG, SVG, WebP via URI or data: URL.
6+
*/
7+
8+
#ifndef MCPD_ICON_H
9+
#define MCPD_ICON_H
10+
11+
#include <Arduino.h>
12+
#include <ArduinoJson.h>
13+
#include <vector>
14+
15+
namespace mcpd {
16+
17+
/**
18+
* A single icon that can be displayed in a client UI.
19+
*/
20+
struct MCPIcon {
21+
String src; // URI or data: URL
22+
String mimeType; // Optional: "image/png", "image/svg+xml", etc.
23+
std::vector<String> sizes; // Optional: "48x48", "96x96", "any"
24+
String theme; // Optional: "light" or "dark"
25+
26+
MCPIcon() = default;
27+
28+
MCPIcon(const char* src) : src(src) {}
29+
30+
MCPIcon(const char* src, const char* mimeType)
31+
: src(src), mimeType(mimeType) {}
32+
33+
MCPIcon& setMimeType(const char* mt) { mimeType = mt; return *this; }
34+
MCPIcon& addSize(const char* s) { sizes.push_back(s); return *this; }
35+
MCPIcon& setTheme(const char* t) { theme = t; return *this; }
36+
37+
void toJson(JsonObject& obj) const {
38+
obj["src"] = src;
39+
if (!mimeType.isEmpty()) obj["mimeType"] = mimeType;
40+
if (!sizes.empty()) {
41+
JsonArray arr = obj["sizes"].to<JsonArray>();
42+
for (const auto& s : sizes) arr.add(s);
43+
}
44+
if (!theme.isEmpty()) obj["theme"] = theme;
45+
}
46+
};
47+
48+
/**
49+
* Serialize a vector of icons into a JsonObject parent.
50+
* Adds "icons" array if icons is non-empty.
51+
*/
52+
inline void iconsToJson(const std::vector<MCPIcon>& icons, JsonObject& parent) {
53+
if (icons.empty()) return;
54+
JsonArray arr = parent["icons"].to<JsonArray>();
55+
for (const auto& icon : icons) {
56+
JsonObject obj = arr.add<JsonObject>();
57+
icon.toJson(obj);
58+
}
59+
}
60+
61+
} // namespace mcpd
62+
63+
#endif // MCPD_ICON_H

src/MCPPrompt.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include <vector>
1515
#include <map>
1616

17+
#include "MCPIcon.h"
18+
1719
namespace mcpd {
1820

1921
/**
@@ -85,9 +87,11 @@ using MCPPromptHandler = std::function<std::vector<MCPPromptMessage>(
8587
*/
8688
struct MCPPrompt {
8789
String name;
90+
String title; // Optional: human-readable display name (MCP 2025-11-25)
8891
String description;
8992
std::vector<MCPPromptArgument> arguments;
9093
MCPPromptHandler handler;
94+
std::vector<MCPIcon> icons; // Optional: icons for UI display (MCP 2025-11-25)
9195

9296
MCPPrompt() = default;
9397

@@ -97,11 +101,18 @@ struct MCPPrompt {
97101
: name(name), description(description),
98102
arguments(std::move(arguments)), handler(handler) {}
99103

104+
/** Set display title (MCP 2025-11-25) */
105+
MCPPrompt& setTitle(const char* t) { title = t; return *this; }
106+
107+
/** Add an icon (MCP 2025-11-25) */
108+
MCPPrompt& addIcon(const MCPIcon& icon) { icons.push_back(icon); return *this; }
109+
100110
/**
101111
* Serialize for prompts/list response.
102112
*/
103113
void toJson(JsonObject& obj) const {
104114
obj["name"] = name;
115+
if (!title.isEmpty()) obj["title"] = title;
105116
obj["description"] = description;
106117

107118
if (!arguments.empty()) {
@@ -113,6 +124,7 @@ struct MCPPrompt {
113124
argObj["required"] = arg.required;
114125
}
115126
}
127+
iconsToJson(icons, obj);
116128
}
117129
};
118130

src/MCPResource.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88
#include <Arduino.h>
99
#include <ArduinoJson.h>
1010
#include <functional>
11+
#include <vector>
12+
13+
#include "MCPIcon.h"
1114

1215
namespace mcpd {
1316

@@ -22,19 +25,22 @@ using MCPResourceHandler = std::function<String()>;
2225
* Describes the intended audience and priority of a resource.
2326
*/
2427
struct MCPResourceAnnotations {
25-
String audience; // "user", "assistant", or empty (both)
26-
float priority = -1; // 0.0 to 1.0, -1 = unset
28+
String audience; // "user", "assistant", or empty (both)
29+
float priority = -1; // 0.0 to 1.0, -1 = unset
30+
String lastModified; // ISO 8601 timestamp (MCP 2025-11-25)
2731
bool hasAnnotations = false;
2832

2933
MCPResourceAnnotations& setAudience(const char* a) { audience = a; hasAnnotations = true; return *this; }
3034
MCPResourceAnnotations& setPriority(float p) { priority = p; hasAnnotations = true; return *this; }
35+
MCPResourceAnnotations& setLastModified(const char* ts) { lastModified = ts; hasAnnotations = true; return *this; }
3136

3237
void toJson(JsonObject& obj) const {
3338
if (!audience.isEmpty()) {
3439
JsonArray arr = obj["audience"].to<JsonArray>();
3540
arr.add(audience);
3641
}
3742
if (priority >= 0.0f) obj["priority"] = priority;
43+
if (!lastModified.isEmpty()) obj["lastModified"] = lastModified;
3844
}
3945
};
4046

@@ -44,10 +50,13 @@ struct MCPResourceAnnotations {
4450
struct MCPResource {
4551
String uri;
4652
String name;
53+
String title; // Optional: human-readable display name (MCP 2025-11-25)
4754
String description;
4855
String mimeType;
56+
int size = -1; // Optional: raw content size in bytes (MCP 2025-11-25), -1 = unset
4957
MCPResourceHandler handler;
5058
MCPResourceAnnotations annotations;
59+
std::vector<MCPIcon> icons; // Optional: icons for UI display (MCP 2025-11-25)
5160

5261
MCPResource() = default;
5362

@@ -57,6 +66,15 @@ struct MCPResource {
5766
: uri(uri), name(name), description(description),
5867
mimeType(mimeType), handler(handler) {}
5968

69+
/** Set display title (MCP 2025-11-25) */
70+
MCPResource& setTitle(const char* t) { title = t; return *this; }
71+
72+
/** Set resource size in bytes (MCP 2025-11-25) */
73+
MCPResource& setSize(int s) { size = s; return *this; }
74+
75+
/** Add an icon (MCP 2025-11-25) */
76+
MCPResource& addIcon(const MCPIcon& icon) { icons.push_back(icon); return *this; }
77+
6078
/** Builder-style: set annotations */
6179
MCPResource& annotate(const MCPResourceAnnotations& ann) {
6280
annotations = ann;
@@ -82,12 +100,15 @@ struct MCPResource {
82100
void toJson(JsonObject& obj) const {
83101
obj["uri"] = uri;
84102
obj["name"] = name;
103+
if (!title.isEmpty()) obj["title"] = title;
85104
obj["description"] = description;
86105
obj["mimeType"] = mimeType;
106+
if (size >= 0) obj["size"] = size;
87107
if (annotations.hasAnnotations) {
88108
JsonObject ann = obj["annotations"].to<JsonObject>();
89109
annotations.toJson(ann);
90110
}
111+
iconsToJson(icons, obj);
91112
}
92113
};
93114

src/MCPResourceTemplate.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <vector>
1818

1919
#include "MCPResource.h" // For MCPResourceAnnotations
20+
#include "MCPIcon.h"
2021

2122
namespace mcpd {
2223

@@ -35,10 +36,12 @@ using MCPResourceTemplateHandler = std::function<String(const std::map<String, S
3536
struct MCPResourceTemplate {
3637
String uriTemplate; // RFC 6570 Level 1 template
3738
String name;
39+
String title; // Optional: human-readable display name (MCP 2025-11-25)
3840
String description;
3941
String mimeType;
4042
MCPResourceTemplateHandler handler;
4143
MCPResourceAnnotations annotations;
44+
std::vector<MCPIcon> icons; // Optional: icons for UI display (MCP 2025-11-25)
4245

4346
MCPResourceTemplate() = default;
4447

@@ -48,6 +51,12 @@ struct MCPResourceTemplate {
4851
: uriTemplate(uriTemplate), name(name), description(description),
4952
mimeType(mimeType), handler(handler) {}
5053

54+
/** Set display title (MCP 2025-11-25) */
55+
MCPResourceTemplate& setTitle(const char* t) { title = t; return *this; }
56+
57+
/** Add an icon (MCP 2025-11-25) */
58+
MCPResourceTemplate& addIcon(const MCPIcon& icon) { icons.push_back(icon); return *this; }
59+
5160
/** Builder-style: set annotations */
5261
MCPResourceTemplate& annotate(const MCPResourceAnnotations& ann) {
5362
annotations = ann;
@@ -73,12 +82,14 @@ struct MCPResourceTemplate {
7382
void toJson(JsonObject& obj) const {
7483
obj["uriTemplate"] = uriTemplate;
7584
obj["name"] = name;
85+
if (!title.isEmpty()) obj["title"] = title;
7686
obj["description"] = description;
7787
obj["mimeType"] = mimeType;
7888
if (annotations.hasAnnotations) {
7989
JsonObject ann = obj["annotations"].to<JsonObject>();
8090
annotations.toJson(ann);
8191
}
92+
iconsToJson(icons, obj);
8293
}
8394

8495
/**

0 commit comments

Comments
 (0)