Skip to content
This repository was archived by the owner on Sep 2, 2026. It is now read-only.

Commit ec18870

Browse files
Add MCP prompt server example
Co-authored-by: anode-agent[bot] <283895490+anode-agent[bot]@users.noreply.github.com>
1 parent 7bf8926 commit ec18870

5 files changed

Lines changed: 430 additions & 20 deletions

File tree

crates/agents-core/src/lib.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,15 +118,17 @@ pub use items::{
118118
pub use lifecycle::{AgentHooks, RunHooks, SharedAgentHooks, SharedRunHooks};
119119
pub use logger::{LOGGER_TARGET, enable_verbose_stdout_logging};
120120
pub use mcp::{
121-
MCPBlobResourceContents, MCPListResourceTemplatesResult, MCPListResourcesResult,
122-
MCPReadResourceResult, MCPResource, MCPResourceContents, MCPResourceTemplate, MCPServer,
123-
MCPServerManager, MCPServerSse, MCPServerSseParams, MCPServerStdio, MCPServerStdioParams,
124-
MCPServerStreamableHttp, MCPServerStreamableHttpParams, MCPTextResourceContents, MCPTool,
125-
MCPToolAnnotations, MCPToolMetaContext, MCPToolMetaResolver, MCPTransportAuth,
126-
MCPTransportClient, MCPTransportClientBuilder, MCPTransportClientConfig, MCPTransportKind,
127-
MCPUtil, RequireApprovalObject, RequireApprovalPolicy, RequireApprovalSetting,
128-
RequireApprovalToolList, RequireApprovalValue, ToolFilter, ToolFilterCallable,
129-
ToolFilterContext, ToolFilterStatic, create_static_tool_filter,
121+
MCPBlobResourceContents, MCPGetPromptResult, MCPListPromptsResult,
122+
MCPListResourceTemplatesResult, MCPListResourcesResult, MCPPrompt, MCPPromptArgument,
123+
MCPPromptContent, MCPPromptMessage, MCPPromptTextContent, MCPReadResourceResult, MCPResource,
124+
MCPResourceContents, MCPResourceTemplate, MCPServer, MCPServerManager, MCPServerSse,
125+
MCPServerSseParams, MCPServerStdio, MCPServerStdioParams, MCPServerStreamableHttp,
126+
MCPServerStreamableHttpParams, MCPTextResourceContents, MCPTool, MCPToolAnnotations,
127+
MCPToolMetaContext, MCPToolMetaResolver, MCPTransportAuth, MCPTransportClient,
128+
MCPTransportClientBuilder, MCPTransportClientConfig, MCPTransportKind, MCPUtil,
129+
RequireApprovalObject, RequireApprovalPolicy, RequireApprovalSetting, RequireApprovalToolList,
130+
RequireApprovalValue, ToolFilter, ToolFilterCallable, ToolFilterContext, ToolFilterStatic,
131+
create_static_tool_filter,
130132
};
131133
pub use memory::Session as SessionABC;
132134
pub use memory::{

crates/agents-core/src/mcp/mod.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ pub mod util;
66

77
pub use manager::MCPServerManager;
88
pub use server::{
9-
MCPBlobResourceContents, MCPListResourceTemplatesResult, MCPListResourcesResult,
10-
MCPReadResourceResult, MCPResource, MCPResourceContents, MCPResourceTemplate, MCPServer,
11-
MCPServerSse, MCPServerSseParams, MCPServerStdio, MCPServerStdioParams,
12-
MCPServerStreamableHttp, MCPServerStreamableHttpParams, MCPTextResourceContents, MCPTool,
13-
MCPToolAnnotations, MCPTransportAuth, MCPTransportClient, MCPTransportClientBuilder,
14-
MCPTransportClientConfig, MCPTransportKind, RequireApprovalObject, RequireApprovalPolicy,
15-
RequireApprovalSetting, RequireApprovalToolList, RequireApprovalValue,
9+
MCPBlobResourceContents, MCPGetPromptResult, MCPListPromptsResult,
10+
MCPListResourceTemplatesResult, MCPListResourcesResult, MCPPrompt, MCPPromptArgument,
11+
MCPPromptContent, MCPPromptMessage, MCPPromptTextContent, MCPReadResourceResult, MCPResource,
12+
MCPResourceContents, MCPResourceTemplate, MCPServer, MCPServerSse, MCPServerSseParams,
13+
MCPServerStdio, MCPServerStdioParams, MCPServerStreamableHttp, MCPServerStreamableHttpParams,
14+
MCPTextResourceContents, MCPTool, MCPToolAnnotations, MCPTransportAuth, MCPTransportClient,
15+
MCPTransportClientBuilder, MCPTransportClientConfig, MCPTransportKind, RequireApprovalObject,
16+
RequireApprovalPolicy, RequireApprovalSetting, RequireApprovalToolList, RequireApprovalValue,
1617
};
1718
pub use util::{
1819
MCPToolMetaContext, MCPToolMetaResolver, MCPUtil, ToolFilter, ToolFilterCallable,

crates/agents-core/src/mcp/server.rs

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,58 @@ pub struct MCPReadResourceResult {
284284
pub contents: Vec<MCPResourceContents>,
285285
}
286286

287+
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
288+
pub struct MCPPromptArgument {
289+
pub name: String,
290+
pub description: Option<String>,
291+
pub required: Option<bool>,
292+
}
293+
294+
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
295+
pub struct MCPPrompt {
296+
pub name: String,
297+
pub description: Option<String>,
298+
#[serde(default)]
299+
pub arguments: Vec<MCPPromptArgument>,
300+
pub meta: Option<Value>,
301+
}
302+
303+
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
304+
pub struct MCPPromptTextContent {
305+
pub text: String,
306+
}
307+
308+
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)]
309+
#[serde(tag = "type", rename_all = "snake_case")]
310+
pub enum MCPPromptContent {
311+
Text(MCPPromptTextContent),
312+
Json { value: Value },
313+
}
314+
315+
impl Default for MCPPromptContent {
316+
fn default() -> Self {
317+
Self::Text(MCPPromptTextContent::default())
318+
}
319+
}
320+
321+
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
322+
pub struct MCPPromptMessage {
323+
pub role: String,
324+
pub content: MCPPromptContent,
325+
}
326+
327+
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
328+
pub struct MCPListPromptsResult {
329+
pub prompts: Vec<MCPPrompt>,
330+
pub next_cursor: Option<String>,
331+
}
332+
333+
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
334+
pub struct MCPGetPromptResult {
335+
pub description: Option<String>,
336+
pub messages: Vec<MCPPromptMessage>,
337+
}
338+
287339
#[async_trait]
288340
pub trait MCPServer: Send + Sync {
289341
fn name(&self) -> &str;
@@ -325,6 +377,22 @@ pub trait MCPServer: Send + Sync {
325377
message: "read_resource is not implemented".to_owned(),
326378
}))
327379
}
380+
381+
async fn list_prompts(&self, _cursor: Option<String>) -> Result<MCPListPromptsResult> {
382+
Err(AgentsError::User(UserError {
383+
message: "list_prompts is not implemented".to_owned(),
384+
}))
385+
}
386+
387+
async fn get_prompt(
388+
&self,
389+
_prompt_name: &str,
390+
_arguments: Value,
391+
) -> Result<MCPGetPromptResult> {
392+
Err(AgentsError::User(UserError {
393+
message: "get_prompt is not implemented".to_owned(),
394+
}))
395+
}
328396
}
329397

