Home

AI Agent Server

Internal automation platform for AI agents; gives access to our data and tech stack

Problem

Off-the-shelf agent platforms (like Notion background agents) couldn't connect to key Patina tools (esp. Xero) so AI agents couldn't actually do the work we needed. Meanwhile, building and iterating on these integrations in no-code tools (e.g. Make.com) was also slow and cumbersome.

Solution

I built a code-first MCP server that exposes Patina-specific capabilities and tools to AI Agents in our Notion workspace. It runs on Vercel with a small admin UI to manage integrations (including OAuth for Gmail, Xero etc.), and enables agents to work with unstructured data to complete real actions like drafting invoices in Xero.

My Role

Owner (Patina Photo), Full-Stack Developer, AI Workflow Development, Systems / Integrations


Tech Stack


Engineering

A few constraints and decisions that shaped the system's security posture and reliability while staying lightweight for an internal beta.

Constraint

Decision

Trade-off

This internal-first tool needed to ship quickly, without spending time on a proper user auth layer.

Admin UI auth was kept as a simple env-var password check, with the expectation that a proper auth layer could be added later if the audience expands.

Not suitable for productisation; expanding beyond an internal beta likely requires a real auth system or a rebuild of the admin surface.

Each provider SDK throws different error shapes and details, making consistent handling and display difficult.

Normalised provider errors into a consistent AppError shape across the app while still capturing the raw/unserialised error for debugging.

The normalised shape is intentionally lossy; deeper debugging may require digging into the captured raw error details.


Architecture

Webhook request flow and provider client boundary: request preflow (auth + Zod), flow orchestration, capability calls, provider client wrapper, and consistent error shaping into public HTTP responses.
Endpoint request flow: request boundary (auth + Zod) → flow orchestration → capability → provider client wrapper (SDK boundary + error normalization) → consistent public HTTP responses.

Deep Dive: Top use cases (AI-in-the-loop automations)

Unlocking new types of automation

Most automation tools work best when the input is clean and structured (a form submission, a fixed payload, a strict database schema). Patina's real workflows aren't like that.

We use Notion extensively, and love how it accommodates our human messiness. But this makes automation difficult and brittle. This custom MCP server enables an agent to read unstructured inputs, make human-like decisions, and then use strongly-scoped tools to take action.

Use case 1: Draft invoices from job notes

We @-comment the agent in Notion on a photo job. It turns unstructured notes into a draft invoice in Xero, then hands it back for review before anything is sent.

  • Read unstructured job notes (start/end, extensions, additional costs)

  • Get the contact from the job and search Xero contacts (create if missing)

  • Search Xero items/products for the best match

  • Create a draft sales invoice in Xero

  • Notify us in Notion with a link to review the invoice

Use case 2: Inbox management + drafted replies

New emails are decision-heavy. The agent triages, pulls relevant context, drafts a reply, and then waits for a human approval step before sending.

  • Watch inbound emails and categorize based on criteria

  • If it's a client email that needs more than a canned reply, gather recent messages for context

  • Draft a reply using our email templates/knowledge base

  • Create a Notion task with the draft reply for review

  • After a human `@`-comments to approve, send the email and update the knowledge base

Use case 3: Dropbox project folders + archive automation

Folder setup and archiving is easy to automate, but it still requires small judgment calls. The agent uses unstructured Notion job data to create the right folder, in the right place, with the right name—then archives it after delivery.

  • Date-based trigger creates a Dropbox project folder ready for photo data

  • Choose the correct parent folder and naming based on unstructured Notion job data

  • After job completion, archive the folder into long-term storage


More Projects