From e1913fb39c277c5c11b0816f0b6affecdc1b1c6c Mon Sep 17 00:00:00 2001 From: Francisco Liberal Date: Thu, 11 Dec 2025 12:51:23 -0300 Subject: [PATCH 1/3] Pagerduty documentation --- app/en/mcp-servers/customer-support/_meta.tsx | 6 + .../customer-support/pagerduty/page.mdx | 518 ++++++++++++++++++ ...get_escalation_policy_example_call_tool.js | 23 + ...get_escalation_policy_example_call_tool.py | 22 + .../get_incident_example_call_tool.js | 23 + .../get_incident_example_call_tool.py | 22 + .../get_service_example_call_tool.js | 23 + .../get_service_example_call_tool.py | 22 + .../pagerduty/get_team_example_call_tool.js | 23 + .../pagerduty/get_team_example_call_tool.py | 22 + ...t_escalation_policies_example_call_tool.js | 24 + ...t_escalation_policies_example_call_tool.py | 23 + .../list_incidents_example_call_tool.js | 30 + .../list_incidents_example_call_tool.py | 29 + .../list_log_entries_example_call_tool.js | 29 + .../list_log_entries_example_call_tool.py | 28 + .../list_oncalls_example_call_tool.js | 30 + .../list_oncalls_example_call_tool.py | 29 + .../list_schedules_example_call_tool.js | 25 + .../list_schedules_example_call_tool.py | 24 + .../list_services_example_call_tool.js | 25 + .../list_services_example_call_tool.py | 24 + .../pagerduty/list_teams_example_call_tool.js | 24 + .../pagerduty/list_teams_example_call_tool.py | 23 + .../pagerduty/list_users_example_call_tool.js | 24 + .../pagerduty/list_users_example_call_tool.py | 23 + .../search_users_example_call_tool.js | 24 + .../search_users_example_call_tool.py | 23 + .../pagerduty/whoami_example_call_tool.js | 19 + .../pagerduty/whoami_example_call_tool.py | 18 + 30 files changed, 1202 insertions(+) create mode 100644 app/en/mcp-servers/customer-support/pagerduty/page.mdx create mode 100644 public/examples/integrations/mcp-servers/pagerduty/get_escalation_policy_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/get_escalation_policy_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/get_incident_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/get_incident_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/get_service_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/get_service_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/get_team_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/get_team_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_escalation_policies_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_escalation_policies_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_incidents_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_incidents_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_log_entries_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_log_entries_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_oncalls_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_oncalls_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_schedules_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_schedules_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_services_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_services_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_teams_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_teams_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_users_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/list_users_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/search_users_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/search_users_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pagerduty/whoami_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pagerduty/whoami_example_call_tool.py diff --git a/app/en/mcp-servers/customer-support/_meta.tsx b/app/en/mcp-servers/customer-support/_meta.tsx index ec6ce7446..c05c04093 100644 --- a/app/en/mcp-servers/customer-support/_meta.tsx +++ b/app/en/mcp-servers/customer-support/_meta.tsx @@ -11,6 +11,12 @@ const meta: MetaRecord = { zendesk: { title: "Zendesk", }, + pylon: { + title: "Pylon", + }, + pagerduty: { + title: "PagerDuty", + }, }; export default meta; diff --git a/app/en/mcp-servers/customer-support/pagerduty/page.mdx b/app/en/mcp-servers/customer-support/pagerduty/page.mdx new file mode 100644 index 000000000..3dd68b672 --- /dev/null +++ b/app/en/mcp-servers/customer-support/pagerduty/page.mdx @@ -0,0 +1,518 @@ +# PagerDuty + +import ToolInfo from "@/app/_components/tool-info"; +import Badges from "@/app/_components/badges"; +import TabbedCodeBlock from "@/app/_components/tabbed-code-block"; +import TableOfContents from "@/app/_components/table-of-contents"; +import ToolFooter from "@/app/_components/tool-footer"; +import { Callout } from "nextra/components"; + + + + + +The PagerDuty MCP Server lets agents list and inspect incidents, on-calls, services, teams, users, schedules, log entries, and escalation policies. Follows the Linear-style docs with code snippets in Python and JavaScript. + + +PagerDuty tools require OAuth2 with scopes per tool (e.g., incidents.read, users.read, oncalls.read). Configure the PagerDuty auth provider and consent users before calling tools. See [PagerDuty auth docs](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTYz-authentication). + + +## Available tools + + + + +If you need a tool that's not listed, [contact us](mailto:contact@arcade.dev) or [build your own](/home/build-tools/create-a-mcp-server). + + + +- `readOnlyHint` — reads data only +- `openWorldHint` — calls PagerDuty's external API +- `destructiveHint` — none of these tools delete data +- `idempotentHint` — repeating the same read call returns the same data + + +--- + +## User context + +### PagerDuty.WhoAmI + +Get the authenticated user's profile plus current on-call info. + + + +**Parameters** +None. + + +- `users.read`, `users:contact_methods.read`, `teams.read`, `oncalls.read` + + + +GET `/users/me`, GET `/oncalls` + + +--- + +## Incident tools + +### PagerDuty.ListIncidents + +List incidents with filters (status, urgency, services, teams, time window). + + + +**Parameters** +- **status** (`enum`, _optional_) Filter by status. +- **urgency** (`enum`, _optional_) Filter by urgency. +- **service_ids** (`array`, _optional_) Filter by service IDs. +- **team_ids** (`array`, _optional_) Filter by team IDs. +- **since** / **until** (`string`, _optional_) ISO-8601 time range. +- **limit** (`integer`, _optional_) 1-50, default 10. +- **offset** (`integer`, _optional_) Pagination offset. + + +- `incidents.read` + + + +GET `/incidents` + + +--- + +### PagerDuty.GetIncident + +Get a single incident by ID. + + + +**Parameters** +- **incident_id** (`string`, **required**) Incident ID. + + +- `incidents.read` + + + +GET `/incidents/{id}` + + +--- + +### PagerDuty.ListLogEntries + +List account log entries (activity feed). + + + +**Parameters** +- **since** / **until** (`string`, _optional_) ISO-8601 time range. +- **team_ids** (`array`, _optional_) Filter by team IDs. +- **time_zone** (`string`, _optional_) IANA time zone. +- **is_overview** (`boolean`, _optional_) Compact mode. Default: `true`. +- **limit** (`integer`, _optional_) 1-50. Default: 10. +- **offset** (`integer`, _optional_) Pagination offset. + + +- `incidents.read` + + + +GET `/log_entries` + + +--- + +## Escalation policy tools + +### PagerDuty.ListEscalationPolicies + +List escalation policies. + + + +**Parameters** +- **limit** (`integer`, _optional_) 1-50. Default: 10. +- **offset** (`integer`, _optional_) Pagination offset. + + +- `escalation_policies.read` + + + +GET `/escalation_policies` + + +--- + +### PagerDuty.GetEscalationPolicy + +Get escalation policy details. + + + +**Parameters** +- **escalation_policy_id** (`string`, **required**) Escalation policy ID. + + +- `escalation_policies.read` + + + +GET `/escalation_policies/{id}` + + +--- + +## Service tools + +### PagerDuty.ListServices + +List services (optional name search). + + + +**Parameters** +- **query** (`string`, _optional_) Search by name. +- **limit** (`integer`, _optional_) 1-50. Default: 10. +- **offset** (`integer`, _optional_) Pagination offset. + + +- `services.read` + + + +GET `/services` + + +--- + +### PagerDuty.GetService + +Get service details. + + + +**Parameters** +- **service_id** (`string`, **required**) Service ID. + + +- `services.read` + + + +GET `/services/{id}` + + +--- + +## Schedule tools + +### PagerDuty.ListSchedules + +List schedules with optional time zone and pagination. + + + +**Parameters** +- **limit** (`integer`, _optional_) 1-50. Default: 10. +- **offset** (`integer`, _optional_) Pagination offset. +- **time_zone** (`string`, _optional_) IANA time zone. + + +- `schedules.read`, `oncalls.read` + + + +GET `/schedules` + + +--- + +## On-call tools + +### PagerDuty.ListOnCalls + +List on-call entries with filters (schedule, escalation policy, team, time). + + + +**Parameters** +- **schedule_ids** (`array`, _optional_) Filter by schedules. +- **escalation_policy_ids** (`array`, _optional_) Filter by escalation policies. +- **team_ids** (`array`, _optional_) Filter by teams. +- **time_zone** (`string`, _optional_) IANA time zone. +- **since** / **until** (`string`, _optional_) ISO times. +- **limit** (`integer`, _optional_) 1-50. Default: 10. +- **offset** (`integer`, _optional_) Pagination offset. + + +- `oncalls.read`, `schedules.read`, `escalation_policies.read` + + + +GET `/oncalls` + + +--- + +## User tools + +### PagerDuty.ListUsers + +List users with pagination. + + + +**Parameters** +- **limit** (`integer`, _optional_) 1-50. Default: 10. +- **offset** (`integer`, _optional_) Pagination offset. + + +- `users.read` + + + +GET `/users` + + +--- + +### PagerDuty.SearchUsers + +Search users by name/email (fuzzy). + + + +**Parameters** +- **query** (`string`, **required**) Name or email fragment. +- **auto_accept_matches** (`boolean`, _optional_) Auto-accept above confidence threshold. Default: `false`. + + +- `users.read` + + + +GET `/users` (fuzzy match locally) + + +--- + +## Team tools + +### PagerDuty.ListTeams + +List teams with pagination. + + + +**Parameters** +- **limit** (`integer`, _optional_) 1-50. Default: 10. +- **offset** (`integer`, _optional_) Pagination offset. + + +- `teams.read` + + + +GET `/teams` + + +--- + +### PagerDuty.GetTeam + +Get team details (members, linked services/policies). + + + +**Parameters** +- **team_id** (`string`, **required**) Team ID. + + +- `teams.read`, `users.read`, `escalation_policies.read`, `services.read` + + + +GET `/teams/{id}` + + +--- + +## Auth + +PagerDuty requires OAuth2. Configure the PagerDuty auth provider and request the scopes shown above per tool. Tokens are passed as Bearer auth: + +``` +Authorization: Bearer +``` + +See PagerDuty auth docs: [PagerDuty API Authentication](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTYz-authentication). + + + diff --git a/public/examples/integrations/mcp-servers/pagerduty/get_escalation_policy_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/get_escalation_policy_example_call_tool.js new file mode 100644 index 000000000..5d8013549 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/get_escalation_policy_example_call_tool.js @@ -0,0 +1,23 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.GetEscalationPolicy"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + escalation_policy_id: "", +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/get_escalation_policy_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/get_escalation_policy_example_call_tool.py new file mode 100644 index 000000000..b55f648f6 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/get_escalation_policy_example_call_tool.py @@ -0,0 +1,22 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.GetEscalationPolicy" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "escalation_policy_id": "", +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/get_incident_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/get_incident_example_call_tool.js new file mode 100644 index 000000000..bea0b6be9 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/get_incident_example_call_tool.js @@ -0,0 +1,23 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.GetIncident"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + incident_id: "", +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/get_incident_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/get_incident_example_call_tool.py new file mode 100644 index 000000000..0da8360f8 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/get_incident_example_call_tool.py @@ -0,0 +1,22 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.GetIncident" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "incident_id": "", +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/get_service_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/get_service_example_call_tool.js new file mode 100644 index 000000000..e4ca91dde --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/get_service_example_call_tool.js @@ -0,0 +1,23 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.GetService"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + service_id: "", +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/get_service_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/get_service_example_call_tool.py new file mode 100644 index 000000000..c18d58372 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/get_service_example_call_tool.py @@ -0,0 +1,22 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.GetService" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "service_id": "", +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/get_team_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/get_team_example_call_tool.js new file mode 100644 index 000000000..6121d4a97 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/get_team_example_call_tool.js @@ -0,0 +1,23 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.GetTeam"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + team_id: "", +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/get_team_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/get_team_example_call_tool.py new file mode 100644 index 000000000..9594a47ce --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/get_team_example_call_tool.py @@ -0,0 +1,22 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.GetTeam" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "team_id": "", +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_escalation_policies_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/list_escalation_policies_example_call_tool.js new file mode 100644 index 000000000..87ad9d982 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_escalation_policies_example_call_tool.js @@ -0,0 +1,24 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.ListEscalationPolicies"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + limit: 10, + offset: null, +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_escalation_policies_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/list_escalation_policies_example_call_tool.py new file mode 100644 index 000000000..34ec9873d --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_escalation_policies_example_call_tool.py @@ -0,0 +1,23 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.ListEscalationPolicies" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "limit": 10, + "offset": None, +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_incidents_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/list_incidents_example_call_tool.js new file mode 100644 index 000000000..6576caa5a --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_incidents_example_call_tool.js @@ -0,0 +1,30 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.ListIncidents"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + status: null, + urgency: null, + service_ids: null, + team_ids: null, + since: null, + until: null, + limit: 10, + offset: null, +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_incidents_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/list_incidents_example_call_tool.py new file mode 100644 index 000000000..8c2a5cd8c --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_incidents_example_call_tool.py @@ -0,0 +1,29 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.ListIncidents" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "status": None, + "urgency": None, + "service_ids": None, + "team_ids": None, + "since": None, + "until": None, + "limit": 10, + "offset": None, +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_log_entries_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/list_log_entries_example_call_tool.js new file mode 100644 index 000000000..b7db7970d --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_log_entries_example_call_tool.js @@ -0,0 +1,29 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.ListLogEntries"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + since: null, + until: null, + team_ids: null, + time_zone: null, + is_overview: true, + limit: 10, + offset: null, +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_log_entries_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/list_log_entries_example_call_tool.py new file mode 100644 index 000000000..1363fb0e8 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_log_entries_example_call_tool.py @@ -0,0 +1,28 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.ListLogEntries" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "since": None, + "until": None, + "team_ids": None, + "time_zone": None, + "is_overview": True, + "limit": 10, + "offset": None, +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_oncalls_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/list_oncalls_example_call_tool.js new file mode 100644 index 000000000..803ea2faf --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_oncalls_example_call_tool.js @@ -0,0 +1,30 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.ListOnCalls"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + schedule_ids: null, + escalation_policy_ids: null, + team_ids: null, + time_zone: null, + since: null, + until: null, + limit: 10, + offset: null, +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_oncalls_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/list_oncalls_example_call_tool.py new file mode 100644 index 000000000..59ccf3612 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_oncalls_example_call_tool.py @@ -0,0 +1,29 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.ListOnCalls" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "schedule_ids": None, + "escalation_policy_ids": None, + "team_ids": None, + "time_zone": None, + "since": None, + "until": None, + "limit": 10, + "offset": None, +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_schedules_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/list_schedules_example_call_tool.js new file mode 100644 index 000000000..81bc11fab --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_schedules_example_call_tool.js @@ -0,0 +1,25 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.ListSchedules"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + limit: 10, + offset: null, + time_zone: null, +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_schedules_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/list_schedules_example_call_tool.py new file mode 100644 index 000000000..8930baa67 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_schedules_example_call_tool.py @@ -0,0 +1,24 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.ListSchedules" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "limit": 10, + "offset": None, + "time_zone": None, +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_services_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/list_services_example_call_tool.js new file mode 100644 index 000000000..ec8c8ea70 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_services_example_call_tool.js @@ -0,0 +1,25 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.ListServices"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + query: null, + limit: 10, + offset: null, +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_services_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/list_services_example_call_tool.py new file mode 100644 index 000000000..5303d632e --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_services_example_call_tool.py @@ -0,0 +1,24 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.ListServices" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "query": None, + "limit": 10, + "offset": None, +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_teams_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/list_teams_example_call_tool.js new file mode 100644 index 000000000..914118ecf --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_teams_example_call_tool.js @@ -0,0 +1,24 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.ListTeams"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + limit: 10, + offset: null, +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_teams_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/list_teams_example_call_tool.py new file mode 100644 index 000000000..92e532959 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_teams_example_call_tool.py @@ -0,0 +1,23 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.ListTeams" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "limit": 10, + "offset": None, +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_users_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/list_users_example_call_tool.js new file mode 100644 index 000000000..077a5a5f2 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_users_example_call_tool.js @@ -0,0 +1,24 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.ListUsers"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + limit: 10, + offset: null, +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/list_users_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/list_users_example_call_tool.py new file mode 100644 index 000000000..e7e24f7d6 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/list_users_example_call_tool.py @@ -0,0 +1,23 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.ListUsers" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "limit": 10, + "offset": None, +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/search_users_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/search_users_example_call_tool.js new file mode 100644 index 000000000..e8f59ff56 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/search_users_example_call_tool.js @@ -0,0 +1,24 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.SearchUsers"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const toolInput = { + query: "Alex", + auto_accept_matches: false, +}; + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: toolInput, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/search_users_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/search_users_example_call_tool.py new file mode 100644 index 000000000..a62cdbfe3 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/search_users_example_call_tool.py @@ -0,0 +1,23 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.SearchUsers" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +tool_input = { + "query": "Alex", + "auto_accept_matches": False, +} + +response = client.tools.execute( + tool_name=TOOL_NAME, + input=tool_input, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) diff --git a/public/examples/integrations/mcp-servers/pagerduty/whoami_example_call_tool.js b/public/examples/integrations/mcp-servers/pagerduty/whoami_example_call_tool.js new file mode 100644 index 000000000..9e0b7d02d --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/whoami_example_call_tool.js @@ -0,0 +1,19 @@ +import { Arcade } from "@arcadeai/arcadejs"; + +const client = new Arcade(); +const USER_ID = "{arcade_user_id}"; +const TOOL_NAME = "PagerDuty.WhoAmI"; + +const authResponse = await client.tools.authorize({ tool_name: TOOL_NAME, user_id: USER_ID }); +if (authResponse.status !== "completed") { + console.log(`Authorize here: ${authResponse.url}`); +} +await client.auth.waitForCompletion(authResponse); + +const response = await client.tools.execute({ + tool_name: TOOL_NAME, + input: {}, + user_id: USER_ID, +}); + +console.log(JSON.stringify(response.output.value, null, 2)); diff --git a/public/examples/integrations/mcp-servers/pagerduty/whoami_example_call_tool.py b/public/examples/integrations/mcp-servers/pagerduty/whoami_example_call_tool.py new file mode 100644 index 000000000..fec657a13 --- /dev/null +++ b/public/examples/integrations/mcp-servers/pagerduty/whoami_example_call_tool.py @@ -0,0 +1,18 @@ +import json +from arcadepy import Arcade + +client = Arcade() +USER_ID = "{arcade_user_id}" +TOOL_NAME = "PagerDuty.WhoAmI" + +auth_response = client.tools.authorize(tool_name=TOOL_NAME, user_id=USER_ID) +if auth_response.status != "completed": + print(f"Authorize here: {auth_response.url}") +client.auth.wait_for_completion(auth_response) + +response = client.tools.execute( + tool_name=TOOL_NAME, + input={}, + user_id=USER_ID, +) +print(json.dumps(response.output.value, indent=2)) From 9fe61c2dc06aac18425813eedb40c0992d56a044 Mon Sep 17 00:00:00 2001 From: Francisco Liberal Date: Thu, 18 Dec 2025 12:26:33 -0300 Subject: [PATCH 2/3] merge --- app/_components/footer.tsx | 5 + app/_components/guide-overview.tsx | 2 +- app/_components/scope-picker.tsx | 122 + app/en/home/_meta.tsx | 11 +- app/en/home/arcade-cli/page.mdx | 191 +- app/en/home/auth-providers/google/page.mdx | 54 +- .../home/auth/secure-auth-production/page.mdx | 75 +- app/en/home/build-tools/_meta.tsx | 2 + .../secure-your-mcp-server/page.mdx | 485 +++ .../server-level-vs-tool-level-auth/page.mdx | 223 ++ app/en/home/changelog/page.mdx | 20 + app/en/home/landing-page.tsx | 8 +- .../home/mcp-clients/claude-desktop/page.mdx | 38 +- app/en/home/mcp-gateway-quickstart/page.mdx | 210 ++ app/en/home/quickstart/page.mdx | 506 ++- .../serve-tools/securing-arcade-mcp/page.mdx | 19 +- app/en/mcp-servers/components/filters-bar.tsx | 4 +- .../customer-support/pylon/page.mdx | 518 +++ .../mcp-servers/productivity/gmail/page.mdx | 92 + .../productivity/google-calendar/page.mdx | 100 + .../productivity/google-contacts/page.mdx | 61 + .../productivity/google-docs/page.mdx | 89 + .../productivity/google-drive/page.mdx | 379 ++- .../google-drive/reference/page.mdx | 107 +- .../productivity/google-sheets/page.mdx | 116 + .../productivity/google-slides/page.mdx | 83 + .../productivity/luma-api/page.mdx | 1313 ++++++++ app/globals.css | 5 + biome.jsonc | 1 - package.json | 8 +- pnpm-lock.yaml | 2924 ++++++++--------- .../gmail/who_am_i_example_call_tool.js | 4 + .../gmail/who_am_i_example_call_tool.py | 4 + .../create_contact_example_call_tool.js | 3 + .../create_contact_example_call_tool.py | 3 + ...rch_contacts_by_email_example_call_tool.js | 2 + ...rch_contacts_by_email_example_call_tool.py | 2 + ...arch_contacts_by_name_example_call_tool.js | 2 + ...arch_contacts_by_name_example_call_tool.py | 2 + ...tacts_by_phone_number_example_call_tool.js | 34 + ...tacts_by_phone_number_example_call_tool.py | 31 + .../comment_on_document_example_call_tool.js | 2 + .../comment_on_document_example_call_tool.py | 2 + ...create_blank_document_example_call_tool.js | 2 + ...create_blank_document_example_call_tool.py | 2 + ...te_document_from_text_example_call_tool.js | 2 + ...te_document_from_text_example_call_tool.py | 2 + .../docs/edit_document_example_call_tool.js | 2 + .../docs/edit_document_example_call_tool.py | 2 + .../get_document_by_id_example_call_tool.js | 2 + .../get_document_by_id_example_call_tool.py | 2 + ...get_document_metadata_example_call_tool.js | 2 + ...get_document_metadata_example_call_tool.py | 2 + ...xt_at_end_of_document_example_call_tool.js | 2 + ...xt_at_end_of_document_example_call_tool.py | 2 + ...ist_document_comments_example_call_tool.js | 2 + ...ist_document_comments_example_call_tool.py | 2 + ...nd_retrieve_documents_example_call_tool.js | 2 + ...nd_retrieve_documents_example_call_tool.py | 2 + .../search_documents_example_call_tool.js | 2 + .../search_documents_example_call_tool.py | 2 + .../google/docs/who_am_i_example_call_tool.js | 4 + .../google/docs/who_am_i_example_call_tool.py | 4 + .../delete_draft_email_example_call_tool.js | 2 + .../delete_draft_email_example_call_tool.py | 2 + .../gmail/get_thread_example_call_tool.js | 2 + .../gmail/get_thread_example_call_tool.py | 2 + .../list_draft_emails_example_call_tool.js | 2 + .../list_draft_emails_example_call_tool.py | 2 + ...list_emails_by_header_example_call_tool.js | 2 + ...list_emails_by_header_example_call_tool.py | 2 + .../gmail/list_emails_example_call_tool.js | 2 + .../gmail/list_emails_example_call_tool.py | 2 + .../gmail/list_threads_example_call_tool.js | 2 + .../gmail/list_threads_example_call_tool.py | 2 + .../gmail/search_threads_example_call_tool.js | 2 + .../gmail/search_threads_example_call_tool.py | 2 + .../send_draft_email_example_call_tool.js | 2 + .../send_draft_email_example_call_tool.py | 2 + .../gmail/send_email_example_call_tool.js | 2 + .../gmail/send_email_example_call_tool.py | 2 + .../gmail/trash_email_example_call_tool.js | 2 + .../gmail/trash_email_example_call_tool.py | 2 + .../update_draft_email_example_call_tool.js | 2 + .../update_draft_email_example_call_tool.py | 2 + .../write_draft_email_example_call_tool.js | 2 + .../write_draft_email_example_call_tool.py | 2 + ...mment_on_presentation_example_call_tool.js | 2 + ...mment_on_presentation_example_call_tool.py | 2 + .../create_presentation_example_call_tool.js | 2 + .../create_presentation_example_call_tool.py | 2 + .../slides/create_slide_example_call_tool.js | 2 + .../slides/create_slide_example_call_tool.py | 2 + ...sentation_as_markdown_example_call_tool.js | 2 + ...sentation_as_markdown_example_call_tool.py | 2 + ...presentation_comments_example_call_tool.js | 2 + ...presentation_comments_example_call_tool.py | 2 + .../search_presentations_example_call_tool.js | 2 + .../search_presentations_example_call_tool.py | 2 + .../slides/who_am_i_example_call_tool.js | 4 + .../slides/who_am_i_example_call_tool.py | 4 + .../create_event_example_call_tool.js | 3 + .../create_event_example_call_tool.py | 3 + .../delete_event_example_call_tool.js | 2 + .../delete_event_example_call_tool.py | 2 + ...when_everyone_is_free_example_call_tool.js | 2 + ...when_everyone_is_free_example_call_tool.py | 2 + .../list_calendars_example_call_tool.js | 3 + .../list_calendars_example_call_tool.py | 3 + .../list_events_example_call_tool.js | 3 + .../list_events_example_call_tool.py | 3 + .../update_event_example_call_tool.js | 2 + .../update_event_example_call_tool.py | 2 + .../who_am_i_example_call_tool.js | 4 + .../who_am_i_example_call_tool.py | 4 + .../who_am_i_example_call_tool.js | 4 + .../who_am_i_example_call_tool.py | 4 + .../google_docs/who_am_i_example_call_tool.js | 4 + .../google_docs/who_am_i_example_call_tool.py | 4 + .../create_folder_example_call_tool.js | 32 + .../create_folder_example_call_tool.py | 30 + .../download_file_chunk_example_call_tool.js | 112 + .../download_file_chunk_example_call_tool.py | 104 + .../download_file_example_call_tool.js | 112 + .../download_file_example_call_tool.py | 104 + ...oogle_file_picker_url_example_call_tool.py | 4 +- ...t_file_tree_structure_example_call_tool.js | 9 +- ...t_file_tree_structure_example_call_tool.py | 9 +- .../move_file_example_call_tool.js | 33 + .../move_file_example_call_tool.py | 31 + .../rename_file_example_call_tool.js | 32 + .../rename_file_example_call_tool.py | 30 + .../search_files_example_call_tool.js | 7 +- .../search_files_example_call_tool.py | 10 +- .../share_file_example_call_tool.js | 35 + .../share_file_example_call_tool.py | 33 + .../upload_file_example_call_tool.js | 33 + .../upload_file_example_call_tool.py | 31 + .../who_am_i_example_call_tool.js | 6 +- .../who_am_i_example_call_tool.py | 10 +- .../add_note_to_cell_example_call_tool.js | 2 + .../add_note_to_cell_example_call_tool.py | 2 + .../create_spreadsheet_example_call_tool.js | 2 + .../create_spreadsheet_example_call_tool.py | 2 + .../get_spreadsheet_example_call_tool.js | 2 + .../get_spreadsheet_example_call_tool.py | 2 + ..._spreadsheet_metadata_example_call_tool.js | 2 + ..._spreadsheet_metadata_example_call_tool.py | 2 + .../search_spreadsheets_example_call_tool.js | 2 + .../search_spreadsheets_example_call_tool.py | 2 + .../update_cells_example_call_tool.js | 2 + .../update_cells_example_call_tool.py | 2 + .../who_am_i_example_call_tool.js | 4 + .../who_am_i_example_call_tool.py | 4 + .../write_to_cell_example_call_tool.js | 2 + .../write_to_cell_example_call_tool.py | 2 + .../add_event_guests_example_call_tool.js | 32 + .../add_event_guests_example_call_tool.py | 31 + .../add_event_host_example_call_tool.js | 32 + .../add_event_host_example_call_tool.py | 31 + ...vent_to_luma_calendar_example_call_tool.js | 32 + ...vent_to_luma_calendar_example_call_tool.py | 29 + ...er_to_membership_tier_example_call_tool.js | 32 + ...er_to_membership_tier_example_call_tool.py | 29 + ...g_to_calendar_members_example_call_tool.js | 35 + ...g_to_calendar_members_example_call_tool.py | 29 + ...check_event_existence_example_call_tool.js | 33 + ...check_event_existence_example_call_tool.py | 31 + .../create_event_coupon_example_call_tool.js | 32 + .../create_event_coupon_example_call_tool.py | 30 + .../create_event_example_call_tool.js | 32 + .../create_event_example_call_tool.py | 31 + ...ate_event_ticket_type_example_call_tool.js | 32 + ...ate_event_ticket_type_example_call_tool.py | 30 + .../create_person_tag_example_call_tool.js | 32 + .../create_person_tag_example_call_tool.py | 29 + .../delete_person_tag_example_call_tool.js | 31 + .../delete_person_tag_example_call_tool.py | 29 + ...generate_event_coupon_example_call_tool.js | 32 + ...generate_event_coupon_example_call_tool.py | 29 + .../generate_upload_url_example_call_tool.js | 32 + .../generate_upload_url_example_call_tool.py | 29 + .../get_event_admin_info_example_call_tool.js | 31 + .../get_event_admin_info_example_call_tool.py | 29 + .../get_event_guest_example_call_tool.js | 32 + .../get_event_guest_example_call_tool.py | 29 + .../get_event_guests_example_call_tool.js | 35 + .../get_event_guests_example_call_tool.py | 33 + ...get_ticket_type_by_id_example_call_tool.js | 32 + ...get_ticket_type_by_id_example_call_tool.py | 29 + .../get_user_info_example_call_tool.js | 29 + .../get_user_info_example_call_tool.py | 29 + ...rt_people_to_calendar_example_call_tool.js | 32 + ...rt_people_to_calendar_example_call_tool.py | 31 + ...list_calendar_coupons_example_call_tool.js | 31 + ...list_calendar_coupons_example_call_tool.py | 29 + .../list_event_coupons_example_call_tool.js | 32 + .../list_event_coupons_example_call_tool.py | 29 + ...st_event_ticket_types_example_call_tool.js | 32 + ...st_event_ticket_types_example_call_tool.py | 29 + ..._luma_calendar_events_example_call_tool.js | 35 + ..._luma_calendar_events_example_call_tool.py | 33 + ...list_membership_tiers_example_call_tool.js | 31 + ...list_membership_tiers_example_call_tool.py | 29 + .../luma_api/list_people_example_call_tool.js | 35 + .../luma_api/list_people_example_call_tool.py | 33 + .../list_person_tags_example_call_tool.js | 33 + .../list_person_tags_example_call_tool.py | 29 + .../luma_entity_lookup_example_call_tool.js | 31 + .../luma_entity_lookup_example_call_tool.py | 29 + .../modify_coupon_example_call_tool.js | 32 + .../modify_coupon_example_call_tool.py | 30 + ...from_calendar_members_example_call_tool.js | 35 + ...from_calendar_members_example_call_tool.py | 30 + ...nd_guest_event_invite_example_call_tool.js | 32 + ...nd_guest_event_invite_example_call_tool.py | 30 + ...ft_delete_ticket_type_example_call_tool.js | 31 + ...ft_delete_ticket_type_example_call_tool.py | 29 + .../update_coupon_example_call_tool.js | 32 + .../update_coupon_example_call_tool.py | 29 + .../update_event_example_call_tool.js | 32 + .../update_event_example_call_tool.py | 31 + .../update_guest_status_example_call_tool.js | 32 + .../update_guest_status_example_call_tool.py | 29 + ...ate_membership_status_example_call_tool.js | 32 + ...ate_membership_status_example_call_tool.py | 29 + .../update_person_tag_example_call_tool.js | 33 + .../update_person_tag_example_call_tool.py | 29 + ...et_type_configuration_example_call_tool.js | 32 + ...et_type_configuration_example_call_tool.py | 29 + .../pylon/add_message_example_call_tool.js | 20 + .../pylon/add_message_example_call_tool.py | 22 + .../pylon/assign_issue_example_call_tool.js | 22 + .../pylon/assign_issue_example_call_tool.py | 24 + .../pylon/get_issue_example_call_tool.js | 20 + .../pylon/get_issue_example_call_tool.py | 22 + ...t_team_and_assignment_example_call_tool.js | 20 + ...t_team_and_assignment_example_call_tool.py | 22 + .../pylon/list_contacts_example_call_tool.js | 18 + .../pylon/list_contacts_example_call_tool.py | 20 + .../pylon/list_issues_example_call_tool.js | 24 + .../pylon/list_issues_example_call_tool.py | 26 + .../pylon/list_teams_example_call_tool.js | 18 + .../pylon/list_teams_example_call_tool.py | 20 + .../pylon/list_users_example_call_tool.js | 19 + .../pylon/list_users_example_call_tool.py | 21 + .../search_contacts_example_call_tool.js | 19 + .../search_contacts_example_call_tool.py | 21 + .../pylon/search_issues_example_call_tool.js | 19 + .../pylon/search_issues_example_call_tool.py | 21 + .../pylon/search_users_example_call_tool.js | 19 + .../pylon/search_users_example_call_tool.py | 21 + .../update_issue_status_example_call_tool.js | 21 + .../update_issue_status_example_call_tool.py | 23 + .../pylon/who_am_i_example_call_tool.js | 14 + .../pylon/who_am_i_example_call_tool.py | 16 + .../mcp-gateway/create-mcp-gateway-dark.png | Bin 0 -> 161954 bytes .../mcp-gateway/create-mcp-gateway-light.png | Bin 0 -> 165531 bytes public/images/mcp-gateway/mcp-url-dark.png | Bin 0 -> 94944 bytes public/images/mcp-gateway/mcp-url-light.png | Bin 0 -> 97581 bytes .../images/mcp-gateway/tool-picker-dark.png | Bin 0 -> 434188 bytes .../images/mcp-gateway/tool-picker-light.png | Bin 0 -> 474261 bytes 262 files changed, 9917 insertions(+), 1793 deletions(-) create mode 100644 app/_components/scope-picker.tsx create mode 100644 app/en/home/build-tools/secure-your-mcp-server/page.mdx create mode 100644 app/en/home/build-tools/server-level-vs-tool-level-auth/page.mdx create mode 100644 app/en/home/mcp-gateway-quickstart/page.mdx create mode 100644 app/en/mcp-servers/customer-support/pylon/page.mdx create mode 100644 app/en/mcp-servers/productivity/luma-api/page.mdx create mode 100644 public/examples/integrations/mcp-servers/google/contacts/search_contacts_by_phone_number_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/google/contacts/search_contacts_by_phone_number_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/google_drive/create_folder_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/google_drive/create_folder_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/google_drive/download_file_chunk_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/google_drive/download_file_chunk_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/google_drive/download_file_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/google_drive/download_file_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/google_drive/move_file_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/google_drive/move_file_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/google_drive/rename_file_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/google_drive/rename_file_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/google_drive/share_file_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/google_drive/share_file_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/google_drive/upload_file_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/google_drive/upload_file_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/add_event_guests_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/add_event_guests_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/add_event_host_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/add_event_host_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/add_event_to_luma_calendar_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/add_event_to_luma_calendar_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/add_user_to_membership_tier_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/add_user_to_membership_tier_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/apply_tag_to_calendar_members_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/apply_tag_to_calendar_members_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/check_event_existence_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/check_event_existence_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/create_event_coupon_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/create_event_coupon_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/create_event_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/create_event_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/create_event_ticket_type_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/create_event_ticket_type_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/create_person_tag_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/create_person_tag_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/delete_person_tag_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/delete_person_tag_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/generate_event_coupon_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/generate_event_coupon_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/generate_upload_url_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/generate_upload_url_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_event_admin_info_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_event_admin_info_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_event_guest_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_event_guest_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_event_guests_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_event_guests_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_ticket_type_by_id_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_ticket_type_by_id_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_user_info_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/get_user_info_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/import_people_to_calendar_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/import_people_to_calendar_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_calendar_coupons_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_calendar_coupons_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_event_coupons_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_event_coupons_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_event_ticket_types_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_event_ticket_types_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_luma_calendar_events_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_luma_calendar_events_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_membership_tiers_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_membership_tiers_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_people_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_people_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_person_tags_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/list_person_tags_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/luma_entity_lookup_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/luma_entity_lookup_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/modify_coupon_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/modify_coupon_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/remove_tag_from_calendar_members_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/remove_tag_from_calendar_members_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/send_guest_event_invite_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/send_guest_event_invite_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/soft_delete_ticket_type_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/soft_delete_ticket_type_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_coupon_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_coupon_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_event_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_event_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_guest_status_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_guest_status_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_membership_status_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_membership_status_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_person_tag_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_person_tag_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_ticket_type_configuration_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/luma_api/update_ticket_type_configuration_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/add_message_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/add_message_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/assign_issue_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/assign_issue_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/get_issue_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/get_issue_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/get_team_and_assignment_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/get_team_and_assignment_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/list_contacts_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/list_contacts_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/list_issues_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/list_issues_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/list_teams_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/list_teams_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/list_users_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/list_users_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/search_contacts_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/search_contacts_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/search_issues_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/search_issues_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/search_users_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/search_users_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/update_issue_status_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/update_issue_status_example_call_tool.py create mode 100644 public/examples/integrations/mcp-servers/pylon/who_am_i_example_call_tool.js create mode 100644 public/examples/integrations/mcp-servers/pylon/who_am_i_example_call_tool.py create mode 100644 public/images/mcp-gateway/create-mcp-gateway-dark.png create mode 100644 public/images/mcp-gateway/create-mcp-gateway-light.png create mode 100644 public/images/mcp-gateway/mcp-url-dark.png create mode 100644 public/images/mcp-gateway/mcp-url-light.png create mode 100644 public/images/mcp-gateway/tool-picker-dark.png create mode 100644 public/images/mcp-gateway/tool-picker-light.png diff --git a/app/_components/footer.tsx b/app/_components/footer.tsx index a9f6cbb88..31fd9efcf 100644 --- a/app/_components/footer.tsx +++ b/app/_components/footer.tsx @@ -110,6 +110,11 @@ const Footer: React.FC = () => { title: "GitHub Container Registry", external: true, }, + { + url: "https://status.arcade.dev/", + title: "Status", + external: true, + }, ]; const resources: Resource[] = [ diff --git a/app/_components/guide-overview.tsx b/app/_components/guide-overview.tsx index f26939e61..b769291e1 100644 --- a/app/_components/guide-overview.tsx +++ b/app/_components/guide-overview.tsx @@ -30,7 +30,7 @@ export function GuideOverview({ children, className }: GuideOverviewProps) { return (
diff --git a/app/_components/scope-picker.tsx b/app/_components/scope-picker.tsx new file mode 100644 index 000000000..782e1f9ea --- /dev/null +++ b/app/_components/scope-picker.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { useState } from "react"; + +interface Tool { + name: string; + scopes: string[]; +} + +interface ScopePickerProps { + tools: Tool[]; +} + +export default function ScopePicker({ tools }: ScopePickerProps) { + const [selectedTools, setSelectedTools] = useState>(new Set()); + + const toggleTool = (toolName: string) => { + const newSelected = new Set(selectedTools); + if (newSelected.has(toolName)) { + newSelected.delete(toolName); + } else { + newSelected.add(toolName); + } + setSelectedTools(newSelected); + }; + + const selectAll = () => { + setSelectedTools(new Set(tools.map((t) => t.name))); + }; + + const clearAll = () => { + setSelectedTools(new Set()); + }; + + // Get unique scopes from selected tools + const requiredScopes = Array.from( + new Set( + tools.filter((t) => selectedTools.has(t.name)).flatMap((t) => t.scopes) + ) + ).sort(); + + return ( +
+
+
+

+ Scope calculator +

+
+ + +
+
+

+ Select the tools you plan to use to see the required OAuth scopes. +

+
+ +
+
+ {tools.map((tool) => ( + + ))} +
+ +
+

+ Required scopes{" "} + {selectedTools.size > 0 && ( + + ({requiredScopes.length}) + + )} +

+ {requiredScopes.length > 0 ? ( +
    + {requiredScopes.map((scope) => ( +
  • + {scope} +
  • + ))} +
+ ) : ( +

+ Select tools above to see required scopes +

+ )} +
+
+
+ ); +} diff --git a/app/en/home/_meta.tsx b/app/en/home/_meta.tsx index ff7c4d069..056b3d3dc 100644 --- a/app/en/home/_meta.tsx +++ b/app/en/home/_meta.tsx @@ -1,4 +1,4 @@ -import { BadgeHelp, Globe, HeartPulse, Home, Shield } from "lucide-react"; +import { BadgeHelp, Globe, Home, Shield } from "lucide-react"; import type { MetaRecord } from "nextra"; function TitleWithIcon({ @@ -40,7 +40,10 @@ export const meta: MetaRecord = { title: "Using Arcade", }, quickstart: { - title: "Hosted Tools Quickstart", + title: "Calling tools in your agent", + }, + "mcp-gateway-quickstart": { + title: "Call a tool in your IDE/MCP Client", }, "custom-mcp-server-quickstart": { title: "Build MCP Server QuickStart", @@ -151,10 +154,6 @@ export const meta: MetaRecord = { security: { title: Security, }, - status: { - title: Status, - href: "https://status.arcade.dev/", - }, }; export default meta; diff --git a/app/en/home/arcade-cli/page.mdx b/app/en/home/arcade-cli/page.mdx index 094c3bebf..c0539f4e6 100644 --- a/app/en/home/arcade-cli/page.mdx +++ b/app/en/home/arcade-cli/page.mdx @@ -41,36 +41,39 @@ In your terminal, run the following command to install the `arcade-mcp` package Usage: arcade [OPTIONS] COMMAND [ARGS]... Arcade CLI - Build, deploy, and manage MCP servers and AI tools. Create - new projects, run servers with multiple transports, configure clients, and - deploy to Arcade Cloud. - -╭─ Options ────────────────────────────────────────────────────────────────╮ -│ --version -v Print version and exit. │ -│ --help -h Show this message and exit. │ -╰──────────────────────────────────────────────────────────────────────────╯ -╭─ User ───────────────────────────────────────────────────────────────────╮ -│ login Log in to Arcade Cloud │ -│ logout Log out of Arcade Cloud │ -│ dashboard Open the Arcade Dashboard in a web browser │ -╰──────────────────────────────────────────────────────────────────────────╯ -╭─ Build ──────────────────────────────────────────────────────────────────╮ -│ new Create a new server package directory. Example usage: arcade new │ -│ my_mcp_server │ -│ show Show the installed tools or details of a specific tool │ -│ evals Run tool calling evaluations │ -╰──────────────────────────────────────────────────────────────────────────╯ -╭─ Run ────────────────────────────────────────────────────────────────────╮ -│ mcp Run MCP servers with different transports │ -│ deploy Deploy MCP servers to Arcade │ -╰──────────────────────────────────────────────────────────────────────────╯ -╭─ Manage ─────────────────────────────────────────────────────────────────╮ -│ configure Configure MCP clients to connect to your server │ -│ server Manage deployments of tool servers (logs, list, etc) │ -│ secret Manage tool secrets in the cloud (set, unset, list) │ -╰──────────────────────────────────────────────────────────────────────────╯ + new projects, run servers with multiple transports, configure clients, + and deploy to Arcade Cloud. + +╭─ Options ──────────────────────────────────────────────────────────────╮ +│ --version -v Print version and exit. │ +│ --help -h Show this message and exit. │ +╰────────────────────────────────────────────────────────────────────────╯ +╭─ User ─────────────────────────────────────────────────────────────────╮ +│ login Log in to Arcade │ +│ logout Log out of Arcade │ +│ whoami Show current login status and active context │ +│ dashboard Open the Arcade Dashboard in a web browser │ +│ org Manage organizations (list, set active) │ +│ project Manage projects (list, set active) │ +╰────────────────────────────────────────────────────────────────────────╯ +╭─ Build ────────────────────────────────────────────────────────────────╮ +│ new Create a new server package directory. Example usage: arcade │ +│ new my_mcp_server │ +│ show Show the installed tools or details of a specific tool │ +│ evals Run tool calling evaluations │ +╰────────────────────────────────────────────────────────────────────────╯ +╭─ Run ──────────────────────────────────────────────────────────────────╮ +│ mcp Run MCP servers with different transports │ +│ deploy Deploy MCP servers to Arcade │ +╰────────────────────────────────────────────────────────────────────────╯ +╭─ Manage ───────────────────────────────────────────────────────────────╮ +│ configure Configure MCP clients to connect to your server │ +│ server Manage deployments of tool servers (logs, list, etc) │ +│ secret Manage tool secrets in the cloud (set, unset, list) │ +╰────────────────────────────────────────────────────────────────────────╯ Pro tip: use --help after any command to see command-specific options. -``` + ``` You can learn more about any of the commands by running `arcade --help`, e.g. `arcade new --help`. @@ -79,62 +82,118 @@ You can learn more about any of the commands by running `arcade --help ```bash Usage: arcade login [OPTIONS] - Log in to Arcade Cloud - -╭─ Options ────────────────────────────────────────────────────────────────╮ -│ --host -h TEXT The Arcade Cloud host to log in to. │ -│ [default: cloud.arcade.dev] │ -│ --port -p INTEGER The port of the Arcade Cloud host (if │ -│ running locally). │ -│ [default: None] │ -│ --callback-host TEXT The host to use to complete the auth │ -│ flow - this should be the same as the │ -│ host that the CLI is running on. │ -│ Include the port if needed. │ -│ [default: None] │ -│ --debug -d Show debug information │ -│ --help Show this message and exit. │ -╰──────────────────────────────────────────────────────────────────────────╯ + Log in to Arcade + +╭─ Options ──────────────────────────────────────────────────────────────╮ +│ --host -h TEXT The Arcade Coordinator host to log in to. │ +│ [default: cloud.arcade.dev] │ +│ --port -p INTEGER The port of the Arcade Coordinator host (if │ +│ running locally). │ +│ [default: None] │ +│ --debug -d Show debug information │ +│ --help Show this message and exit. │ +╰────────────────────────────────────────────────────────────────────────╯ ``` ## `arcade logout` ```bash - Usage: arcade logout [OPTIONS] + Usage: arcade logout [OPTIONS] - Log out of Arcade Cloud + Log out of Arcade -╭─ Options ────────────────────────────────────────────────────────────────╮ -│ --debug -d Show debug information │ -│ --help -h Show this message and exit. │ -╰──────────────────────────────────────────────────────────────────────────╯ +╭─ Options ──────────────────────────────────────────────────────────────╮ +│ --debug -d Show debug information │ +│ --help -h Show this message and exit. │ +╰────────────────────────────────────────────────────────────────────────╯ ``` -## `arcade dashboard` +## `arcade whoami` ```bash + Usage: arcade whoami [OPTIONS] + + Show current login status and active context +╭─ Options ──────────────────────────────────────────────────────────────╮ +│ --debug -d Show debug information │ +│ --help -h Show this message and exit. │ +╰────────────────────────────────────────────────────────────────────────╯ +``` + +## `arcade dashboard` + +```bash Usage: arcade dashboard [OPTIONS] Open the Arcade Dashboard in a web browser -╭─ Options ────────────────────────────────────────────────────────────────╮ -│ --host -h TEXT The Arcade Engine host that serves the │ -│ dashboard. │ -│ [default: api.arcade.dev] │ -│ --port -p INTEGER The port of the Arcade Engine. │ -│ [default: None] │ -│ --local -l Open the local dashboard instead of the │ -│ default remote dashboard. │ -│ --tls Whether to force TLS for the connection to │ -│ the Arcade Engine. │ -│ --no-tls Whether to disable TLS for the connection to │ -│ the Arcade Engine. │ -│ --debug -d Show debug information │ -│ --help Show this message and exit. │ -╰──────────────────────────────────────────────────────────────────────────╯ +╭─ Options ──────────────────────────────────────────────────────────────╮ +│ --host -h TEXT The Arcade Engine host that serves the │ +│ dashboard. │ +│ [default: api.arcade.dev] │ +│ --port -p INTEGER The port of the Arcade Engine. │ +│ [default: None] │ +│ --local -l Open the local dashboard instead of the │ +│ default remote dashboard. │ +│ --tls Whether to force TLS for the connection to │ +│ the Arcade Engine. │ +│ --no-tls Whether to disable TLS for the connection │ +│ to the Arcade Engine. │ +│ --debug -d Show debug information │ +│ --help Show this message and exit. │ +╰────────────────────────────────────────────────────────────────────────╯ +``` + +## `arcade org` + +```bash + Usage: arcade org [OPTIONS] COMMAND [ARGS]... + + Manage organizations (list, set active) + +╭─ Options ──────────────────────────────────────────────────────────────╮ +│ --host -h TEXT The Arcade Coordinator host. │ +│ [default: cloud.arcade.dev] │ +│ --port -p INTEGER The port of the Arcade Coordinator host. │ +│ [default: None] │ +│ --tls Whether to force TLS for the connection to │ +│ Arcade Coordinator. │ +│ --no-tls Whether to disable TLS for the connection │ +│ to Arcade Coordinator. │ +│ --help Show this message and exit. │ +╰────────────────────────────────────────────────────────────────────────╯ +╭─ Commands ─────────────────────────────────────────────────────────────╮ +│ list List organizations you belong to │ +│ set Set the active organization │ +╰────────────────────────────────────────────────────────────────────────╯ ``` +## `arcade project` + +```bash + Usage: arcade project [OPTIONS] COMMAND [ARGS]... + + Manage projects (list, set active) + +╭─ Options ──────────────────────────────────────────────────────────────╮ +│ --host -h TEXT The Arcade Coordinator host. │ +│ [default: cloud.arcade.dev] │ +│ --port -p INTEGER The port of the Arcade Coordinator host. │ +│ [default: None] │ +│ --tls Whether to force TLS for the connection to │ +│ Arcade Coordinator. │ +│ --no-tls Whether to disable TLS for the connection │ +│ to Arcade Coordinator. │ +│ --help Show this message and exit. │ +╰────────────────────────────────────────────────────────────────────────╯ +╭─ Commands ─────────────────────────────────────────────────────────────╮ +│ list List projects in the active organization │ +│ set Set the active project │ +╰────────────────────────────────────────────────────────────────────────╯ +``` + + ## `arcade new` ```bash diff --git a/app/en/home/auth-providers/google/page.mdx b/app/en/home/auth-providers/google/page.mdx index 7c43ce697..866ee0ad3 100644 --- a/app/en/home/auth-providers/google/page.mdx +++ b/app/en/home/auth-providers/google/page.mdx @@ -22,6 +22,57 @@ This auth provider is used by: ## Configuring Google auth +You can either use Arcade's default Google OAuth provider (fastest way to get started), or configure your own Google OAuth provider for production. + +## Arcade's default Google OAuth provider + +Arcade provides a default Google OAuth provider (the Arcade-managed Google app) that you can use to quickly get started. This provider supports a fixed set of scopes and is shared across all Arcade users. + +### Supported scopes + +The default Arcade Google OAuth provider supports the following scopes: + +- `https://www.googleapis.com/auth/calendar.readonly` +- `https://www.googleapis.com/auth/calendar.events` +- `https://www.googleapis.com/auth/calendar.events.readonly` +- `https://www.googleapis.com/auth/calendar.settings.readonly` +- `https://www.googleapis.com/auth/contacts` +- `https://www.googleapis.com/auth/contacts.readonly` +- `https://www.googleapis.com/auth/drive.file` +- `https://www.googleapis.com/auth/gmail.readonly` +- `https://www.googleapis.com/auth/gmail.compose` +- `https://www.googleapis.com/auth/gmail.send` +- `https://www.googleapis.com/auth/gmail.modify` +- `https://www.googleapis.com/auth/gmail.labels` +- `https://www.googleapis.com/auth/userinfo.email` +- `https://www.googleapis.com/auth/userinfo.profile` +- `openid` + + + If you try to use a scope that is not in this list with the default Arcade Google provider, you will get a `400 invalid authorization challenge: requesting unsupported scopes` error. For example, scopes like `https://www.googleapis.com/auth/drive.readonly` are not supported. + + To use scopes beyond this list, you must create your own Google OAuth provider (see below). + + +### Limitations + +The default provider has some limitations: + +- **Fixed scope list**: You can only use the scopes listed above +- **Shared rate limits**: All Arcade users share the same rate limits +- **Arcade branding**: Your users will see "Arcade" as the requesting application + + + For production use, we strongly recommend creating your own Google OAuth provider. This gives you: + + - Full control over which scopes to request + - Dedicated rate limits for your application + - Your own branding in the OAuth consent screen + - Better security and compliance with your organization's policies + + +## Configuring your own Google OAuth provider + When using your own app credentials, make sure you configure your project to use a [custom user @@ -40,6 +91,7 @@ Before showing how to configure your Google app credentials, let's go through th - Follow Google's guide to [setting up OAuth credentials](https://support.google.com/cloud/answer/6158849?hl=en) - Choose the [scopes](https://developers.google.com/identity/protocols/oauth2/scopes) (permissions) you need for your app - At a minimum, you must enable these scopes: + - `https://www.googleapis.com/auth/userinfo.email` - `https://www.googleapis.com/auth/userinfo.profile` - Add the redirect URL generated by Arcade (see below) to the Authorized redirect URIs list @@ -47,7 +99,7 @@ Before showing how to configure your Google app credentials, let's go through th Next, add the Google app to Arcade. -## Configuring your own Google Auth Provider in Arcade +### Setting up your Google OAuth provider in Arcade diff --git a/app/en/home/auth/secure-auth-production/page.mdx b/app/en/home/auth/secure-auth-production/page.mdx index cf7f07789..08c71bb62 100644 --- a/app/en/home/auth/secure-auth-production/page.mdx +++ b/app/en/home/auth/secure-auth-production/page.mdx @@ -8,19 +8,24 @@ description: "How to secure and brand your auth flows in production" To keep your users safe, Arcade.dev performs a user verification check when a tool is authorized for the first time. This check verifies that the user who is authorizing the tool is the same user who started the authorization flow, which helps prevent phishing attacks. There are two ways to secure your auth flows with Arcade.dev: + - Use the **Arcade user verifier** for development (enabled by default) - Implement a **custom user verifier** for production This setting is configured in the [Auth > Settings section](https://api.arcade.dev/dashboard/auth/settings) of the Arcade Dashboard. - ## Use the Arcade user verifier If you're building a proof-of-concept app or a solo project, use the Arcade user verifier. This option requires no custom development and is on by default when you create a new Arcade.dev account. This setting is configured in the [Auth > Settings section](https://api.arcade.dev/dashboard/auth/settings) of the Arcade Dashboard: -An image showing how to pick the Arcade user verifier option in the Arcade Dashboard +An image showing how to pick the Arcade user verifier option in the Arcade Dashboard When you authorize a tool, you'll be prompted to sign in to your Arcade.dev account. If you are already signed in (to the Arcade Dashboard, for example), this verification will succeed silently. @@ -28,8 +33,10 @@ The Arcade.dev user verifier helps keep your auth flows secure while you are bui Arcade's default OAuth apps can *only* be used with the Arcade user verifier. - If you are building a multi-user production app, you must obtain your own OAuth app credentials and add them to Arcade. - For example, see our docs on [configuring Google auth in the Arcade Dashboard](/home/auth-providers/google#access-the-arcade-dashboard). + If you are building a multi-user production app, you must obtain your own + OAuth app credentials and add them to Arcade. For example, see our docs on + [configuring Google auth in the Arcade + Dashboard](/home/auth-providers/google#access-the-arcade-dashboard). ## Build a custom user verifier @@ -55,26 +62,27 @@ The route must gather the following information: How the user's unique ID is retrieved varies depending on how your app is built, but it is typically retrieved from a session cookie or other secure storage. It **must** match the user ID that your code specified at the start of the authorization flow. - ### Verify the user's identity Use the Arcade SDK (or our REST API) to verify the user's identity. - Because this request uses your Arcade API key, it *must not* be made from the client side (a browser or desktop app). This code must be run on a server. + Because this request uses your Arcade API key, it *must not* be made from the + client side (a browser or desktop app). This code must be run on a server. + ```ts {13-16} -import { Arcade } from '@arcadeai/arcadejs'; +import { Arcade } from "@arcadeai/arcadejs"; const client = new Arcade(); // Looks for process.env.ARCADE_API_KEY by default // Within a server GET handler: // Validate required parameters if (!flow_id) { - throw new Error('Missing required parameters: flow_id'); + throw new Error("Missing required parameters: flow_id"); } // Confirm the user's identity @@ -84,16 +92,24 @@ try { user_id: user_id_from_your_app_session, // Replace with the user's ID }); } catch (error) { - console.error('Error during verification', 'status code:', error.status, 'data:', error.data); + console.error( + "Error during verification", + "status code:", + error.status, + "data:", + error.data + ); throw error; } ``` + + ```python {12-15} from arcadepy import AsyncArcade -client = AsyncArcade() # Looks for ARCADE_API_KEY environment variable by default +client = AsyncArcade() # Looks for ARCADE_API_KEY environment variable by default # Within a server GET handler: # Validate required parameters @@ -104,14 +120,16 @@ if not flow_id: try: result = await client.auth.confirm_user( flow_id=flow_id, - user_id=user_id_from_your_app_session, # Replace with the user's ID + user_id=user_id_from_your_app_session, # Replace with the user's ID ) except Exception as error: print("Error during verification:", error) raise Exception("Failed to verify the request") ``` + + ```text POST https://cloud.arcade.dev/api/v1/oauth/confirm_user Authorization: Bearer @@ -121,7 +139,9 @@ Content-Type: application/json "flow_id": "", "user_id": "" } + ``` + @@ -133,9 +153,9 @@ If the user's ID matches the ID specified at the start of the authorization flow - Redirect to a different route in your application - Look up the auth flow's status in the Arcade API and render a success page - + ```ts // Either redirect to result.next_uri, or render a success page: const authResponse = await client.auth.waitForCompletion(result.auth_id); @@ -146,8 +166,10 @@ if (authResponse.status === "completed") { return "Something went wrong. Please try again."; } ``` + + ```python # Either redirect to result.next_uri, or render a success page: auth_response = await client.auth.wait_for_completion(result.auth_id) @@ -157,8 +179,10 @@ if auth_response.status == "completed": else: return "Something went wrong. Please try again." ``` + + ```text HTTP 200 OK Content-Type: application/json @@ -170,29 +194,41 @@ Content-Type: application/json // Optional: URL to redirect the user to after the authorization flow is complete "next_uri": "https://..." } + ``` + - **Invalid Response** If the user's ID does not match the ID specified at the start of the authorization flow, the response will contain an error. + ```ts -console.error('Error during verification', 'status code:', error.status, 'data:', error.data); +console.error( + "Error during verification", + "status code:", + error.status, + "data:", + error.data +); throw error; ``` + + ```python print("Error during verification:", error) raise Exception("Failed to verify the request") ``` + + ```text HTTP 400 Bad Request Content-Type: application/json @@ -202,6 +238,7 @@ Content-Type: application/json "msg": "An error occurred during verification" } ``` + @@ -209,11 +246,17 @@ Content-Type: application/json In the [Auth > Settings section](https://api.arcade.dev/dashboard/auth/settings) of the Arcade Dashboard, pick the **Custom verifier** option and add the URL of your verifier route. -An image showing how to pick the custom verifier option in the Arcade Dashboard +An image showing how to pick the custom verifier option in the Arcade Dashboard Arcade's default OAuth apps *only* support the Arcade user verifier. - Authorization flows using Arcade's default OAuth apps will use the Arcade user verifier even if you have a custom verifier route set up. + Authorization flows using Arcade's default OAuth apps will use the Arcade user + verifier even if you have a custom verifier route set up. diff --git a/app/en/home/build-tools/_meta.tsx b/app/en/home/build-tools/_meta.tsx index 88373013a..74349d63f 100644 --- a/app/en/home/build-tools/_meta.tsx +++ b/app/en/home/build-tools/_meta.tsx @@ -7,5 +7,7 @@ export default { "providing-useful-tool-errors": "Providing useful tool errors", "retry-tools-with-improved-prompt": "Retry tools with improved prompt", "call-tools-from-mcp-clients": "Call tools from MCP clients", + "secure-your-mcp-server": "Secure Your MCP Server with OAuth", + "server-level-vs-tool-level-auth": "Server-Level vs Tool-Level Authorization", "migrate-from-toolkits": "Migrate from toolkits", }; diff --git a/app/en/home/build-tools/secure-your-mcp-server/page.mdx b/app/en/home/build-tools/secure-your-mcp-server/page.mdx new file mode 100644 index 000000000..f11a899c6 --- /dev/null +++ b/app/en/home/build-tools/secure-your-mcp-server/page.mdx @@ -0,0 +1,485 @@ +--- +title: "Adding Resource Server Auth" +description: "Secure your HTTP MCP server with OAuth 2.1 Resource Server auth" +--- + +import { Steps, Tabs, Callout } from "nextra/components"; + +# Adding Resource Server Auth to Your MCP Server + +You've built an MCP server with tools that require authorization or secrets. Now you want to deploy it over HTTP so others can use it. But how do you secure it so only authorized users can access your tools? + + +**Want Arcade to handle this for you?** Use [`arcade deploy`](/home/serve-tools/arcade-deploy) to deploy your MCP server to Arcade. We'll secure it automatically with no OAuth configuration on your end required. This guide is for self-hosted deployments where you manage your own authorization server. + + +Resource Server auth enables your HTTP MCP server to act as an OAuth 2.1 Protected Resource (compliant with [MCP's specification for Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization)), validating Bearer tokens on every request. This unlocks support for tool-level authorization and secrets on HTTP servers, allowing you to host secure MCP servers anywhere (local, on-premise, or third-party hosted). + + + + +Add [MCP compliant OAuth 2.1 front-door authentication](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) to your HTTP MCP server to enable tool-level authorization and secrets. + + + + + +- An existing MCP server created with `arcade new` (see [Create an MCP Server](/home/build-tools/create-a-mcp-server)) +- Understanding of [MCP Authorization](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization) +- An OAuth 2.1 compliant authorization server (e.g., WorkOS AuthKit, Auth0, Descope, etc.) +- Authorization server's JWKS endpoint URL + + + + + +- What Resource Server auth is and why it's needed +- How to configure your MCP server to validate OAuth tokens +- How to support multiple authorization servers +- How to use environment variables for production deployments + + + + +## Understanding Resource Server Auth + +### What is it? + +Resource Server auth turns your MCP server into an [OAuth 2.1 Protected Resource](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles) that validates Bearer tokens on every HTTP request. Your MCP server trusts one or more [Authorization Servers](https://www.ietf.org/archive/id/draft-ietf-oauth-v2-1-13.html#name-roles) to issue valid tokens for accessing your MCP server. + +### Why is it needed? + +By default, HTTP MCP servers cannot use tools that require authorization or secrets for security reasons. + +Resource Server auth solves this by: + +1. **Authenticating every request** - Validates the Bearer token before processing any MCP messages +2. **Extracting user identity** - The token's `sub` claim becomes the `context.user_id` for tool execution +3. **Enabling secure tools** - Tools requiring authorization or secrets can now safely execute over HTTP, but tools with tool-level auth will still require authenticating to the downstream service +4. **Supporting OAuth discovery** - MCP clients can automatically discover your authentication requirements + + +**Resource Server auth vs Tool-level auth**: Resource Server auth secures _access to your MCP server_, while tool-level authorization secures _access to third-party APIs that your tools use_. They work together: Resource Server auth identifies _who_ is calling your server, and tool-level auth enables tools to act _on behalf of that user_. + + + + +## Choose Your Configuration Approach + +The `arcade_mcp_server.resource_server` module provides two validators: + + + + +**`ResourceServerAuth`** - Full-featured OAuth 2.1 Resource Server with: +- Support for multiple authorization servers (multi-IdP, regional endpoints) +- OAuth discovery metadata endpoint +- Environment variable configuration +- Best for production deployments + + + + +**`JWKSTokenValidator`** - Direct JWKS-based validation with: +- Simple setup for single authorization server +- No OAuth discovery endpoint +- Requires explicit configuration +- Best for development or simple use cases + + + + +## Configure Your Authorization Server + +First, gather these details from your authorization server: + +- **Authorization Server URL** - The base URL of your authorization server (e.g., `https://your-app.authkit.app`) +- **Issuer** - The expected `iss` claim in tokens (usually same as authorization server URL) +- **JWKS URI** - Where to fetch public keys for token verification (e.g., `https://your-app.authkit.app/oauth2/jwks`) +- **Canonical URL** - Your MCP server's public URL (e.g., `http://127.0.0.1:8000/mcp` if running locally) + + +By default, your MCP server expects the **Canonical URL** to match the `aud` (audience) claim in tokens. If your authorization server uses a different audience, you can override this with the `expected_audiences` parameter on `AuthorizationServerEntry`. + + +## Add Authentication to Your Server + +Update your `server.py` to add the `auth` parameter to `MCPApp`: + + + + +```python filename="server.py" {5-9,11-26,32} showLineNumbers +#!/usr/bin/env python3 +"""my_server MCP server""" + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import ( + AccessTokenValidationOptions, + AuthorizationServerEntry, + ResourceServerAuth, +) + +# Setup your resource server that trusts a single Authkit authorization server +resource_server_auth = ResourceServerAuth( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://your-workos.authkit.app", + issuer="https://your-workos.authkit.app", + jwks_uri="https://your-workos.authkit.app/oauth2/jwks", + algorithm="RS256", + # Authkit doesn't set the aud claim as the MCP server's canonical URL by default + expected_audiences=["your-authkit-client-id"], + ) + ], +) + +# Pass the resource_server_auth to MCPApp +app = MCPApp( + name="my_server", + version="1.0.0", + auth=resource_server_auth # Enable Resource Server auth +) + +# Your tools here... +@app.tool +def greet(name: Annotated[str, "The name of the person to greet"]) -> str: + """Greet a person by name.""" + return f"Hello, {name}!" + +if __name__ == "__main__": + app.run(transport="http", host="127.0.0.1", port=8000) +``` + + + + +```python filename="server.py" {10-29} showLineNumbers +#!/usr/bin/env python3 +"""my_server MCP server""" + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import ( + ResourceServerAuth, + AuthorizationServerEntry, +) + +# Support multiple authorization servers (multi-IdP) +resource_server_auth = ResourceServerAuth( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://your-workos.authkit.app", + issuer="https://your-workos.authkit.app", + jwks_uri="https://your-workos.authkit.app/oauth2/jwks", + # Authkit doesn't set the aud claim as the MCP server's canonical URL + expected_audiences=["your-authkit-client-id"], + ), + AuthorizationServerEntry( # Keycloak example configuration + authorization_server_url="http://localhost:8080/realms/mcp-test", + issuer="http://localhost:8080/realms/mcp-test", + jwks_uri="http://localhost:8080/realms/mcp-test/protocol/openid-connect/certs", + algorithm="RS256", + expected_audiences=["your-keycloak-client-id"], + ) + ], +) + +app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth) +``` + + +Multiple authorization servers enable scenarios like: +- **Multi-IdP**: Accept tokens from WorkOS _and_ GitHub +- **Regional endpoints**: Multiple authorization server URLs with shared keys +- **Migration**: Smoothly transition between authorization servers + + + + + +```python filename="server.py" {5,8-9} showLineNumbers +#!/usr/bin/env python3 +"""my_server MCP server""" + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import ResourceServerAuth + +# Configuration loaded from environment variables +# No parameters needed! +resource_server_auth = ResourceServerAuth() + +app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth) +``` + +Create a `.env` file: + +```bash filename=".env" +MCP_SERVER_AUTH_CANONICAL_URL=http://127.0.0.1:8000/mcp +MCP_SERVER_AUTH_AUTHORIZATION_SERVERS='[ + { + "authorization_server_url": "https://your-workos.authkit.app", + "issuer": "https://your-workos.authkit.app", + "jwks_uri": "https://your-workos.authkit.app/oauth2/jwks", + "algorithm": "RS256", + "expected_audiences": ["my-client-id"] + } +]' +``` + + +Environment variable configuration is **recommended for production** as it separates auth configuration from your code and allows deployment-time configuration. Note that **explicit parameters take precedence over environment variables**, allowing you to override specific settings when needed. + + + + + +```python filename="server.py" {5,7-12,15} showLineNumbers +#!/usr/bin/env python3 +"""my_server MCP server""" + +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import JWKSTokenValidator + +# Configure JWKS token validation +validator = JWKSTokenValidator( + jwks_uri="https://your-workos.authkit.app/oauth2/jwks", + issuer="https://your-workos.authkit.app", + audience="http://127.0.0.1:8000/mcp", +) + +app = MCPApp( + name="my_server", + version="1.0.0", + auth=validator +) + +# Your tools here... +``` + + + + +## Run Your Authenticated Server + +Start your server with HTTP transport: + +```bash +uv run server.py +``` + +Your server now requires valid Bearer tokens for all requests. You should see output like: + +```bash +INFO | 14:23:45 | Starting my_server v1.0.0 with 3 tools +INFO | 14:23:45 | Resource Server auth enabled: True +INFO | 14:23:45 | Accepted authorization server(s): https://your-app.authkit.app +``` + +## OAuth Discovery +Now that your server is protected, you can see that your server exposes an OAuth discovery endpoint at `http://127.0.0.1:8000/mcp/.well-known/oauth-protected-resource`. This endpoint is used by MCP clients to discover the authorization servers that are trusted by your server. + +```bash +curl http://127.0.0.1:8000/.well-known/oauth-protected-resource +``` + +You should see a response like: + +```http +{ + "resource":"http://127.0.0.1:8000/mcp", + "authorization_servers":["https://your-workos.authkit.app"] +} +``` + + +MCP clients can use this endpoint to automatically discover which authorization servers issue valid tokens for your server. + + +## Verify Your Server is Protected + +Try calling your server without a token: + +```bash +curl -i http://127.0.0.1:8000/mcp/ +``` + +You should receive a 401 response with `WWW-Authenticate` header: + +```http {4} +HTTP/1.1 401 Unauthorized +date: Tue, 02 Dec 2025 01:00:54 GMT +server: uvicorn +www-authenticate: Bearer, resource_metadata="http://127.0.0.1:8000/mcp/.well-known/oauth-protected-resource" +access-control-allow-origin: * +access-control-allow-methods: GET, POST, DELETE +access-control-allow-headers: Content-Type, Authorization, Mcp-Session-Id +access-control-expose-headers: WWW-Authenticate, Mcp-Session-Id +content-length: 12 + +Unauthorized +``` + +### Test Your Server with a Valid Token + +The easiest way to test your secure server by using the [MCP Inspector](https://modelcontextprotocol.io/docs/tools/inspector) as your client & connecting to your server from it. + +
+ +
+ + +
+ +## Advanced Configuration + +### Custom Token Validation Options + +Disable specific validations when needed: + +```python +from arcade_mcp_server.resource_server import ( + ResourceServerAuth, + AuthorizationServerEntry, + AccessTokenValidationOptions, +) + +resource_server_auth = ResourceServerAuth( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://your-app.authkit.app", + issuer="https://your-app.authkit.app", + jwks_uri="https://your-app.authkit.app/oauth2/jwks", + expected_audiences=["my-client-id"], + validation_options=AccessTokenValidationOptions( + verify_exp=True, # Still verify expiration (default) + verify_iat=True, # Still verify issued-at (default) + verify_iss=True, # Still verify issuer (default) + ), + ) + ], +) +``` + + +**Security Note**: Token signature verification is always enabled and cannot be disabled. Additionally, the `sub` claim must always be present. Only disable other validations if your authorization server doesn't comply with MCP and you accept the risk of not validating all claims in the token. + + +### Custom Expected Audiences + +By default, your MCP server expects the token's `aud` claim to match the `canonical_url`. If your authorization server uses a different audience value (like a client ID), override it with `expected_audiences`: + +```python +AuthorizationServerEntry( + authorization_server_url="https://your-app.authkit.app", + issuer="https://your-app.authkit.app", + jwks_uri="https://your-app.authkit.app/oauth2/jwks", + expected_audiences=["my-client-id"], # Override audience validation +) +``` + +You can also accept multiple audiences: + +```python +AuthorizationServerEntry( + authorization_server_url="https://auth.example.com", + issuer="https://auth.example.com", + jwks_uri="https://auth.example.com/jwks", + expected_audiences=["client-id-1", "client-id-2"], # Accept tokens for either audience +) +``` + +### Different JWT Algorithms + +```python +AuthorizationServerEntry( + authorization_server_url="https://auth.example.com", + issuer="https://auth.example.com", + jwks_uri="https://auth.example.com/jwks", + algorithm="ES256", # Use ECDSA instead of RSA +) +``` + +Supported algorithms: `RS256`, `RS384`, `RS512`, `ES256`, `ES384`, `ES512`, `PS256`, `PS384`, `PS512` + +### Regional Authorization Servers with Shared Keys + +```python +resource_server_auth = ResourceServerAuth( + canonical_url="https://mcp.example.com", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://auth-us.example.com", + issuer="https://auth.example.com", # Same issuer + jwks_uri="https://auth.example.com/jwks", # Shared JWKS + ), + AuthorizationServerEntry( + authorization_server_url="https://auth-eu.example.com", + issuer="https://auth.example.com", # Same issuer + jwks_uri="https://auth.example.com/jwks", # Shared JWKS + ), + ], +) +``` + +## How It Works + +1. **Client makes request** with `Authorization: Bearer ` header +2. **Middleware intercepts** every HTTP request before MCP processing +3. **Token validation** occurs: + - Fetches JWKS from authorization server + - Verifies token signature + - Checks expiration, issuer, and audience + - Extracts `sub` claim as user ID +4. **Resource owner stored** in request context +5. **MCP processing continues** with authenticated user +6. **Tools execute** with `context.user_id` set from token's `sub` claim + +### Security Features + +- ✅ **No token caching** - Every request validates the token fresh (per MCP spec) +- ✅ **JWKS caching** - Public keys cached for performance (default 1 hour) +- ✅ **Algorithm enforcement** - Prevents algorithm confusion attacks +- ✅ **Signature verification** - Always enabled, cannot be disabled +- ✅ **RFC 6750 compliant** - Standard OAuth 2.0 Bearer token usage +- ✅ **RFC 9728 compliant** - OAuth 2.0 Protected Resource Metadata + +## Common Authorization Server Configurations + +### WorkOS AuthKit + +```python +AuthorizationServerEntry( + authorization_server_url="https://your-app.authkit.app", + issuer="https://your-app.authkit.app", + jwks_uri="https://your-app.authkit.app/oauth2/jwks", + expected_audiences=["my-client-id"], +) +``` + +### Keycloak + +```python +AuthorizationServerEntry( + authorization_server_url="http://localhost:8080/realms/mcp-test", + issuer="http://localhost:8080/realms/mcp-test", + jwks_uri="http://localhost:8080/realms/mcp-test/protocol/openid-connect/certs", + algorithm="RS256", + expected_audiences=["your-keycloak-client-id"], +) +``` + +## Next Steps + +- **Let Arcade secure your server instead**: [Learn about `arcade deploy`](/home/serve-tools/arcade-deploy) +- **Build tools with authorization**: [Create tools that use OAuth](/home/build-tools/create-a-tool-with-auth) +- **Use secrets securely**: [Create tools with secrets](/home/build-tools/create-a-tool-with-secrets) diff --git a/app/en/home/build-tools/server-level-vs-tool-level-auth/page.mdx b/app/en/home/build-tools/server-level-vs-tool-level-auth/page.mdx new file mode 100644 index 000000000..a50d3841b --- /dev/null +++ b/app/en/home/build-tools/server-level-vs-tool-level-auth/page.mdx @@ -0,0 +1,223 @@ +--- +title: "Server-Level vs Tool-Level Authorization" +description: "Understanding the difference between Resource Server auth and tool-level authorization in Arcade MCP servers" +--- + +import { Callout } from "nextra/components"; + +# Server-Level vs Tool-Level Authorization + +Arcade MCP servers support two distinct layers of authorization that work together to provide comprehensive security. Understanding the difference is crucial for building secure, production-ready MCP servers. + + +**Using `arcade deploy`?** If you deploy your MCP server with [`arcade deploy`](/home/serve-tools/arcade-deploy), Arcade handles server-level security for you automatically. + + +## Quick Comparison + +| Aspect | [Resource Server Auth (Front-Door)](/home/build-tools/secure-your-mcp-server) | [Tool-Level Authorization](/home/build-tools/create-a-tool-with-auth) | +|--------|-----------------------------------|--------------------------| +| **What it secures** | Access to your MCP server | Access to third-party APIs | +| **Who authenticates** | The user calling your server | The user's access to external services | +| **When it happens** | Every HTTP request to your server | When a tool calls an external API | +| **Token source** | Authorization Server (e.g., WorkOS, Auth0) | Arcade authorization platform | +| **Required for** | HTTP servers in production | Tools that access user data from APIs | +| **Configuration** | `MCPApp(auth=resource_server)` | `@app.tool(requires_auth=GitHub(...))` | + +## Resource Server Auth (Server-Level) + +Resource Server auth (also called "front-door auth") validates Bearer tokens on **every HTTP request** to your MCP server. The end user only needs to go through the OAuth flow once. Afterwards, the MCP client will send the token in the Authorization header for every request to your MCP server. Your MCP server acts as an OAuth 2.1 Protected Resource. + +Resource Server auth ensures every request identifies the caller. It blocks unauthenticated requests at the door, so only users with valid tokens can access your MCP server. This security lets you run tools that require authorization or secrets over HTTP. + +### When You Need It + +✅ **You need Resource Server auth if:** +- You've determined that [arcade deploy](/home/serve-tools/arcade-deploy) is not a good fit for your use case +- You're running an HTTP MCP server in production +- Your server has tools that require authorization or secrets +- You need to identify which user is calling your server +- You want to control who can access your MCP server + +❌ **You don't need it if:** +- You're using [arcade deploy](/home/serve-tools/arcade-deploy) to secure your server +- You're using stdio transport +- Your server only has public tools (no auth/secrets required) +- You're doing local development only + +### Example + +```python filename="server.py" showLineNumbers +from arcade_mcp_server import MCPApp +from arcade_mcp_server.resource_server import ResourceServerAuth, AuthorizationServerEntry + +# Configure who can access your MCP server +resource_server_auth = ResourceServerAuth( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://auth.example.com", + issuer="https://auth.example.com", + jwks_uri="https://auth.example.com/jwks", + ) + ], +) + +app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth) +``` + + +**Result**: Only users with valid tokens from `https://auth.example.com` can call ANY tools on your server. + + +## Tool-Level Authorization + +Tool-level authorization enables individual tools to access third-party APIs on behalf of the authenticated user. Arcade manages the OAuth flow and token storage. + +Tool-level authorization lets your tools authenticate to external APIs using OAuth tokens for services like Gmail or GitHub. Each tool acts on behalf of the user by using their connected accounts, and requests only the scopes it needs. Arcade manages the entire OAuth flow, including token refresh and secure storage, so you don't have to handle these details yourself. + +### When You Need It + +✅ **You need tool-level auth if:** +- Your tool calls external APIs (Gmail, GitHub, Slack, etc.) that require user-specific OAuth tokens +- You want to access user data from third-party services +- The tool needs to act on behalf of the user + +❌ **You don't need it if:** +- Your tool doesn't call external APIs +- The API uses API keys instead of OAuth +- The tool accesses public data only + +### Example + +```python filename="server.py" showLineNumbers +from typing import Annotated + +from arcade_mcp_server import Context, MCPApp +from arcade_mcp_server.auth import GitHub +import httpx + +app = MCPApp(name="my_server", version="1.0.0") + +# This tool requires GitHub auth +@app.tool(requires_auth=GitHub(scopes=["repo", "read:user"])) +async def create_github_issue( + context: Context, + repo: Annotated[str, "The repository to create the issue in"], + title: Annotated[str, "The title of the issue"], + body: Annotated[str, "The body of the issue"], +) -> Annotated[dict, "The created issue"]: + """Create a GitHub issue""" + # Arcade provides the OAuth token for this user in the context + token = context.get_auth_token_or_empty() + + headers = {"Authorization": f"Bearer {token}"} + url = f"https://api.github.com/repos/{repo}/issues" + + async with httpx.AsyncClient() as client: + response = await client.post( + url, + headers=headers, + json={"title": title, "body": body} + ) + return response.json() + +if __name__ == "__main__": + app.run(transport="stdio") +``` + + +**stdio transport doesn't need Resource Server auth** because the connection is local and doesn't go over the network. + + +## How They Work Together + +The two authorization layers complement each other. Below is an example of a +protected HTTP server with both server-level and tool-level authorization. + +```python filename="server.py" {9-18, 20, 24, 33, 48} showLineNumbers +from typing import Annotated + +import httpx +from arcade_mcp_server import Context, MCPApp +from arcade_mcp_server.auth import GitHub +from arcade_mcp_server.resource_server import AuthorizationServerEntry, ResourceServerAuth + +# Configure who can access your MCP server +resource_server_auth = ResourceServerAuth( + canonical_url="http://127.0.0.1:8000/mcp", + authorization_servers=[ + AuthorizationServerEntry( + authorization_server_url="https://auth.example.com", + issuer="https://auth.example.com", + jwks_uri="https://auth.example.com/jwks", + ) + ], +) + +app = MCPApp(name="my_server", version="1.0.0", auth=resource_server_auth) + + +# This tool requires GitHub auth +@app.tool(requires_auth=GitHub(scopes=["repo", "read:user"])) +async def create_github_issue( + context: Context, + repo: Annotated[str, "The repository to create the issue in"], + title: Annotated[str, "The title of the issue"], + body: Annotated[str, "The body of the issue"], +) -> Annotated[dict, "The created issue"]: + """Create a GitHub issue""" + # Arcade provides the OAuth token for this user in the context + token = context.get_auth_token_or_empty() + + headers = {"Authorization": f"Bearer {token}"} + url = f"https://api.github.com/repos/{repo}/issues" + + async with httpx.AsyncClient() as client: + response = await client.post( + url, + headers=headers, + json={"title": title, "body": body}, + ) + return response.json() + + +if __name__ == "__main__": + app.run(transport="http") + +``` + +**Flow:** +1. MCP client sends request with Bearer token to `http://127.0.0.1:8000/mcp` +2. Resource Server middleware validates token → extracts `user_id` from `sub` claim +3. MCP processes tool call with authenticated user context +4. Tool requests GitHub token from Arcade for this `user_id` +5. Tool uses GitHub token to call GitHub API +6. Response returns to MCP client + +## Common Questions + +### Q: Can I use tool-level auth without Resource Server auth? + +**A:** Yes, but only for stdio transport or when using [arcade deploy](/home/serve-tools/arcade-deploy) (Arcade will protect your MCP server for you). + +### Q: Do I need Resource Server auth for local development? + +**A:** No, you can use stdio transport for local development. Resource Server auth is primarily for production HTTP servers. + +### Q: Does Resource Server auth replace tool-level auth? + +**A:** No, they serve different purposes. Resource Server auth secures _your server_, tool-level auth secures _external APIs_. + +### Q: Can I have different authorization servers for different tools in the same server? + +**A:** No. Resource Server auth applies to the entire server. However, you can accept tokens from multiple authorization servers (multi-IdP). + +## Key Takeaways + +- **Resource Server auth secures your MCP server** - Controls who can call your tools +- **Tool-level auth secures external APIs** - Controls what your tools can access +- **They work together** - Resource Server provides user identity, tool-level provides API access +- **HTTP requires Resource Server auth** - For tools with auth/secrets in production +- **stdio doesn't need Resource Server auth** - Local connections are already secure +- **Choose based on transport and requirements** - Different scenarios need different combinations diff --git a/app/en/home/changelog/page.mdx b/app/en/home/changelog/page.mdx index a55f56ab0..a5d5643ba 100644 --- a/app/en/home/changelog/page.mdx +++ b/app/en/home/changelog/page.mdx @@ -9,6 +9,26 @@ import { Callout } from "nextra/components"; _Here's what's new at Arcade.dev!_ +## 2025-12-12 + +**Arcade MCP Servers** +- `[feature - 🚀]` OAuth authentication for `arcade-mcp` servers. Learn more about it [here](/home/build-tools/secure-your-mcp-server)! +- `[maintenance - 🔧]` Ability to run multiple uvicorn workers +- `[maintenance - 🔧]` Include type annotations for `arcade_mcp_server` + +**Arcade CLI** +- `[feature - 🚀]` Support multiple orgs & projects in Arcade's CLI. Learn more about it [here](/home/arcade-cli)! + +**Platform and Engine** +- `[bugfix - 🐛]` Idempotent project invite acceptance + +**Toolkits** +- `[feature - 🚀]` Support phone numbers in Google contacts +- `[feature - 🚀]` Support downloading and uploading files to Google Drive +- `[feature - 🚀]` Figma Optimized Toolkit +- `[bugfix - 🐛]` Fix bugs with bad data types in Jira and Confluence tools +- `[maintenance - 🔧]` Gmail list tools enforce page-size limits + ## 2025-12-05 diff --git a/app/en/home/landing-page.tsx b/app/en/home/landing-page.tsx index 90a4d2dcb..c59341eb7 100644 --- a/app/en/home/landing-page.tsx +++ b/app/en/home/landing-page.tsx @@ -40,10 +40,10 @@ export function LandingPage() {