330398
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
@@ -479,6 +547,8 @@ pub struct MCPServerStdio {
479547
resources: Arc<Mutex<Vec<MCPResource>>>,
480548
resource_templates: Arc<Mutex<Vec<MCPResourceTemplate>>>,
481549
resource_contents: Arc<Mutex<HashMap<String, MCPReadResourceResult>>>,
550+
prompts: Arc<Mutex<Vec<MCPPrompt>>>,
551+
prompt_results: Arc<Mutex<HashMap<String, MCPGetPromptResult>>>,
482552
}
483553

484554
impl fmt::Debug for MCPServerStdio {
@@ -503,6 +573,8 @@ impl MCPServerStdio {
503573
resources: Arc::new(Mutex::new(Vec::new())),
504574
resource_templates: Arc::new(Mutex::new(Vec::new())),
505575
resource_contents: Arc::new(Mutex::new(HashMap::new())),
576+
prompts: Arc::new(Mutex::new(Vec::new())),
577+
prompt_results: Arc::new(Mutex::new(HashMap::new())),
506578
}
507579
}
508580

@@ -545,6 +617,19 @@ impl MCPServerStdio {
545617
self
546618
}
547619

620+
pub fn with_prompts(mut self, prompts: Vec<MCPPrompt>) -> Self {
621+
self.prompts = Arc::new(Mutex::new(prompts));
622+
self
623+
}
624+
625+
pub fn with_prompt_results(
626+
mut self,
627+
prompt_results: HashMap<String, MCPGetPromptResult>,
628+
) -> Self {
629+
self.prompt_results = Arc::new(Mutex::new(prompt_results));
630+
self
631+
}
632+
548633
fn ensure_connected(&self) -> Result<()> {
549634
if self.connected.load(Ordering::SeqCst) {
550635
Ok(())
@@ -627,6 +712,28 @@ impl MCPServer for MCPServerStdio {
627712
})
628713
})
629714
}
715+
716+
async fn list_prompts(&self, _cursor: Option<String>) -> Result<MCPListPromptsResult> {
717+
self.ensure_connected()?;
718+
Ok(MCPListPromptsResult {
719+
prompts: self.prompts.lock().await.clone(),
720+
next_cursor: None,
721+
})
722+
}
723+
724+
async fn get_prompt(&self, prompt_name: &str, _arguments: Value) -> Result<MCPGetPromptResult> {
725+
self.ensure_connected()?;
726+
self.prompt_results
727+
.lock()
728+
.await
729+
.get(prompt_name)
730+
.cloned()
731+
.ok_or_else(|| {
732+
AgentsError::User(UserError {
733+
message: format!("prompt `{prompt_name}` not found"),
734+
})
735+
})
736+
}
630737
}
631738

