Give Your MAUI Agent Hands — Contacts, Reminders & Location AI Tools
When we shipped Shiny.Health.Extensions.AI, it proved a simple point: a chat model becomes far more useful when it can do things instead of just talk about them. Give it a tool to read step counts and “how did I sleep last week?” stops being a guess and starts being an answer pulled from the device.
So we did the same for three more device services. Three new packages expose Shiny modules as Microsoft.Extensions.AI tool functions:
| Package | Description |
|---|---|
Shiny.Contacts.Extensions.AI |
search and manage the device address book |
Shiny.Notifications.Extensions.AI |
schedule and cancel reminders |
Shiny.Locations.Extensions.AI |
read-only GPS: where you are, and how far/long to somewhere else |
They all follow the same shape as Health: you opt-in to exactly which operations the model can see (a read/write allow-list you control on behalf of the agent — not an OS permission prompt), it’s read-only by default, and the whole thing is IsAotCompatible — schemas are hand-built and results come back as JsonNode, so there’s no reflection in the tool path.
Contacts
Section titled “Contacts”dotnet add package Shiny.Contacts.Extensions.AIbuilder.Services.AddContactStore();builder.Services.AddContactsAITools(tools => tools .AddContacts(ContactAICapabilities.ReadWrite) // Read is the default; ReadWrite adds create/update/delete);This generates search_contacts, get_contact, and — when you opt-in to Write — create_contact, update_contact, and delete_contact.
What you’d use it for: a personal-assistant or CRM-lite app where the user talks to the address book instead of tapping through it — “grab that number so I can text them,” “add the person on this business card,” “which of my contacts work at Acme?” The agent looks up an id with search_contacts, then acts on it.
Example questions:
- “What’s Jane Doe’s mobile number?”
- “Add a contact for my new dentist — Dr. Patel at Bright Smiles Dental, 555-0142.”
- “Change my brother Mike’s email to mike@newjob.com.”
- “Do I have anyone saved from Acme Corp?”
- “Delete the contact for my old landlord.” (destructive — tell the model to confirm first)
Reminders
Section titled “Reminders”dotnet add package Shiny.Notifications.Extensions.AIbuilder.Services.AddNotifications();builder.Services.AddNotificationAITools(tools => tools .AddReminders(ReminderAICapabilities.ReadWrite, channel: "reminders") // channel is optional);Local notifications, framed as reminders. You get list_reminders, and — with Write — create_reminder (fire now, at a specific date/time, or daily at a set time) and cancel_reminder.
What you’d use it for: any “remind me…” flow in a chat assistant — todo apps, habit and medication trackers, follow-up nudges. The user speaks a reminder in natural language and the model schedules a real local notification; scheduleFor and repeatDailyAt cover one-shot and recurring.
Example questions:
- “Remind me to call the plumber at 3pm today.”
- “Set a daily reminder to take my meds at 8:30am.”
- “What reminders do I have set?”
- “Cancel the one about the dentist.”
- “Nudge me to water the plants every evening at 6.”
Location
Section titled “Location”dotnet add package Shiny.Locations.Extensions.AIbuilder.Services.AddGps();builder.Services.AddLocationAITool(); // read-only — there's no write capability for GPSGPS is read-only, so there’s no builder to configure — a single AddLocationAITool() registers get_current_location, get_distance_to, and estimate_travel_time. The agent can learn where the user is and reason about distance and rough travel time to a destination.
What you’d use it for: travel and field-service apps, delivery/ETA estimates, “am I near…” checks, or any assistant that should be location-aware. One honest caveat baked into the tools: distances are great-circle (straight-line) and travel times use an assumed average speed — the results say so in a note field, so the model caveats its answer rather than pretending to be a routing engine.
Example questions:
- “Where am I right now?”
- “How far is it from here to 51.4700, -0.4543?”
- “Roughly how long to cycle to the park at 51.51, -0.12?”
- “Am I within 2 km of the office?”
- “What’s my current speed and heading?”
Compose them into one agent
Section titled “Compose them into one agent”Each package hands you a small bundle you resolve from DI. Concatenate their .Tools and pass the lot to any IChatClient:
var tools = new List<AITool>();tools.AddRange(sp.GetRequiredService<ContactAITools>().Tools);tools.AddRange(sp.GetRequiredService<NotificationAITools>().Tools);tools.AddRange(sp.GetRequiredService<LocationAITools>().Tools);
var response = await chatClient.GetResponseAsync( messages, new ChatOptions { Tools = tools });Now a single message can fan out across all three:
“I’m meeting Sarah at the office at 5 — how far am I, and remind me to leave in an hour.”
The model calls search_contacts for Sarah if it needs her details, get_distance_to for the office, and create_reminder for the nudge — one turn, three device services.
Permissions still belong to your app
Section titled “Permissions still belong to your app”The capability builders (AddContacts(...), AddReminders(...)) are an allow-list for the agent — they decide which operations the model can see. They are not an OS permission prompt. The underlying platform permission must already be granted: call IContactStore.RequestAccess, INotificationManager.RequestAccess, or start a GPS listener from your app before you invoke the agent. The tools assume access is in place and return a clean error object if it isn’t.
Get started
Section titled “Get started”dotnet add package Shiny.Contacts.Extensions.AIdotnet add package Shiny.Notifications.Extensions.AIdotnet add package Shiny.Locations.Extensions.AIFull details on each: Contacts AI Tools, Reminder AI Tools, and Location AI Tools. Same pattern as Health AI Tools — opt-in, read-only by default, AOT-safe, and now your agent has hands.
Update: your app’s own functions
Section titled “Update: your app’s own functions”Since this post, Shiny.AppFunctions.Extensions.AI joined the set. It turns the
functions your app exposes to Siri and Gemini through App Functions into tools with the same
bundle shape, so they go straight into the same list:
builder.Services.AddAppFunctions(); // source-generatedbuilder.Services.AddAppFunctionAITools(tools => tools.AddAllFunctions());
tools.AddRange(sp.GetRequiredService<AppFunctionAITools>().Tools);Now “remind me to follow up with Acme, and create an order for them” uses create_reminder, search_customer and
your own create_order in one turn. The calls go through the same handlers and delegates Siri and Gemini use.


