From 3bf6710834d80690399851c8ef06574ee67fab20 Mon Sep 17 00:00:00 2001 From: kriptoburak Date: Thu, 14 May 2026 22:34:53 +0300 Subject: [PATCH] Add Xquik adapter --- .env.example | 1 + README.md | 8 +- config/tool_provider_config.yaml | 22 ++++ src/adapters/registry.ts | 6 + src/adapters/xquik/index.ts | 186 +++++++++++++++++++++++++++++++ src/config/env.ts | 3 + src/mcp/tool-definitions.ts | 38 +++++++ src/schemas/index.ts | 2 + src/schemas/xquik.schema.ts | 69 ++++++++++++ 9 files changed, 331 insertions(+), 4 deletions(-) create mode 100644 src/adapters/xquik/index.ts create mode 100644 src/schemas/xquik.schema.ts diff --git a/.env.example b/.env.example index d5445ff..2c360a9 100644 --- a/.env.example +++ b/.env.example @@ -50,3 +50,4 @@ PROVIDER_KEY_OPENWEATHER=CHANGE_ME PROVIDER_KEY_COINGECKO=CHANGE_ME PROVIDER_KEY_POLYMARKET=CHANGE_ME PROVIDER_KEY_AVIASALES=CHANGE_ME +PROVIDER_KEY_XQUIK=CHANGE_ME diff --git a/README.md b/README.md index 00768bd..7b66569 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # APIbase.pro — The API Hub for AI Agents -> One MCP endpoint. 576 tools. 177 providers. Pay per call with x402 (USDC on Base) or MPP (USDC on Tempo). +> One MCP endpoint. 580 tools. 178 providers. Pay per call with x402 (USDC on Base) or MPP (USDC on Tempo). **[Live Platform](https://apibase.pro)** | **[Tool Catalog](https://apibase.pro/api/v1/tools)** | **[MCP Endpoint](https://apibase.pro/mcp)** | **[Frameworks](https://apibase.pro/frameworks)** | **[Dashboard](https://apibase.pro/dashboard)** @@ -27,7 +27,7 @@ https://github.com/user-attachments/assets/9e598d61-b2d0-486c-bd34-f0cb0354d09c ## What is APIbase? -Production MCP server that gives AI agents access to 576 real-world API tools through a single endpoint. Agents connect once to `https://apibase.pro/mcp` and can search flights, get stock quotes, check weather and tides, query US Census and CDC health data, search ML models on HuggingFace, look up World Bank indicators, track streamflow from USGS stations, search 7M+ CS papers on DBLP, generate images, send emails, decode VINs, look up chemical compounds, scan npm/PyPI vulnerabilities, find EV chargers, search art at the Met Museum, batch multiple calls, track usage analytics — and 300+ more tools across 30+ categories. +Production MCP server that gives AI agents access to 580 real-world API tools through a single endpoint. Agents connect once to `https://apibase.pro/mcp` and can search flights, get stock quotes, check weather and tides, query US Census and CDC health data, search ML models on HuggingFace, look up World Bank indicators, track streamflow from USGS stations, search 7M+ CS papers on DBLP, generate images, send emails, decode VINs, look up chemical compounds, scan npm/PyPI vulnerabilities, find EV chargers, search art at the Met Museum, batch multiple calls, track usage analytics - and 300+ more tools across 30+ categories. **Built for AI agents, not humans.** Auto-registration, zero setup, pay-per-call via x402 USDC micropayments on Base or MPP (Machine Payments Protocol) on Tempo. @@ -91,13 +91,13 @@ curl -X POST https://apibase.pro/api/v1/tools/finnhub.quote/call \ --- -## Tool Categories (576 tools, 160 providers) +## Tool Categories (580 tools, 161 providers) | Category | Tools | Providers | Examples | |----------|-------|-----------|----------| | **Web Search** | 11 | Serper, Tavily, Exa, Spider.cloud | Google search, AI search, semantic search, web scraping | | **News & Events** | 10 | NewsData, GDELT, Mastodon, Currents API | Global news (65 langs), crypto news, trending | -| **Social** | 7 | Bluesky, TwitterAPI.io | Search posts, profiles, feeds (AT Protocol, X/Twitter) | +| **Social** | 11 | Bluesky, TwitterAPI.io, Xquik | Search posts, profiles, feeds, followers, trends (AT Protocol, X/Twitter) | | **Travel & Flights** | 17 | Amadeus, Sabre, Aviasales | Flight search, pricing, status, airports | | **Finance & Stocks** | 17 | Finnhub, CoinGecko, ECB, FRED, World Bank | Stock quotes, OHLCV, FX rates, economic data, global indicators | | **Banking Data** | 6 | FDIC BankFind, IBANAPI | US bank financials, branch locations, institution search, IBAN validation | diff --git a/config/tool_provider_config.yaml b/config/tool_provider_config.yaml index afdfaed..d7d8011 100644 --- a/config/tool_provider_config.yaml +++ b/config/tool_provider_config.yaml @@ -2420,6 +2420,28 @@ tools: price_usd: "0.002" cache_ttl: 60 + # --- Xquik (X/Twitter Data) --- + - tool_id: xquik.search_tweets + name: Search X Tweets + provider: xquik + price_usd: "0.002" + cache_ttl: 60 + - tool_id: xquik.user + name: X User Profile + provider: xquik + price_usd: "0.002" + cache_ttl: 300 + - tool_id: xquik.followers + name: X User Followers + provider: xquik + price_usd: "0.003" + cache_ttl: 300 + - tool_id: xquik.trends + name: X Trending Topics + provider: xquik + price_usd: "0.002" + cache_ttl: 60 + # --- UC-210: Currents API (Global News) --- - tool_id: currents.latest name: Latest Global News diff --git a/src/adapters/registry.ts b/src/adapters/registry.ts index 46daeb7..e5c9a7c 100644 --- a/src/adapters/registry.ts +++ b/src/adapters/registry.ts @@ -109,6 +109,7 @@ import { WhoAdapter } from './who'; import { GdacsAdapter } from './gdacs'; import { RateApiAdapter } from './rateapi'; import { TwitterApiAdapter } from './twitterapi'; +import { XquikAdapter } from './xquik'; import { CurrentsAdapter } from './currents'; import { IbanApiAdapter } from './ibanapi'; import { PubchemAdapter } from './pubchem'; @@ -778,6 +779,11 @@ export function resolveAdapter(toolId: string): BaseAdapter | undefined { if (!twKey) return undefined; return getOrCreate('twitterapi', () => new TwitterApiAdapter(twKey)); } + case 'xquik': { + const xquikKey = (config as Record).PROVIDER_KEY_XQUIK as string | undefined; + if (!xquikKey) return undefined; + return getOrCreate('xquik', () => new XquikAdapter(xquikKey)); + } case 'pubchem': { const ncbiKey = ((config as Record).PROVIDER_KEY_NCBI as string) || ''; return getOrCreate('pubchem', () => new PubchemAdapter(ncbiKey)); diff --git a/src/adapters/xquik/index.ts b/src/adapters/xquik/index.ts new file mode 100644 index 0000000..1e8a939 --- /dev/null +++ b/src/adapters/xquik/index.ts @@ -0,0 +1,186 @@ +import { BaseAdapter } from '../base.adapter'; +import { + type ProviderRawResponse, + type ProviderRequest, + ProviderErrorCode, +} from '../../types/provider'; + +/** + * Xquik adapter. + * + * Supported tools: + * xquik.search_tweets -> GET /api/v1/x/tweets/search + * xquik.user -> GET /api/v1/x/users/{id} + * xquik.followers -> GET /api/v1/x/users/{id}/followers + * xquik.trends -> GET /api/v1/x/trends + * + * Auth: x-api-key header. + */ +export class XquikAdapter extends BaseAdapter { + private readonly apiKey: string; + + constructor(apiKey: string) { + super({ + provider: 'xquik', + baseUrl: 'https://xquik.com/api/v1', + }); + this.apiKey = apiKey; + } + + protected buildRequest(req: ProviderRequest): { + url: string; + method: string; + headers: Record; + } { + const params = req.params as Record; + const headers: Record = { + Accept: 'application/json', + 'x-api-key': this.apiKey, + }; + + switch (req.toolId) { + case 'xquik.search_tweets': { + const qs = new URLSearchParams(); + qs.set('q', String(params.query ?? 'news')); + qs.set('queryType', params.sort_order === 'top' ? 'Top' : 'Latest'); + if (params.cursor) qs.set('cursor', String(params.cursor)); + if (params.since_time) qs.set('sinceTime', String(params.since_time)); + if (params.until_time) qs.set('untilTime', String(params.until_time)); + if (params.limit) qs.set('limit', String(params.limit)); + return { + url: `${this.baseUrl}/x/tweets/search?${qs.toString()}`, + method: 'GET', + headers, + }; + } + + case 'xquik.user': { + const userId = this.getUserId(params); + return { + url: `${this.baseUrl}/x/users/${encodeURIComponent(userId)}`, + method: 'GET', + headers, + }; + } + + case 'xquik.followers': { + const qs = new URLSearchParams(); + const userId = this.getUserId(params); + if (params.cursor) qs.set('cursor', String(params.cursor)); + if (params.page_size) qs.set('pageSize', String(params.page_size)); + const suffix = qs.toString(); + return { + url: `${this.baseUrl}/x/users/${encodeURIComponent(userId)}/followers${suffix ? `?${suffix}` : ''}`, + method: 'GET', + headers, + }; + } + + case 'xquik.trends': { + const qs = new URLSearchParams(); + qs.set('woeid', String(params.woeid ?? 1)); + if (params.count) qs.set('count', String(params.count)); + return { + url: `${this.baseUrl}/x/trends?${qs.toString()}`, + method: 'GET', + headers, + }; + } + + default: + throw { + code: ProviderErrorCode.INVALID_RESPONSE, + httpStatus: 502, + message: `Unsupported tool: ${req.toolId}`, + provider: this.provider, + toolId: req.toolId, + durationMs: 0, + }; + } + } + + protected parseResponse(raw: ProviderRawResponse, req: ProviderRequest): unknown { + const body = raw.body as Record; + + switch (req.toolId) { + case 'xquik.search_tweets': { + const tweets = (body.tweets as Array>) ?? []; + return { + total: tweets.length, + has_next: body.has_next_page ?? false, + next_cursor: body.next_cursor ?? null, + tweets: tweets.map((tweet) => this.mapTweet(tweet)), + }; + } + + case 'xquik.user': + return this.mapUser(body); + + case 'xquik.followers': { + const users = (body.users as Array>) ?? []; + return { + total: users.length, + has_next: body.has_next_page ?? false, + next_cursor: body.next_cursor ?? null, + followers: users.map((user) => this.mapUser(user)), + }; + } + + case 'xquik.trends': { + const trends = (body.trends as Array>) ?? []; + return { + total: body.count ?? trends.length, + woeid: body.woeid, + trends: trends.map((trend) => ({ + name: trend.name, + description: trend.description, + query: trend.query, + rank: trend.rank, + })), + }; + } + + default: + return body; + } + } + + private getUserId(params: Record): string { + return String(params.username ?? params.user_id ?? ''); + } + + private mapTweet(tweet: Record): Record { + const author = tweet.author as Record | undefined; + return { + id: tweet.id, + text: tweet.text, + created_at: tweet.createdAt, + author: author ? this.mapUser(author) : null, + likes: tweet.likeCount, + retweets: tweet.retweetCount, + replies: tweet.replyCount, + quotes: tweet.quoteCount, + views: tweet.viewCount, + bookmarks: tweet.bookmarkCount, + lang: tweet.lang, + }; + } + + private mapUser(user: Record): Record { + return { + id: user.id, + username: user.username, + name: user.name, + bio: user.description, + location: user.location, + followers: user.followers, + following: user.following, + tweets_count: user.statusesCount, + verified: user.verified, + verified_type: user.verifiedType, + profile_image: user.profilePicture, + profile_banner: user.coverPicture, + created_at: user.createdAt, + }; + } +} diff --git a/src/config/env.ts b/src/config/env.ts index 9a2af67..64122ca 100644 --- a/src/config/env.ts +++ b/src/config/env.ts @@ -325,6 +325,9 @@ export const appEnvSchema = z.object({ // TwitterAPI.io (UC-198) — Twitter/X data, pay-per-call PROVIDER_KEY_TWITTERAPI: z.string().optional().default(''), + // Xquik - X/Twitter data API + PROVIDER_KEY_XQUIK: z.string().optional().default(''), + // Currents API (UC-210) — global news 70+ countries PROVIDER_KEY_CURRENTS: z.string().optional().default(''), diff --git a/src/mcp/tool-definitions.ts b/src/mcp/tool-definitions.ts index 4bf4fdd..2afc809 100644 --- a/src/mcp/tool-definitions.ts +++ b/src/mcp/tool-definitions.ts @@ -4071,6 +4071,44 @@ export const TOOL_DEFINITIONS: McpToolDefinition[] = [ annotations: READ_ONLY, }, + // Xquik (4) + { + toolId: 'xquik.search_tweets', + mcpName: 'xquik.tweets.search', + title: 'Search X Tweets with Xquik', + description: + 'Search X tweets with X query operators, cursor pagination, optional time bounds, and engagement-ranked or chronological sort. Returns tweet text, author, metrics, timestamps, and pagination cursors.', + category: 'social', + annotations: READ_ONLY, + }, + { + toolId: 'xquik.user', + mcpName: 'xquik.users.profile', + title: 'X User Profile with Xquik', + description: + 'Look up an X user profile by username or user ID. Returns display name, bio, follower and following counts, verification status, profile images, location, and account creation date.', + category: 'social', + annotations: READ_ONLY, + }, + { + toolId: 'xquik.followers', + mcpName: 'xquik.users.followers', + title: 'X User Followers with Xquik', + description: + 'Get a paginated follower list for an X user by username or user ID. Returns profile details with cursor pagination and configurable page size.', + category: 'social', + annotations: READ_ONLY, + }, + { + toolId: 'xquik.trends', + mcpName: 'xquik.trends.regional', + title: 'X Trending Topics with Xquik', + description: + 'Get current X trending topics by WOEID region. Returns trend names, optional descriptions, search queries, rank, count, and the region used.', + category: 'social', + annotations: READ_ONLY, + }, + // Currents API (3) — UC-210 { toolId: 'currents.latest', diff --git a/src/schemas/index.ts b/src/schemas/index.ts index 73ca15a..e6bb7b0 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -109,6 +109,7 @@ import { whoSchemas } from './who.schema'; import { gdacsSchemas } from './gdacs.schema'; import { rateapiSchemas } from './rateapi.schema'; import { twitterapiSchemas } from './twitterapi.schema'; +import { xquikSchemas } from './xquik.schema'; import { currentsSchemas } from './currents.schema'; import { ibanapiSchemas } from './ibanapi.schema'; import { pubchemSchemas } from './pubchem.schema'; @@ -289,6 +290,7 @@ export const toolSchemas: Record = { ...gdacsSchemas, ...rateapiSchemas, ...twitterapiSchemas, + ...xquikSchemas, ...currentsSchemas, ...ibanapiSchemas, ...pubchemSchemas, diff --git a/src/schemas/xquik.schema.ts b/src/schemas/xquik.schema.ts new file mode 100644 index 0000000..657fd21 --- /dev/null +++ b/src/schemas/xquik.schema.ts @@ -0,0 +1,69 @@ +import { z, type ZodSchema } from 'zod'; + +const searchTweetsSchema = z + .object({ + query: z + .string() + .min(1) + .describe( + 'X search query with operators such as from:username, #hashtag, quoted phrases, since:YYYY-MM-DD, until:YYYY-MM-DD', + ), + sort_order: z + .enum(['latest', 'top']) + .optional() + .describe('Sort order: latest for chronological results, top for engagement-ranked results'), + cursor: z.string().optional().describe('Pagination cursor from a previous response'), + since_time: z + .string() + .optional() + .describe('ISO 8601 timestamp. Only return tweets after this time'), + until_time: z + .string() + .optional() + .describe('ISO 8601 timestamp. Only return tweets before this time'), + limit: z.number().int().min(1).max(200).optional().describe('Maximum tweets to return'), + }) + .strip(); + +const userSchema = z + .object({ + username: z.string().optional().describe('X username without @, for example xquikcom'), + user_id: z.string().optional().describe('X numeric user ID as an alternative to username'), + }) + .strip(); + +const followersSchema = z + .object({ + username: z.string().optional().describe('X username without @'), + user_id: z.string().optional().describe('X numeric user ID as an alternative to username'), + cursor: z.string().optional().describe('Pagination cursor from a previous response'), + page_size: z + .number() + .int() + .min(20) + .max(200) + .optional() + .describe('Users to request in one page'), + }) + .strip(); + +const trendsSchema = z + .object({ + woeid: z + .number() + .int() + .positive() + .optional() + .describe( + 'Where On Earth ID for the trend region. 1 is worldwide, 23424977 is US, 23424975 is UK', + ), + count: z.number().int().min(1).max(50).optional().describe('Number of trends to return'), + }) + .strip(); + +export const xquikSchemas: Record = { + 'xquik.search_tweets': searchTweetsSchema, + 'xquik.user': userSchema, + 'xquik.followers': followersSchema, + 'xquik.trends': trendsSchema, +};