632739
#[derive(Clone)]
@@ -642,6 +749,8 @@ pub struct MCPServerSse {
642749
resources: Arc<Mutex<Vec<MCPResource>>>,
643750
resource_templates: Arc<Mutex<Vec<MCPResourceTemplate>>>,
644751
resource_contents: Arc<Mutex<HashMap<String, MCPReadResourceResult>>>,
752+
prompts: Arc<Mutex<Vec<MCPPrompt>>>,
753+
prompt_results: Arc<Mutex<HashMap<String, MCPGetPromptResult>>>,
645754
}
646755

647756
impl fmt::Debug for MCPServerSse {
@@ -668,6 +777,8 @@ impl MCPServerSse {
668777
resources: Arc::new(Mutex::new(Vec::new())),
669778
resource_templates: Arc::new(Mutex::new(Vec::new())),
670779
resource_contents: Arc::new(Mutex::new(HashMap::new())),
780+
prompts: Arc::new(Mutex::new(Vec::new())),
781+
prompt_results: Arc::new(Mutex::new(HashMap::new())),
671782
}
672783
}
673784

@@ -715,6 +826,19 @@ impl MCPServerSse {
715826
self
716827
}
717828

829+
pub fn with_prompts(mut self, prompts: Vec<MCPPrompt>) -> Self {
830+
self.prompts = Arc::new(Mutex::new(prompts));
831+
self
832+
}
833+
834+
pub fn with_prompt_results(
835+
mut self,
836+
prompt_results: HashMap<String, MCPGetPromptResult>,
837+
) -> Self {
838+
self.prompt_results = Arc::new(Mutex::new(prompt_results));
839+
self
840+
}
841+
718842
fn ensure_connected(&self) -> Result<()> {
719843
if self.connected.load(Ordering::SeqCst) {
720844
Ok(())
@@ -835,6 +959,28 @@ impl MCPServer for MCPServerSse {
835959
})
836960
})
837961
}
962+
963+
async fn list_prompts(&self, _cursor: Option<String>) -> Result<MCPListPromptsResult> {
964+
self.ensure_connected()?;
965+
Ok(MCPListPromptsResult {
966+
prompts: self.prompts.lock().await.clone(),
967+
next_cursor: None,
968+
})
969+
}
970+
971+
async fn get_prompt(&self, prompt_name: &str, _arguments: Value) -> Result<MCPGetPromptResult> {
972+
self.ensure_connected()?;
973+
self.prompt_results
974+
.lock()
975+
.await
976+
.get(prompt_name)
977+
.cloned()
978+
.ok_or_else(|| {
979+
AgentsError::User(UserError {
980+
message: format!("prompt `{prompt_name}` not found"),
981+
})
982+
})
983+
}
838984
}
839985

840986
#[derive(Clone)]
@@ -850,6 +996,8 @@ pub struct MCPServerStreamableHttp {
850996
resources: Arc<Mutex<Vec<MCPResource>>>,
851997
resource_templates: Arc<Mutex<Vec<MCPResourceTemplate>>>,
852998
resource_contents: Arc<Mutex<HashMap<String, MCPReadResourceResult>>>,
999+
prompts: Arc<Mutex<Vec<MCPPrompt>>>,
1000+
prompt_results: Arc<Mutex<HashMap<String, MCPGetPromptResult>>>,
8531001
}
8541002

8551003
impl fmt::Debug for MCPServerStreamableHttp {
@@ -876,6 +1024,8 @@ impl MCPServerStreamableHttp {
8761024
resources: Arc::new(Mutex::new(Vec::new())),
8771025
resource_templates: Arc::new(Mutex::new(Vec::new())),
8781026
resource_contents: Arc::new(Mutex::new(HashMap::new())),
1027+
prompts: Arc::new(Mutex::new(Vec::new())),
1028+
prompt_results: Arc::new(Mutex::new(HashMap::new())),
8791029
}
8801030
}
8811031

