@@ -12,6 +12,7 @@ use serde::{
1212use super :: types:: ResourcePath ;
1313use crate :: agent:: consts:: DEFAULT_AGENT_NAME ;
1414use crate :: agent:: tools:: BuiltInToolName ;
15+ use crate :: mcp:: oauth_util:: OAuthConfig ;
1516
1617#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
1718#[ serde( untagged) ]
@@ -215,13 +216,16 @@ pub struct McpServers {
215216}
216217
217218#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
218- #[ serde( untagged ) ]
219+ #[ serde( tag = "type" ) ]
219220pub enum McpServerConfig {
221+ #[ serde( rename = "stdio" ) ]
220222 Local ( LocalMcpServerConfig ) ,
221- StreamableHTTP ( StreamableHTTPMcpServerConfig ) ,
223+ #[ serde( rename = "http" ) ]
224+ Remote ( RemoteMcpServerConfig ) ,
222225}
223226
224227#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
228+ #[ serde( rename_all = "camelCase" ) ]
225229pub struct LocalMcpServerConfig {
226230 /// The command string used to initialize the mcp server
227231 pub command : String ,
@@ -241,7 +245,8 @@ pub struct LocalMcpServerConfig {
241245}
242246
243247#[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
244- pub struct StreamableHTTPMcpServerConfig {
248+ #[ serde( rename_all = "camelCase" ) ]
249+ pub struct RemoteMcpServerConfig {
245250 /// The URL endpoint for HTTP-based MCP servers
246251 pub url : String ,
247252 /// HTTP headers to include when communicating with HTTP-based MCP servers
@@ -251,6 +256,12 @@ pub struct StreamableHTTPMcpServerConfig {
251256 #[ serde( alias = "timeout" ) ]
252257 #[ serde( default = "default_timeout" ) ]
253258 pub timeout_ms : u64 ,
259+ /// OAuth scopes required for authentication with the remote MCP server
260+ #[ serde( default ) ]
261+ pub oauth_scopes : Vec < String > ,
262+ /// OAuth configuration for this server
263+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
264+ pub oauth : Option < OAuthConfig > ,
254265}
255266
256267pub fn default_timeout ( ) -> u64 {
@@ -392,4 +403,94 @@ mod tests {
392403
393404 let _: AgentConfig = serde_json:: from_value ( agent) . unwrap ( ) ;
394405 }
406+
407+ #[ test]
408+ fn test_mcp_server_config_http_deser ( ) {
409+ // Test HTTP server without oauth scopes
410+ let config = serde_json:: json!( {
411+ "type" : "http" ,
412+ "url" : "https://mcp.api.coingecko.com/sse"
413+ } ) ;
414+ let result: McpServerConfig = serde_json:: from_value ( config) . unwrap ( ) ;
415+ match result {
416+ McpServerConfig :: Remote ( remote) => {
417+ assert_eq ! ( remote. url, "https://mcp.api.coingecko.com/sse" ) ;
418+ assert ! ( remote. oauth_scopes. is_empty( ) ) ;
419+ }
420+ _ => panic ! ( "Expected Remote variant" ) ,
421+ }
422+
423+ // Test HTTP server with oauth scopes
424+ let config = serde_json:: json!( {
425+ "type" : "http" ,
426+ "url" : "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp" ,
427+ "oauthScopes" : [ "mcp" , "profile" , "email" ]
428+ } ) ;
429+ let result: McpServerConfig = serde_json:: from_value ( config) . unwrap ( ) ;
430+ match result {
431+ McpServerConfig :: Remote ( remote) => {
432+ assert_eq ! ( remote. url, "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp" ) ;
433+ assert_eq ! ( remote. oauth_scopes, vec![ "mcp" , "profile" , "email" ] ) ;
434+ }
435+ _ => panic ! ( "Expected Remote variant" ) ,
436+ }
437+
438+ // Test HTTP server with empty oauth scopes
439+ let config = serde_json:: json!( {
440+ "type" : "http" ,
441+ "url" : "https://example-server.modelcontextprotocol.io/mcp" ,
442+ "oauthScopes" : [ ]
443+ } ) ;
444+ let result: McpServerConfig = serde_json:: from_value ( config) . unwrap ( ) ;
445+ match result {
446+ McpServerConfig :: Remote ( remote) => {
447+ assert_eq ! ( remote. url, "https://example-server.modelcontextprotocol.io/mcp" ) ;
448+ assert ! ( remote. oauth_scopes. is_empty( ) ) ;
449+ }
450+ _ => panic ! ( "Expected Remote variant" ) ,
451+ }
452+ }
453+
454+ #[ test]
455+ fn test_mcp_server_config_stdio_deser ( ) {
456+ let config = serde_json:: json!( {
457+ "type" : "stdio" ,
458+ "command" : "node" ,
459+ "args" : [ "server.js" ]
460+ } ) ;
461+ let result: McpServerConfig = serde_json:: from_value ( config) . unwrap ( ) ;
462+ match result {
463+ McpServerConfig :: Local ( local) => {
464+ assert_eq ! ( local. command, "node" ) ;
465+ assert_eq ! ( local. args, vec![ "server.js" ] ) ;
466+ }
467+ _ => panic ! ( "Expected Local variant" ) ,
468+ }
469+ }
470+
471+ #[ test]
472+ fn test_mcp_servers_map_deser ( ) {
473+ let servers = serde_json:: json!( {
474+ "coin-gecko" : {
475+ "type" : "http" ,
476+ "url" : "https://mcp.api.coingecko.com/sse"
477+ } ,
478+ "datadog" : {
479+ "type" : "http" ,
480+ "url" : "https://mcp.datadoghq.com/api/unstable/mcp-server/mcp" ,
481+ "oauthScopes" : [ "mcp" , "profile" , "email" ]
482+ } ,
483+ "local-server" : {
484+ "type" : "stdio" ,
485+ "command" : "npx" ,
486+ "args" : [ "-y" , "@modelcontextprotocol/server-filesystem" , "/tmp" ]
487+ }
488+ } ) ;
489+
490+ let result: HashMap < String , McpServerConfig > = serde_json:: from_value ( servers) . unwrap ( ) ;
491+ assert_eq ! ( result. len( ) , 3 ) ;
492+ assert ! ( result. contains_key( "coin-gecko" ) ) ;
493+ assert ! ( result. contains_key( "datadog" ) ) ;
494+ assert ! ( result. contains_key( "local-server" ) ) ;
495+ }
395496}
0 commit comments