diff --git a/navi/mcp/keystore.py b/navi/mcp/keystore.py index 74be9d0..780b134 100644 --- a/navi/mcp/keystore.py +++ b/navi/mcp/keystore.py @@ -113,6 +113,27 @@ self._cache[(user_id, server_name)] = (self._now() + self._ttl, key) return key + async def list_servers(self, user_id: str) -> dict[str, datetime]: + """{server_name: updated_at} — servers where the user has a key. + + McpManager.visible_servers() calls this through the resolver, so it + must exist here, not only on McpKeyStore. Delegates to the store via + the same async factory :meth:`resolve` uses; any failure resolves to + an empty dict — "no personal keys" — instead of an exception. + """ + try: + store = self._store_factory() + if inspect.isawaitable(store): # the pool behind the store is async + store = await store + return await store.list_servers(user_id) + except Exception: + log.warning( + "mcp user key list failed: user=%s — treating as no keys", + user_id, + exc_info=True, + ) + return {} + def invalidate(self, user_id: str, server_name: str | None = None) -> None: """Drop cached key(s); *server_name* None clears every server of the user.""" if server_name is None: