diff --git a/examples/code/guides/tool-callling/get-tools-helper.py b/examples/code/guides/tool-callling/get-tools-helper.py new file mode 100644 index 00000000..a5426480 --- /dev/null +++ b/examples/code/guides/tool-callling/get-tools-helper.py @@ -0,0 +1,58 @@ +from typing import Optional +from arcadepy import NOT_GIVEN, AsyncArcade +from arcadepy.types import ToolDefinition + + +async def get_tool_definitions( + client: Optional[AsyncArcade] = None, + tools: Optional[list[str]] = None, + toolkits: Optional[list[str]] = None, + raise_on_empty: bool = True, + limit: Optional[int] = None, + offset: Optional[int] = None, +) -> list[ToolDefinition]: + """ + Retrieve tool definitions asynchronously from the Arcade client, accounting for pagination. + + Args: + tools: Optional list of specific tool names to include. + toolkits: Optional list of toolkit names to include all tools from. + raise_on_empty: Whether to raise an error if no tools or toolkits are provided. + limit: Optional limit on the number of tools to retrieve per request. + offset: Optional offset for paginated requests. + + Returns: + List of ToolDefinition instances. + + Raises: + ValueError: If no tools or toolkits are provided and raise_on_empty is True. + """ + if client is None: + client = AsyncArcade() + all_tools: list[ToolDefinition] = [] + + # If no specific tools or toolkits are requested, raise an error. + if not tools and not toolkits: + if raise_on_empty: + raise ValueError("No tools or toolkits provided to retrieve tool definitions.") + return [] + + # First, gather single tools if the user specifically requested them. + if tools: + for tool_id in tools: + # ToolsResource.get(...) returns a single ToolDefinition. + single_tool = await client.tools.get(name=tool_id) + all_tools.append(single_tool) + + # Next, gather tool definitions from any requested toolkits. + if toolkits: + for tk in toolkits: + paginated_tools = await client.tools.list( + toolkit=tk, + limit=NOT_GIVEN if limit is None else limit, + offset=NOT_GIVEN if offset is None else offset, + ) + async for tool in paginated_tools: + all_tools.append(tool) + + return all_tools diff --git a/examples/code/guides/tool-callling/get-tools-helper.ts b/examples/code/guides/tool-callling/get-tools-helper.ts new file mode 100644 index 00000000..799ccc26 --- /dev/null +++ b/examples/code/guides/tool-callling/get-tools-helper.ts @@ -0,0 +1,64 @@ +import Arcade from "@arcadeai/arcadejs"; + +export type ToolDefinition = Record; + +export type ArcadeClient = InstanceType; + +/** + * Retrieve tool definitions from the Arcade client, accounting for pagination. + * + * - If `tools` are provided, fetch each by name via `client.tools.get`. + * - If `toolkits` are provided, fetch lists via `client.tools.list` and collect `items`. + * - If neither `tools` nor `toolkits` are provided and `raiseOnEmpty` is true, throw an error. + */ +export async function getToolDefinitions( + client?: ArcadeClient, + tools?: string[], + toolkits?: string[], + raiseOnEmpty: boolean = true, + limit?: number, + offset?: number, +): Promise { + const arcade = client ?? new Arcade(); + const allTools: ToolDefinition[] = []; + + const noTools = !tools || tools.length === 0; + const noToolkits = !toolkits || toolkits.length === 0; + + if (noTools && noToolkits) { + if (raiseOnEmpty) { + throw new Error( + "No tools or toolkits provided to retrieve tool definitions.", + ); + } + return []; + } + + // Fetch specific tools by name + if (tools && tools.length > 0) { + const fetched = await Promise.all( + tools.map((toolName) => arcade.tools.get(toolName)), + ); + allTools.push(...fetched); + } + + // Fetch tools from provided toolkits (respecting optional pagination) + if (toolkits && toolkits.length > 0) { + for (const tk of toolkits) { + const response: any = await arcade.tools.list({ + toolkit: tk, + ...(limit !== undefined ? { limit } : {}), + ...(offset !== undefined ? { offset } : {}), + }); + + const items: ToolDefinition[] = Array.isArray(response?.items) + ? response.items + : []; + allTools.push(...items); + } + } + + return allTools; +} + +export default getToolDefinitions; diff --git a/examples/code/guides/tool-callling/google-adk/google-adk-arcade-init.py b/examples/code/guides/tool-callling/google-adk/google-adk-arcade-init.py new file mode 100644 index 00000000..138daa5d --- /dev/null +++ b/examples/code/guides/tool-callling/google-adk/google-adk-arcade-init.py @@ -0,0 +1,72 @@ +import asyncio +from arcadepy import AsyncArcade +from google.adk import Agent, Runner +from google.adk.artifacts import InMemoryArtifactService +from google.adk.sessions import InMemorySessionService, Session +from google.genai import types +from google_adk_arcade.tools import get_arcade_tools +from dotenv import load_dotenv + + +load_dotenv(override=True) + + +async def main(): + # initialize the Arcade client + client = AsyncArcade() + app_name = "Arcade Google ADK" + user_id = "mateo@arcade.dev" + session_service = InMemorySessionService() + artifact_service = InMemoryArtifactService() + + # This function returns a list of tools in the format expected by ADK + gmail_list_emails = await get_arcade_tools(client, tools=["Gmail_ListEmails"]) + + # We've updated the agent to access a single tool + agent = Agent( + model="gemini-2.0-flash", + name="simple_agent", + instruction="You are a helpful assistant that can help users with" + " everyday tasks. It is very important that you use" + " the provided tools to ensure the task is successfully" + " achieved", + tools=gmail_list_emails, # pass the tool to the agent + ) + session = await session_service.create_session( + app_name=app_name, user_id=user_id, state={ + "user_id": user_id, + } + ) + runner = Runner( + app_name=app_name, + agent=agent, + artifact_service=artifact_service, + session_service=session_service, + ) + + # This is a helper function to run the agent with a prompt. + async def run_prompt(session: Session, new_message: str): + content = types.Content( + role='user', parts=[types.Part.from_text(text=new_message)] + ) + async for event in runner.run_async( + user_id=user_id, + session_id=session.id, + new_message=content, + ): + if event.content.parts and event.content.parts[0].text: + print(f'** {event.author}: {event.content.parts[0].text}') + + # This is the main agent loop to prompt the user until they exit. + while True: + user_input = input("User: ") + if user_input.lower() == "exit": + print("Goodbye!") + break + await run_prompt(session, user_input) + + +if __name__ == "__main__": + asyncio.run(main()) + + diff --git a/examples/code/guides/tool-callling/google-adk/google-adk-arcade-more-tools.py b/examples/code/guides/tool-callling/google-adk/google-adk-arcade-more-tools.py new file mode 100644 index 00000000..8a6f6071 --- /dev/null +++ b/examples/code/guides/tool-callling/google-adk/google-adk-arcade-more-tools.py @@ -0,0 +1,72 @@ +import asyncio +from arcadepy import AsyncArcade +from google.adk import Agent, Runner +from google.adk.artifacts import InMemoryArtifactService +from google.adk.sessions import InMemorySessionService, Session +from google.genai import types +from google_adk_arcade.tools import get_arcade_tools +from dotenv import load_dotenv + + +load_dotenv(override=True) + + +async def main(): + # initialize the Arcade client + client = AsyncArcade() + app_name = "Arcade Google ADK" + user_id = "mateo@arcade.dev" + session_service = InMemorySessionService() + artifact_service = InMemoryArtifactService() + + # This function returns a list of tools in the format expected by ADK + all_gmail_tools = await get_arcade_tools(client, toolkits=["Gmail"]) + + # We've updated the agent to access a single tool + agent = Agent( + model="gemini-2.0-flash", + name="simple_agent", + instruction="You are a helpful assistant that can help users with" + " everyday tasks. It is very important that you use" + " the provided tools to ensure the task is successfully" + " achieved", + tools=all_gmail_tools, # pass the tools to the agent + ) + session = await session_service.create_session( + app_name=app_name, user_id=user_id, state={ + "user_id": user_id, + } + ) + runner = Runner( + app_name=app_name, + agent=agent, + artifact_service=artifact_service, + session_service=session_service, + ) + + # This is a helper function to run the agent with a prompt. + async def run_prompt(session: Session, new_message: str): + content = types.Content( + role='user', parts=[types.Part.from_text(text=new_message)] + ) + async for event in runner.run_async( + user_id=user_id, + session_id=session.id, + new_message=content, + ): + if event.content.parts and event.content.parts[0].text: + print(f'** {event.author}: {event.content.parts[0].text}') + + # This is the main agent loop to prompt the user until they exit. + while True: + user_input = input("User: ") + if user_input.lower() == "exit": + print("Goodbye!") + break + await run_prompt(session, user_input) + + +if __name__ == "__main__": + asyncio.run(main()) + + diff --git a/examples/code/guides/tool-callling/google-adk/google-adk-only.py b/examples/code/guides/tool-callling/google-adk/google-adk-only.py new file mode 100644 index 00000000..d23fe691 --- /dev/null +++ b/examples/code/guides/tool-callling/google-adk/google-adk-only.py @@ -0,0 +1,60 @@ +import asyncio +from google.adk import Agent, Runner +from google.adk.artifacts import InMemoryArtifactService +from google.adk.sessions import InMemorySessionService, Session +from google.genai import types +from dotenv import load_dotenv + + +load_dotenv(override=True) + + +async def main(): + app_name = 'Arcade Google ADK' + user_id = '{arcade_user_id}' + session_service = InMemorySessionService() + artifact_service = InMemoryArtifactService() + + # This creates a simple agent, and for now it does not have any tools. + agent = Agent( + model="gemini-2.0-flash", + name="simple_agent", + instruction="You are a helpful assistant that can help users with" + " everyday tasks.", + ) + session = await session_service.create_session( + app_name=app_name, user_id=user_id, state={ + "user_id": user_id, + } + ) + runner = Runner( + app_name=app_name, + agent=agent, + artifact_service=artifact_service, + session_service=session_service, + ) + + # This is a helper function to run the agent with a prompt. + async def run_prompt(session: Session, new_message: str): + content = types.Content( + role='user', parts=[types.Part.from_text(text=new_message)] + ) + async for event in runner.run_async( + user_id=user_id, + session_id=session.id, + new_message=content, + ): + if event.content.parts and event.content.parts[0].text: + print(f'** {event.author}: {event.content.parts[0].text}') + + # This is the main agent loop to prompt the user until they exit. + while True: + user_input = input("User: ") + if user_input.lower() == "exit": + print("Goodbye!") + break + await run_prompt(session, user_input) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/package.json b/package.json index d831b43e..8c03fe45 100644 --- a/package.json +++ b/package.json @@ -101,5 +101,6 @@ "prismjs@<1.30.0": ">=1.30.0", "form-data@>=4.0.0 <4.0.4": ">=4.0.4" } - } + }, + "packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977" } diff --git a/pages/_meta.ts b/pages/_meta.ts index eb66762c..9d3eba7f 100644 --- a/pages/_meta.ts +++ b/pages/_meta.ts @@ -10,6 +10,11 @@ export default { title: "Integrations", href: "/toolkits", }, + learn: { + type: "page", + title: "Learn", + href: "/learn", + }, reference: { type: "page", title: "Reference", diff --git a/pages/home/_meta.tsx b/pages/home/_meta.tsx index be102e59..fd1e7593 100644 --- a/pages/home/_meta.tsx +++ b/pages/home/_meta.tsx @@ -1,4 +1,4 @@ -import { BadgeHelp, Code2, Home, Plug } from "lucide-react"; +import { BadgeHelp, Code2, Home, Plug, BookMarked } from "lucide-react"; export default { "*": { @@ -15,7 +15,7 @@ export default { layout: "full", }, }, - learn: { + integrations: { title: ( @@ -24,6 +24,15 @@ export default { ), href: "/toolkits", }, + learn: { + title: ( + + + Learn + + ), + href: "/learn", + }, reference: { title: ( @@ -53,6 +62,9 @@ export default { type: "separator", title: "Using Arcade", }, + installation: { + title: "Installation", + }, quickstart: { title: "Quickstart", }, diff --git a/pages/home/auth/_meta.ts b/pages/home/auth/_meta.ts index 025a383e..452ed88c 100644 --- a/pages/home/auth/_meta.ts +++ b/pages/home/auth/_meta.ts @@ -1,7 +1,4 @@ export default { "how-arcade-helps": "How Arcade Helps", - "auth-tool-calling": "Authorized Tool Calling", - "tool-auth-status": "Checking Authorization Status", - "call-third-party-apis-directly": "Direct Third-Party API Call", "secure-auth-production": "Secure Auth in Production", }; diff --git a/pages/home/auth/how-arcade-helps.mdx b/pages/home/auth/how-arcade-helps.mdx index fc497145..7b2380fc 100644 --- a/pages/home/auth/how-arcade-helps.mdx +++ b/pages/home/auth/how-arcade-helps.mdx @@ -36,7 +36,7 @@ When a tool is called, the Arcade Engine will check if the user has granted the ### How to implement OAuth2-authorized tool calling -To learn how Arcade allows for actions (tools) to be authorized through OAuth2 and how to implement it, check out [Authorized Tool Calling](/home/auth/auth-tool-calling). +To learn how Arcade allows for actions (tools) to be authorized through OAuth2 and how to implement it, check out [Authorized Tool Calling](/learn/tool-calling/auth-tool-calling). ### Tools that don't require authorization diff --git a/pages/home/glossary.mdx b/pages/home/glossary.mdx index 01ecd636..a0b14e5a 100644 --- a/pages/home/glossary.mdx +++ b/pages/home/glossary.mdx @@ -101,7 +101,7 @@ An 'auth provider' is a service that your users sign in with to let the agent ac An 'authorization scope' is a permission that a user can grant to an agent. This is used to control what the agent can do with the user's data. Available authorization scopes are defined by the authentication provider, and each tool defines the scopes it requires. -Learn more about [authorized tool calling](/home/auth/auth-tool-calling). +Learn more about [authorized tool calling](/learn/tool-calling/auth-tool-calling). ### Tool Executions diff --git a/pages/home/index.mdx b/pages/home/index.mdx index d34688ce..a7ef075f 100644 --- a/pages/home/index.mdx +++ b/pages/home/index.mdx @@ -4,5 +4,6 @@ description: "Welcome to the Arcade Docs! Here you'll find everything you need t --- import { LandingPage } from "@components/LandingPage"; +import { MCPLanding } from "@components/MCPLanding"; - + diff --git a/pages/home/installation.mdx b/pages/home/installation.mdx new file mode 100644 index 00000000..17fd541a --- /dev/null +++ b/pages/home/installation.mdx @@ -0,0 +1,170 @@ +--- +title: "Installation" +description: "Learn how to install the Arcade client" +--- + +import { Steps, Tabs } from "nextra/components"; +import { SignupLink } from "@/components/Analytics"; + +# Installation + +Arcade provides [client libraries for multiple languages](/home/arcade-clients). These are the easiest way to integrate Arcade into your applications. + +## Overview + +In this guide, you'll learn how to install the Arcade client for your preferred language, and test it with a simple example. + + +You need to have an Arcade API key to use the clients. You can get one by [following this guide](/home/api-keys). + + + + + +### Setup your Python environment + +The Arcade client requires Python 3.10 or higher. We recommend using `uv` to manage your Python environment. First, [install uv](https://docs.astral.sh/uv/getting-started/installation/). Then you can safely install the latest version of Python. + +```bash +uv python install +``` + +It's also a good idea to create a virtual environment for your project in a new directory. + +```bash +mkdir arcade-project +cd arcade-project +uv venv +source .venv/bin/activate +``` + +### Install the Arcade client + +Now that you have Python and a virtual environment, let's install the Arcade client. + +```bash +uv pip install arcadepy +``` + +### Test the client + +Now that you have the Arcade client installed, let's test it with a simple example. We'll use the `Arcade` class to create a client, and then use the `list` method to get a list of tools. This will return a list of tools that are available to you in the `GitHub` toolkit. + + +```python +# main.py +from arcadepy import Arcade + +client = Arcade() + +tools = client.tools.list(toolkit="github") +print(f"Found these tools in the GitHub toolkit:") +for tool in tools: + print(f" - {tool.name}") +``` + +And run it with: +```bash +python main.py +``` + +Example output: + +``` +Found these tools in the GitHub toolkit: + - CountStargazers + - CreateIssue + - CreateIssueComment + - CreateReplyForReviewComment + - CreateReviewComment + - GetPullRequest + - GetRepository + - ListOrgRepositories + - ListPullRequestCommits + - ListPullRequests + - ListRepositoryActivities + - ListReviewCommentsInARepository + - ListReviewCommentsOnPullRequest + - ListStargazers + - SetStarred + - UpdatePullRequest +``` + + + + + +### Setup your environment + +The Arcade client requires a JavaScript runtime. We recommend using `bun` to manage your JavaScript environment. First, [install bun](https://bun.com/docs/installation). + +We recommend creating a new project directory and installing the client in it. + +```bash +mkdir arcade-project +cd arcade-project +bun init +``` + +### Install the Arcade client + +Now that you have bun, let's install the Arcade client. + +```bash +bun add @arcadeai/arcadejs +``` + +### Test the client + +Now that you have the Arcade client installed, let's test it with a simple example. We'll use the `Arcade` class to create a client, and then use the `list` method to get a list of tools. This will return a list of tools that are available to you in the `GitHub` toolkit. + +```javascript +// main.ts +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); + +const tools = await client.tools.list({toolkit:"github"}); + +console.log("Found these tools in the GitHub toolkit:"); +for (const tool of tools.items) { + console.log(` - ${tool.name}`); +} +``` +And run it with: +```bash +bun run main.ts +``` + +Example output: + +``` +Found these tools in the GitHub toolkit: + - CountStargazers + - CreateIssue + - CreateIssueComment + - CreateReplyForReviewComment + - CreateReviewComment + - GetPullRequest + - GetRepository + - ListOrgRepositories + - ListPullRequestCommits + - ListPullRequests + - ListRepositoryActivities + - ListReviewCommentsInARepository + - ListReviewCommentsOnPullRequest + - ListStargazers + - SetStarred + - UpdatePullRequest +``` + + + + + +The guide for Go is coming soon. + + + + + diff --git a/pages/home/langchain/use-arcade-tools.mdx b/pages/home/langchain/use-arcade-tools.mdx index fe5a9209..1f3c0c39 100644 --- a/pages/home/langchain/use-arcade-tools.mdx +++ b/pages/home/langchain/use-arcade-tools.mdx @@ -77,7 +77,7 @@ print(manager.tools) ``` -Arcade offers methods to convert tools into Zod schemas, which is essential since LangGraph defines tools using Zod. The `toZod` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with LangGraph. Learn more about Arcade's Zod integration options [here](/home/use-tools/get-tool-definitions#get-zod-tool-definitions). +Arcade offers methods to convert tools into Zod schemas, which is essential since LangGraph defines tools using Zod. The `toZod` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with LangGraph. Learn more about Arcade's Zod integration options [here](/learn/tool-calling/get-tool-definitions#get-zod-tool-definitions). ```javascript import { Arcade } from "@arcadeai/arcadejs"; import { executeOrAuthorizeZodTool, toZod } from "@arcadeai/arcadejs/lib"; diff --git a/pages/home/local-deployment/configure/engine.mdx b/pages/home/local-deployment/configure/engine.mdx index e9f57e6e..d0b003de 100644 --- a/pages/home/local-deployment/configure/engine.mdx +++ b/pages/home/local-deployment/configure/engine.mdx @@ -88,7 +88,7 @@ For local development, set `api.development = true`. In development mode, Arcade ## Auth configuration -Arcade Engine manages auth for [AI tools](/home/auth/auth-tool-calling) and [direct API calls](/home/auth/call-third-party-apis-directly). It supports many built-in [auth providers](/home/auth-providers), and can also connect to any [OAuth 2.0](/home/auth-providers/oauth2) authorization server. +Arcade Engine manages auth for [AI tools](/learn/tool-calling/auth-tool-calling) and [direct API calls](/learn/tool-calling/call-third-party-apis-directly). It supports many built-in [auth providers](/home/auth-providers), and can also connect to any [OAuth 2.0](/home/auth-providers/oauth2) authorization server. The `auth.providers` section defines the providers that users can authorize with. Each provider must have a unique `id` in the array. There are two ways to configure a provider: diff --git a/pages/home/mastra/use-arcade-tools.mdx b/pages/home/mastra/use-arcade-tools.mdx index 4d0b60eb..d3b871e9 100644 --- a/pages/home/mastra/use-arcade-tools.mdx +++ b/pages/home/mastra/use-arcade-tools.mdx @@ -72,7 +72,7 @@ process.env.ANTHROPIC_API_KEY = "your_anthropic_api_key"; // or another supporte ### Convert Arcade tools to Mastra tools -Arcade offers methods to convert tools into Zod schemas, which is essential since Mastra defines tools using Zod. The `toZodToolSet` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with Mastra. Learn more about Arcade's Zod integration options [here](/home/use-tools/get-tool-definitions#get-zod-tool-definitions). +Arcade offers methods to convert tools into Zod schemas, which is essential since Mastra defines tools using Zod. The `toZodToolSet` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with Mastra. Learn more about Arcade's Zod integration options [here](/learn/tool-calling/get-tool-definitions#get-zod-tool-definitions). ```ts import { Arcade } from "@arcadeai/arcadejs"; diff --git a/pages/home/use-tools/_meta.ts b/pages/home/use-tools/_meta.ts index a4c3d622..bed1ae87 100644 --- a/pages/home/use-tools/_meta.ts +++ b/pages/home/use-tools/_meta.ts @@ -1,4 +1,3 @@ export default { "tools-overview": "Introduction", - "get-tool-definitions": "Tool formats", }; diff --git a/pages/home/vercelai/use-arcade-tools.mdx b/pages/home/vercelai/use-arcade-tools.mdx index 827c2fb1..58ba709b 100644 --- a/pages/home/vercelai/use-arcade-tools.mdx +++ b/pages/home/vercelai/use-arcade-tools.mdx @@ -82,7 +82,7 @@ const arcade = new Arcade() ### Get tools from Arcade's Gmail toolkit and convert them to Zod -Arcade offers methods to convert tools into Zod schemas, which is essential since the Vercel AI SDK defines tools using Zod. The `toZodToolSet` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with the Vercel AI SDK. Learn more about Arcade's Zod integration options [here](/home/use-tools/get-tool-definitions#get-zod-tool-definitions). +Arcade offers methods to convert tools into Zod schemas, which is essential since the Vercel AI SDK defines tools using Zod. The `toZodToolSet` method is particularly useful, as it simplifies this integration and makes it easier to use Arcade's tools with the Vercel AI SDK. Learn more about Arcade's Zod integration options [here](/learn/tool-calling/get-tool-definitions#get-zod-tool-definitions). ```ts // Get Arcade's gmail toolkit diff --git a/pages/learn/_meta.tsx b/pages/learn/_meta.tsx new file mode 100644 index 00000000..7c2d856b --- /dev/null +++ b/pages/learn/_meta.tsx @@ -0,0 +1,23 @@ +export default { + "*": { + breadcrumb: true, + }, + index: { + title: Guides, + theme: { + layout: "full", + }, + }, + mcp: { + title: "MCP", + }, + "tool-calling": { + title: "Tool Calling", + }, + "multi-user-setups": { + title: "Multi-user apps", + }, + "managing-deployments": { + title: "Managing deployments", + }, +}; diff --git a/pages/learn/index.mdx b/pages/learn/index.mdx new file mode 100644 index 00000000..77b7daad --- /dev/null +++ b/pages/learn/index.mdx @@ -0,0 +1,8 @@ +--- +title: "Learn" +description: "Welcome to the Arcade Learn! Here you'll find everything you need to know about Arcade." +--- + +import { Learn } from "@components/Learn"; + + diff --git a/pages/learn/managing-deployments/_meta.tsx b/pages/learn/managing-deployments/_meta.tsx new file mode 100644 index 00000000..e69de29b diff --git a/pages/learn/mcp/_meta.tsx b/pages/learn/mcp/_meta.tsx new file mode 100644 index 00000000..8235ab20 --- /dev/null +++ b/pages/learn/mcp/_meta.tsx @@ -0,0 +1,4 @@ +export default { + "build-mcp-servers": "Build MCP Servers", + "implement-an-mcp-client": "Implement an MCP Client", +}; diff --git a/pages/learn/mcp/build-mcp-servers.mdx b/pages/learn/mcp/build-mcp-servers.mdx new file mode 100644 index 00000000..457ae6aa --- /dev/null +++ b/pages/learn/mcp/build-mcp-servers.mdx @@ -0,0 +1,87 @@ +--- +title: "Build MCP Servers" +description: "Learn how to build MCP servers" +--- + +# Build MCP Servers + +MCP is a protocol for building servers that can be used to build AI agents. In this guide, you'll learn how to build MCP servers using Arcade. + +## Overview + +In this guide, you'll learn how to build MCP servers using `arcade-mcp`. + +The main purpose of an MCP server is to provide tools to other applications. Let's create a simple toolkit that returns up to date information about the user. + + + +### Setup your environment + +This guide assumes you have `uv` installed. Let's create a new project and install the necessary dependencies: + +```bash +uv init +source .venv/bin/activate +uv pip install arcade-mcp +``` + +### Create an MCP app + +An MCP app is the main entry point for your MCP server. It contains the tools you want to expose, as well as the configuration for the server. Let's create a simple MCP app that contains utility tools. + +```python +from arcade_mcp import MCPApp + +app = MCPApp( + name="my_server", + version="1.0.0", + title="My MCP Server", + instructions="This server provides utility tools", + log_level="INFO" +) +``` + +### Add tools to the app + +You can use the `@app.tool` decorator to add tools to the app. It's recommended to annotate the parameters and return type of the tool. + +```python +from typing import Annotated +from datetime import datetime + +@app.tool +def get_current_date_and_time() -> Annotated[str, "The current date and time"]: + """Get the current date and time""" + return datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + +@app.tool +def calculate_average( + numbers: Annotated[list[float], "The numbers to calculate the average of"], +) -> Annotated[float, "The average of the numbers"]: + """Calculate the average of a list of numbers""" + if not numbers: + return 0.0 + return sum(numbers) / len(numbers) +``` + +### Adding context to the tools + +From the tools you can access a `Context` object that gives you several runtime features: + +- Logging +- Secrets management +- User Context +- Progress Reporting + +You can also decorate the tool with the secrets it requires to run, which must be present in the `Context` object. + +```python +@app.tool(requires_secrets=["DATABASE_CONNECTION_STRING"]) +def get_current_date_and_time(context: Context) -> Annotated[str, "The current date and time"]: + """Get the current date and time""" + return datetime.now().strftime('%Y-%m-%d %H:%M:%S') +``` + + + diff --git a/pages/learn/mcp/implement-an-mcp-client.mdx b/pages/learn/mcp/implement-an-mcp-client.mdx new file mode 100644 index 00000000..d8da6140 --- /dev/null +++ b/pages/learn/mcp/implement-an-mcp-client.mdx @@ -0,0 +1,442 @@ +--- +title: "Implement an MCP Client" +description: "Learn how to implement an MCP client" +--- + +import { Steps, Tabs } from "nextra/components"; + +# Implement an MCP Client + +In this guide, you'll learn how to implement an MCP client. + +## Overview + +The MCP client is the application that will communicate with one or more MCP servers, and use the tools and resources they provide. Our guide will focus on implementing a client that can call tools using the Streamable HTTP transport. The [MCP Protocol](https://modelcontextprotocol.io/docs/getting-started/intro) includes several transports, and other primitives besides tools, such as prompts and resources. To learn more about those primitives and alternative transports, please refer to the MCP Protocol documentation. + + +### Features of the client + +The client will have the following features: + +- Connect to an MCP server using the Streamable HTTP transport +- If the MCP server requires authentication, the client will handle the authorization flow +- List available tools +- Call tools + + + + + + + If you're looking for a ready-made client, you can use the [reference implementation](https://github.com/modelcontextprotocol/python-sdk/tree/main/examples/clients/simple-auth-client) available in the MCP reference implementation repository, which is the base for this guide. + + +### Setup your environment + +We'll use `uv` to manage our Python environment. Let's create a new project and install the necessary dependencies: + +```bash +mkdir mcp-client +cd mcp-client +uv init +uv venv +source .venv/bin/activate +uv add mcp +``` + +### Implementation + +Our client will have the following structure: + +- A volatile (in memory) token storage +- A thin HTTP server to have a callback endpoint for the authorization flow +- The actual MCP client, that manages: + - Connecting to the MCP server + - Listing available tools + - Calling tools + - A simple CLI to interact with the client + + +### Import all required libraries + +To keep things organized, let's import all required libraries at the top of the file. + +```python +import asyncio +import threading +import time +import webbrowser +from datetime import timedelta +from http.server import BaseHTTPRequestHandler, HTTPServer +from typing import Any +from urllib.parse import parse_qs, urlparse + +from mcp.client.auth import OAuthClientProvider, TokenStorage +from mcp.client.session import ClientSession +from mcp.client.streamable_http import streamablehttp_client +from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken +``` + +We're importing: +- `asyncio` and `threading` to handle asynchronous operations for our small server and the MCP client. +- `webbrowser` to open the browser for the authorization flow. +- `timedelta` to handle the expiration time of the tokens. +- `BaseHTTPRequestHandler` and `HTTPServer` to create a small HTTP server to handle the callback endpoint for the authorization flow. +- `Any` to handle the type of the tokens. +- `OAuthClientProvider` and `TokenStorage` to handle the OAuth client provider and the token storage. +- `ClientSession` to handle the client session. +- `streamablehttp_client` to handle the Streamable HTTP client. +- `OAuthClientInformationFull`, `OAuthClientMetadata` and `OAuthToken` to handle the OAuth client information, metadata and token. + +### The token storage + +The token storage is a simple in-memory class that will store the tokens for the MCP server. We're using the `TokenStorage` class from the MCP client library to handle the token storage, as it implements a compatible interface. + +```python +class RAMTokenStorage(TokenStorage): + """Volatile token storage""" + + def __init__(self): + self.tokens: OAuthToken | None = None + self.client_information: OAuthClientInformationFull | None = None +``` + +### The HTTP server + +The HTTP server is a simple class that will handle the callback endpoint for the authorization flow. We're using the `BaseHTTPRequestHandler` class from the `http.server` module to create a small HTTP server to handle the callback endpoint for the authorization flow. It's important to [not use this server in production](https://docs.python.org/3/library/http.server.html#http-server-security). + +Our server has two parts: +- The callback handler, that handles the callback endpoint for the authorization flow +- The callback server, that starts the HTTP server and waits for the callback handler to complete the authorization flow + +The callback handler only cares about: +- successfully completing the authorization flow +- displaying errors from the authorization flow +- capturing the state from the authorization flow + +```python +class HTTPHandler(BaseHTTPRequestHandler): + """HTTP handler to capture OAuth callback""" + + def __init__(self, request, client_addr, server, callback_data): + self.callback_data = callback_data + super().__init__(request, client_addr, server) + + def do_GET(self): + """Handle GET requests from OAuth redirect""" + parsed = urlparse(self.path) + query_params = parse_qs(parsed.query) + + if "code" in query_params: # we've successfully completed the authorization flow + self.callback_data["authorization_code"] = query_params["code"][0] + self.callback_data["state"] = query_params.get("state", [None])[0] + self.send_response(200) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(b""" + + +

