@@ -1429,6 +1429,219 @@ def test_existing_entry_gets_reconfigured_for_newly_added_clients(self, monkeypa
14291429 ]
14301430
14311431
1432+ class TestConfigureMcpServicesSubset :
1433+ """`--location <schema> --services a,b,...` configures exactly the named subset."""
1434+
1435+ def test_configures_only_the_requested_subset (self , monkeypatch ):
1436+ configured : list [tuple [str , str , str , dict ]] = []
1437+ saved_states : list [dict ] = []
1438+ _stub_location_base (monkeypatch , {** CLAUDE_STATE })
1439+ monkeypatch .setattr (
1440+ mcp ,
1441+ "list_mcp_services" ,
1442+ lambda workspace , token , parent : (
1443+ ["system.ai.github" , "system.ai.slack" , "system.ai.gmail" ],
1444+ None ,
1445+ ),
1446+ )
1447+ monkeypatch .setattr (
1448+ mcp ,
1449+ "configure_client_mcp_server" ,
1450+ lambda client , name , url , entry : configured .append ((client , name , url , entry )) or [],
1451+ )
1452+ monkeypatch .setattr (mcp , "save_state" , lambda state : saved_states .append (state .copy ()))
1453+
1454+ assert (
1455+ mcp .configure_mcp_command (
1456+ location = "system.ai" , services = {"system.ai.github" , "system.ai.gmail" }
1457+ )
1458+ == 0
1459+ )
1460+
1461+ # slack is dropped; only the two requested services are configured.
1462+ assert sorted (c [1 ] for c in configured ) == ["system-ai-github" , "system-ai-gmail" ]
1463+ assert sorted (s ["name" ] for s in saved_states [- 1 ]["mcp_servers" ]) == [
1464+ "system-ai-github" ,
1465+ "system-ai-gmail" ,
1466+ ]
1467+
1468+ def test_matches_bare_short_names (self , monkeypatch ):
1469+ configured : list [tuple [str , str , str , dict ]] = []
1470+ _stub_location_base (monkeypatch , {** CLAUDE_STATE })
1471+ monkeypatch .setattr (
1472+ mcp ,
1473+ "list_mcp_services" ,
1474+ lambda workspace , token , parent : (["system.ai.github" , "system.ai.slack" ], None ),
1475+ )
1476+ monkeypatch .setattr (
1477+ mcp ,
1478+ "configure_client_mcp_server" ,
1479+ lambda client , name , url , entry : configured .append ((client , name , url , entry )) or [],
1480+ )
1481+ monkeypatch .setattr (mcp , "save_state" , lambda state : None )
1482+
1483+ assert mcp .configure_mcp_command (location = "system.ai" , services = {"github" }) == 0
1484+
1485+ assert [c [1 ] for c in configured ] == ["system-ai-github" ]
1486+
1487+ def test_unknown_requested_service_warns_and_skips (self , monkeypatch ):
1488+ configured : list [tuple [str , str , str , dict ]] = []
1489+ warnings : list [str ] = []
1490+ _stub_location_base (monkeypatch , {** CLAUDE_STATE })
1491+ monkeypatch .setattr (
1492+ mcp ,
1493+ "list_mcp_services" ,
1494+ lambda workspace , token , parent : (["system.ai.github" ], None ),
1495+ )
1496+ monkeypatch .setattr (
1497+ mcp ,
1498+ "configure_client_mcp_server" ,
1499+ lambda client , name , url , entry : configured .append ((client , name , url , entry )) or [],
1500+ )
1501+ monkeypatch .setattr (mcp , "save_state" , lambda state : None )
1502+ monkeypatch .setattr (mcp , "print_warning" , lambda msg : warnings .append (msg ))
1503+
1504+ assert (
1505+ mcp .configure_mcp_command (
1506+ location = "system.ai" , services = {"system.ai.github" , "system.ai.ghost" }
1507+ )
1508+ == 0
1509+ )
1510+
1511+ # The known service is still configured; the unknown one is reported, not fatal.
1512+ assert [c [1 ] for c in configured ] == ["system-ai-github" ]
1513+ assert any ("system.ai.ghost" in w for w in warnings )
1514+
1515+ def test_empty_services_removes_everything (self , monkeypatch ):
1516+ existing = {
1517+ "name" : "system-ai-github" ,
1518+ "url" : f"{ WS } /ai-gateway/mcp-services/system.ai.github" ,
1519+ "auth" : "env:OAUTH_TOKEN" ,
1520+ "clients" : ["claude" ],
1521+ }
1522+ configured : list [tuple [str , str , str , dict ]] = []
1523+ removed : list [tuple [str , str ]] = []
1524+ saved_states : list [dict ] = []
1525+ _stub_location_base (monkeypatch , {** CLAUDE_STATE , "mcp_servers" : [existing ]})
1526+ monkeypatch .setattr (
1527+ mcp ,
1528+ "list_mcp_services" ,
1529+ lambda workspace , token , parent : (["system.ai.github" ], None ),
1530+ )
1531+ monkeypatch .setattr (
1532+ mcp ,
1533+ "configure_client_mcp_server" ,
1534+ lambda client , name , url , entry : configured .append ((client , name , url , entry )) or [],
1535+ )
1536+ monkeypatch .setattr (
1537+ mcp ,
1538+ "remove_client_mcp_server" ,
1539+ lambda client , name : removed .append ((client , name )) or [],
1540+ )
1541+ monkeypatch .setattr (mcp , "save_state" , lambda state : saved_states .append (state .copy ()))
1542+
1543+ assert mcp .configure_mcp_command (location = "system.ai" , services = set ()) == 0
1544+
1545+ assert configured == []
1546+ assert removed == [("claude" , "system-ai-github" )]
1547+ assert saved_states [- 1 ]["mcp_servers" ] == []
1548+
1549+ def test_adds_and_removes_to_match_new_selection (self , monkeypatch ):
1550+ # The live case teammates want mid-session: started with github+slack,
1551+ # then the user deselects slack and selects gmail.
1552+ github = {
1553+ "name" : "system-ai-github" ,
1554+ "url" : f"{ WS } /ai-gateway/mcp-services/system.ai.github" ,
1555+ "auth" : "env:OAUTH_TOKEN" ,
1556+ "clients" : ["claude" ],
1557+ }
1558+ slack = {
1559+ "name" : "system-ai-slack" ,
1560+ "url" : f"{ WS } /ai-gateway/mcp-services/system.ai.slack" ,
1561+ "auth" : "env:OAUTH_TOKEN" ,
1562+ "clients" : ["claude" ],
1563+ }
1564+ configured : list [tuple [str , str , str , dict ]] = []
1565+ removed : list [tuple [str , str ]] = []
1566+ saved_states : list [dict ] = []
1567+ _stub_location_base (monkeypatch , {** CLAUDE_STATE , "mcp_servers" : [github , slack ]})
1568+ monkeypatch .setattr (
1569+ mcp ,
1570+ "list_mcp_services" ,
1571+ lambda workspace , token , parent : (
1572+ ["system.ai.github" , "system.ai.slack" , "system.ai.gmail" ],
1573+ None ,
1574+ ),
1575+ )
1576+ monkeypatch .setattr (
1577+ mcp ,
1578+ "configure_client_mcp_server" ,
1579+ lambda client , name , url , entry : configured .append ((client , name , url , entry )) or [],
1580+ )
1581+ monkeypatch .setattr (
1582+ mcp ,
1583+ "remove_client_mcp_server" ,
1584+ lambda client , name : removed .append ((client , name )) or [],
1585+ )
1586+ monkeypatch .setattr (mcp , "save_state" , lambda state : saved_states .append (state .copy ()))
1587+
1588+ assert (
1589+ mcp .configure_mcp_command (
1590+ location = "system.ai" , services = {"system.ai.github" , "system.ai.gmail" }
1591+ )
1592+ == 0
1593+ )
1594+
1595+ # slack removed, gmail added, github untouched (entry unchanged).
1596+ assert removed == [("claude" , "system-ai-slack" )]
1597+ assert [c [1 ] for c in configured ] == ["system-ai-gmail" ]
1598+ assert sorted (s ["name" ] for s in saved_states [- 1 ]["mcp_servers" ]) == [
1599+ "system-ai-github" ,
1600+ "system-ai-gmail" ,
1601+ ]
1602+
1603+ def test_full_names_without_location_derive_the_schema (self , monkeypatch ):
1604+ configured : list [tuple [str , str , str , dict ]] = []
1605+ seen : dict [str , str ] = {}
1606+ _stub_location_base (monkeypatch , {** CLAUDE_STATE })
1607+
1608+ def fake_list (workspace , token , parent ):
1609+ seen ["parent" ] = parent
1610+ return ["system.ai.github" , "system.ai.slack" ], None
1611+
1612+ monkeypatch .setattr (mcp , "list_mcp_services" , fake_list )
1613+ monkeypatch .setattr (
1614+ mcp ,
1615+ "configure_client_mcp_server" ,
1616+ lambda client , name , url , entry : configured .append ((client , name , url , entry )) or [],
1617+ )
1618+ monkeypatch .setattr (mcp , "save_state" , lambda state : None )
1619+
1620+ # No --location: the `<catalog>.<schema>` is derived from the full names.
1621+ assert mcp .configure_mcp_command (services = {"system.ai.github" , "system.ai.slack" }) == 0
1622+
1623+ assert seen == {"parent" : "system.ai" }
1624+ assert sorted (c [1 ] for c in configured ) == ["system-ai-github" , "system-ai-slack" ]
1625+
1626+ def test_short_name_without_location_raises (self ):
1627+ try :
1628+ mcp .configure_mcp_command (services = {"github" })
1629+ except RuntimeError as exc :
1630+ assert "--location" in str (exc )
1631+ else :
1632+ raise AssertionError ("expected RuntimeError for a bare short name without --location" )
1633+
1634+ def test_full_names_spanning_multiple_schemas_without_location_raises (self ):
1635+ try :
1636+ mcp .configure_mcp_command (services = {"system.ai.github" , "other.cat.thing" })
1637+ except RuntimeError as exc :
1638+ assert "--location" in str (exc )
1639+ else :
1640+ raise AssertionError (
1641+ "expected RuntimeError for multi-schema services without --location"
1642+ )
1643+
1644+
14321645class TestRevertMcpConfigs :
14331646 def test_removes_cli_registered_servers_and_restores_copilot_config (self , monkeypatch ):
14341647 removed : list [tuple [str , str ]] = []
0 commit comments