Integrate
Artificial intelligence

Google GenAI

Use MCP tools with Google's Generative AI SDK

The Integrate SDK provides seamless integration with Google's Generative AI SDK (Gemini), allowing AI models to access your integrations through MCP tools.

Installation

Install the Integrate SDK and Google GenAI packages:

bun add integrate-sdk @google/generative-ai

Setup

Setting up Google GenAI integration requires 3 files (or 5 files if not using a database):

1. Server Configuration

Create a server configuration file with your OAuth credentials:

// lib/integrate.ts
import { createMCPServer, githubIntegration } from "integrate-sdk/server";

export const { client: serverClient } = createMCPServer({
  apiKey: process.env.INTEGRATE_API_KEY,
  integrations: [
    githubIntegration({
      scopes: ["repo", "user"],
    }),
  ],
});

2. Providers (only if no database)

If you're not using a database, create a providers component that injects tokens:

// app/providers.tsx
"use client";

import { client } from "integrate-sdk";
import { useIntegrateAI } from "integrate-sdk/react";

export function Providers({ children }: { children: React.ReactNode }) {
  useIntegrateAI(client);
  return <>{children}</>;
}

3. Layout (only if no database)

Wrap your app with the Providers component:

// app/layout.tsx
import { Providers } from "./providers";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

4. OAuth Handler

Create a catch-all route that handles OAuth operations at app/api/integrate/[...all]/route.ts:

import { serverClient } from "@/lib/integrate";
import { toNextJsHandler } from "integrate-sdk/server";

export const { POST, GET } = toNextJsHandler(serverClient);

5. Chat API Route

Create a chat route that converts MCP tools to Google GenAI format:

// app/api/chat/route.ts
import { serverClient } from "@/lib/integrate";
import { GoogleGenAI } from "@google/genai";
import {
  executeGoogleFunctionCalls,
  getGoogleTools,
} from "integrate-sdk/server";

const ai = new GoogleGenAI({
  apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
});

export async function POST(req: Request) {
  const { messages } = await req.json();

  const response = await ai.models.generateContent({
    model: "gemini-2.0-flash-001",
    contents: messages,
    config: {
      tools: [{ functionDeclarations: await getGoogleTools(serverClient) }],
    },
  });

  if (response.functionCalls && response.functionCalls.length > 0) {
    const results = await executeGoogleFunctionCalls(
      serverClient,
      response.functionCalls
    );
    return Response.json(results);
  }

  return Response.json(response);
}

Usage

Use Google GenAI in any component:

// app/page.tsx
"use client";

import { useState, useEffect } from "react";
import { client } from "integrate-sdk";
import { Streamdown } from "streamdown";

interface Message {
  role: "user" | "assistant";
  content: string;
}

