Back to Blog

How ChecklistSquad Ships an AI Assistant With Agenetix

A technical walkthrough of how ChecklistSquad embeds an authenticated AI agent directly in its product using Agenetix's Host, Gateway, and Agent SDK.

Written byRoss Slaney

Founder, Agenetix

ChecklistSquad is a SaaS checklist tool. It has an API, a web app, and now an AI assistant that can create checklists, manage items, and answer questions about your data — all while authenticated as the current user.

This post walks through how we built it. Not the marketing version. The actual architecture, the OAuth flow, the code, and the decisions behind them.

If you're building a SaaS product and want to give your users an AI agent that works inside your app — not as a separate chatbot, but as a native surface that calls your API on behalf of the signed-in user — this is the reference implementation.

The architecture#

Here's the system as a sequence diagram. Every arrow is a real HTTP call.

MERMAID
sequenceDiagram
    box rgb(245,245,245) User / Client Zone
        participant U as User
        participant C as MCP Client (Agent, Embed, External - vscode,claude, cgpt)
    end
 
    box rgb(232,244,255) Agenetix Platform Zone
        participant E as Agenetix Gateway
        participant W as Hosted App Runtime
    end
 
    box rgb(245,250,235) Customer App Zone
        participant T as ChecklistSquad Auth Server
        participant A as ChecklistSquad API
    end
 
    U->>C: Click "Connect ChecklistSquad"
    C->>E: OAuth authorize for MCP server
    E->>T: Redirect to ChecklistSquad login
    T->>E: OAuth callback with code
    E->>T: Exchange code for ChecklistSquad grant
    E->>E: Store ChecklistSquad grant server-side
    E->>C: Return Agenetix MCP token
 
    U->>C: "List my checklists"
    C->>E: MCP tool call with Agenetix MCP token
    E->>E: Validate MCP token and load ChecklistSquad grant
    E->>W: Internal execution request
    W->>A: Call ChecklistSquad API with ChecklistSquad access token
    A->>W: Return ChecklistSquad data
    W->>E: Return tool result
    E->>C: MCP response

Three zones. The user never touches the ChecklistSquad API directly. The agent SDK talks to the Agenetix Gateway, which handles token storage and forwards calls to the Hosted App Runtime, which calls the real API with the user's actual access token.

