@getabrain/mcp-server
Advanced tools
+48
-25
@@ -59,21 +59,42 @@ #!/usr/bin/env node | ||
| name: "get_balance", | ||
| description: "Get the prepaid balance (in cents) available to fund GetABrain queries.", | ||
| description: 'Check the prepaid balance (in cents) available to fund submit_query calls, plus account mode. Call before submit_query if unsure funds suffice, or whenever a query fails/stalls for balance reasons. Read-only, no side effects, free in test and live mode. Response includes mode ("test" = sandbox key, free simulated responses; "live" = real key, real spend), and when relevant whether auto_reload is on, plus an auto_reload_setup_url + hint to enable it so a live account does not stall at zero balance. Disambiguation: reports funds available to spend; does not list queries (list_queries) or responses (get_responses/wait_for_responses).', | ||
| inputShape: {}, | ||
| run: async (_args, client) => { | ||
| const b = await client.account.balance(); | ||
| return ok({ balance_cents: b.balance_cents, company_name: b.company_name }); | ||
| return ok({ | ||
| balance_cents: b.balance_cents, | ||
| company_name: b.company_name, | ||
| ...b.mode !== void 0 ? { mode: b.mode } : {}, | ||
| ...b.auto_reload_enabled !== void 0 ? { auto_reload_enabled: b.auto_reload_enabled } : {}, | ||
| ...b.auto_reload_setup_url !== void 0 ? { auto_reload_setup_url: b.auto_reload_setup_url } : {}, | ||
| ...b.hint !== void 0 ? { hint: b.hint } : {} | ||
| }); | ||
| } | ||
| }, | ||
| { | ||
| name: "create_topup_link", | ||
| description: "Generate a one-time Stripe Checkout URL for adding funds to the prepaid balance. Use when get_balance shows insufficient funds for an upcoming submit_query, or a human asks to add money. Side effect: creates a pending Stripe session (no charge yet); the returned checkout_url must be OPENED IN A BROWSER BY A HUMAN to enter payment details and complete the charge -- the agent cannot complete payment itself; this is an out-of-band, human-in-the-loop step. Works with a test-mode or live-mode key (funding a test-mode account is how you move a sandbox integration to real spending power). Balance updates only after checkout completes; poll get_balance to confirm. Disambiguation: only mints a payment link -- never moves money or blocks waiting for payment itself.", | ||
| inputShape: { | ||
| amount_cents: z.number().int().min(500).max(1e7).describe( | ||
| "Amount to add to the prepaid balance, in whole cents (min 500 = $5.00, max 10000000 = $100,000.00). E.g. 5000 = $50.00." | ||
| ) | ||
| }, | ||
| run: async (args, client) => ok(await client.account.createTopupLink(args.amount_cents)) | ||
| }, | ||
| { | ||
| name: "submit_query", | ||
| description: `Ask a human / add a human in the loop: submit a human-judgment query to real human workers and get its id back. Use this for any human-in-the-loop (HITL) step, human approval, or when your model needs a real human opinion. "type" is one of: ${QUERY_TYPES.join(", ")}. "content_data" is the type-specific payload (e.g. ab_test: {question, variant_a:{description}, variant_b:{description}}; yes_no: {question}; rating_scale: {question, scale_type, scale_min, scale_max}). Cost = (bid_amount_cents + bonus_amount_cents) * required_responses, deducted from your balance.`, | ||
| description: `Submit a structured question to real human workers, returning a query_id -- the entry point for any human-in-the-loop (HITL) step: judgment calls, subjective evaluation, approval/review, or "ask a real person" tasks a model should not answer itself. "type" selects the question format (one of: ${QUERY_TYPES.join(", ")}); "content_data" is the matching type-specific payload (e.g. ab_test: {question, variant_a:{description}, variant_b:{description}}; yes_no: {question}; rating_scale: {question, scale_type, scale_min, scale_max}). Cost/side effects: with a LIVE key this deducts (bid_amount_cents + bonus_amount_cents) * required_responses from balance immediately and dispatches to paid workers (fails if balance too low -- check get_balance or use create_topup_link). With a TEST key, no balance is touched and responses are synthetic, marked simulated: true, so you can build/test a full pipeline for free before going live. Returns immediately, does not wait -- use get_responses (one-shot) or wait_for_responses (bounded polling) to retrieve answers.`, | ||
| inputShape: { | ||
| type: z.enum(QUERY_TYPES), | ||
| title: z.string().min(5).max(255), | ||
| content_data: z.record(z.any()), | ||
| required_responses: z.number().int().min(1).max(1e3), | ||
| bid_amount_cents: z.number().int().min(5).max(1e7), | ||
| description: z.string().optional(), | ||
| bonus_amount_cents: z.number().int().min(0).optional(), | ||
| min_worker_quality: z.number().min(0).max(5).optional() | ||
| type: z.enum(QUERY_TYPES).describe(`Question format/template. One of: ${QUERY_TYPES.join(", ")}. Determines the required shape of content_data.`), | ||
| title: z.string().min(5).max(255).describe("Short human-readable title for the query, shown to workers as the task headline (5-255 characters)."), | ||
| content_data: z.record(z.any()).describe( | ||
| 'Type-specific payload whose required fields depend on "type" (e.g. {question, variant_a, variant_b} for ab_test; {question} for yes_no; {question, scale_type, scale_min, scale_max} for rating_scale). See the API docs for the full schema per type.' | ||
| ), | ||
| required_responses: z.number().int().min(1).max(1e3).describe("Number of distinct human worker responses to collect before the query is considered complete (1-1000)."), | ||
| bid_amount_cents: z.number().int().min(5).max(1e7).describe( | ||
| "Cents paid to EACH worker per accepted response (min 5 = $0.05, max 10000000 = $100,000.00). Total cost = (bid_amount_cents + bonus_amount_cents) * required_responses, deducted from balance in live mode. No charge occurs in test mode." | ||
| ), | ||
| description: z.string().optional().describe("Optional longer explanation/context shown to workers alongside the title, for extra instructions or background."), | ||
| bonus_amount_cents: z.number().int().min(0).optional().describe("Optional extra cents paid to EACH worker on top of bid_amount_cents per accepted response (default 0). Included in the total cost calculation."), | ||
| min_worker_quality: z.number().min(0).max(5).optional().describe("Optional minimum worker quality score (0-5) required to accept this query; higher restricts to more experienced/reliable workers.") | ||
| }, | ||
@@ -84,4 +105,6 @@ run: async (args, client) => ok(await client.queries.create(args)) | ||
| name: "get_responses", | ||
| description: "Get a query and the human responses submitted so far.", | ||
| inputShape: { query_id: z.string() }, | ||
| description: `One-shot read: fetch a query's current status and whatever human responses have been submitted so far, without waiting. Use this to check progress on demand, or after wait_for_responses reports "pending" if you want an immediate snapshot instead of polling again. Read-only, no cost, returns instantly (does not block or retry). Disambiguation: unlike wait_for_responses, this never delays or blocks waiting for more answers to arrive -- it just reports what exists right now, which may be fewer than required_responses.`, | ||
| inputShape: { | ||
| query_id: z.string().describe("The id returned by submit_query, identifying which query to read.") | ||
| }, | ||
| run: async (args, client) => { | ||
@@ -99,7 +122,7 @@ const q = await client.queries.get(args.query_id); | ||
| name: "wait_for_responses", | ||
| description: 'Poll for up to max_wait_seconds (default 50) for human responses to a query. Returns status "ready" with the responses if enough arrived, otherwise status "pending" \u2014 call again to keep waiting.', | ||
| description: 'Poll for human responses to a query, blocking for up to max_wait_seconds (default 50, max 50) before returning. Use right after submit_query to wait for real answers in one call instead of manually re-checking with get_responses. DOES NOT GUARANTEE COMPLETION -- if min_responses have not arrived within the time budget it returns status "pending" (with a hint to call again) rather than erroring; call again to keep waiting. Returns status "ready" with the responses array once enough have arrived (or the query otherwise completed). Read-only / free -- cost was already charged by submit_query. Disambiguation: unlike get_responses (instant, one-shot, may return 0 responses), this actively waits, trading time for a higher chance of a complete result.', | ||
| inputShape: { | ||
| query_id: z.string(), | ||
| min_responses: z.number().int().min(1).optional(), | ||
| max_wait_seconds: z.number().int().min(1).max(50).optional() | ||
| query_id: z.string().describe("The id returned by submit_query, identifying which query to wait on."), | ||
| min_responses: z.number().int().min(1).optional().describe(`How many responses must arrive before returning status "ready" (default: the query's required_responses).`), | ||
| max_wait_seconds: z.number().int().min(1).max(50).optional().describe('Maximum seconds to poll before giving up and returning status "pending" if not enough responses arrived yet (default 50, max 50).') | ||
| }, | ||
@@ -133,6 +156,6 @@ run: async (args, client) => { | ||
| name: "list_queries", | ||
| description: "List your recent GetABrain queries (most recent first).", | ||
| description: 'List your recent GetABrain queries, most recent first. Use this to get an overview of past/active queries, recover a query_id you lost track of, or filter by status (e.g. find everything still "active" or "pending"). Read-only, no cost. Disambiguation: this lists MANY queries at a summary level; it does not return the individual worker responses for any one query -- use get_responses or wait_for_responses with a specific query_id for that.', | ||
| inputShape: { | ||
| status: z.string().optional(), | ||
| limit: z.number().int().min(1).max(100).optional() | ||
| status: z.string().optional().describe('Optional filter to only return queries in this status (e.g. "active", "pending", "completed", "cancelled", "failed", "expired"). Omit to return all statuses.'), | ||
| limit: z.number().int().min(1).max(100).optional().describe("Maximum number of queries to return, most recent first (1-100, default server-side).") | ||
| }, | ||
@@ -143,8 +166,8 @@ run: async (args, client) => ok(await client.queries.list(args)) | ||
| name: "rate_response", | ||
| description: "Rate a worker's response 1-5 (feeds the quality system). Optionally include feedback_text.", | ||
| description: "Rate a single worker's response 1-5 to feed the worker quality/reputation system, optionally with free-text feedback. Use after reviewing a response from get_responses/wait_for_responses, to reward good answers and flag poor ones -- this affects the worker's quality score and future eligibility (e.g. queries with min_worker_quality set) and can trigger rewards/suspension server-side. Side effect: writes a rating record and returns the updated worker quality score; does not resubmit or modify the original response. Disambiguation: rates a response you already have -- does not fetch new responses (use get_responses/wait_for_responses first).", | ||
| inputShape: { | ||
| query_id: z.string(), | ||
| response_id: z.string(), | ||
| score: z.number().int().min(1).max(5), | ||
| feedback_text: z.string().optional() | ||
| query_id: z.string().describe("The id of the query the response belongs to (from submit_query)."), | ||
| response_id: z.string().describe("The id of the specific response to rate (from get_responses/wait_for_responses output)."), | ||
| score: z.number().int().min(1).max(5).describe("Quality rating for the response, 1 (worst) to 5 (best). Feeds the worker's ongoing quality score."), | ||
| feedback_text: z.string().optional().describe("Optional free-text comment explaining the rating, visible to the worker.") | ||
| }, | ||
@@ -151,0 +174,0 @@ run: async (args, client) => ok(await client.responses.rate(args.query_id, args.response_id, { score: args.score, feedback_text: args.feedback_text })) |
+5
-4
| { | ||
| "name": "@getabrain/mcp-server", | ||
| "version": "0.1.5", | ||
| "version": "0.2.0", | ||
| "description": "MCP server for GetABrain.ai \u2014 real human judgment as agent tools", | ||
@@ -22,3 +22,3 @@ "type": "module", | ||
| "dependencies": { | ||
| "@getabrain/sdk": "^0.1.1", | ||
| "@getabrain/sdk": "^0.2.0", | ||
| "@modelcontextprotocol/sdk": "^1.18.0", | ||
@@ -34,4 +34,5 @@ "zod": "^3.25.0" | ||
| "type": "git", | ||
| "url": "https://github.com/Guitarmaniac24/Getabrain.ai" | ||
| } | ||
| "url": "https://github.com/Guitarmaniac24/getabrain-mcp-server" | ||
| }, | ||
| "homepage": "https://getabrain.ai" | ||
| } |
+39
-9
@@ -26,17 +26,47 @@ # @getabrain/mcp-server | ||
| ## Test mode | ||
| Test mode is a flag on the key, not a different key format. When you mint an API key — via | ||
| `POST /api/v1/requestor/keys` with `{"mode":"test"}`, or by choosing "test" in the dashboard — you get | ||
| back a completely normal `gab_k_…` / `gab_s_…` key pair. There's no `_test_` in the string; the | ||
| test-ness lives in the database as an `is_test` flag on that key. No funding or card required. | ||
| Point `GETABRAIN_API_KEY` / `GETABRAIN_API_SECRET` at a test-mode key and the server behaves identically, except: | ||
| - `submit_query` never touches your balance — no charge, no `insufficient_balance` errors. | ||
| - Responses come back synthetic and are always marked **`simulated: true`**, so your pipeline (submit → | ||
| wait/poll → rate) can be built and exercised end-to-end before any real human worker or real money is | ||
| involved. | ||
| - `get_balance` reports `mode: "test"` so the agent/human can tell at a glance which environment it's in. | ||
| When you're ready to go live: mint a **live-mode key** (same call, `{"mode":"live"}` or the dashboard | ||
| default), fund the account with `create_topup_link` (works with either key type — a test-mode agent can | ||
| generate the link, a human completes checkout to add real funds), and swap the env vars. `get_balance` | ||
| then reports `mode: "live"`, and `submit_query` starts spending real balance and dispatching to real paid | ||
| workers. | ||
| ## Tools | ||
| - `get_balance` — check your prepaid balance. | ||
| - `submit_query` — ask real humans a question (16 query types: A/B test, rating, ranking, sentiment, yes/no, image/video/audio review, voice/video/photo capture, …). Returns a `query_id`. | ||
| - `get_responses` — fetch the human answers for a query. | ||
| - `wait_for_responses` — poll (bounded) until enough humans answer; call again while it returns `pending`. | ||
| - `list_queries` — your recent queries. | ||
| - `rate_response` — rate a worker's answer 1–5. | ||
| - `get_balance` — read-only: prepaid balance (cents), `mode` (`"test"`/`"live"`), and `auto_reload_enabled` | ||
| (with a setup link + hint when it's off and would otherwise stall a live account at zero balance). | ||
| - `create_topup_link` — mints a Stripe Checkout URL to add funds (min $5); a human opens it in a browser to | ||
| pay — the agent cannot complete payment itself. | ||
| - `submit_query` — ask real humans a question (16 query types: A/B test, rating, ranking, sentiment, yes/no, | ||
| image/video/audio review, voice/video/photo capture, …). Returns a `query_id`. Spends balance on a live | ||
| key; free and `simulated: true` on a test key. | ||
| - `get_responses` — one-shot, read-only: current status + whatever responses exist right now, no waiting. | ||
| - `wait_for_responses` — bounded polling (up to `max_wait_seconds`, default/max 50s); returns `ready` with | ||
| responses once enough arrive, or `pending` — call again to keep waiting. Use this instead of `get_responses` | ||
| when you want the tool call itself to wait. | ||
| - `list_queries` — read-only: your recent queries, optionally filtered by `status`. | ||
| - `rate_response` — rate a worker's answer 1–5 (optional `feedback_text`); feeds the worker quality system. | ||
| ## Example agent flow | ||
| 1. `get_balance` → ensure funds. | ||
| 2. `submit_query` → get `query_id`. | ||
| 3. `wait_for_responses` (repeat while `pending`) → read the human answers. | ||
| 1. `get_balance` → confirm funds (or `mode: "test"` for a free sandbox run). | ||
| 2. If funds are short on a live key: `create_topup_link` → human completes checkout → `get_balance` again. | ||
| 3. `submit_query` → get `query_id`. | ||
| 4. `wait_for_responses` (repeat while `pending`) → read the human (or simulated, in test mode) answers. | ||
| 5. `rate_response` → optionally rate each response to improve future worker matching. | ||
| Full API docs: https://getabrain.ai/docs/api |
No website
QualityPackage does not have a website.
18296
104.68%204
20.71%2
-33.33%72
71.43%+ Added
- Removed
Updated