Skip to content

Commit c9763a7

Browse files
committed
feat(virtual-desktop): add usePrivateState hook for localStorage persistence
Provides OpenAI-style privateContent workaround using localStorage with hostContext.toolInfo.id as the storage key. This allows UI state to persist across sessions without being sent to the model.
1 parent 4bc85b6 commit c9763a7

File tree

1 file changed

+44
-0
lines changed

1 file changed

+44
-0
lines changed

examples/virtual-desktop-server/src/mcp-app.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,50 @@ const log = {
5353
error: console.error.bind(console, "[VNC]"),
5454
};
5555

56+
/**
57+
* Private state persistence using localStorage + toolInfo.id.
58+
* This is a workaround for OpenAI-style privateContent - state that persists
59+
* across sessions but is NOT sent to the model.
60+
*/
61+
export function usePrivateState<T>(
62+
toolId: string | undefined,
63+
initialState: T,
64+
): [T, (state: T | ((prev: T) => T)) => void] {
65+
const storageKey = toolId ? `mcp-app-private:${toolId}` : null;
66+
67+
const [state, setState] = useState<T>(() => {
68+
if (!storageKey) return initialState;
69+
try {
70+
const stored = localStorage.getItem(storageKey);
71+
return stored ? JSON.parse(stored) : initialState;
72+
} catch {
73+
return initialState;
74+
}
75+
});
76+
77+
const setStateAndPersist = useCallback(
78+
(newState: T | ((prev: T) => T)) => {
79+
setState((prev) => {
80+
const next =
81+
typeof newState === "function"
82+
? (newState as (prev: T) => T)(prev)
83+
: newState;
84+
if (storageKey) {
85+
try {
86+
localStorage.setItem(storageKey, JSON.stringify(next));
87+
} catch (e) {
88+
log.warn("Failed to persist private state:", e);
89+
}
90+
}
91+
return next;
92+
});
93+
},
94+
[storageKey],
95+
);
96+
97+
return [state, setStateAndPersist];
98+
}
99+
56100
// noVNC loading state
57101
let RFBClass: new (
58102
target: HTMLElement,

0 commit comments

Comments
 (0)