This matters because the MCP client (whether it's the embedded agent in the web app, Claude Desktop, or Cursor) never sees the user's ChecklistSquad credentials. The Gateway holds those server-side.

Step 1: Import the OpenAPI spec#

ChecklistSquad has a standard OpenAPI spec. Agenetix's CLI reads it and generates an MCP server with one tool per endpoint.

BASH
agenetix deploy --spec openapi.yaml

That command does three things:

  1. Parses the OpenAPI spec and extracts every route
  2. Generates MCP tool definitions (name, description, parameters, return type) from the spec
  3. Deploys a hosted runtime that knows how to call each endpoint

The output is a live MCP server URL. Any MCP-compatible client can connect to it.

What you get isn't a wrapper around your API. It's a runtime that understands your API's shape — parameter types, required fields, response schemas — and exposes those as MCP tools that an LLM can invoke with structured arguments.

Step 2: The OAuth dance#

This is where most "AI integrations" fall apart. They use a shared API key or a service account. Every agent call looks the same on the server side. You can't attribute actions to users, enforce per-user permissions, or audit who did what.

Agenetix takes a different approach. The Gateway runs a full OAuth 2.1 flow against the customer's authorization server. Here's what that looks like concretely.

When a user opens the agent panel in ChecklistSquad, the app binds the SDK to the signed-in user session:

TYPESCRIPT
import { useAppAgent } from "@emcy/agent-sdk/react";
 
const agent = useAppAgent({
  apiKey: process.env.NEXT_PUBLIC_AGENT_API_KEY!,
  agentId,
  serviceUrl: process.env.NEXT_PUBLIC_AGENT_SERVICE_URL!,
  appSessionKey: authSessionKey,
  userIdentity: {
    subject: session.user.id,
    email: session.user.email,
    displayName: session.user.name,
    organizationId: session.user.organizationId,
  },
  clientTools,
});

The appSessionKey and userIdentity config tell the SDK which signed-in host user the MCP connection belongs to. When the user first connects, they see the same consent screen any third-party app would show. They approve, and the Gateway stores the resulting token server-side.

From that point on, every MCP tool call carries the user's identity. The ChecklistSquad API sees jane@acme.com making a request, not agenetix-bot.

Why this matters#

Consider a checklist management app where users have different roles. A viewer shouldn't be able to delete checklists. A team admin shouldn't be able to access other teams' data. With per-user OAuth, your existing authorization logic handles all of this. The agent doesn't bypass anything.

Step 3: Host the MCP server#

The generated MCP server runs on Agenetix Cloud as a managed Azure Container App. You don't provision infrastructure, manage TLS certificates, or configure auto-scaling.

But the hosting isn't the interesting part. What matters is what the runtime does at execution time.

When the Gateway receives an MCP tool call, it:

  1. Validates the Agenetix MCP token
  2. Loads the user's stored ChecklistSquad grant
  3. Forwards the request to the Hosted App Runtime
  4. The runtime calls the ChecklistSquad API using the user's access token
  5. Returns the result through the Gateway back to the client

The ChecklistSquad API has no idea it's talking to an AI agent. It receives a normal authenticated HTTP request with a valid bearer token. The response flows back through the same path.

PLAINTEXT
User → Agent SDK → Gateway → Runtime → ChecklistSquad API

User ← Agent SDK ← Gateway ← Runtime ← ChecklistSquad API

If the user's token expires, the Gateway handles refresh transparently. If the user revokes access, the next tool call fails with a 401 and the agent prompts for re-authorization.

Step 4: Embed the agent in the product#

ChecklistSquad uses @emcy/agent-sdk to power a fully custom in-product agent. The SDK owns the runtime, conversation, resume/recovery, approvals, input requests, and connection state. ChecklistSquad owns the visual shell.

Here's the core of how tool calls work in the embedded agent. The clientTools map lets you define tools that execute on the client side — things like navigating to a checklist, refreshing the UI after a mutation, or showing a preview:

TYPESCRIPT
import type { ClientToolsMap } from "@emcy/agent-sdk/app";
 
const clientTools: ClientToolsMap = {
  navigate_to_checklist: {
    description: "Navigate to a specific checklist in the app",
    parameters: {
      type: "object",
      properties: {
        checklistId: { type: "string" },
      },
      required: ["checklistId"],
    },
    execute: async ({ checklistId }) => {
      router.push(`/checklists/${checklistId}`);
      return { success: true };
    },
  },
  refresh_dashboard: {
    description: "Refresh the dashboard data after a mutation",
    parameters: { type: "object", properties: {} },
    execute: async () => {
      await mutate(); // SWR revalidation
      return { success: true };
    },
  },
};

Client tools are how you close the loop between the agent and the UI. When the agent creates a new checklist via an MCP tool call, it can then call refresh_dashboard to update the UI immediately, and navigate_to_checklist to take the user to the new checklist. The user sees the result before the agent finishes its response. The same bridge also powers client-side approvals, structured input requests, highlights, and visual canvas rendering.

Sending messages programmatically#

The agent panel isn't the only entry point. ChecklistSquad lets users trigger agent actions from context menus, keyboard shortcuts, and inline buttons:

TYPESCRIPT
agent.composer.send(`Suggest improvements for checklist item ${itemId}`);
 
// From a context menu on a checklist item
function handleAiSuggest(itemId: string) {
  agent.composer.send(`Suggest improvements for checklist item ${itemId}`);
}

This means the agent isn't siloed in a chat panel. It's woven into the product surface. A user can right-click an item, hit "AI suggest," and the agent runs in context.

Step 5: Monitor usage#

Every MCP tool invocation is logged with:

  • Which tool was called
  • Which user made the call
  • Latency and status code
  • The full request/response payload (for debugging)

This isn't just observability. It's how you understand what users actually do with the agent. If create_checklist_item is called 10x more than search_checklists, that tells you something about the product.

You can also set per-user budgets. If jane@acme.com has a $5/day limit, the agent stops making calls when she hits it. The budget applies at the Gateway level, so it works regardless of which client she's using.

What's running where#

To be explicit about the deployment:

ComponentRuns onPurpose
ChecklistSquad WebYour infrastructureThe SaaS product
ChecklistSquad APIYour infrastructureYour REST API
ChecklistSquad AuthYour infrastructureYour OAuth server
MCP ServerAgenetix CloudGenerated from your OpenAPI spec
GatewayAgenetix EdgeOAuth token management, routing
Agent SDKUser's browserRenders the agent UI, calls Gateway

You keep your API, your auth server, and your web app. Agenetix adds the MCP layer, the Gateway, and the agent surface. Your API doesn't change. Your auth server doesn't change. You add a few lines of React code and an OpenAPI spec.

The security model#

A few things worth calling out:

The MCP client never sees the user's API token. The Gateway stores it server-side and attaches it to requests. If someone inspects the browser's network tab, they see Agenetix MCP tokens — not ChecklistSquad access tokens.

Token refresh is transparent. The Gateway handles OAuth refresh flows. The client doesn't know or care when a token is refreshed.

Revocation works. If a user revokes the agent's access in ChecklistSquad's settings, the stored grant becomes invalid. The next tool call fails, and the agent prompts for re-authorization.

Your existing authorization still applies. The API sees a normal authenticated request. Role-based access, team scoping, resource-level permissions — all of it works exactly as it does for your web app.

Try it#

ChecklistSquad is live at checklistsquad.com. Create an account, open the agent panel, and try asking it to create a checklist. The agent is using the architecture described in this post.

If you want to build the same thing for your product, start with the Agenetix docs or import your OpenAPI spec at agenetix.ai/wizard.

Tags