Skip to content

Commit 51a17fa

Browse files
committed
Remove dead code, fix SSE timeout, and correct GET test
- Remove unused `BoardDetail::get_work_item_types()` and `HashSet` import - Remove 60s connection timeout that would kill SSE streams; MCP Streamable HTTP uses GET for long-lived SSE connections - Fix invalid-method test to use PUT instead of GET (GET is valid for SSE) - Add `test_http_server_accepts_get_for_sse` verifying GET is accepted
1 parent dff2f68 commit 51a17fa

File tree

3 files changed

+44
-37
lines changed

3 files changed

+44
-37
lines changed

src/azure/boards.rs

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
use crate::azure::client::{AzureDevOpsClient, AzureError};
22
use reqwest::Method;
33
use serde::{Deserialize, Serialize};
4-
use std::collections::HashSet;
54

65
#[derive(Debug, Serialize, Deserialize)]
76
pub struct Team {
@@ -118,27 +117,6 @@ pub struct BoardDetail {
118117
// Skipping _links as it's not needed
119118
}
120119

121-
impl BoardDetail {
122-
/// Extract work item types from the board's allowed mappings
123-
pub fn get_work_item_types(&self) -> Vec<String> {
124-
let mut types = HashSet::new();
125-
126-
if let Some(mappings) = &self.allowed_mappings
127-
&& let Some(obj) = mappings.as_object()
128-
{
129-
for (_column_type, type_mappings) in obj {
130-
if let Some(type_obj) = type_mappings.as_object() {
131-
for (work_item_type, _states) in type_obj {
132-
types.insert(work_item_type.clone());
133-
}
134-
}
135-
}
136-
}
137-
138-
types.into_iter().collect()
139-
}
140-
}
141-
142120
#[derive(Debug, Serialize, Deserialize)]
143121
pub struct BoardListResponse {
144122
pub value: Vec<BoardSummary>,

src/server/http.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,9 @@ use rmcp::transport::streamable_http_server::{
88
StreamableHttpService, session::local::LocalSessionManager,
99
};
1010
use std::sync::Arc;
11-
use std::time::Duration;
1211
use tokio::sync::Semaphore;
1312

1413
const MAX_CONNECTIONS: usize = 256;
15-
const CONNECTION_TIMEOUT: Duration = Duration::from_secs(60);
1614

1715
pub async fn run_server(
1816
server: AzureMcpServer,
@@ -39,16 +37,11 @@ pub async fn run_server(
3937
let service = service.clone();
4038

4139
tokio::spawn(async move {
42-
let result = tokio::time::timeout(
43-
CONNECTION_TIMEOUT,
44-
Builder::new(TokioExecutor::default()).serve_connection(io, service),
45-
)
46-
.await;
47-
48-
match result {
49-
Ok(Ok(())) => {}
50-
Ok(Err(err)) => log::error!("Error serving connection: {:?}", err),
51-
Err(_) => log::warn!("Connection timed out after {:?}", CONNECTION_TIMEOUT),
40+
if let Err(err) = Builder::new(TokioExecutor::default())
41+
.serve_connection(io, service)
42+
.await
43+
{
44+
log::error!("Error serving connection: {:?}", err);
5245
}
5346

5447
drop(permit);

tests/test_server_http.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,53 @@ mod tests {
5353

5454
let client = reqwest::Client::new();
5555
let response = client
56-
.get(format!("http://{}/mcp", addr))
56+
.put(format!("http://{}/mcp", addr))
5757
.send()
5858
.await
5959
.unwrap();
6060

6161
assert!(
62-
!response.status().is_success() || response.status().as_u16() == 405,
63-
"GET should not succeed with 2xx (or should be 405), got {}",
62+
!response.status().is_success(),
63+
"PUT should not succeed, got {}",
6464
response.status()
6565
);
6666

6767
server_handle.abort();
6868
}
69+
70+
#[tokio::test]
71+
async fn test_http_server_accepts_get_for_sse() {
72+
let mock = MockAzureDevOpsApi::new();
73+
let server = AzureMcpServer::new_with_api(mock);
74+
75+
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
76+
let addr = listener.local_addr().unwrap();
77+
78+
let server_handle = tokio::spawn(async move {
79+
let _ = http::run_server(server, listener).await;
80+
});
81+
82+
tokio::time::sleep(std::time::Duration::from_millis(100)).await;
83+
84+
let client = reqwest::Client::new();
85+
let response = client
86+
.get(format!("http://{}/mcp", addr))
87+
.header("Accept", "text/event-stream")
88+
.send()
89+
.await
90+
.unwrap();
91+
92+
let status = response.status().as_u16();
93+
assert!(
94+
status != 405,
95+
"GET /mcp must be a supported method for SSE streams, got 405 Method Not Allowed",
96+
);
97+
assert!(
98+
status == 200 || status == 400 || status == 401,
99+
"GET /mcp for SSE should return 200 (stream), 400 (bad request), or 401 (no session), got {}",
100+
status
101+
);
102+
103+
server_handle.abort();
104+
}
69105
}

0 commit comments

Comments
 (0)