Authorization Successful

+

You can close this window and return to your app

+ + + + """) + elif "error" in query_params: # we've encountered an error + self.callback_data["error"] = query_params["error"][0] + self.send_response(400) + self.send_header("Content-type", "text/html") + self.end_headers() + self.wfile.write(f""" + + +

Authorization Failed

+

Error: {query_params["error"][0]}

+

You can close this window and return to your app

+ + + """.encode()) + else: # for everything else, we return a not found response + self.send_response(404) + self.end_headers() + + def log_message(self, format, *args): + # supress default logging + pass + +``` + +The callback server is responsible for: +- Starting the HTTP server +- Waiting for the callback handler to complete the authorization flow +- Stopping the HTTP server + +```python +class CallbackServer: + def __init__(self, port=3000): + self.port = port + self.server = None + self.thread = None + self.callback_data = { "authorization_code": None, "state": None, "error": None, } + + def _create_handler_with_data(self): + callback_data = self.callback_data + + class DataCallbackHandler(HTTPHandler): + def __init__(self, request, client_address, server): + super().__init__(request, client_address, server, callback_data) + + return DataCallbackHandler + + def start(self): + handler_class = self._create_handler_with_data() + self.server = HTTPServer(("localhost", self.port), handler_class) + self.thread = threading.Thread(target=self.server.serve_forever, + daemon=True) + self.thread.start() + print(f"Started callback server on http://localhost:{self.port}") + + def stop(self): + if self.server: + self.server.shutdown() + self.server.server_close() + if self.thread: + self.thread.join(timeout=1) + + def wait_for_callback(self, timeout=300): + start_time = time.time() + while time.time() - start_time < timeout: + if self.callback_data["authorization_code"]: + return self.callback_data["authorization_code"] + elif self.callback_data["error"]: + raise Exception(f"OAuth error: {self.callback_data["error"]}") + time.sleep(0.1) + raise Exception("Timeout waiting for OAuth callback") + + def get_state(self): + return self.callback_data["state"] +``` + +### The MCP client + +The central class is the actual MCP client, that will handle: +- Connecting to the MCP server +- Listing available tools +- Calling tools +- A simple CLI to interact with the client + +As with many other protocols, the main component is the session. Each function that we add to the client will interact with the session to achieve its purpose. + +```python +class MCPOAuthClient: + + def __init__(self, server_url: str): + self.server_url = server_url + self.session: ClientSession | None = None +``` + +The first step is to initialize the session, connecting to the provided MCP Server URL. The function may seem complex, but it simply initializes the client components: + +- The callback server (on port 3030) +- The callback handler +- The OAuth client provider +- The Streamable HTTP client + +The function also sets the `server_url` attribute, which will be used to connect to the MCP server. + +```python +async def connect(self): + print(f"Attempting to connect to {self.server_url}") + + try: + callback_server = CallbackServer(port=3030) + callback_server.start() + + async def callback_handler() -> tuple[str, str | None]: + print("Waiting for authorization callback...") + try: + auth_code = callback_server.wait_for_callback(timeout=300) + return auth_code, callback_server.get_state() + finally: + callback_server.stop() + + client_metadata = { + "client_name": "MCP OAuth client", + "redirect_uris": ["http://localhost:3030/callback"], + "grant_types": ["authorization_code", "refresh_token"], + "response_types": ["code"], + "token_ednpoint_auth_method": "client_secret_post", + } + + async def _default_redirect_handler(authorization_url: str) -> None: + print(f"Opening browser for authorization: {authorization_url}") + webbrowser.open(authorization_url) + + oauth_auth = OAuthClientProvider( + server_url=self.server_url.replace("/mcp", ""), + client_metadata=OAuthClientMetadata.model_validate(client_metadata), + storage=RAMTokenStorage(), + redirect_handler=_default_redirect_handler, + callback_handler=callback_handler + ) + + print("Opening StreamableHTTP transport connection with auth...") + async with streamablehttp_client( + url=self.server_url, + auth=oauth_auth, + timeout=timedelta(seconds=60), + ) as (read_stream, write_stream, get_session_id): + await self._run_session(read_stream, + write_stream, + get_session_id) + except Exception as e: + print(f"Failed to connect: {e}") + import traceback + traceback.print_exc() +``` + +The `streamablehttp_client` function creates two streams (read and write) and a function to get the session ID. These 3 components are the actual transport we use to communicate with the MCP server. The `_run_session` function then uses these components to initialize the session (enabling our client to interact with the MCP server), and then starts the interactive loop, which runs indefinitely in our client. + +```python +async def _run_session(self, read_stream, write_stream, get_session_id): + print("Initializing MCP session") + async with ClientSession(read_stream, write_stream) as session: + self.session = session + print("Starting session initialization...") + await session.initialize() + print("Completed session initialization") + + print(f"Conntected to MCP server at {self.server_url}") + session_id = get_session_id() + if session_id: + print(f"Session ID: {session_id}") + + await self.interactive_loop() +``` + +The interactive loop is the "app" portion of this client. In more sophisticated clients, you would normally interact with convenience functions (which we'll implement in a second!). But this simple CLI is illustrative of how you would use the client to interact with the MCP server. + +If you've written other CLI's, you will be familiar with this pattern. An infinite loop that waits for user input, parses and validates it, and then calls the appropriate function. In this case, we have 3 commands: +- `list` - List available tools +- `call [args]` - Call a tool +- `quit` - Exit the client, which will also close the session and the transport as the objects are cleaned up + +```python +async def interactive_loop(self): + """Run interactive command loop.""" + print("\nšŸŽÆ Interactive MCP Client") + print("Commands:") + print(" list - List available tools") + print(" call [args] - Call a tool") + print(" quit - Exit the client") + print() + + while True: + try: + command = input("mcp> ").strip() + if not command: + continue + if command == "quit": + break + elif command == "list": + await self.list_tools() + elif command.startswith("call "): + parts = command.split(maxsplit=2) + tool_name = parts[1] if len(parts) > 1 else "" + if not tool_name: + print("āŒ Please specify a tool name") + continue + # Parse arguments (simple JSON-like format) + arguments = {} + if len(parts) > 2: + import json + try: + arguments = json.loads(parts[2]) + except json.JSONDecodeError: + print("āŒ Invalid arguments format (expected JSON)") + continue + await self.call_tool(tool_name, arguments) + else: + print("āŒ Unknown command. Try 'list', 'call ', or 'quit'") + except KeyboardInterrupt: + print("\n\nšŸ‘‹ Goodbye!") + break + except EOFError: + break +``` + +What remains to implement inside the `MCPOAuthClient` class are the functions to list available tools and call tools. + +Both follow a very simple pattern, the session object already provides these functions, and we are simply adding a small amount of error handling, and printing the results to the terminal. On a real app, you would typically return these results to the caller, and perhaps inject it into an LLM for another turn. + +```python +async def list_tools(self): + """List available tools from the server.""" + if not self.session: + print("āŒ Not connected to server") + return + + try: + result = await self.session.list_tools() + if hasattr(result, "tools") and result.tools: + print("\nšŸ“‹ Available tools:") + for i, tool in enumerate(result.tools, 1): + print(f"{i}. {tool.name}") + if tool.description: + print(f" Description: {tool.description}") + if tool.inputSchema: + print(f" Input Schema: {tool.inputSchema}") + print() + else: + print("No tools available") + except Exception as e: + print(f"āŒ Failed to list tools: {e}") + +async def call_tool(self, tool_name: str, arguments: dict[str, Any] | None = None): + """Call a specific tool.""" + if not self.session: + print("āŒ Not connected to server") + return + + try: + result = await self.session.call_tool(tool_name, arguments or {}) + print(f"\nšŸ”§ Tool '{tool_name}' result:") + if hasattr(result, "content"): + for content in result.content: + if content.type == "text": + print(content.text) + else: + print(content) + else: + print(result) + except Exception as e: + print(f"āŒ Failed to call tool '{tool_name}': {e}") +``` + +### The main function + +With the above, we have a working MCP client!. Let's add a main function to run it. We encourage you to configure your own MCP server URL, and we've included the Arcade public MCP server as an example. + +```python +async def main(): + print("Hello from mcp-client!") + + server_url = "https://api.arcade.dev/v1/mcps/arcade-anon/mcp" + client = MCPOAuthClient(server_url) + await client.connect() + +if __name__ == "__main__": + asyncio.run(main()) +``` + + +
+
+ + + +### Setup your environment + + + +
diff --git a/pages/learn/multi-user-setups/_meta.tsx b/pages/learn/multi-user-setups/_meta.tsx new file mode 100644 index 00000000..e69de29b diff --git a/pages/learn/multi-user-setups/check-user-connections.mdx b/pages/learn/multi-user-setups/check-user-connections.mdx new file mode 100644 index 00000000..e69de29b diff --git a/pages/learn/multi-user-setups/implement-an-user-verifier.mdx b/pages/learn/multi-user-setups/implement-an-user-verifier.mdx new file mode 100644 index 00000000..e69de29b diff --git a/pages/learn/multi-user-setups/manage-user-connections.mdx b/pages/learn/multi-user-setups/manage-user-connections.mdx new file mode 100644 index 00000000..e69de29b diff --git a/pages/learn/tool-calling/_meta.tsx b/pages/learn/tool-calling/_meta.tsx new file mode 100644 index 00000000..13f947ba --- /dev/null +++ b/pages/learn/tool-calling/_meta.tsx @@ -0,0 +1,16 @@ +export default { + "auth-tool-calling": "Authorized Tool Calling", + "call-tools-directly": "Call a tool directly", + "get-tool-definitions": "Tool formats", + "call-a-tool-from-an-llm": "Call a tool from an LLM", + "call-third-party-apis-directly": "Direct Third-Party API Call", + "auth-tool-calling-langgraph": "Authorized tool calling using LangGraph", + "auth-tool-calling-openai-agents": + "Authorized tool calling using OpenAI Agents SDK", + "auth-tool-calling-google-adk": "Authorized tool calling using Google ADK", + "jit-auth": "Just-in-time auth", + "bulk-auth": "Bulk-auth before the agent runs", + "tool-auth-status": "Checking Authorization Status", + hitl: "Implementing Human-in-the-loop", + "call-tools-with-mcp": "Call tools with MCP", +}; diff --git a/pages/learn/tool-calling/auth-tool-calling-google-adk.mdx b/pages/learn/tool-calling/auth-tool-calling-google-adk.mdx new file mode 100644 index 00000000..1cf3b3ce --- /dev/null +++ b/pages/learn/tool-calling/auth-tool-calling-google-adk.mdx @@ -0,0 +1,101 @@ +--- +title: "Authorized tool calling using Google ADK" +description: "Learn how to use Arcade to authorize tools using Google ADK" +--- + +import { Steps, Tabs } from "nextra/components"; + +# Authorized tool calling using Google ADK + +Google ADK is a framework for building AI agents. Arcade provides an integration with Google ADK to allow you to use Arcade tools in your Google ADK agents with ease. + +## Overview + +In this guide, you'll learn how to use Arcade to call tools using Google ADK. In particular, you'll learn how to call tools that require user authorization. + +We'll start with a barebones example of a Google ADK agent that can only chat. Then, we'll add tools one by one, we'll add entire toolkits at once, and we'll handle authorization for tools that require it. + +### Prerequisites + +- [Obtain an Arcade API key](/home/api-keys) +- [Obtain a Google API key](https://console.cloud.google.com/apis/credentials) + + + +### Setup your environment + +Google ADK uses Python. We'll use the `uv` package manager to install python, as well as the necessary dependencies. + +Create a new directory for this project. This guides assumes you will create it in your home directory, but feel free to create it anywhere you like. + +```bash +mkdir -p ~/auth-tool-calling-adk +cd ~/auth-tool-calling-adk +``` + +Create a new uv project, and activate the virtual environment. + +```bash +uv init +source .venv/bin/activate +``` + +This will have created a `.venv` directory with the virtual environment, as well as a `pyproject.toml` file with the project metadata, and a `main.py` file with the entry point for the application. We'll use the `main.py` file to write our code. + +Install the necessary dependencies: + +```bash +uv add google-adk-arcade +``` + +### Configure your API keys + +Set the following environment variables: + +```bash +export ARCADE_API_KEY='{arcade_api_key}' +export GOOGLE_API_KEY='YOUR_GOOGLE_API_KEY' +export GOOGLE_GENAI_USE_VERTEXAI=FALSE +``` + + +or create a `.env` file with the following contents: + +```bash +# .env +ARCADE_API_KEY={arcade_api_key} +GOOGLE_API_KEY=YOUR_GOOGLE_API_KEY +GOOGLE_GENAI_USE_VERTEXAI=FALSE +``` + + +We need to set `GOOGLE_GENAI_USE_VERTEXAI=FALSE` because we're using the Gemini API, not Vertex AI. + + +### Create a simple agent + +We'll start with a simple agent that can only chat. + +```python file=/examples/code/guides/tool-callling/google-adk/google-adk-only.py +``` + +So far, we've only used Google ADK's primitives. The agent is already functional and will invoke the model and keep the messages in memory for the duration of the conversation. + +Now, let's add a tool to the agent. + +### Add a tool to the agent + +Now, let's update the code to add: +- An Arcade client +- Get a specific tool from the Arcade Platform +- Pass the tool to the agent + +In 5 lines of code, we've done all of this. And now the agent can invoke the tool. If the tool requires authorization, the tool will reply with a URL for the user to visit, which will be displayed to the user. If the tool does not require authorization, the tool will be invoked directly. + +```python file=/examples/code/guides/tool-callling/google-adk/google-adk-arcade-init.py {2,7,15-16,22-23,30-34} +``` + + + + + diff --git a/pages/learn/tool-calling/auth-tool-calling-langgraph.mdx b/pages/learn/tool-calling/auth-tool-calling-langgraph.mdx new file mode 100644 index 00000000..d72af314 --- /dev/null +++ b/pages/learn/tool-calling/auth-tool-calling-langgraph.mdx @@ -0,0 +1 @@ +asd diff --git a/pages/learn/tool-calling/auth-tool-calling-openai-agents.mdx b/pages/learn/tool-calling/auth-tool-calling-openai-agents.mdx new file mode 100644 index 00000000..d72af314 --- /dev/null +++ b/pages/learn/tool-calling/auth-tool-calling-openai-agents.mdx @@ -0,0 +1 @@ +asd diff --git a/pages/home/auth/auth-tool-calling.mdx b/pages/learn/tool-calling/auth-tool-calling.mdx similarity index 100% rename from pages/home/auth/auth-tool-calling.mdx rename to pages/learn/tool-calling/auth-tool-calling.mdx diff --git a/pages/learn/tool-calling/bulk-auth.mdx b/pages/learn/tool-calling/bulk-auth.mdx new file mode 100644 index 00000000..bb5f0419 --- /dev/null +++ b/pages/learn/tool-calling/bulk-auth.mdx @@ -0,0 +1,93 @@ +--- +title: "Authorize tools before the agent runs" +description: "Learn how to use Arcade to authorize multiple tools before the agent runs" +--- + +import { Steps, Tabs } from "nextra/components"; + +# Authorize tools before the agent runs + +With [Just-in-time authentication](/learn/tool-calling/auth-tool-calling), the agent will request the user to authorize each tool before running it. In many cases, this experience is not what you want to provide to your users. For example, you may expect the users to be able to list their emails, as well as send emails immediately after. With JIT auth, the user would have to authorize each tool separately, which may result in too much friction. + +## Overview + +In this guide, you'll learn how to use Arcade to authorize multiple tools before the agent runs. + +This involves the following steps: +- Get a list of all the tools that the agent will use +- Authorize all the tools (grouped by their Auth provider) +- Run the agent only after all the tools are authorized + + +This guide will rely on Arcade's built-in user verification system. If you want to run this in production, you'll need to implement a custom user verifier. Learn more about [user verification in production](/home/auth/secure-auth-production). + + +### Prerequisites + +- [Obtain an Arcade API key](/home/api-keys) +- [Install the Arcade Client](/home/installation) + + + +### Setup your environment + +Export your Arcade API key as an environment variable: +```bash +export ARCADE_API_KEY='{arcade_api_key}' +``` + +### Initialize the client + + + +```python +from arcadepy import Arcade + +client = Arcade() +user_id = "{arcade_user_id}" +``` + + +```javascript +import Arcade from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const user_id = "{arcade_user_id}"; +``` + + + +### Write a helper function to get the tools + +This guide is more useful when we are dealing with tools from multiple toolkits. We'll write a helper function to get the tools from the `Gmail` and `GitHub` toolkits. + + + +```python file=/examples/code/guides/tool-callling/get-tools-helper.py +``` + + +```python file=/examples/code/guides/tool-callling/get-tools-helper.py +``` + + + + +### Define the tools that the agent will use + +To make this example representative, we'll use tools from two different toolkits: `Gmail` and `GitHub`. + + + +```python +tools = await get_arcade_tools(client, toolkits=["Gmail", "GitHub"]) +``` + + +```javascript +const tools = await get_arcade_tools(client, toolkits=["Gmail", "GitHub"]) +``` + + + + diff --git a/pages/learn/tool-calling/call-a-tool-from-an-llm.mdx b/pages/learn/tool-calling/call-a-tool-from-an-llm.mdx new file mode 100644 index 00000000..d72af314 --- /dev/null +++ b/pages/learn/tool-calling/call-a-tool-from-an-llm.mdx @@ -0,0 +1 @@ +asd diff --git a/pages/home/auth/call-third-party-apis-directly.mdx b/pages/learn/tool-calling/call-third-party-apis-directly.mdx similarity index 100% rename from pages/home/auth/call-third-party-apis-directly.mdx rename to pages/learn/tool-calling/call-third-party-apis-directly.mdx diff --git a/pages/learn/tool-calling/call-tools-directly.mdx b/pages/learn/tool-calling/call-tools-directly.mdx new file mode 100644 index 00000000..d72af314 --- /dev/null +++ b/pages/learn/tool-calling/call-tools-directly.mdx @@ -0,0 +1 @@ +asd diff --git a/pages/learn/tool-calling/call-tools-with-mcp.mdx b/pages/learn/tool-calling/call-tools-with-mcp.mdx new file mode 100644 index 00000000..d72af314 --- /dev/null +++ b/pages/learn/tool-calling/call-tools-with-mcp.mdx @@ -0,0 +1 @@ +asd diff --git a/pages/home/use-tools/get-tool-definitions.mdx b/pages/learn/tool-calling/get-tool-definitions.mdx similarity index 100% rename from pages/home/use-tools/get-tool-definitions.mdx rename to pages/learn/tool-calling/get-tool-definitions.mdx diff --git a/pages/learn/tool-calling/hitl.mdx b/pages/learn/tool-calling/hitl.mdx new file mode 100644 index 00000000..e1476cfc --- /dev/null +++ b/pages/learn/tool-calling/hitl.mdx @@ -0,0 +1 @@ +sd diff --git a/pages/learn/tool-calling/jit-auth.mdx b/pages/learn/tool-calling/jit-auth.mdx new file mode 100644 index 00000000..d72af314 --- /dev/null +++ b/pages/learn/tool-calling/jit-auth.mdx @@ -0,0 +1 @@ +asd diff --git a/pages/home/auth/tool-auth-status.mdx b/pages/learn/tool-calling/tool-auth-status.mdx similarity index 100% rename from pages/home/auth/tool-auth-status.mdx rename to pages/learn/tool-calling/tool-auth-status.mdx diff --git a/public/images/icons/claude-dark.svg b/public/images/icons/claude-dark.svg new file mode 100644 index 00000000..1c6c0290 --- /dev/null +++ b/public/images/icons/claude-dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/icons/claude-light.svg b/public/images/icons/claude-light.svg new file mode 100644 index 00000000..97a6318c --- /dev/null +++ b/public/images/icons/claude-light.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/images/icons/cursor.svg b/public/images/icons/cursor.svg new file mode 100644 index 00000000..c074bf27 --- /dev/null +++ b/public/images/icons/cursor.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/icons/vscode-alt.svg b/public/images/icons/vscode-alt.svg new file mode 100644 index 00000000..66699157 --- /dev/null +++ b/public/images/icons/vscode-alt.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/icons/vscode.svg b/public/images/icons/vscode.svg new file mode 100644 index 00000000..c453e633 --- /dev/null +++ b/public/images/icons/vscode.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/Learn.tsx b/src/components/Learn.tsx new file mode 100644 index 00000000..ae4469d0 --- /dev/null +++ b/src/components/Learn.tsx @@ -0,0 +1,370 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { motion } from "framer-motion"; +import { + // Bot, + // Cloud, + // Code, + // Key, + // PencilRuler, + // Puzzle, + // RefreshCcw, + Rocket, + // Shield, + // Users, + Wrench, + // Zap, +} from "lucide-react"; +import Link from "next/link"; +// import { ChallengeSolution } from "./ChallengeSolution"; +// import { QuickStartCard } from "./QuickStartCard"; +import { SampleAppCard } from "./SampleAppCard"; +import Image from "next/image"; + +export function Learn() { + return ( +
+
+ + + +
+ Give your AI IDE access to Arcade.dev's documentation using our + llms.txt files ( + + short llms.txt + + ,{" "} + + llms-full.txt + + ), or use{" "} + + context7 + + . +
+
+
+ + ); +} diff --git a/src/components/MCPLanding.tsx b/src/components/MCPLanding.tsx new file mode 100644 index 00000000..6a440f6c --- /dev/null +++ b/src/components/MCPLanding.tsx @@ -0,0 +1,354 @@ +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { motion } from "framer-motion"; +import { + // Bot, + Cloud, + // Code, + // Key, + // PencilRuler, + // Puzzle, + // RefreshCcw, + Rocket, + // Shield, + Users, + Wrench, + // Zap, +} from "lucide-react"; +import Link from "next/link"; +// import { ChallengeSolution } from "./ChallengeSolution"; +import { QuickStartCard } from "./QuickStartCard"; +import { SampleAppCard } from "./SampleAppCard"; +import Image from "next/image"; + +export function MCPLanding() { + return ( +
+
+
+ + {/*
+
+
+

+ Start Building in Minutes +

+

+ Everything you need to get started with Arcade. +

+
+
+ + + + +
+
+
*/} + + {/*
+
+
+

+ Why Developers Choose Arcade +

+

+ Arcade solves the fundamental challenges that have limited AI + agents, empowering them to take meaningful actions in real-world + applications. +

+
+
+ + + + +
+
+
*/} + +
+
+
+

+ Sample Applications +

+

+ Get started quickly with our pre-built templates and example + applications. +

+
+
+ + + +
+
+
+ +
+
+
+

+ Arcade Overview +

+
+
+ {"arcade +
+
+ + + + +
+
+
+
+ ); +} diff --git a/src/components/SampleAppCard.tsx b/src/components/SampleAppCard.tsx index f88d8de8..48a56d17 100644 --- a/src/components/SampleAppCard.tsx +++ b/src/components/SampleAppCard.tsx @@ -5,7 +5,7 @@ import Link from "next/link"; interface SampleAppCardProps { title: string; - description: string; + description: string | React.ReactNode; image: string; href: string; blank?: boolean; @@ -41,9 +41,9 @@ export function SampleAppCard({

{title}

-

+

{description} -

+
diff --git a/tsconfig.json b/tsconfig.json index 17c5e0d9..73dcd6e8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -31,5 +31,5 @@ "pages/home/_meta.tsz", "src/components/pages/quickstart/prerequisites.mdx" ], - "exclude": ["node_modules", "agents"] + "exclude": ["node_modules", "agents", "examples"] }