# Run Google ADK in your TypeScript SaaS app without a Python service

> Run a typed Google ADK agent behind your existing TypeScript API route, with Zod tool inputs and tenant-scoped access.
> By Dave · 2026-10-13
> Source: https://otf-kit.dev/blog/google-adk-typescript-saas-agent

Your app server is already TypeScript. The next agent feature should not force your team to operate a second Python service.

Google’s 13 October announcement changes that choice: its Agent Development Kit now covers TypeScript alongside Python, Go, Java, and Kotlin. The TypeScript ADK uses Zod schemas for tool inputs and runs in the Node.js ecosystem. A team can put an agent route in its existing service, then keep the same authentication, deployment, and observability boundaries around it.

## What the TypeScript ADK gives you

The TypeScript package lets you define an agent, model, instructions, and tools in code. Google’s example builds a `FunctionTool` from a Zod object schema, then passes that tool into an `LlmAgent`. The schema acts as the contract for generated arguments and gives the tool callback typed inputs.

That is useful when a SaaS feature needs an agent to read application state and call one or two business operations. It is not a reason to expose your whole database to the model. Your existing API still owns authentication, authorization, tenant scope, input validation, and the side effects.

![Dark team-management screen showing members and workspace controls](https://cdn.otf-kit.dev/img/dash/dark-teams.png)

*Optional SaaS app UI only; no Google ADK integration is shown.*

## Define one tenant-scoped tool

Begin with a function the app already needs. For example, a project assistant might look up a project summary for the signed-in workspace. Keep tenant identity out of model-generated arguments: derive it from the verified request session and pass it to your repository layer.

```ts
import { FunctionTool, LlmAgent } from '@google/adk';
import { z } from 'zod';

const lookupProject = new FunctionTool({
  name: 'lookup_project',
  description: 'Return a summary for a project in the current workspace.',
  parameters: z.object({
    projectId: z.string().min(1),
  }),
  execute: async ({ projectId }) => {
    return projects.findVisibleToCurrentWorkspace(projectId);
  },
});

const agent = new LlmAgent({
  name: 'project_helper',
  model: 'gemini-3.8-flash',
  instruction: 'Answer project questions using the available project tool.',
  tools: [lookupProject],
});
```

The data-access function in this example is intentionally application-owned. In a real route, bind the workspace to the authenticated user before invoking the agent; do not trust a model-supplied workspace ID. Add a timeout, return a small result, and handle a missing project as an ordinary not-found outcome. The tool should not perform a write just because the model asks it to.

## Put the runner behind the existing route

Create the ADK runner as part of your service setup, then invoke it from the route after authenticating the caller. A session ID should be scoped to the account and user, and the server should reject a session that belongs to a different tenant.

```ts
import { InMemorySessionService, Runner } from '@google/adk';

const appName = 'saas-assistant';
const sessionService = new InMemorySessionService();
const runner = new Runner({ appName, agent, sessionService });

async function runProjectQuestion(userId: string, sessionId: string, question: string) {
  const events = [];
  for await (const event of runner.runAsync({
    userId,
    sessionId,
    newMessage: { parts: [{ text: question }] },
  })) {
    if (event.content?.parts) {
      for (const part of event.content.parts) {
        if (part.text) events.push(part.text);
      }
    }
  }
  return events.join('');
}
```

This is the execution shape, not a complete production persistence setup. The in-memory session service is convenient for a local prototype and loses its state when the process restarts. Use a persistent session service that fits the ADK version and deployment you choose, and define how long conversation history is retained. If you scale the Node service across instances, test session reads and writes across instances before routing real users to it.

In your HTTP handler, parse a bounded request body, authenticate the user, load the selected session using server-side identifiers, and call `runProjectQuestion`. Return a JSON response or stream events through your framework’s supported response mechanism. Keep the runner’s errors separate from internal stack traces: log the request ID and failure class, and return a safe retry message to the browser.

## Keep the agent inside your security model

The agent is an application feature, not an authorization layer. Every tool callback should repeat the permissions that a normal route would enforce. A Zod schema validates the shape of arguments; it does not prove that the user owns the referenced record.

Prefer tools that read a narrow view or create a draft. Require explicit confirmation before sending a customer email, changing a subscription, or deleting data. Bound the agent’s run time and number of model calls, and handle cancellation and partial output. Treat uploaded documents and retrieved records as untrusted content that can contain misleading instructions.

Keep model credentials on the server. Apply your normal rate limits by user and workspace, and log the model, request ID, timing, tool names, and final status. Do not log full prompts, secrets, or entire tool outputs by default. Review what data each tool returns, especially when workspaces contain customer records.

## Choose deployment and interoperability deliberately

Google’s announcement describes MCP support across ADK languages and A2A communication between agents over HTTP. Those protocols can help when a TypeScript service later needs to call a separate tool server or work with an agent written in another language. You do not need to add a second runtime just to use the TypeScript ADK, and you do not need to adopt MCP or A2A for a single local function tool.

Keep the first route small: one task, one or two tools, one model, and one failure path. Test a missing session, a cross-tenant project ID, a tool timeout, a model error, and a user who cancels the request. Then observe latency, cost, and the share of questions that finish without a human correction. Expand only when those results show which capability is missing.\n\nBefore deploying, run your existing TypeScript checks against the ADK code and exercise the route with a fake model response or a test double. Verify that the application sends the intended fields to the agent, that the tool never escapes the signed-in tenant, and that the browser receives a stable response when the model or a tool fails. Keep the route’s response contract ordinary JSON or a documented stream so the rest of the SaaS app does not depend on a framework-specific agent UI. Put model selection and tool definitions in a server module, not in a React component. That keeps credentials and database access on the trusted side and lets the UI evolve independently.

![Dark task-management screen with work items and status indicators](https://cdn.otf-kit.dev/img/dash/dark-tasks.png)

*Generic app workflow UI only; this screen is not an ADK-provided feature.*

## Where a SaaS starter fits

The OTF SaaS Dashboard is an optional TypeScript app and API starting point with screens such as projects, teams, and data tables. It does not bundle ADK, Google model credentials, MCP, A2A, or Cloud Run setup. You would add the package, the route, the tool definitions, and the deployment configuration yourself.

If you need an app shell to adapt, see the [SaaS Dashboard template](https://otf-kit.dev/templates/saas-dashboard) and the [getting-started guide](https://otf-kit.dev/start). For a related session design, see [recovering OpenCode sessions after Durable Object eviction](https://otf-kit.dev/blog/cloudflare-opencode-durable-sessions).

## Sources

- [One Agent Development Kit, five languages](https://cloud.google.com/blog/topics/developers-practitioners/one-agent-development-kit-five-languages) (Google Cloud Blog, 13 October 2026).