use rmcp::handler::server::wrapper::Parameters;
use rmcp::model::{PromptMessage, Role};
use rmcp::{prompt, prompt_router};
use crate::core::mcp_server::McpifyServer;
use crate::prompts::{
IndexTuningRecommendationsArgs, IndexesConstraintsArgs, MasterWorkflowArgs,
SecurityProvisioningArgs, SqlAgentJobsArgs, render_context_header,
};
#[prompt_router(vis = "pub(crate)")]
impl McpifyServer {
#[prompt(
name = "sqlserver_workflow",
description = "Start here. Presents the available SQL Server operational \
workflows, routes to the right guided sub-workflow based on \
the user's goal, and — where the environment supports it — \
delegates that whole sub-workflow to an isolated sub-task to \
spare this conversation's context window."
)]
async fn sqlserver_workflow_prompt(
&self,
Parameters(args): Parameters<MasterWorkflowArgs>,
) -> Vec<PromptMessage> {
let header = render_context_header(&[("goal", args.goal.as_deref())]);
vec![PromptMessage::new_text(
Role::User,
format!("{header}\n{}", include_str!("content/master.md")),
)]
}
#[prompt(
name = "sqlserver_workflow_sql_agent_jobs",
description = "Guided SQL Agent job setup: create a job, add one or more \
steps, attach a schedule, and start it — each step gated on \
the previous one being confirmed to exist, with cleanup \
guidance for test runs."
)]
async fn sqlserver_workflow_sql_agent_jobs_prompt(
&self,
Parameters(args): Parameters<SqlAgentJobsArgs>,
) -> Vec<PromptMessage> {
let header = render_context_header(&[
("job_name", args.job_name.as_deref()),
("database", args.database.as_deref()),
]);
vec![PromptMessage::new_text(
Role::User,
format!("{header}\n{}", include_str!("content/sql_agent_jobs.md")),
)]
}
#[prompt(
name = "sqlserver_workflow_schema_exploration",
description = "Discover databases/schemas/tables/views/columns/types/triggers, \
either via `sys.*` catalog views or `INFORMATION_SCHEMA.*`."
)]
async fn sqlserver_workflow_schema_exploration_prompt(&self) -> Vec<PromptMessage> {
vec![PromptMessage::new_text(
Role::User,
include_str!("content/schema_exploration.md"),
)]
}
#[prompt(
name = "sqlserver_workflow_indexes_constraints",
description = "Inspect indexes, index columns, foreign keys, and check \
constraints on a table; estimate compression savings."
)]
async fn sqlserver_workflow_indexes_constraints_prompt(
&self,
Parameters(args): Parameters<IndexesConstraintsArgs>,
) -> Vec<PromptMessage> {
let header = render_context_header(&[
("database", args.database.as_deref()),
("schema", args.schema.as_deref()),
("table", args.table.as_deref()),
]);
vec![PromptMessage::new_text(
Role::User,
format!(
"{header}\n{}",
include_str!("content/indexes_constraints.md")
),
)]
}
#[prompt(
name = "sqlserver_workflow_security_provisioning",
description = "Guided login/user/role provisioning, including the \
built-in-vs-custom-role fork and the new-login-vs-existing-login fork."
)]
async fn sqlserver_workflow_security_provisioning_prompt(
&self,
Parameters(args): Parameters<SecurityProvisioningArgs>,
) -> Vec<PromptMessage> {
let header = render_context_header(&[
("login_name", args.login_name.as_deref()),
("database", args.database.as_deref()),
("role_name", args.role_name.as_deref()),
]);
vec![PromptMessage::new_text(
Role::User,
format!(
"{header}\n{}",
include_str!("content/security_provisioning.md")
),
)]
}
#[prompt(
name = "sqlserver_workflow_server_administration",
description = "Server/database config, renaming objects, disk-space usage, \
dependency lookup, bulk per-table/per-db operations, linked servers."
)]
async fn sqlserver_workflow_server_administration_prompt(&self) -> Vec<PromptMessage> {
vec![PromptMessage::new_text(
Role::User,
include_str!("content/server_administration.md"),
)]
}
#[prompt(
name = "sqlserver_workflow_performance_diagnostics",
description = "Thin pointer to the right read-only signal (wait stats, query plans, \
transactions, resource governor, I/O, In-Memory OLTP/columnstore \
health, OS-level pressure)."
)]
async fn sqlserver_workflow_performance_diagnostics_prompt(&self) -> Vec<PromptMessage> {
vec![PromptMessage::new_text(
Role::User,
include_str!("content/performance_diagnostics.md"),
)]
}
#[prompt(
name = "sqlserver_workflow_blocking_and_locks",
description = "Diagnose a blocking chain down to its head blocker and last \
statement, then — only with explicit user confirmation — \
terminate the blocking session and verify the chain cleared."
)]
async fn sqlserver_workflow_blocking_and_locks_prompt(&self) -> Vec<PromptMessage> {
vec![PromptMessage::new_text(
Role::User,
include_str!("content/blocking_and_locks.md"),
)]
}
#[prompt(
name = "sqlserver_workflow_index_tuning_recommendations",
description = "Find missing-index candidates ranked by estimated improvement, \
cross-check for overlap with existing indexes, and — only with \
explicit user confirmation — create and verify the index."
)]
async fn sqlserver_workflow_index_tuning_recommendations_prompt(
&self,
Parameters(args): Parameters<IndexTuningRecommendationsArgs>,
) -> Vec<PromptMessage> {
let header = render_context_header(&[
("database", args.database.as_deref()),
("schema", args.schema.as_deref()),
("table", args.table.as_deref()),
]);
vec![PromptMessage::new_text(
Role::User,
format!(
"{header}\n{}",
include_str!("content/index_tuning_recommendations.md")
),
)]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::auth::auth_manager::AuthManager;
use crate::core::config_schema::AuthMethod;
fn server() -> McpifyServer {
let config: crate::core::config_schema::Config =
serde_json::from_value(serde_json::json!({
"url": "localhost",
"auth_method": "sql_server"
}))
.unwrap();
McpifyServer::new(
"2025".to_string(),
config,
std::sync::Arc::new(tokio::sync::Mutex::new(AuthManager::new(
AuthMethod::SqlServer,
))),
)
}
#[test]
fn prompt_router_registers_every_prompt_name() {
let prompts = McpifyServer::prompt_router().list_all();
let names: std::collections::BTreeSet<&str> =
prompts.iter().map(|p| p.name.as_str()).collect();
assert_eq!(
names,
std::collections::BTreeSet::from([
"sqlserver_workflow",
"sqlserver_workflow_sql_agent_jobs",
"sqlserver_workflow_schema_exploration",
"sqlserver_workflow_indexes_constraints",
"sqlserver_workflow_security_provisioning",
"sqlserver_workflow_server_administration",
"sqlserver_workflow_performance_diagnostics",
"sqlserver_workflow_blocking_and_locks",
"sqlserver_workflow_index_tuning_recommendations",
])
);
}
#[test]
fn security_provisioning_advertises_every_argument_as_optional() {
let prompts = McpifyServer::prompt_router().list_all();
let security = prompts
.iter()
.find(|p| p.name == "sqlserver_workflow_security_provisioning")
.expect("sqlserver_workflow_security_provisioning is registered");
let arguments = security
.arguments
.as_ref()
.expect("prompt declares arguments");
let names: std::collections::BTreeSet<&str> =
arguments.iter().map(|a| a.name.as_str()).collect();
assert_eq!(
names,
std::collections::BTreeSet::from(["login_name", "database", "role_name"])
);
for arg in arguments {
assert_ne!(
arg.required,
Some(true),
"{} must not be a required argument",
arg.name
);
}
}
#[tokio::test]
async fn master_prompt_links_to_the_sql_agent_jobs_sub_workflow() {
let messages = server()
.sqlserver_workflow_prompt(Parameters(MasterWorkflowArgs { goal: None }))
.await;
let text = &messages[0].content.as_text().unwrap().text;
assert!(text.contains("sqlserver_workflow_sql_agent_jobs"));
}
#[tokio::test]
async fn sql_agent_jobs_prompt_echoes_supplied_args_and_lists_missing_ones() {
let messages = server()
.sqlserver_workflow_sql_agent_jobs_prompt(Parameters(SqlAgentJobsArgs {
job_name: Some("nightly_backup".to_string()),
database: None,
}))
.await;
let text = &messages[0].content.as_text().unwrap().text;
assert!(text.contains("- job_name: nightly_backup"));
assert!(text.contains("- database"));
assert!(!text.contains("- database: "));
}
#[tokio::test]
async fn blocking_and_locks_prompt_gates_termination_on_explicit_confirmation() {
let messages = server()
.sqlserver_workflow_blocking_and_locks_prompt()
.await;
let text = &messages[0].content.as_text().unwrap().text;
assert!(text.to_lowercase().contains("confirm"));
assert!(text.contains("sp_executesql"));
}
#[tokio::test]
async fn index_tuning_recommendations_prompt_echoes_supplied_args_and_gates_on_confirmation() {
let messages = server()
.sqlserver_workflow_index_tuning_recommendations_prompt(Parameters(
IndexTuningRecommendationsArgs {
database: Some("sandbox".to_string()),
schema: None,
table: Some("orders".to_string()),
},
))
.await;
let text = &messages[0].content.as_text().unwrap().text;
assert!(text.contains("- database: sandbox"));
assert!(text.contains("- table: orders"));
assert!(text.contains("- schema"));
assert!(text.to_lowercase().contains("confirm"));
}
#[tokio::test]
async fn performance_diagnostics_points_to_the_dedicated_blocking_workflow() {
let messages = server()
.sqlserver_workflow_performance_diagnostics_prompt()
.await;
let text = &messages[0].content.as_text().unwrap().text;
assert!(text.contains("sqlserver_workflow_blocking_and_locks"));
assert!(text.contains("sqlserver_workflow_index_tuning_recommendations"));
}
}