@@ -923,6 +1073,19 @@ impl MCPServerStreamableHttp {
9231073
self
9241074
}
9251075

1076+
pub fn with_prompts(mut self, prompts: Vec<MCPPrompt>) -> Self {
1077+
self.prompts = Arc::new(Mutex::new(prompts));
1078+
self
1079+
}
1080+
1081+
pub fn with_prompt_results(
1082+
mut self,
1083+
prompt_results: HashMap<String, MCPGetPromptResult>,
1084+
) -> Self {
1085+
self.prompt_results = Arc::new(Mutex::new(prompt_results));
1086+
self
1087+
}
1088+
9261089
fn ensure_connected(&self) -> Result<()> {
9271090
if self.connected.load(Ordering::SeqCst) {
9281091
Ok(())
@@ -1051,6 +1214,28 @@ impl MCPServer for MCPServerStreamableHttp {
10511214
})
10521215
})
10531216
}
1217+
1218+
async fn list_prompts(&self, _cursor: Option<String>) -> Result<MCPListPromptsResult> {
1219+
self.ensure_connected()?;
1220+
Ok(MCPListPromptsResult {
1221+
prompts: self.prompts.lock().await.clone(),
1222+
next_cursor: None,
1223+
})
1224+
}
1225+
1226+
async fn get_prompt(&self, prompt_name: &str, _arguments: Value) -> Result<MCPGetPromptResult> {
1227+
self.ensure_connected()?;
1228+
self.prompt_results
1229+
.lock()
1230+
.await
1231+
.get(prompt_name)
1232+
.cloned()
1233+
.ok_or_else(|| {
1234+
AgentsError::User(UserError {
1235+
message: format!("prompt `{prompt_name}` not found"),
1236+
})
1237+
})
1238+
}
10541239
}
10551240

10561241
#[cfg(test)]
@@ -1399,4 +1584,67 @@ mod tests {
13991584

14001585
server.cleanup().await.expect("cleanup should succeed");
14011586
}
1587+
1588+
#[tokio::test]
1589+
async fn streamable_http_prompts_can_roundtrip() {
1590+
let server = MCPServerStreamableHttp::new(
1591+
"prompt-server",
1592+
MCPServerStreamableHttpParams {
1593+
url: "http://localhost:8000/mcp".to_owned(),
1594+
..MCPServerStreamableHttpParams::default()
1595+
},
1596+
)
1597+
.with_prompts(vec![MCPPrompt {
1598+
name: "generate_code_review_instructions".to_owned(),
1599+
description: Some("Generate code review instructions.".to_owned()),
1600+
arguments: vec![MCPPromptArgument {
1601+
name: "focus".to_owned(),
1602+
description: Some("Review focus area.".to_owned()),
1603+
required: Some(false),
1604+
}],
1605+
..MCPPrompt::default()
1606+
}])
1607+
.with_prompt_results(HashMap::from([(
1608+
"generate_code_review_instructions".to_owned(),
1609+
MCPGetPromptResult {
1610+
description: Some("Code review prompt".to_owned()),
1611+
messages: vec![MCPPromptMessage {
1612+
role: "user".to_owned(),
1613+
content: MCPPromptContent::Text(MCPPromptTextContent {
1614+
text: "You are a senior code reviewer.".to_owned(),
1615+
}),
1616+
}],
1617+
},
1618+
)]));
1619+
1620+
assert!(server.list_prompts(None).await.is_err());
1621+
assert!(
1622+
server
1623+
.get_prompt("generate_code_review_instructions", json!({}))
1624+
.await
1625+
.is_err()
1626+
);
1627+
1628+
server.connect().await.expect("connect should succeed");
1629+
let prompts = server
1630+
.list_prompts(None)
1631+
.await
1632+
.expect("prompts should load");
1633+
let prompt = server
1634+
.get_prompt(
1635+
"generate_code_review_instructions",
1636+
json!({"focus": "security"}),
1637+
)
1638+
.await
1639+
.expect("prompt should load");
1640+
1641+
assert_eq!(prompts.prompts[0].name, "generate_code_review_instructions");
1642+
assert_eq!(prompt.messages.len(), 1);
1643+
assert_eq!(
1644+
prompt.messages[0].content,
1645+
MCPPromptContent::Text(MCPPromptTextContent {
1646+
text: "You are a senior code reviewer.".to_owned()
1647+
})
1648+
);
1649+
}
14021650
}

0 commit comments

Comments
 (0)