Artificial intelligence
Anthropic Claude
Use MCP tools with Anthropic's Claude API
The Integrate SDK provides seamless integration with Anthropic's Claude API, allowing Claude to access your integrations through MCP tools.
Installation
Install the Integrate SDK and Anthropic SDK packages:
bun add integrate-sdk @anthropic-ai/sdkSetup
Setting up Anthropic 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 Anthropic format:
// app/api/chat/route.ts
import { serverClient } from "@/lib/integrate";
import { getAnthropicTools } from "integrate-sdk/server";
import Anthropic from "@anthropic-ai/sdk";
import { handleAnthropicMessage } from "integrate-sdk/ai/anthropic";
const anthropic = new Anthropic();
export async function POST(req: Request) {
const { messages } = await req.json();
const msg = await anthropic.messages.create({
model: "claude-sonnet-4-5",
messages,
tools: await getAnthropicTools(serverClient),
max_tokens: 1000,
});
const result = await handleAnthropicMessage(serverClient, msg);
return Response.json(result);
}Usage
Use Anthropic's Claude in any component:
// app/page.tsx
"use client";
import { useState, useEffect } from "react";
import { client } from "integrate-sdk";
import Anthropic from "@anthropic-ai/sdk";
import { Streamdown } from "streamdown";
export default function ChatPage() {
const [messages, setMessages] = useState<Anthropic.MessageParam[]>([]);
const [input, setInput] = useState("");
const [loading, setLoading] = useState(false);
const [githubAuthorized, setGithubAuthorized] = useState(false);
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);
}
}
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!input.trim()) return;
const userMessage: Anthropic.MessageParam = {
role: "user",
content: input,
};
setMessages((prev) => [...prev, userMessage]);
setInput("");
setLoading(true);
try {
const response = await fetch("/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
messages: [...messages, userMessage],
}),
});
const data = await response.json();
const newMessages = Array.isArray(data) ? data : [data];
setMessages((prev) => [
...prev,
...newMessages.map((msg: Anthropic.MessageParam) => ({
role: msg.role,
content: msg.content,
})),
]);
} catch (error) {
console.error("Error:", error);
} finally {
setLoading(false);
}
}
const renderContent = (
content: Anthropic.MessageParam["content"],
isAssistant: boolean
) => {
if (typeof content === "string") {
if (isAssistant) {
return <Streamdown isAnimating={loading}>{content}</Streamdown>;
}
return <div className="whitespace-pre-wrap">{content}</div>;
}
if (Array.isArray(content)) {
return (
<div className="space-y-2">
{content.map((block, index) => {
if (block.type === "text") {
if (isAssistant) {
return (
<Streamdown key={index} isAnimating={loading}>
{block.text}
</Streamdown>
);
}
return (
<div key={index} className="whitespace-pre-wrap">
{block.text}
</div>
);
}
if (block.type === "tool_use") {
return null;
}
if (block.type === "tool_result") {
let displayContent = block.content;
if (typeof block.content === "string") {
try {
const parsed = JSON.parse(block.content);
if (parsed.content && Array.isArray(parsed.content)) {
return (
<div key={index} className="space-y-2">
{parsed.content.map(
(
innerBlock: { type: string; text: string },
innerIndex: number
) => {
if (innerBlock.type === "text") {
return (
<div
key={innerIndex}
className="whitespace-pre-wrap"
>
{innerBlock.text}
</div>
);
}
return null;
}
)}
</div>
);
}
displayContent = JSON.stringify(parsed, null, 2);
} catch {}
}
return (
<div key={index} className="whitespace-pre-wrap">
{typeof displayContent === "string"
? displayContent
: JSON.stringify(displayContent)}
</div>
);
}
return null;
})}
</div>
);
}
return JSON.stringify(content);
};
return (
<div className="flex flex-col h-screen">
<div className="flex-1 overflow-y-auto p-4 space-y-4">
{messages.map((m, i) => {
let hasRenderableContent = false;
let isToolResult = false;
if (typeof m.content === "string") hasRenderableContent = true;
else if (Array.isArray(m.content)) {
hasRenderableContent = m.content.some(
(block) => block.type === "text" || block.type === "tool_result"
);
isToolResult = m.content.some(
(block) => block.type === "tool_result"
);
}
if (!hasRenderableContent) return null;
const alignmentClass =
m.role === "user" && !isToolResult ? "text-right" : "text-left";
return (
<div key={i} className={alignmentClass}>
<div className="inline-block max-w-2xl">
{renderContent(
m.content,
m.role === "assistant" || isToolResult
)}
</div>
</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 Claude 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
- Explore Advanced Configuration
- Check the API Reference for complete type definitions