Build a voice-controlled dashboard note app using Mira Tool Calls, Simple Storage, and Dashboard API
Learn how to build a simple but practical app that lets users save a quick note to their dashboard using voice commands. This cookbook demonstrates three core MentraOS features working together.
{ "id": "save_note", "description": "Save a short note to display on the user's dashboard. The note will be visible when they look up at their glasses. Use this when the user wants to remember something quickly.", "parameters": { "note": { "type": "string", "description": "The note content to save. Keep it short for dashboard display.", "required": true } }}
Good tool description: Notice how the description tells Mira when to use this tool (“when the user wants to remember something quickly”) and what it does (“visible when they look up”). This helps Mira understand context.
{ "id": "read_note", "description": "Read the current note saved on the user's dashboard. Use this when the user asks what their note is or wants to check what they saved.", "parameters": {}}
{ "id": "clear_note", "description": "Clear and remove the note from the user's dashboard. Use this when the user wants to delete or remove their saved note.", "parameters": {}}
These tool definitions are configured in the Developer Console, not in your code. Mira uses these descriptions to decide when to call each tool.
import { AppServer, AppSession, ToolCall, GIVE_APP_CONTROL_OF_TOOL_RESPONSE } from '@mentra/sdk';class DashboardNoteApp extends AppServer { /** * Called when a user starts a session with your app * Load any saved note and display it on the dashboard */ protected async onSession( session: AppSession, sessionId: string, userId: string ): Promise<void> { session.logger.info(`Session started for user ${userId}`); // Load saved note from Simple Storage const savedNote = await session.simpleStorage.get('dashboard_note'); if (savedNote) { // Display it on the dashboard session.dashboard.content.writeToMain(savedNote); session.logger.info(`Loaded saved note: ${savedNote}`); } else { session.logger.info('No saved note found'); } } /** * Called when Mira triggers one of your tools * Handle save, read, and clear operations */ protected async onToolCall(toolCall: ToolCall): Promise<string | undefined> { const session = this.getSessionByUserId(toolCall.userId); if (!session) { return "Could not find your session"; } // Handle save_note tool if (toolCall.toolId === "save_note") { const note = toolCall.toolParameters.note as string; // Validate note length if (note.length > 100) { return "That note is too long for the dashboard. Please keep it under 100 characters."; } // Save to Simple Storage (persists across sessions) await session.simpleStorage.set('dashboard_note', note); // Display on dashboard (bottom-right, visible when user looks up) session.dashboard.content.writeToMain(note); session.logger.info(`Saved note: ${note}`); // Return context for Mira - she'll formulate a natural response return `Note saved successfully: "${note}"`; } // Handle read_note tool if (toolCall.toolId === "read_note") { const note = await session.simpleStorage.get('dashboard_note'); if (note) { session.logger.info(`Reading note: ${note}`); // Mira will speak this naturally to the user return `Your note says: "${note}"`; } else { return "You don't have a note saved on your dashboard"; } } // Handle clear_note tool if (toolCall.toolId === "clear_note") { // Delete from Simple Storage await session.simpleStorage.delete('dashboard_note'); // Clear from dashboard session.dashboard.content.writeToMain(''); session.logger.info('Note cleared'); return "Your note has been cleared from the dashboard"; } return undefined; }}// Start the serverconst server = new DashboardNoteApp({ packageName: 'your.package.name', // Replace with your package name from console apiKey: process.env.MENTRA_API_KEY!, port: 3000,});server.start();
protected async onToolCall(toolCall: ToolCall): Promise<string | undefined> { // Get the session for this user const session = this.getSessionByUserId(toolCall.userId); // Check which tool was called if (toolCall.toolId === "save_note") { // Get parameters Mira extracted const note = toolCall.toolParameters.note as string; // Do your logic... }}
What’s happening:
User says something like “Save a note saying pick up milk”
Mira recognizes this matches the save_note tool
Mira extracts parameters: { note: "pick up milk" }
Your onToolCall is triggered with the tool ID and parameters
You handle the logic (save to storage, update dashboard)
You return context for Mira to formulate a response
This is context for Mira, not what the user sees/hears. Mira uses this to formulate a natural response like:
“Got it, I’ve saved that note for you”
“Your note has been added to the dashboard”
“Done, I’ve saved that”
Taking control of the response:
Copy
Ask AI
// If you want to control the exact responsesession.audio.speak("Note saved!");session.layouts.showTextWall("Saved");return GIVE_APP_CONTROL_OF_TOOL_RESPONSE;
This tells Mira “I’ve handled the response myself, don’t say anything.”
When to use each approach
Let Mira respond (default - recommended):
Natural, conversational responses
User expects voice assistant behavior
Simple confirmations
Take control:
Need specific formatting
Want to show custom UI
Need to display data that doesn’t translate well to speech
Simple Storage provides localStorage-like API with cloud sync:
Copy
Ask AI
// Get a value (returns Promise<string | undefined>)const note = await session.simpleStorage.get('dashboard_note');// Set a value (returns Promise<void>)await session.simpleStorage.set('dashboard_note', 'Buy milk');// Delete a value (returns Promise<boolean>)await session.simpleStorage.delete('dashboard_note');// Check if key exists (returns Promise<boolean>)const hasNote = await session.simpleStorage.hasKey('dashboard_note');// Get all keys (returns Promise<string[]>)const keys = await session.simpleStorage.keys();// Clear all data (returns Promise<boolean>)await session.simpleStorage.clear();
Key features:
Per-user isolation - Each user has their own storage
Cloud sync - Data persists across devices and sessions
Local caching - Fast reads after initial fetch
String values - Store strings (use JSON.stringify/parse for objects)
The dashboard displays persistent UI in the bottom-right when user looks up:
Copy
Ask AI
// Write to main dashboard areasession.dashboard.content.writeToMain('Your note here');// Clear dashboardsession.dashboard.content.writeToMain('');// Write to expanded view (more detail)session.dashboard.content.writeToExpanded('Detailed info here');
Best practices:
Keep text short (dashboard space is limited)
Use for glanceable information
Update when data changes
Clear when no longer relevant
Dashboard updates are automatically throttled to 1 per 300ms by MentraOS Cloud to prevent display desync.
Problem: Mira doesn’t recognize your voice commandSolution: Improve tool descriptions
Copy
Ask AI
// Bad - too vague{ "description": "Save note"}// Good - clear context{ "description": "Save a short note to display on the user's dashboard. The note will be visible when they look up at their glasses. Use this when the user wants to remember something quickly."}
// Save with categoryawait session.simpleStorage.set('work_note', workNote);await session.simpleStorage.set('personal_note', personalNote);// Display on dashboardsession.dashboard.content.writeToMain( `Work: ${workNote}\nPersonal: ${personalNote}`);
Tool descriptions matter - They help Mira understand when to call your tools Simple Storage persists data - Perfect for user preferences and quick data Dashboard is glanceable - Great for persistent, at-a-glance information Tool responses are context - Mira uses them to formulate natural responses Load data in onSession - Always restore saved data when user opens app