1use std::collections::HashMap;
60use std::convert::Infallible;
61use std::future::Future;
62use std::pin::Pin;
63use std::sync::Arc;
64use std::task::{Context, Poll};
65
66use tower::{Layer, Service};
67use tower_mcp::router::{RouterRequest, RouterResponse};
68use tower_mcp_types::protocol::{McpRequest, McpResponse};
69
70#[derive(Clone)]
88pub struct AliasLayer {
89 aliases: AliasMap,
90}
91
92impl AliasLayer {
93 pub fn new(aliases: AliasMap) -> Self {
95 Self { aliases }
96 }
97}
98
99impl<S> Layer<S> for AliasLayer {
100 type Service = AliasService<S>;
101
102 fn layer(&self, inner: S) -> Self::Service {
103 AliasService::new(inner, self.aliases.clone())
104 }
105}
106
107#[derive(Clone)]
109pub struct AliasMap {
110 pub forward: HashMap<String, String>,
112 reverse: HashMap<String, String>,
114}
115
116impl AliasMap {
117 pub fn new(mappings: Vec<(String, String, String)>) -> Option<Self> {
119 if mappings.is_empty() {
120 return None;
121 }
122 let mut forward = HashMap::new();
123 let mut reverse = HashMap::new();
124 for (namespace, from, to) in mappings {
125 let original = format!("{}{}", namespace, from);
126 let aliased = format!("{}{}", namespace, to);
127 forward.insert(original.clone(), aliased.clone());
128 reverse.insert(aliased, original);
129 }
130 Some(Self { forward, reverse })
131 }
132}
133
134#[derive(Clone)]
136pub struct AliasService<S> {
137 inner: S,
138 aliases: Arc<AliasMap>,
139}
140
141impl<S> AliasService<S> {
142 pub fn new(inner: S, aliases: AliasMap) -> Self {
144 Self {
145 inner,
146 aliases: Arc::new(aliases),
147 }
148 }
149}
150
151impl<S> Service<RouterRequest> for AliasService<S>
152where
153 S: Service<RouterRequest, Response = RouterResponse, Error = Infallible>
154 + Clone
155 + Send
156 + 'static,
157 S::Future: Send,
158{
159 type Response = RouterResponse;
160 type Error = Infallible;
161 type Future = Pin<Box<dyn Future<Output = Result<RouterResponse, Infallible>> + Send>>;
162
163 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
164 self.inner.poll_ready(cx)
165 }
166
167 fn call(&mut self, mut req: RouterRequest) -> Self::Future {
168 let aliases = Arc::clone(&self.aliases);
169
170 match &mut req.inner {
172 McpRequest::CallTool(params) => {
173 if let Some(original) = aliases.reverse.get(¶ms.name) {
174 params.name = original.clone();
175 }
176 }
177 McpRequest::ReadResource(params) => {
178 if let Some(original) = aliases.reverse.get(¶ms.uri) {
179 params.uri = original.clone();
180 }
181 }
182 McpRequest::GetPrompt(params) => {
183 if let Some(original) = aliases.reverse.get(¶ms.name) {
184 params.name = original.clone();
185 }
186 }
187 _ => {}
188 }
189
190 let fut = self.inner.call(req);
191
192 Box::pin(async move {
193 let mut result = fut.await;
194
195 let Ok(ref mut resp) = result;
197 if let Ok(mcp_resp) = &mut resp.inner {
198 match mcp_resp {
199 McpResponse::ListTools(r) => {
200 for tool in &mut r.tools {
201 if let Some(aliased) = aliases.forward.get(&tool.name) {
202 tool.name = aliased.clone();
203 }
204 }
205 }
206 McpResponse::ListResources(r) => {
207 for res in &mut r.resources {
208 if let Some(aliased) = aliases.forward.get(&res.uri) {
209 res.uri = aliased.clone();
210 }
211 }
212 }
213 McpResponse::ListPrompts(r) => {
214 for prompt in &mut r.prompts {
215 if let Some(aliased) = aliases.forward.get(&prompt.name) {
216 prompt.name = aliased.clone();
217 }
218 }
219 }
220 _ => {}
221 }
222 }
223
224 result
225 })
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use tower_mcp::protocol::{McpRequest, McpResponse};
232
233 use super::{AliasMap, AliasService};
234 use crate::test_util::{MockService, call_service};
235
236 fn test_aliases() -> AliasMap {
237 AliasMap::new(vec![
238 ("files/".into(), "read_file".into(), "read".into()),
239 ("files/".into(), "write_file".into(), "write".into()),
240 ])
241 .unwrap()
242 }
243
244 #[test]
245 fn test_alias_map_empty_returns_none() {
246 assert!(AliasMap::new(vec![]).is_none());
247 }
248
249 #[test]
250 fn test_alias_map_forward_and_reverse() {
251 let aliases = test_aliases();
252 assert_eq!(
253 aliases.forward.get("files/read_file").unwrap(),
254 "files/read"
255 );
256 assert_eq!(aliases.forward.len(), 2);
257 }
258
259 #[tokio::test]
260 async fn test_alias_rewrites_list_tools() {
261 let mock = MockService::with_tools(&["files/read_file", "files/write_file", "db/query"]);
262 let mut svc = AliasService::new(mock, test_aliases());
263
264 let resp = call_service(&mut svc, McpRequest::ListTools(Default::default())).await;
265 match resp.inner.unwrap() {
266 McpResponse::ListTools(result) => {
267 let names: Vec<&str> = result.tools.iter().map(|t| t.name.as_str()).collect();
268 assert!(names.contains(&"files/read"));
269 assert!(names.contains(&"files/write"));
270 assert!(names.contains(&"db/query")); }
272 other => panic!("expected ListTools, got: {:?}", other),
273 }
274 }
275
276 #[tokio::test]
277 async fn test_alias_reverse_maps_call_tool() {
278 let mock = MockService::with_tools(&["files/read_file"]);
279 let mut svc = AliasService::new(mock, test_aliases());
280
281 let resp = call_service(
282 &mut svc,
283 McpRequest::CallTool(tower_mcp::protocol::CallToolParams {
284 name: "files/read".to_string(),
285 arguments: serde_json::json!({}),
286 input_responses: None,
287 request_state: None,
288 meta: None,
289 task: None,
290 }),
291 )
292 .await;
293
294 match resp.inner.unwrap() {
295 McpResponse::CallTool(result) => {
296 assert_eq!(result.all_text(), "called: files/read_file");
297 }
298 other => panic!("expected CallTool, got: {:?}", other),
299 }
300 }
301
302 #[tokio::test]
303 async fn test_alias_passthrough_non_aliased() {
304 let mock = MockService::with_tools(&["db/query"]);
305 let mut svc = AliasService::new(mock, test_aliases());
306
307 let resp = call_service(
308 &mut svc,
309 McpRequest::CallTool(tower_mcp::protocol::CallToolParams {
310 name: "db/query".to_string(),
311 arguments: serde_json::json!({}),
312 input_responses: None,
313 request_state: None,
314 meta: None,
315 task: None,
316 }),
317 )
318 .await;
319
320 match resp.inner.unwrap() {
321 McpResponse::CallTool(result) => {
322 assert_eq!(result.all_text(), "called: db/query");
323 }
324 other => panic!("expected CallTool, got: {:?}", other),
325 }
326 }
327}