Skip to content

Commit 911122e

Browse files
author
Matteo Morelli
authored
Let 11 OAuth connectors authorize with the provider instead of a pasted refresh token (#917)
Box, Dropbox, Exact Online, Fatture in Cloud, Fortnox, FreshBooks, Google Analytics 4, Nimble, Teamleader, Visma eAccounting and Zoho CRM use the authorization code flow but had no authorizationUrl, so "Authorize with Provider" could not start and the refresh token had to be pasted by hand. With providers that rotate refresh tokens, testing the token first spent it and the connector failed with invalid_grant. Each adapter now carries the provider's authorization URL and the scopes its tools need. The refresh token moves to optionalEnvVars with a hint that the authorization fills it in, and the setup instructions lead with the redirect URI and Authorize with Provider, keeping the manual token as a fallback.
1 parent 6db50a5 commit 911122e

11 files changed

Lines changed: 152 additions & 28 deletions

File tree

‎packages/backend/src/adapters/be/teamleader.json‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,22 @@
66
"category": "crm",
77
"icon": "teamleader",
88
"docsUrl": "https://developer.teamleader.eu/",
9-
"instructions": "**Getting a token**\n1. Register an integration in the **Teamleader Marketplace developer area** (developer.teamleader.eu). You receive a client id and client secret.\n2. Run the authorization-code flow once for the account and keep the **refresh token** — Teamleader's access tokens last one hour.\n3. Set `TEAMLEADER_CLIENT_ID`, `TEAMLEADER_CLIENT_SECRET` and `TEAMLEADER_REFRESH_TOKEN`. AnythingMCP refreshes the access token on its own.\n\n**Every endpoint is a POST, including the reads.** Teamleader's API is action-shaped — `companies.list`, `deals.info`, `invoices.list` — and all of them are POSTs whose JSON body carries the filter. That is why these tools look like writes in an HTTP trace even when they only read; the tool names here say what each one actually does.\n\n**Filters are a nested object.** `{\"filter\": {\"term\": \"Acme\"}, \"page\": {\"size\": 20, \"number\": 1}, \"sort\": [{\"field\": \"name\", \"order\": \"asc\"}]}`. The available filter keys differ per action and are listed in Teamleader's reference.\n\n**Refresh tokens rotate.** Teamleader issues a new refresh token on every refresh and invalidates the old one. AnythingMCP persists the rotated token back into the connector, so the stored value stays current — but if you also use the same refresh token from another script, one of the two will start failing.\n\n**Ids are UUIDs**, and `deals.info` needs the deal's id, not its reference number.\n\n**Cloud reachability**: api.focus.teamleader.eu is public with a valid certificate.",
9+
"instructions": "**Setup**\n1. Register an integration in the **Teamleader Marketplace developer area** (marketplace.focus.teamleader.eu/build). Add the redirect URI `https://cloud.anythingmcp.com/api/mcp-oauth/callback` on AnythingMCP Cloud, or `<your AnythingMCP server URL>/api/mcp-oauth/callback` when you host AnythingMCP yourself, and tick the scopes for users, companies, contacts, deals, invoices, quotations, projects and time tracking. You receive a client id and client secret.\n2. Set `TEAMLEADER_CLIENT_ID` and `TEAMLEADER_CLIENT_SECRET`, leave `TEAMLEADER_REFRESH_TOKEN` empty, and install.\n3. Open the connector and click **Authorize with Provider**. Sign in to Teamleader, approve, and you land back on the connector page. Teamleader's access tokens last one hour; AnythingMCP refreshes them on its own.\n\nTeamleader takes no scope in the authorization request: the integration gets the scopes ticked on its settings page.\n\n**Already have a refresh token?** You can paste it into `TEAMLEADER_REFRESH_TOKEN` instead of step 3, if you obtained it yourself for the same integration and have not used it since (see below).\n\n**Every endpoint is a POST, including the reads.** Teamleader's API is action-shaped — `companies.list`, `deals.info`, `invoices.list` — and all of them are POSTs whose JSON body carries the filter. That is why these tools look like writes in an HTTP trace even when they only read; the tool names here say what each one actually does.\n\n**Filters are a nested object.** `{\"filter\": {\"term\": \"Acme\"}, \"page\": {\"size\": 20, \"number\": 1}, \"sort\": [{\"field\": \"name\", \"order\": \"asc\"}]}`. The available filter keys differ per action and are listed in Teamleader's reference.\n\n**Refresh tokens rotate.** Teamleader issues a new refresh token on every refresh and invalidates the old one. AnythingMCP persists the rotated token back into the connector, so the stored value stays current, but a refresh token you tested by hand, or also use from another script, is spent and the connector fails with `invalid_grant`. Authorize again from the connector page to get a fresh one.\n\n**Ids are UUIDs**, and `deals.info` needs the deal's id, not its reference number.\n\n**Cloud reachability**: api.focus.teamleader.eu is public with a valid certificate.",
1010
"requiredEnvVars": [
1111
"TEAMLEADER_CLIENT_ID",
12-
"TEAMLEADER_CLIENT_SECRET",
12+
"TEAMLEADER_CLIENT_SECRET"
13+
],
14+
"optionalEnvVars": [
1315
"TEAMLEADER_REFRESH_TOKEN"
1416
],
17+
"envVarMeta": {
18+
"TEAMLEADER_REFRESH_TOKEN": {
19+
"label": "Refresh token",
20+
"kind": "credential",
21+
"help": "Leave empty: Authorize with Provider fills it in. Paste one only if you already obtained a refresh token for the same app yourself. Teamleader replaces the refresh token on every renewal, so a token that was already used once (to test it, say) no longer works.",
22+
"advanced": true
23+
}
24+
},
1525
"connector": {
1626
"name": "Teamleader Focus API",
1727
"type": "REST",
@@ -21,6 +31,7 @@
2131
"clientId": "{{TEAMLEADER_CLIENT_ID}}",
2232
"clientSecret": "{{TEAMLEADER_CLIENT_SECRET}}",
2333
"refreshToken": "{{TEAMLEADER_REFRESH_TOKEN}}",
34+
"authorizationUrl": "https://focus.teamleader.eu/oauth2/authorize",
2435
"tokenUrl": "https://focus.teamleader.eu/oauth2/access_token"
2536
},
2637
"headers": {

‎packages/backend/src/adapters/intl/box.json‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@
22
"slug": "box",
33
"name": "Box",
44
"description": "Manage Box (enterprise content: files, folders, collaborations, comments, tasks, metadata) from any AI agent. 10 tools, OAuth2 token.",
5-
"instructions": "This connector wraps the Box Content API v2 (api.box.com/2.0).\n\n**Setup — OAuth2**:\n1. Register at https://app.box.com/developers/console → **Create New App** → 'OAuth 2.0 with JWT' or 'OAuth 2.0 with User Authentication'.\n2. For user OAuth: complete the auth flow at `https://account.box.com/api/oauth2/authorize?response_type=code&client_id=...&redirect_uri=...`.\n3. Exchange the code at `https://api.box.com/oauth2/token`. Save the refresh_token.\n4. For JWT (server auth): generate an enterprise-level access token differently — out of scope for this OAuth-based adapter.\n5. Set `BOX_CLIENT_ID`, `BOX_CLIENT_SECRET`, `BOX_REFRESH_TOKEN`.\n\n**Authentication**: OAuth2 refresh handled by engine. Sends `Authorization: Bearer ACCESS_TOKEN`.\n\n**Folder ID 0** is the root folder of the authenticated user.\n\n**File-vs-Folder IDs**: Box assigns numeric IDs that are unique within file-space and folder-space but can collide across (file 123 ≠ folder 123). Always know whether you're operating on a file or folder.\n\n**Upload**: file upload uses a different host (`upload.box.com`) with multipart. Out of scope here for content upload — use the create_shared_link pattern with externally-hosted files for ingest.\n\n**Pagination**: `limit` (max 1000) + `offset` (0-based). Some endpoints support `marker` cursor pagination — pass `usemarker=true`.\n\n**Rate limits**: ~16 req/sec per token, soft. 429 with `Retry-After`.\n\n**Out of scope here**: file upload multipart, Box Sign workflows, retention policies, the new Box AI inference endpoints.",
5+
"instructions": "This connector wraps the Box Content API v2 (api.box.com/2.0). You authorize it once, in AnythingMCP; access tokens are then renewed automatically.\n\n**Setup**:\n1. Create an app at https://app.box.com/developers/console → **Create New App** → **Custom App** → **User Authentication (OAuth 2.0)**.\n2. In the app's **Configuration** tab, add the redirect URI `https://cloud.anythingmcp.com/api/mcp-oauth/callback` on AnythingMCP Cloud, or `<your AnythingMCP server URL>/api/mcp-oauth/callback` when you host AnythingMCP yourself. Under **Application Scopes**, tick **Write all files and folders stored in Box**.\n3. Enter the app's **Client ID** as `BOX_CLIENT_ID` and its **Client Secret** as `BOX_CLIENT_SECRET`, leave `BOX_REFRESH_TOKEN` empty, and install.\n4. Open the connector and click **Authorize with Provider**. Sign in to Box, grant access, and you land back on the connector page with the tools ready.\n\n**Scopes**: `root_readwrite`, which covers every tool here (read, create, move, copy, delete and share).\n\n**Already have a refresh token?** You can paste it into `BOX_REFRESH_TOKEN` instead of step 4, if you obtained it yourself for the same app (authorization code from `https://account.box.com/api/oauth2/authorize`, exchanged at `https://api.box.com/oauth2/token`). Box refresh tokens work only once: a token you already exchanged, for example while testing it, is spent and the connector fails with `invalid_grant`. Authorizing from the connector page avoids this.\n\n**JWT (server authentication)** is out of scope for this OAuth-based adapter.\n\n**Authentication**: OAuth2 refresh handled by engine. Sends `Authorization: Bearer ACCESS_TOKEN`.\n\n**Folder ID 0** is the root folder of the authenticated user.\n\n**File-vs-Folder IDs**: Box assigns numeric IDs that are unique within file-space and folder-space but can collide across (file 123 ≠ folder 123). Always know whether you're operating on a file or folder.\n\n**Upload**: file upload uses a different host (`upload.box.com`) with multipart. Out of scope here for content upload — use the create_shared_link pattern with externally-hosted files for ingest.\n\n**Pagination**: `limit` (max 1000) + `offset` (0-based). Some endpoints support `marker` cursor pagination — pass `usemarker=true`.\n\n**Rate limits**: ~16 req/sec per token, soft. 429 with `Retry-After`.\n\n**Out of scope here**: file upload multipart, Box Sign workflows, retention policies, the new Box AI inference endpoints.",
66
"region": "intl",
77
"category": "storage",
88
"icon": "box",
99
"docsUrl": "https://developer.box.com/reference/",
10-
"requiredEnvVars": ["BOX_CLIENT_ID", "BOX_CLIENT_SECRET", "BOX_REFRESH_TOKEN"],
10+
"requiredEnvVars": ["BOX_CLIENT_ID", "BOX_CLIENT_SECRET"],
11+
"optionalEnvVars": ["BOX_REFRESH_TOKEN"],
12+
"envVarMeta": {
13+
"BOX_REFRESH_TOKEN": {
14+
"label": "Refresh token",
15+
"kind": "credential",
16+
"help": "Leave empty: Authorize with Provider fills it in. Paste one only if you already obtained a refresh token for the same app yourself. Box replaces the refresh token on every renewal, so a token that was already used once (to test it, say) no longer works.",
17+
"advanced": true
18+
}
19+
},
1120
"connector": {
1221
"name": "Box Content API v2",
1322
"type": "REST",
@@ -17,7 +26,9 @@
1726
"clientId": "{{BOX_CLIENT_ID}}",
1827
"clientSecret": "{{BOX_CLIENT_SECRET}}",
1928
"refreshToken": "{{BOX_REFRESH_TOKEN}}",
20-
"tokenUrl": "https://api.box.com/oauth2/token"
29+
"authorizationUrl": "https://account.box.com/api/oauth2/authorize",
30+
"tokenUrl": "https://api.box.com/oauth2/token",
31+
"scopes": "root_readwrite"
2132
}
2233
},
2334
"tools": [

‎packages/backend/src/adapters/intl/dropbox.json‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,21 @@
22
"slug": "dropbox",
33
"name": "Dropbox",
44
"description": "Manage Dropbox (file storage: list, download, upload, move, copy, delete, search, share, metadata) from any AI agent. 10 tools, OAuth2 token.",
5-
"instructions": "This connector wraps the Dropbox API v2 (api.dropboxapi.com).\n\n**Setup — OAuth2**:\n1. Register at https://www.dropbox.com/developers/apps → **Create app** → 'Scoped access' → 'App folder' or 'Full Dropbox' depending on need.\n2. Add the scopes you need (files.metadata.read, files.content.read, files.content.write, sharing.write, ...).\n3. Generate a **Refresh token** via OAuth flow with `token_access_type=offline`.\n4. Set `DROPBOX_CLIENT_ID`, `DROPBOX_CLIENT_SECRET`, `DROPBOX_REFRESH_TOKEN`.\n\n**Authentication**: OAuth2 refresh handled by engine. Sends `Authorization: Bearer ACCESS_TOKEN`.\n\n**Two API hosts**: most endpoints live at `api.dropboxapi.com/2`. Content endpoints (upload/download) use `content.dropboxapi.com/2` — out of scope here for upload (multipart streaming). Use the link-based pattern: have content already accessible via URL → use `dropbox_save_url` to ingest, or use external upload then create_shared_link to share.\n\n**Path format**: paths use `/Folder/Subfolder/File.txt` (case-insensitive, no leading drive). Root is empty string or `/`. For App-folder apps, paths are relative to the app folder.\n\n**Pagination**: most list endpoints use a `cursor` returned in the response. Pass back as `cursor` to continue.\n\n**Rate limits**: per-endpoint quotas, generally 100-1000 req/min. 429 with `Retry-After`.\n\n**Out of scope here**: direct file upload (multipart, separate API), Paper docs, team admin, the legacy v1 API.",
5+
"instructions": "This connector wraps the Dropbox API v2 (api.dropboxapi.com). You authorize it once, in AnythingMCP; access tokens are then renewed automatically.\n\n**Setup**:\n1. Register at https://www.dropbox.com/developers/apps → **Create app** → 'Scoped access' → 'App folder' or 'Full Dropbox' depending on need.\n2. In the app's **Permissions** tab, tick `account_info.read`, `files.metadata.read`, `files.content.write` and `sharing.write`, and submit.\n3. In the **Settings** tab, add the redirect URI `https://cloud.anythingmcp.com/api/mcp-oauth/callback` on AnythingMCP Cloud, or `<your AnythingMCP server URL>/api/mcp-oauth/callback` when you host AnythingMCP yourself.\n4. Enter the app's **App key** as `DROPBOX_CLIENT_ID` and its **App secret** as `DROPBOX_CLIENT_SECRET`, leave `DROPBOX_REFRESH_TOKEN` empty, and install.\n5. Open the connector and click **Authorize with Provider**. Sign in to Dropbox, allow access, and you land back on the connector page with the tools ready.\n\n**Scopes**: `account_info.read files.metadata.read files.content.write sharing.write`, which covers every tool here. The authorization URL carries `token_access_type=offline`, which is what makes Dropbox issue a refresh token.\n\n**Already have a refresh token?** You can paste it into `DROPBOX_REFRESH_TOKEN` instead of step 5, if you obtained it yourself for the same app with `token_access_type=offline`. Connectors set up that way keep working as they are.\n\n**Authentication**: OAuth2 refresh handled by engine. Sends `Authorization: Bearer ACCESS_TOKEN`.\n\n**Two API hosts**: most endpoints live at `api.dropboxapi.com/2`. Content endpoints (upload/download) use `content.dropboxapi.com/2` — out of scope here for upload (multipart streaming). Use the link-based pattern: have content already accessible via URL → use `dropbox_save_url` to ingest, or use external upload then create_shared_link to share.\n\n**Path format**: paths use `/Folder/Subfolder/File.txt` (case-insensitive, no leading drive). Root is empty string or `/`. For App-folder apps, paths are relative to the app folder.\n\n**Pagination**: most list endpoints use a `cursor` returned in the response. Pass back as `cursor` to continue.\n\n**Rate limits**: per-endpoint quotas, generally 100-1000 req/min. 429 with `Retry-After`.\n\n**Out of scope here**: direct file upload (multipart, separate API), Paper docs, team admin, the legacy v1 API.",
66
"region": "intl",
77
"category": "storage",
88
"icon": "dropbox",
99
"docsUrl": "https://www.dropbox.com/developers/documentation/http/documentation",
10-
"requiredEnvVars": ["DROPBOX_CLIENT_ID", "DROPBOX_CLIENT_SECRET", "DROPBOX_REFRESH_TOKEN"],
10+
"requiredEnvVars": ["DROPBOX_CLIENT_ID", "DROPBOX_CLIENT_SECRET"],
11+
"optionalEnvVars": ["DROPBOX_REFRESH_TOKEN"],
12+
"envVarMeta": {
13+
"DROPBOX_REFRESH_TOKEN": {
14+
"label": "Refresh token",
15+
"kind": "credential",
16+
"help": "Leave empty: Authorize with Provider fills it in. Paste one only if you already obtained a refresh token for the same app yourself.",
17+
"advanced": true
18+
}
19+
},
1120
"connector": {
1221
"name": "Dropbox API v2",
1322
"type": "REST",
@@ -17,7 +26,9 @@
1726
"clientId": "{{DROPBOX_CLIENT_ID}}",
1827
"clientSecret": "{{DROPBOX_CLIENT_SECRET}}",
1928
"refreshToken": "{{DROPBOX_REFRESH_TOKEN}}",
20-
"tokenUrl": "https://api.dropboxapi.com/oauth2/token"
29+
"authorizationUrl": "https://www.dropbox.com/oauth2/authorize?token_access_type=offline",
30+
"tokenUrl": "https://api.dropboxapi.com/oauth2/token",
31+
"scopes": "account_info.read files.metadata.read files.content.write sharing.write"
2132
}
2233
},
2334
"tools": [

0 commit comments

Comments
 (0)