feat: port over MCP prompts to developer extension (#832)
This commit is contained in:
@@ -15,8 +15,10 @@ use std::{
|
|||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use include_dir::{include_dir, Dir};
|
||||||
|
use mcp_core::prompt::{Prompt, PromptArgument, PromptTemplate};
|
||||||
use mcp_core::{
|
use mcp_core::{
|
||||||
handler::{ResourceError, ToolError},
|
handler::{PromptError, ResourceError, ToolError},
|
||||||
protocol::ServerCapabilities,
|
protocol::ServerCapabilities,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
tool::Tool,
|
tool::Tool,
|
||||||
@@ -27,20 +29,66 @@ use mcp_server::Router;
|
|||||||
use mcp_core::content::Content;
|
use mcp_core::content::Content;
|
||||||
use mcp_core::role::Role;
|
use mcp_core::role::Role;
|
||||||
|
|
||||||
|
use self::shell::{
|
||||||
|
expand_path, format_command_for_platform, get_shell_config, is_absolute_path,
|
||||||
|
normalize_line_endings,
|
||||||
|
};
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use std::process::Stdio;
|
use std::process::Stdio;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use xcap::{Monitor, Window};
|
use xcap::{Monitor, Window};
|
||||||
|
|
||||||
use self::shell::{
|
// Embeds the prompts directory to the build
|
||||||
expand_path, format_command_for_platform, get_shell_config, is_absolute_path,
|
static PROMPTS_DIR: Dir = include_dir!("$CARGO_MANIFEST_DIR/src/developer/prompts");
|
||||||
normalize_line_endings,
|
|
||||||
};
|
/// Loads prompt files from the embedded PROMPTS_DIR and returns a HashMap of prompts.
|
||||||
|
/// Ensures that each prompt name is unique.
|
||||||
|
pub fn load_prompt_files() -> HashMap<String, Prompt> {
|
||||||
|
let mut prompts = HashMap::new();
|
||||||
|
|
||||||
|
for entry in PROMPTS_DIR.files() {
|
||||||
|
let prompt_str = String::from_utf8_lossy(entry.contents()).into_owned();
|
||||||
|
|
||||||
|
let template: PromptTemplate = match serde_json::from_str(&prompt_str) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
"Failed to parse prompt template in {}: {}",
|
||||||
|
entry.path().display(),
|
||||||
|
e
|
||||||
|
);
|
||||||
|
continue; // Skip invalid prompt file
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let arguments = template
|
||||||
|
.arguments
|
||||||
|
.into_iter()
|
||||||
|
.map(|arg| PromptArgument {
|
||||||
|
name: arg.name,
|
||||||
|
description: arg.description,
|
||||||
|
required: arg.required,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let prompt = Prompt::new(&template.id, &template.template, arguments);
|
||||||
|
|
||||||
|
if prompts.contains_key(&prompt.name) {
|
||||||
|
eprintln!("Duplicate prompt name '{}' found. Skipping.", prompt.name);
|
||||||
|
continue; // Skip duplicate prompt name
|
||||||
|
}
|
||||||
|
|
||||||
|
prompts.insert(prompt.name.clone(), prompt);
|
||||||
|
}
|
||||||
|
|
||||||
|
prompts
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DeveloperRouter {
|
pub struct DeveloperRouter {
|
||||||
tools: Vec<Tool>,
|
tools: Vec<Tool>,
|
||||||
file_history: Arc<Mutex<HashMap<PathBuf, Vec<String>>>>,
|
prompts: Arc<HashMap<String, Prompt>>,
|
||||||
instructions: String,
|
instructions: String,
|
||||||
|
file_history: Arc<Mutex<HashMap<PathBuf, Vec<String>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DeveloperRouter {
|
impl Default for DeveloperRouter {
|
||||||
@@ -273,8 +321,9 @@ impl DeveloperRouter {
|
|||||||
list_windows_tool,
|
list_windows_tool,
|
||||||
screen_capture_tool,
|
screen_capture_tool,
|
||||||
],
|
],
|
||||||
file_history: Arc::new(Mutex::new(HashMap::new())),
|
prompts: Arc::new(load_prompt_files()),
|
||||||
instructions,
|
instructions,
|
||||||
|
file_history: Arc::new(Mutex::new(HashMap::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,7 +775,10 @@ impl Router for DeveloperRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn capabilities(&self) -> ServerCapabilities {
|
fn capabilities(&self) -> ServerCapabilities {
|
||||||
CapabilitiesBuilder::new().with_tools(false).build()
|
CapabilitiesBuilder::new()
|
||||||
|
.with_tools(false)
|
||||||
|
.with_prompts(false)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_tools(&self) -> Vec<Tool> {
|
fn list_tools(&self) -> Vec<Tool> {
|
||||||
@@ -762,14 +814,58 @@ impl Router for DeveloperRouter {
|
|||||||
) -> Pin<Box<dyn Future<Output = Result<String, ResourceError>> + Send + 'static>> {
|
) -> Pin<Box<dyn Future<Output = Result<String, ResourceError>> + Send + 'static>> {
|
||||||
Box::pin(async move { Ok("".to_string()) })
|
Box::pin(async move { Ok("".to_string()) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn list_prompts(&self) -> Option<Vec<Prompt>> {
|
||||||
|
if self.prompts.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self.prompts.values().cloned().collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_prompt(
|
||||||
|
&self,
|
||||||
|
prompt_name: &str,
|
||||||
|
) -> Option<Pin<Box<dyn Future<Output = Result<String, PromptError>> + Send + 'static>>> {
|
||||||
|
let prompt_name = prompt_name.trim().to_owned();
|
||||||
|
|
||||||
|
// Validate prompt name is not empty
|
||||||
|
if prompt_name.is_empty() {
|
||||||
|
return Some(Box::pin(async move {
|
||||||
|
Err(PromptError::InvalidParameters(
|
||||||
|
"Prompt name cannot be empty".to_string(),
|
||||||
|
))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
let prompts = Arc::clone(&self.prompts);
|
||||||
|
|
||||||
|
Some(Box::pin(async move {
|
||||||
|
match prompts.get(&prompt_name) {
|
||||||
|
Some(prompt) => {
|
||||||
|
if prompt.description.trim().is_empty() {
|
||||||
|
Err(PromptError::InternalError(format!(
|
||||||
|
"Prompt '{prompt_name}' has an empty description"
|
||||||
|
)))
|
||||||
|
} else {
|
||||||
|
Ok(prompt.description.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(PromptError::NotFound(format!(
|
||||||
|
"Prompt '{prompt_name}' not found"
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for DeveloperRouter {
|
impl Clone for DeveloperRouter {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tools: self.tools.clone(),
|
tools: self.tools.clone(),
|
||||||
file_history: Arc::clone(&self.file_history),
|
prompts: Arc::clone(&self.prompts),
|
||||||
instructions: self.instructions.clone(),
|
instructions: self.instructions.clone(),
|
||||||
|
file_history: Arc::clone(&self.file_history),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"id": "unit_test",
|
||||||
|
"template": "Generate or update unit tests for a given source code file.\n\nThe source code file is provided in {source_code}.\nPlease update the existing tests, ensure they are passing, and add any new tests as needed.\n\nThe test suite should:\n- Follow language-specific test naming conventions for {language}\n- Include all necessary imports and annotations\n- Thoroughly test the specified functionality\n- Ensure tests are passing before completion\n- Handle edge cases and error conditions\n- Use clear test names that reflect what is being tested",
|
||||||
|
"arguments": [
|
||||||
|
{
|
||||||
|
"name": "source_code",
|
||||||
|
"description": "The source code file content to be tested",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "language",
|
||||||
|
"description": "The programming language of the source code",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user