diff --git a/CITATION.cff b/CITATION.cff index ac0c56a41..951026a1b 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -13,7 +13,7 @@ abstract: > server that turns REST/OpenAPI, SOAP/WSDL and GraphQL APIs, SQL/NoSQL databases and other MCP servers into tools for AI clients such as Claude, ChatGPT, Google Gemini, GitHub Copilot and Cursor, without writing code. - It ships 474 pre-built adapters, with a focus on ERP and e-commerce + It ships 475 pre-built adapters, with a focus on ERP and e-commerce systems (SAP Business One, Odoo, Xentral, JTL-Wawi, weclapp, Shopware, WooCommerce, Amazon Seller, Kaufland, OTTO, etc.), a per-workspace knowledge graph served over MCP, per-tool response mapping, diff --git a/README.de.md b/README.de.md index f23da753a..646c064b2 100644 --- a/README.de.md +++ b/README.de.md @@ -1,5 +1,5 @@
-
+
AnythingMCP ist ein quelloffenes, selbst gehostetes MCP-Gateway, das jedes REST-/OpenAPI-, SOAP-, GraphQL-, OData- oder SQL-System in MCP-Tools für Claude, ChatGPT und Copilot verwandelt, ohne dass du einen MCP-Server programmierst.
- Es bringt 474 fertige Adapter mit, darunter SAP, Etsy, weclapp und Amazon Seller; 20 davon kommen ohne API-Schlüssel aus.
+ Es bringt 475 fertige Adapter mit, darunter SAP, Etsy, weclapp und Amazon Seller; 20 davon kommen ohne API-Schlüssel aus.
@@ -111,7 +111,7 @@ Tools werden zur Laufzeit registriert, ohne Neustart. `{{VAR}}`-Werte pro Connec ## Connector-Katalog -474 Adapter mit über 2.400 Tools. Zu jedem gibt es eine Einrichtungsanleitung auf [anythingmcp.com/de/guides](https://anythingmcp.com/de/guides), in sieben Sprachen. +475 Adapter mit über 2.400 Tools. Zu jedem gibt es eine Einrichtungsanleitung auf [anythingmcp.com/de/guides](https://anythingmcp.com/de/guides), in sieben Sprachen. | Kategorie | Beispiele | |---|---| diff --git a/README.ja.md b/README.ja.md index 10be89fe6..d40051e1a 100644 --- a/README.ja.md +++ b/README.ja.md @@ -1,5 +1,5 @@
-
+
AnythingMCP は、オープンソースのセルフホスト型 MCP ゲートウェイです。MCP サーバーを書かずに、REST/OpenAPI、SOAP、GraphQL、OData、SQL のあらゆるシステムを Claude、ChatGPT、Copilot 用の MCP ツールに変換します。
- SAP、Etsy、weclapp、Amazon Seller などを含む 474 種類の既製アダプターを同梱しており、うち 20 は API キー不要です。
+ SAP、Etsy、weclapp、Amazon Seller などを含む 475 種類の既製アダプターを同梱しており、うち 20 は API キー不要です。
@@ -111,7 +111,7 @@ amd64 ではイメージの取得に約 30 秒、その 24 秒後に API が利 ## コネクターカタログ -474 個のアダプターで 2,400 以上のツールを提供しています。どのアダプターにも [anythingmcp.com/ja/guides](https://anythingmcp.com/ja/guides) に 7 言語の設定ガイドがあります。 +475 個のアダプターで 2,400 以上のツールを提供しています。どのアダプターにも [anythingmcp.com/ja/guides](https://anythingmcp.com/ja/guides) に 7 言語の設定ガイドがあります。 | カテゴリー | 例 | |---|---| diff --git a/README.md b/README.md index a6f917bec..c3efe3acd 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@
-
+
AnythingMCP is an open-source, self-hosted MCP gateway that turns any REST/OpenAPI, SOAP, GraphQL, OData or SQL system into MCP tools for Claude, ChatGPT and Copilot, without writing an MCP server.
- It ships 474 ready connectors, among them SAP, Etsy, weclapp and Amazon Seller, and 20 of them need no API key.
+ It ships 475 ready connectors, among them SAP, Etsy, weclapp and Amazon Seller, and 20 of them need no API key.
@@ -103,7 +103,7 @@ Tools register at runtime, without a restart. Per-connector `{{VAR}}` values are ## Connector catalog -474 adapters, exposing 2,400+ tools. Every one has a setup guide on [anythingmcp.com/guides](https://anythingmcp.com/guides), in seven languages. +475 adapters, exposing 2,400+ tools. Every one has a setup guide on [anythingmcp.com/guides](https://anythingmcp.com/guides), in seven languages. | Category | Examples | |---|---| diff --git a/README.zh-CN.md b/README.zh-CN.md index 12aae0e45..6a48a3227 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,5 +1,5 @@
-
+
AnythingMCP 是一个开源、可自行托管的 MCP 网关,无需编写 MCP 服务器,即可将任意 REST/OpenAPI、SOAP、GraphQL、OData 或 SQL 系统转化为 Claude、ChatGPT 和 Copilot 可用的 MCP 工具。
- 它自带 474 个现成适配器,涵盖 SAP、Etsy、weclapp 和 Amazon Seller 等,其中 20 个无需 API 密钥。
+ 它自带 475 个现成适配器,涵盖 SAP、Etsy、weclapp 和 Amazon Seller 等,其中 20 个无需 API 密钥。
@@ -111,7 +111,7 @@ docker compose up -d
## 连接器目录
-共 474 个适配器,提供 2,400 多个工具。每个适配器都在 [anythingmcp.com/zh/guides](https://anythingmcp.com/zh/guides) 上提供七种语言的配置指南。
+共 475 个适配器,提供 2,400 多个工具。每个适配器都在 [anythingmcp.com/zh/guides](https://anythingmcp.com/zh/guides) 上提供七种语言的配置指南。
| 类别 | 示例 |
|---|---|
diff --git a/glama.json b/glama.json
index 014ec6284..ea66d19f3 100644
--- a/glama.json
+++ b/glama.json
@@ -3,5 +3,5 @@
"maintainers": [
"helpcodeai"
],
- "description": "Turn any REST/OpenAPI, SOAP/WSDL, GraphQL, OData or SQL API into MCP tools for Claude, ChatGPT and Copilot, no code. 474 pre-built adapters for ERP and e-commerce (SAP S/4HANA, SAP Business One, Odoo, Xentral, JTL-Wawi, weclapp, Shopware, WooCommerce, Amazon Seller, Kaufland, OTTO\u2026) and more. Self-hosted, knowledge graph, per-tool response mapping, OAuth2/RBAC/SSO/audit. Open source under AGPL-3.0."
+ "description": "Turn any REST/OpenAPI, SOAP/WSDL, GraphQL, OData or SQL API into MCP tools for Claude, ChatGPT and Copilot, no code. 475 pre-built adapters for ERP and e-commerce (SAP S/4HANA, SAP Business One, Odoo, Xentral, JTL-Wawi, weclapp, Shopware, WooCommerce, Amazon Seller, Kaufland, OTTO\u2026) and more. Self-hosted, knowledge graph, per-tool response mapping, OAuth2/RBAC/SSO/audit. Open source under AGPL-3.0."
}
diff --git a/package.json b/package.json
index 81da7be1e..81e3ca201 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "anythingmcp",
"version": "0.36.3",
- "description": "Turn any REST/OpenAPI, SOAP/WSDL, GraphQL, OData or SQL API into MCP tools for Claude, ChatGPT and Copilot, no code. 474 pre-built adapters for ERP, e-commerce and more (SAP S/4HANA, SAP Business One, Odoo, Xentral, JTL-Wawi, Shopware, WooCommerce, Amazon Seller). Self-hosted, open source (AGPL-3.0).",
+ "description": "Turn any REST/OpenAPI, SOAP/WSDL, GraphQL, OData or SQL API into MCP tools for Claude, ChatGPT and Copilot, no code. 475 pre-built adapters for ERP, e-commerce and more (SAP S/4HANA, SAP Business One, Odoo, Xentral, JTL-Wawi, Shopware, WooCommerce, Amazon Seller). Self-hosted, open source (AGPL-3.0).",
"private": true,
"license": "AGPL-3.0-only",
"engines": {
diff --git a/packages/backend/src/adapters/catalog.ts b/packages/backend/src/adapters/catalog.ts
index 200e542be..81450e6eb 100644
--- a/packages/backend/src/adapters/catalog.ts
+++ b/packages/backend/src/adapters/catalog.ts
@@ -68,6 +68,7 @@ import * as zalandoZds from './de/zalando-zds.json';
import * as zammad from './de/zammad.json';
import * as znuny from './de/znuny.json';
import * as companiesHouse from './gb/companies-house.json';
+import * as oddsrelay from './gb/oddsrelay.json';
import * as peoplehr from './gb/peoplehr.json';
import * as pinpoint from './gb/pinpoint.json';
import * as sage200 from './gb/sage-200.json';
@@ -778,6 +779,7 @@ const RAW_ADAPTERS: AdapterDefinition[] = [
zammad as unknown as AdapterDefinition,
znuny as unknown as AdapterDefinition,
companiesHouse as unknown as AdapterDefinition,
+ oddsrelay as unknown as AdapterDefinition,
peoplehr as unknown as AdapterDefinition,
pinpoint as unknown as AdapterDefinition,
sage200 as unknown as AdapterDefinition,
diff --git a/packages/backend/src/adapters/gb/oddsrelay.json b/packages/backend/src/adapters/gb/oddsrelay.json
new file mode 100644
index 000000000..f0c2245c2
--- /dev/null
+++ b/packages/backend/src/adapters/gb/oddsrelay.json
@@ -0,0 +1,302 @@
+{
+ "slug": "oddsrelay",
+ "name": "OddsRelay",
+ "description": "Pre-match odds from UK and Irish bookmakers with exchange lay prices. Look up sports, events and bookmakers, then read one matched board or one event's prices.",
+ "instructions": "## OddsRelay\nThis connector reads the OddsRelay odds API (`https://api.oddsrelay.io/v2`): pre-match odds from UK and Irish bookmakers with exchange lay prices; `oddsrelay_list_bookmakers` lists the bookmakers and exchanges. Every tool only reads. Create an API key from your account at https://oddsrelay.io and paste it as `ODDSRELAY_KEY`: it is sent as a Bearer token on every call and is never a tool argument.\n\n**Tokens.** `oddsrelay_list_sports`, `oddsrelay_list_events`, `oddsrelay_list_bookmakers`, `oddsrelay_get_coverage`, `oddsrelay_get_usage` and the two quote tools cost nothing. `oddsrelay_get_board` and `oddsrelay_get_event_odds` spend tokens from the account behind the key, and this connector has no cap: it cannot refuse a call that costs more than the user expects. Before either one, call the matching quote tool with exactly the same arguments, tell the user the `cost` and `remaining` it returns, and go ahead only when they agree. Keep every call narrow: one competition key rather than a whole sport, only the bookmakers the user asked about, a `markets` filter and a start-time window.\n\n**Workflow.**\n1. `oddsrelay_get_coverage`: for each board the key can read, the bookmakers and exchanges with prices now and their sports.\n2. `oddsrelay_list_sports`: competition keys. Each starts with its sport, such as `soccer`, and the sport alone covers all of its competitions.\n3. `oddsrelay_list_bookmakers`: bookmaker and exchange keys such as `william_hill` or `betfair_exchange`.\n4. `oddsrelay_quote_board`, then `oddsrelay_get_board` with `type`, `sport` and `bookmakers`.\n5. For one event: an `event_id` from a board, or `oddsrelay_list_events`; then `oddsrelay_quote_event_odds` and `oddsrelay_get_event_odds`. Event odds need a key that can read raw odds.\n\n**Paging.** `oddsrelay_list_sports` and `oddsrelay_list_events` return `meta.next_cursor`. Pass it back as `cursor`, with the same other arguments, until it is null. A cursor expires after a few minutes; start again from the first page when it does.\n\n**Errors** carry an `error.code` and a message:\n- 401: the key is missing or wrong.\n- 402 `insufficient_tokens`: the balance cannot cover this call; `oddsrelay_get_usage` shows the balance and when it resets.\n- 400 with `valid`: an unknown sport, bookmaker, exchange or market; pick a key from the `valid` list.\n- 403: the request reaches past what the key may read; pick from `valid` when it is given, otherwise tell the user.\n- 404 `unknown_event`: the event is no longer listed; find it again with `oddsrelay_list_events`.\n- 429: too many calls at once; wait for the `Retry-After` time, then try once more.\n- 503: not available right now; try again later.\n\n**Times** are ISO 8601 in UTC, such as `2026-10-10T12:00:00Z`.",
+ "region": "gb",
+ "category": "sports",
+ "icon": "oddsrelay",
+ "docsUrl": "https://oddsrelay.io/docs",
+ "prerequisites": "An OddsRelay account and an API key from it. Event odds need a key that can read raw odds.",
+ "requiredEnvVars": ["ODDSRELAY_KEY"],
+ "envVarMeta": {
+ "ODDSRELAY_KEY": {
+ "label": "API key",
+ "kind": "credential",
+ "secret": true,
+ "help": "Create a key from your account at oddsrelay.io, then paste it here.",
+ "link": "https://oddsrelay.io"
+ }
+ },
+ "probe": { "tool": "oddsrelay_get_usage" },
+ "connector": {
+ "name": "OddsRelay",
+ "type": "REST",
+ "baseUrl": "https://api.oddsrelay.io",
+ "authType": "BEARER_TOKEN",
+ "authConfig": {
+ "token": "{{ODDSRELAY_KEY}}"
+ },
+ "headers": {
+ "Accept-Encoding": "gzip"
+ },
+ "healthcheckPath": "/v2/usage"
+ },
+ "tools": [
+ {
+ "name": "oddsrelay_list_sports",
+ "description": "Lists the competition keys for the sport argument of the board and event tools. Each starts with its sport (such as soccer), which alone covers all of that sport's competitions. Costs no tokens. Paged: pass meta.next_cursor back as cursor until it is null.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "cursor": {
+ "type": "string",
+ "description": "meta.next_cursor from the previous page. Leave out for the first page."
+ }
+ }
+ },
+ "endpointMapping": {
+ "method": "GET",
+ "path": "/v2/sports",
+ "queryParams": {
+ "cursor": "$cursor"
+ }
+ }
+ },
+ {
+ "name": "oddsrelay_list_events",
+ "description": "Lists the events of one sport with their event ids, teams and start times, for oddsrelay_get_event_odds. Costs no tokens. Paged: pass meta.next_cursor back as cursor until it is null.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "sport": {
+ "type": "string",
+ "pattern": "^[a-z0-9_.-]+$",
+ "description": "One competition key from oddsrelay_list_sports, or a sport such as tennis for all of its competitions."
+ },
+ "commenceTimeFrom": {
+ "type": "string",
+ "description": "Only events starting at or after this time, ISO 8601 in UTC."
+ },
+ "commenceTimeTo": {
+ "type": "string",
+ "description": "Only events starting at or before this time, ISO 8601 in UTC."
+ },
+ "cursor": {
+ "type": "string",
+ "description": "meta.next_cursor from the previous page, with the same other arguments."
+ }
+ },
+ "required": ["sport"]
+ },
+ "endpointMapping": {
+ "method": "GET",
+ "path": "/v2/events",
+ "queryParams": {
+ "sport": "$sport",
+ "commenceTimeFrom": "$commenceTimeFrom",
+ "commenceTimeTo": "$commenceTimeTo",
+ "cursor": "$cursor"
+ }
+ }
+ },
+ {
+ "name": "oddsrelay_list_bookmakers",
+ "description": "Lists the bookmaker and exchange keys (such as william_hill or betfair_exchange) that the board tools take. Costs no tokens.",
+ "parameters": {
+ "type": "object",
+ "properties": {}
+ },
+ "endpointMapping": {
+ "method": "GET",
+ "path": "/v2/bookmakers"
+ }
+ },
+ {
+ "name": "oddsrelay_get_coverage",
+ "description": "Shows, for each matched board the key can read, which bookmakers and exchanges have prices now and for which sports. Costs no tokens.",
+ "parameters": {
+ "type": "object",
+ "properties": {}
+ },
+ "endpointMapping": {
+ "method": "GET",
+ "path": "/v2/coverage"
+ },
+ "responseMapping": {
+ "transform": {
+ "mode": "jmespath",
+ "expression": "{generated_at: generated_at, products: from_items(items(products || `{}`)[*].[@[0], {bookmakers: from_items((@[1].bookmakers || `[]`)[*].[key, keys(sports || `{}`)]), exchanges: from_items((@[1].exchanges || `[]`)[*].[key, keys(sports || `{}`)])}])}"
+ }
+ }
+ },
+ {
+ "name": "oddsrelay_quote_board",
+ "description": "Returns what a matched board request would cost in tokens, and the tokens remaining, without reading the board. Costs no tokens. Call it with exactly the arguments you will pass to oddsrelay_get_board, and confirm the cost with the user first.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["standard", "2up", "dutching", "each-way", "extra-place", "bog"],
+ "description": "Which matched board. oddsrelay_get_coverage lists the bookmakers and sports on each."
+ },
+ "sport": {
+ "type": "string",
+ "pattern": "^[a-z0-9_.-]+$",
+ "description": "One competition key from oddsrelay_list_sports, or a sport such as soccer for all of its competitions. A competition key spends fewer tokens than a whole sport."
+ },
+ "bookmakers": {
+ "type": "string",
+ "pattern": "^[a-z0-9_.-]+(,[a-z0-9_.-]+){0,4}$",
+ "description": "Bookmaker keys from oddsrelay_list_bookmakers, comma-separated, such as ladbrokes,william_hill. At most five keys; fewer keys spend fewer tokens."
+ },
+ "exchanges": {
+ "type": "string",
+ "pattern": "^[a-z0-9_.-]+(,[a-z0-9_.-]+){0,3}$",
+ "description": "Optional exchange keys, comma-separated, such as betfair_exchange. Left out, every exchange on the board."
+ },
+ "markets": {
+ "type": "string",
+ "pattern": "^[a-z0-9_.-]+(,[a-z0-9_.-]+){0,4}$",
+ "description": "Optional market keys for this board, comma-separated, such as h2h. Left out, every market on the board. An unknown key is refused with the valid keys listed."
+ },
+ "commenceTimeFrom": {
+ "type": "string",
+ "description": "Optional: only events starting at or after this time, ISO 8601 in UTC."
+ },
+ "commenceTimeTo": {
+ "type": "string",
+ "description": "Optional: only events starting at or before this time, ISO 8601 in UTC."
+ }
+ },
+ "required": ["type", "sport", "bookmakers"]
+ },
+ "endpointMapping": {
+ "method": "GET",
+ "path": "/v2/odds/{type}",
+ "queryParams": {
+ "quote": "true",
+ "sports": "$sport",
+ "bookmakers": "$bookmakers",
+ "exchanges": "$exchanges",
+ "markets": "$markets",
+ "commenceTimeFrom": "$commenceTimeFrom",
+ "commenceTimeTo": "$commenceTimeTo"
+ }
+ }
+ },
+ {
+ "name": "oddsrelay_get_board",
+ "description": "Reads one matched board: bookmaker back prices beside exchange lay prices, for one sport at the bookmakers you name. Spends tokens, and this connector has no cap: run oddsrelay_quote_board with the same arguments first and confirm the cost with the user.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "type": {
+ "type": "string",
+ "enum": ["standard", "2up", "dutching", "each-way", "extra-place", "bog"],
+ "description": "Which matched board. oddsrelay_get_coverage lists the bookmakers and sports on each."
+ },
+ "sport": {
+ "type": "string",
+ "pattern": "^[a-z0-9_.-]+$",
+ "description": "One competition key from oddsrelay_list_sports, or a sport such as soccer for all of its competitions. A competition key spends fewer tokens than a whole sport."
+ },
+ "bookmakers": {
+ "type": "string",
+ "pattern": "^[a-z0-9_.-]+(,[a-z0-9_.-]+){0,4}$",
+ "description": "Bookmaker keys from oddsrelay_list_bookmakers, comma-separated, such as ladbrokes,william_hill. At most five keys; fewer keys spend fewer tokens."
+ },
+ "exchanges": {
+ "type": "string",
+ "pattern": "^[a-z0-9_.-]+(,[a-z0-9_.-]+){0,3}$",
+ "description": "Optional exchange keys, comma-separated, such as betfair_exchange. Left out, every exchange on the board."
+ },
+ "markets": {
+ "type": "string",
+ "pattern": "^[a-z0-9_.-]+(,[a-z0-9_.-]+){0,4}$",
+ "description": "Optional market keys for this board, comma-separated, such as h2h. Left out, every market on the board. An unknown key is refused with the valid keys listed."
+ },
+ "commenceTimeFrom": {
+ "type": "string",
+ "description": "Optional: only events starting at or after this time, ISO 8601 in UTC."
+ },
+ "commenceTimeTo": {
+ "type": "string",
+ "description": "Optional: only events starting at or before this time, ISO 8601 in UTC."
+ }
+ },
+ "required": ["type", "sport", "bookmakers"]
+ },
+ "endpointMapping": {
+ "method": "GET",
+ "path": "/v2/odds/{type}",
+ "queryParams": {
+ "sports": "$sport",
+ "bookmakers": "$bookmakers",
+ "exchanges": "$exchanges",
+ "markets": "$markets",
+ "commenceTimeFrom": "$commenceTimeFrom",
+ "commenceTimeTo": "$commenceTimeTo"
+ }
+ },
+ "annotations": {
+ "readOnlyHint": false,
+ "destructiveHint": false,
+ "idempotentHint": false,
+ "openWorldHint": true
+ }
+ },
+ {
+ "name": "oddsrelay_quote_event_odds",
+ "description": "Returns what one event's odds would cost in tokens, and the tokens remaining, without reading them. Costs no tokens. Call it with the event_id you will pass to oddsrelay_get_event_odds, and confirm the cost with the user first.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "event_id": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]+$",
+ "description": "An event id such as or_evt_917dd44bce05, from a board or oddsrelay_list_events."
+ }
+ },
+ "required": ["event_id"]
+ },
+ "endpointMapping": {
+ "method": "GET",
+ "path": "/v2/odds/event/{event_id}",
+ "encodePathParams": true,
+ "queryParams": {
+ "quote": "true"
+ }
+ }
+ },
+ {
+ "name": "oddsrelay_get_event_odds",
+ "description": "Reads one event's prices at every bookmaker and exchange carrying it, market by market. Needs a key that can read raw odds. Spends tokens, and this connector has no cap: run oddsrelay_quote_event_odds first and confirm the cost with the user.",
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "event_id": {
+ "type": "string",
+ "pattern": "^[A-Za-z0-9_-]+$",
+ "description": "An event id such as or_evt_917dd44bce05, from a board or oddsrelay_list_events."
+ }
+ },
+ "required": ["event_id"]
+ },
+ "endpointMapping": {
+ "method": "GET",
+ "path": "/v2/odds/event/{event_id}",
+ "encodePathParams": true
+ },
+ "annotations": {
+ "readOnlyHint": false,
+ "destructiveHint": false,
+ "idempotentHint": false,
+ "openWorldHint": true
+ }
+ },
+ {
+ "name": "oddsrelay_get_usage",
+ "description": "Shows the key's token balance and when it resets, and which boards the key can read. Costs no tokens, and is the connection check.",
+ "parameters": {
+ "type": "object",
+ "properties": {}
+ },
+ "endpointMapping": {
+ "method": "GET",
+ "path": "/v2/usage"
+ }
+ }
+ ]
+}
diff --git a/packages/backend/src/adapters/gb/oddsrelay.live.spec.ts b/packages/backend/src/adapters/gb/oddsrelay.live.spec.ts
new file mode 100644
index 000000000..419a73130
--- /dev/null
+++ b/packages/backend/src/adapters/gb/oddsrelay.live.spec.ts
@@ -0,0 +1,103 @@
+import * as adapter from './oddsrelay.json';
+import { RestEngine } from '../../connectors/engines/rest.engine';
+import { OAuth2TokenService } from '../../connectors/engines/oauth2-token.service';
+import { LoginTokenService } from '../../connectors/engines/login-token.service';
+import { applyResponseTransform } from '../../connectors/response-transform.util';
+
+/** Live (spends no tokens): RUN_ODDSRELAY_LIVE=1 ODDSRELAY_KEY=