export default function ChatPage() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);
  const [githubAuthorized, setGithubAuthorized] = useState(false);

  const isLoading = loading;

  useEffect(() => {
    client.isAuthorized("github").then(setGithubAuthorized);
  }, []);

  async function handleGithubClick() {
    try {
      if (githubAuthorized) {
        await client.disconnectProvider("github");
      } else {
        await client.authorize("github");
      }
      setGithubAuthorized(client.isAuthorized("github"));
    } catch (error) {
      console.error("Error:", error);
    }
  }

  function formatFunctionOutput(output: unknown): string {
    try {
      const data = typeof output === "string" ? JSON.parse(output) : output;

      if (data.structuredContent?.repositories) {
        let displayText = "\n\n📦 **GitHub Repositories:**\n\n";
        for (const repo of data.structuredContent.repositories) {
          displayText += `**${repo.name}**\n`;
          displayText += `${repo.html_url}\n`;
          if (repo.description) {
            displayText += `_${repo.description}_\n`;
          }
          displayText += `Language: ${repo.language || "N/A"} • Stars: ${
            repo.stargazers_count
          } • ${repo.private ? "Private" : "Public"}\n\n`;
        }
        return displayText;
      }

      if (data.content && Array.isArray(data.content)) {
        let text = "";
        for (const item of data.content) {
          if (item.type === "text" && item.text) {
            try {
              const parsed = JSON.parse(item.text);
              if (parsed.repositories) {
                let repoText = "\n\n📦 **GitHub Repositories:**\n\n";
                for (const repo of parsed.repositories) {
                  repoText += `**${repo.name}**\n`;
                  repoText += `${repo.html_url}\n`;
                  if (repo.description) {
                    repoText += `_${repo.description}_\n`;
                  }
                  repoText += `Language: ${repo.language || "N/A"} • Stars: ${
                    repo.stargazers_count
                  } • ${repo.private ? "Private" : "Public"}\n\n`;
                }
                text += repoText;
              } else {
                text += JSON.stringify(parsed, null, 2);
              }
            } catch {
              text += item.text;
            }
          }
        }
        return text;
      }

      return JSON.stringify(data, null, 2);
    } catch (e) {
      console.error("Error formatting function output:", e);
      return String(output);
    }
  }

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    if (!input.trim()) return;

    const userMessage: Message = { role: "user", content: input };
    setMessages((prev) => [...prev, userMessage]);
    setInput("");
    setLoading(true);

    try {
      const googleMessages = [...messages, userMessage].map((msg) => ({
        role: msg.role === "assistant" ? "model" : "user",
        parts: [{ text: msg.content }],
      }));

      const response = await fetch("/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          messages: googleMessages,
        }),
      });

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const result = await response.json();

      let assistantContent = "";

      if (result.structuredContent) {
        assistantContent = formatFunctionOutput(result);
      } else if (result.text) {
        assistantContent = result.text;
      } else if (result.candidates && result.candidates[0]?.content) {
        const content = result.candidates[0].content;
        if (content.parts) {
          for (const part of content.parts) {
            if (part.text) {
              assistantContent += part.text;
            }
          }
        }
      } else if (Array.isArray(result)) {
        for (const item of result) {
          if (typeof item === "string") {
            try {
              const parsed = JSON.parse(item);
              if (parsed.structuredContent) {
                assistantContent += formatFunctionOutput(parsed);
              } else {
                assistantContent += item;
              }
            } catch {
              assistantContent += item;
            }
          } else if (item.structuredContent) {
            assistantContent += formatFunctionOutput(item);
          } else if (item.type === "function_call_output") {
            assistantContent += formatFunctionOutput(item.output);
          } else if (item.content && Array.isArray(item.content)) {
            for (const contentItem of item.content) {
              if (contentItem.text) {
                assistantContent += formatFunctionOutput(contentItem.text);
              }
            }
          }
        }
      }

      if (!assistantContent.trim()) {
        assistantContent =
          "Response received but no displayable content found.";
      }

      setMessages((prev) => [
        ...prev,
        { role: "assistant", content: assistantContent.trim() },
      ]);
    } catch (error) {
      console.error("Error:", error);
      setMessages((prev) => [
        ...prev,
        {
          role: "assistant",
          content: `Error: ${
            error instanceof Error ? error.message : "Unknown error"
          }`,
        },
      ]);
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="flex flex-col h-screen">
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.map((m, i) => (
          <div
            key={i}
            className={m.role === "user" ? "text-right" : "text-left"}
          >
            <Streamdown
              isAnimating={
                isLoading && m.role === "assistant" && i === messages.length - 1
              }
              className="inline-block max-w-2xl whitespace-pre-wrap"
            >
              {m.content}
            </Streamdown>
          </div>
        ))}
      </div>
      <form
        onSubmit={handleSubmit}
        className="p-4 border-t border-gray-500 space-y-2"
      >
        <input
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Ask AI anything..."
          className="w-full px-4 py-2 border border-gray-500 rounded-lg"
          disabled={loading}
        />
        <div className="flex justify-between">
          <button
            type="button"
            onClick={handleGithubClick}
            className="px-4 py-2 bg-black text-white rounded-lg disabled:opacity-50"
          >
            {githubAuthorized ? "Disconnect" : "Connect"} GitHub
          </button>
          <button
            type="submit"
            disabled={loading || !input.trim()}
            className="px-4 py-2 bg-black text-white rounded-lg disabled:opacity-50"
          >
            Send
          </button>
        </div>
      </form>
    </div>
  );
}

Next Steps