Skip to content

Commit 858fccc

Browse files
authored
Add support for MCP prompts (#336)
1 parent 404c01e commit 858fccc

19 files changed

+1284
-203
lines changed

README.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_lib-nanoframework.WebServer&metric=alert_status)](https://sonarcloud.io/dashboard?id=nanoframework_lib-nanoframework.WebServer) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_lib-nanoframework.WebServer&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=nanoframework_lib-nanoframework.WebServer) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.WebServer.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.WebServer/) [![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T)
1+
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_lib-nanoframework.WebServer&metric=alert_status)](https://sonarcloud.io/dashboard?id=nanoframework_lib-nanoframework.WebServer) [![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=nanoframework_lib-nanoframework.WebServer&metric=reliability_rating)](https://sonarcloud.io/dashboard?id=nanoframework_lib-nanoframework.WebServer) [![NuGet](https://img.shields.io/nuget/dt/nanoFramework.WebServer.svg?label=NuGet&style=flat&logo=nuget)](https://www.nuget.org/packages/nanoFramework.WebServer/) [![#yourfirstpr](https://img.shields.io/badge/first--timers--only-friendly-blue.svg)](https://github.com/nanoframework/Home/blob/main/CONTRIBUTING.md) [![Discord](https://img.shields.io/discord/478725473862549535.svg?logo=discord&logoColor=white&label=Discord&color=7289DA)](https://discord.gg/gCyBu8T)
22

33
![nanoFramework logo](https://raw.githubusercontent.com/nanoframework/Home/main/resources/logo/nanoFramework-repo-logo.png)
44

@@ -60,7 +60,7 @@ private static void ServerCommandReceived(object source, WebServerEventArgs e)
6060

6161
### Controller-Based WebServer
6262

63-
Controllers are supported including with parametarized routes like `api/led/{id}/dosomething/{order}`.
63+
Controllers are supported including with parametarized routes like 'api/led/{id}/dosomething/{order}'.
6464

6565
```csharp
6666
using (WebServer server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(MyController) }))
@@ -119,6 +119,30 @@ public class LedCommand
119119
}
120120
```
121121

122+
### Defining MCP Prompts
123+
124+
You can define reusable, high-level prompts for AI agents using the `McpServerPrompt` attribute. Prompts encapsulate multi-step instructions or workflows that can be invoked by agents.
125+
126+
Here's a simple example:
127+
128+
```csharp
129+
using nanoFramework.WebServer.Mcp;
130+
131+
public class McpPrompts
132+
{
133+
[McpServerPrompt("echo_sanity_check", "Echo test prompt")]
134+
public static PromptMessage[] EchoSanityCheck()
135+
{
136+
return new PromptMessage[]
137+
{
138+
new PromptMessage("Call Echo with the string 'Hello MCP world!' and return the response.")
139+
};
140+
}
141+
}
142+
```
143+
144+
Prompts can be discovered and invoked by AI agents in the same way as tools. You can also define prompts with parameters using the `McpPromptParameter` attribute.
145+
122146
### Setting Up MCP Server
123147

124148
```csharp
@@ -129,6 +153,9 @@ public static void Main()
129153

130154
// Discover and register MCP tools
131155
McpToolRegistry.DiscoverTools(new Type[] { typeof(IoTTools) });
156+
157+
// Discover and register MCP prompts
158+
McpPromptRegistry.DiscoverPrompts(new Type[] { typeof(McpPrompts) });
132159

133160
// Start WebServer with MCP support
134161
using (var server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(McpServerController) }))
@@ -187,11 +214,12 @@ POST /mcp
187214
- No compression support in request/response streams
188215
- MCP implementation supports server features only (no notifications or SSE)
189216
- No or single parameter limitation for MCP tools (use complex objects for multiple parameters)
217+
- Prompt parameters, when declared, are always mandatory.
190218

191219
## Installation
192220

193-
Install `nanoFramework.WebServer` for the Web Server without File System support. Install `nanoFramework.WebServer.FileSystem` for file serving, so with devices supporting File System.
194-
Install `nanoFramework.WebServer.Mcp` for MCP support. It does contains the full `nanoFramework.WebServer` but does not include native file serving. You can add this feature fairly easilly by reusing the code function serving it.
221+
Install 'nanoFramework.WebServer' for the Web Server without File System support. Install 'nanoFramework.WebServer.FileSystem' for file serving, so with devices supporting File System.
222+
Install 'nanoFramework.WebServer.Mcp' for MCP support. It does contains the full 'nanoFramework.WebServer' but does not include native file serving. You can add this feature fairly easilly by reusing the code function serving it.
195223

196224
## Contributing
197225

doc/model-context-protocol.md

Lines changed: 185 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,50 @@ The Model Context Protocol (MCP) is an open standard that enables seamless integ
2727

2828
### Key Features
2929

30-
- **Automatic tool discovery** through reflection and attributes
30+
- **Automatic tool and prompt discovery** through reflection and attributes
31+
- **MCP Prompts**: Define reusable, high-level prompt workflows for AI agents, with support for parameters
3132
- **JSON-RPC 2.0 compliant** request/response handling
3233
- **Type-safe parameter handling** with automatic deserialization from JSON to .NET objects
3334
- **Flexible authentication** options (none, basic auth, API key)
3435
- **Complex object support** for both input parameters and return values
3536
- **Robust error handling** and validation
3637
- **Memory efficient** implementation optimized for embedded devices
3738
- **HTTPS support** with SSL/TLS encryption
39+
## Defining MCP Prompts
40+
41+
MCP Prompts allow you to define reusable, multi-step instructions or workflows that can be invoked by AI agents. Prompts are discovered and registered similarly to tools, using the `[McpServerPrompt]` attribute on static methods that return an array of `PromptMessage`.
42+
43+
Prompts can encapsulate complex logic, multi-step flows, or provide high-level instructions for agents. You can also define parameters for prompts using the `[McpPromptParameter]` attribute. **All parameters defined for a prompt are mandatory.**
44+
45+
### Example: Defining a Prompt
46+
47+
```csharp
48+
using nanoFramework.WebServer.Mcp;
49+
50+
public class McpPrompts
51+
{
52+
[McpServerPrompt("echo_sanity_check", "Echo test prompt")]
53+
public static PromptMessage[] EchoSanityCheck()
54+
{
55+
return new PromptMessage[]
56+
{
57+
new PromptMessage("Call Echo with the string 'Hello MCP world!' and return the response.")
58+
};
59+
}
60+
61+
[McpServerPrompt("summarize_person", "Summarize a person with age threshold")]
62+
[McpPromptParameter("ageThreshold", "The age threshold to determine if the person is a senior or junior.")]
63+
public static PromptMessage[] SummarizePerson(string ageThreshold)
64+
{
65+
return new PromptMessage[]
66+
{
67+
new PromptMessage($"Call GetDefaultPerson, then if person.Age > {ageThreshold} label as senior, else junior.")
68+
};
69+
}
70+
}
71+
```
72+
73+
Prompts are listed and invoked via the MCP protocol, just like tools.
3874

3975
### Supported Version
4076

@@ -309,6 +345,11 @@ public static void Main()
309345
typeof(SensorTools)
310346
});
311347

348+
// Discover and register prompts
349+
McpPromptRegistry.DiscoverPrompts(new Type[] {
350+
typeof(McpPrompts)
351+
});
352+
312353
// Step 3: Start WebServer with MCP support
313354
using (var server = new WebServer(80, HttpProtocol.Http, new Type[] { typeof(McpServerController) }))
314355
{
@@ -482,7 +523,8 @@ POST /mcp
482523
}
483524
```
484525

485-
### 2. Tool Discovery
526+
527+
### 2. Tool and Prompt Discovery
486528

487529
Agent discovers available tools:
488530

@@ -495,7 +537,18 @@ POST /mcp
495537
}
496538
```
497539

498-
The response will be the list of the tools. See next section for detailed examples.
540+
And prompts:
541+
542+
```json
543+
POST /mcp
544+
{
545+
"jsonrpc": "2.0",
546+
"method": "prompts/list",
547+
"id": 2
548+
}
549+
```
550+
551+
There will be responses for tools and prompts. See next section for detailed examples.
499552

500553
### 3. Tool Invocation
501554

@@ -519,15 +572,15 @@ POST /mcp
519572

520573
## Request/Response Examples
521574

522-
This section shows real exampled of requests and responses.
575+
This section shows real examples of requests and responses.
576+
523577

524578
### Tool Discovery
525579

526580
**Request:**
527581

528582
```json
529583
POST /mcp
530-
531584
{
532585
"jsonrpc": "2.0",
533586
"method": "tools/list",
@@ -611,6 +664,61 @@ POST /mcp
611664
}
612665
```
613666

667+
### Prompt Discovery
668+
669+
**Request:**
670+
671+
```json
672+
POST /mcp
673+
{
674+
"jsonrpc": "2.0",
675+
"method": "prompts/list",
676+
"id": 1
677+
}
678+
```
679+
680+
**Response:**
681+
682+
```json
683+
{
684+
"jsonrpc": "2.0",
685+
"id": 1,
686+
"result": {
687+
"prompts": [
688+
{
689+
"name": "echo_sanity_check",
690+
"description": "Echo test prompt",
691+
"parameters": [],
692+
"messages": [
693+
{
694+
"role": "system",
695+
"content": "Call Echo with the string 'Hello MCP world!' and return the response."
696+
}
697+
]
698+
},
699+
{
700+
"name": "summarize_person",
701+
"description": "Summarize a person with age threshold",
702+
"parameters": [
703+
{
704+
"name": "ageThreshold",
705+
"description": "The age threshold to determine if the person is a senior or junior.",
706+
"type": "string"
707+
}
708+
],
709+
"messages": [
710+
{
711+
"role": "system",
712+
"content": "Call GetDefaultPerson, then if person.Age > {ageThreshold} label as senior, else junior."
713+
}
714+
]
715+
}
716+
],
717+
"nextCursor": null
718+
}
719+
}
720+
```
721+
614722
### Simple Tool Invocation
615723

616724
**Request:**
@@ -714,6 +822,78 @@ POST /mcp
714822
}
715823
```
716824

825+
### Prompt usage
826+
827+
#### Prompt Retrieval Example
828+
829+
To retrieve a prompt, use the `prompts/get` method. Provide the prompt name and any required parameters.
830+
831+
**Request:**
832+
833+
```json
834+
POST /mcp
835+
{
836+
"jsonrpc": "2.0",
837+
"method": "prompts/get",
838+
"params": {
839+
"name": "echo_sanity_check",
840+
"arguments": {}
841+
},
842+
"id": 5
843+
}
844+
```
845+
846+
**Response:**
847+
848+
```json
849+
{
850+
"jsonrpc": "2.0",
851+
"id": 5,
852+
"result": {
853+
"messages": [
854+
{
855+
"role": "system",
856+
"content": "Call Echo with the string 'Hello MCP world!' and return the response."
857+
}
858+
]
859+
}
860+
}
861+
```
862+
863+
If the prompt requires parameters, include them in the `arguments` object:
864+
865+
```json
866+
POST /mcp
867+
{
868+
"jsonrpc": "2.0",
869+
"method": "prompts/get",
870+
"params": {
871+
"name": "summarize_person",
872+
"arguments": {
873+
"ageThreshold": "65"
874+
}
875+
},
876+
"id": 6
877+
}
878+
```
879+
880+
**Response:**
881+
882+
```json
883+
{
884+
"jsonrpc": "2.0",
885+
"id": 6,
886+
"result": {
887+
"messages": [
888+
{
889+
"role": "system",
890+
"content": "Call GetDefaultPerson, then if person.Age > 65 label as senior, else junior."
891+
}
892+
]
893+
}
894+
}
895+
```
896+
717897
## Error Handling
718898

719899
The .NET nanoFramework MCP Server knows how to handle properly errors. The following will show examples of request and error responses.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace nanoFramework.WebServer.Mcp
5+
{
6+
/// <summary>
7+
/// Represents a message within the Model Context Protocol (MCP) system, used for communication between clients and AI models.
8+
/// </summary>
9+
/// <remarks>
10+
/// <para>
11+
/// A <see cref="PromptMessage"/> encapsulates content sent to or received from AI models in the Model Context Protocol.
12+
/// Each message has a specific role (<see cref="Role.User"/> or <see cref="Role.Assistant"/>) and contains content which can be text.
13+
/// </para>
14+
/// <para>
15+
/// It serves as a core data structure in the MCP message exchange flow, particularly in prompt formation and model responses.
16+
/// </para>
17+
/// </remarks>
18+
public sealed class PromptMessage
19+
{
20+
/// <summary>
21+
/// Gets or sets the text content of the message.
22+
/// </summary>
23+
public string Text { get; set; }
24+
25+
/// <summary>
26+
/// Gets or sets the role of the message sender, specifying whether it's from a "user" or an "assistant".
27+
/// </summary>
28+
/// <remarks>
29+
/// In the Model Context Protocol, each message must have a clear role assignment to maintain
30+
/// the conversation flow. User messages represent queries or inputs from users, while assistant
31+
/// messages represent responses generated by AI models.
32+
/// </remarks>
33+
public Role Role { get; set; } = Role.User;
34+
35+
/// <summary>
36+
/// Initializes a new instance of the <see cref="PromptMessage"/> class with this prompt text.
37+
/// </summary>
38+
/// <param name="text">The text content of the message.</param>
39+
public PromptMessage(string text)
40+
{
41+
Text = text;
42+
}
43+
44+
/// <inheritdoc/>
45+
public override string ToString()
46+
{
47+
return $"{{\"role\":\"{RoleToString(Role)}\",\"content\":{{\"type\": \"text\",\"text\":\"{Text}\"}}}}";
48+
}
49+
50+
private static string RoleToString(Role role)
51+
{
52+
return role == Role.User ? "user" : "assistant";
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)