Skip to content

MCP

Connect a client

Configuration for MCP hosts, and the raw contract for one you write yourself. Claude Code is the client this setup was verified with. The Cursor blocks are untested, and each section says what was and was not checked.

Before you start

  1. Make an API key in the developer console at https://app.gogoscreen.com/dashboard/developers. Making a key needs a verified email and a paid plan or top up time. The plaintext is shown once, when the key is made, and never again.
  2. Grant it the scopes the work needs and no more. A key’s scopes are fixed when it is made; widening them means issuing a new key.
  3. Choose a transport. The hosted server is the one to reach for: it needs a host that can send an HTTP header. The stdio bridge is not published as a package or a download, so it is only an option if GogoScreen has given you a copy of it.

A key is a bearer credential. Anyone holding it can spend the account’s seconds. Keep it out of files you commit, and revoke it in the console the moment you think it has leaked.

Claude Desktop

Claude Desktop’s configuration file launches local commands, which is what the stdio bridge is. It needs a copy of the bridge that GogoScreen has given you; see the note below. The file is at:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Create it if it does not exist, edit it, then quit Claude Desktop completely and start it again. The file is read at startup.

jsonclaude_desktop_config.json
{
  "mcpServers": {
    "gogoscreen": {
      "command": "node",
      "args": ["/absolute/path/to/stdio.js"],
      "env": {
        "GOGOSCREEN_API_KEY": "gsk_live_YOUR_KEY",
        "GOGOSCREEN_API_URL": "https://api.gogoscreen.com"
      }
    }
  }
}
The stdio bridge, for a copy of it that GogoScreen has given you. Replace the path with where that copy is and the key with your own. GOGOSCREEN_API_URL takes an origin or a full /api/v1 base; both work. This block was not run inside Claude Desktop; the bridge it launches was driven by a protocol library client.

Claude Code

One command. It writes the configuration for you. This is the setup that was run and checked for these docs.

bash
claude mcp add --transport http gogoscreen https://api.gogoscreen.com/mcp \
  --header "Authorization: Bearer gsk_live_YOUR_KEY"

The default scope is local, which is this project and your machine only. Add --scope user to make it available in every project, or --scope project to write a .mcp.json in the repository you are standing in.

A project scope entry is written into a .mcp.json inside the repository, so a key written there is a key you have given to everyone you commit it to. Keep the key in a local or user scope entry instead.

.mcp.json
{
  "mcpServers": {
    "gogoscreen": {
      "type": "http",
      "url": "https://api.gogoscreen.com/mcp",
      "headers": {
        "Authorization": "Bearer gsk_live_YOUR_KEY"
      }
    },
    "gogoscreen-stdio": {
      "type": "stdio",
      "command": "node",
      "args": [
        "/absolute/path/to/stdio.js"
      ],
      "env": {
        "GOGOSCREEN_API_KEY": "gsk_live_YOUR_KEY",
        "GOGOSCREEN_API_URL": "https://api.gogoscreen.com"
      }
    }
  }
}
The .mcp.json below is the file the CLI wrote, not a block written by hand. Verified against claude 2.1.278 on 2026-09-21, with the key replaced. The stdio entry needs a copy of the bridge that GogoScreen has given you.

Check it with claude mcp list, which reports each server’s connection state. A server added at project scope has to be approved once before it connects.

Cursor (untested)

~/.cursor/mcp.json
{
  "mcpServers": {
    "gogoscreen": {
      "url": "https://api.gogoscreen.com/mcp",
      "headers": { "Authorization": "Bearer gsk_live_YOUR_KEY" }
    }
  }
}
Untested. The stdio block needs a copy of the bridge that GogoScreen has given you.

A client you write yourself

Many hosts take a block of roughly the shape below, in a configuration file of their own. Check your host’s documentation for where the file lives, how it spells the transport and where the header goes. Whatever the host, it has to send the key as a header on every request.

jsonthe usual shape
{
  "mcpServers": {
    "gogoscreen": {
      "type": "http",
      "url": "https://api.gogoscreen.com/mcp",
      "headers": { "Authorization": "Bearer gsk_live_YOUR_KEY" }
    }
  }
}

Under that block is one POST. The hosted transport is Streamable HTTP and the server is stateless, so one POST is a complete exchange. There is no session to open, nothing to keep, and no initialize handshake required before a useful call.

Recorded off the wire by a forwarding proxy in front of a running server, from a real protocol library client.
HeaderValueWhy
AuthorizationBearer gsk_live_…Or X-API-Key: gsk_live_…. Both are accepted. A dashboard session token is not.
Content-Typeapplication/jsonThe body is one JSON-RPC message. An array of messages (a batch) is refused with 400.
Acceptapplication/json, text/event-streamBoth, because the specification allows the server to answer either way. This server always answers plain JSON and never opens an event stream.
MCP-Protocol-Version2025-11-25Sent on every request after the handshake. Omitting it is accepted; a protocol library sends it for you.
bashone call, no library
curl -sS https://api.gogoscreen.com/mcp \
  -H "Authorization: Bearer gsk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-11-25" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"renders_list","arguments":{"limit":1}}}'
Answered 200. The response carries x-request-id, and the RateLimit headers a host backs off on.
jsonthe answer
{
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"data\":[{\"id\":\"1d10eb11-6bd4-4150-9ed1-b86656e71a60\",\"state\":\"queued\",\"internalStatus\":\"queued\",\"targetOrigin\":\"https://www.iana.org\",\"targetPath\":\"/\",\"hint\":\"Sign in and show the dashboard.\",\"steps\":null,\"durationSeconds\":null,\"progress\":null,\"failureCode\":null,\"watermark\":false,\"secondsReserved\":300,\"secondsCharged\":null,\"createdAt\":\"2026-09-21T17:19:40.000Z\",\"finishedAt\":null}],\"nextCursor\":\"eyJ0IjoiMjAyNi0wOS0yMVQxNzoxOTo0MC4wMDBaIiwiaSI6IjFkMTBlYjExLTZiZDQtNDE1MC05ZWQxLWI4NjY1NmU3MWE2MCJ9\"}"
      }
    ],
    "structuredContent": {
      "data": [
        {
          "id": "1d10eb11-6bd4-4150-9ed1-b86656e71a60",
          "state": "queued",
          "internalStatus": "queued",
          "targetOrigin": "https://www.iana.org",
          "targetPath": "/",
          "hint": "Sign in and show the dashboard.",
          "steps": null,
          "durationSeconds": null,
          "progress": null,
          "failureCode": null,
          "watermark": false,
          "secondsReserved": 300,
          "secondsCharged": null,
          "createdAt": "2026-09-21T17:19:40.000Z",
          "finishedAt": null
        }
      ],
      "nextCursor": "eyJ0IjoiMjAyNi0wOS0yMVQxNzoxOTo0MC4wMDBaIiwiaSI6IjFkMTBlYjExLTZiZDQtNDE1MC05ZWQxLWI4NjY1NmU3MWE2MCJ9"
    }
  },
  "jsonrpc": "2.0",
  "id": 2
}

Two limits. The body may be up to 1 MB, which is larger than the REST surface allows, because a tool call carries its arguments as JSON and a long marketing brief is a legitimate caller. And a request that carries an Origin header must carry an allowed one; a program that is not a browser sends none, and the absence is allowed.

With a protocol library

The program below connects, lists the tools and calls one. It was run against a live server and its output is pasted underneath, unedited.

javascriptgogoscreen-mcp.mjs
// gogoscreen-mcp.mjs — connect, list the tools, call one.
//   Needs the official MCP TypeScript SDK, @modelcontextprotocol/sdk, in this project.
//   GOGOSCREEN_API_KEY=gsk_live_... node gogoscreen-mcp.mjs
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const url = new URL(process.env.GOGOSCREEN_MCP_URL ?? "https://api.gogoscreen.com/mcp");
const client = new Client({ name: "gogoscreen-example", version: "1.0.0" });

await client.connect(
  new StreamableHTTPClientTransport(url, {
    requestInit: { headers: { Authorization: `Bearer ${process.env.GOGOSCREEN_API_KEY}` } },
  }),
);

const { tools } = await client.listTools();
console.log(`${tools.length} tools, for example ${tools.slice(0, 3).map((t) => t.name).join(", ")}`);

const answer = await client.callTool({ name: "catalog_voices_list", arguments: {} });

if (answer.isError) {
  // Every refusal is the same envelope, in the text block. Branch on the code.
  const { error } = JSON.parse(answer.content[0].text);
  console.error(`${error.code}: ${error.message} (requestId ${error.requestId})`);
  process.exitCode = 1;
} else {
  // structuredContent is the same body the HTTP operation returns.
  for (const voice of answer.structuredContent.voices) {
    console.log(`${voice.id.padEnd(10)} ${voice.name} — ${voice.languages.join(", ")}`);
  }
}

await client.close();
textGOGOSCREEN_API_KEY=gsk_live_YOUR_KEY node gogoscreen-mcp.mjs
61 tools, for example account_me, catalog_marketing_templates_list, catalog_plans_list
sarah      Sarah — en, es, pt, fr, de, it
george     George — en, es, pt, fr, de, it
brian      Brian — en, es, pt, fr, de, it
matilda    Matilda — en, es, pt, fr, de, it
jessica    Jessica — en
roger      Roger — en
lily       Lily — en, es, pt, fr, de, it
…
Run 21 September 2026 against a running server, exit code 0. It listed 61 tools and printed every voice; the first lines are shown.

A protocol library will open a GET stream after connecting, to listen for notifications this stateless server never sends. The server answers 405 and the client carries on over POST. That is normal.

The stdio bridge in detail

The stdio bridge speaks MCP on stdin and stdout and turns every tool call into an HTTPS call to https://api.gogoscreen.com/api/v1 carrying your key. It holds no authority of its own: scopes, idempotency, rate limits and the wallet are all enforced by the server on the other end. It serves the same tool list as the hosted server.

The stdio bridge is not published as a package or a download, so there is nothing to install; running it needs a copy GogoScreen has given you, started with node. Without one, use the hosted server.

VariableRequiredWhat it does
GOGOSCREEN_API_KEYyesYour API key. Without it the tools are still listed and every call answers unauthorized, with details.reason: "no_api_key_configured".
GOGOSCREEN_API_URLnoAn origin or a full /api/v1 base. Both work. Defaults to https://api.gogoscreen.com. Point it at http://127.0.0.1:8000 to drive a local server.
GOGOSCREEN_API_TIMEOUT_MSnoHow long the bridge waits for one answer. 30000 by default, five seconds past the API's own request budget.

Diagnostics go to stderr, never to stdout, because stdout is the protocol. A driven bridge printed [gogoscreen-mcp] bridging to http://127.0.0.1:8000/api/v1 on startup, listed 61 tools, and its tool names matched the hosted server’s exactly (true).

Troubleshooting

What you seeWhat it meansWhat to do
401 with details.reason: "api_key_required"The credential reached the server but is not an API key. A dashboard session token is the usual cause; the MCP server does not accept one.Send a key that starts with gsk_, as Authorization: Bearer or X-API-Key. The REST surface accepts both kinds; this one accepts keys only.
401 with details.reason: "no_credential"No credential arrived at all. Usually a header the host did not forward, or a key left in a file the host is not reading.Check the header is on the request, not only in the configuration file. Restart the host after editing its configuration.
403 with -32000 and a sentence about OriginThe request carried a browser Origin header that is not on the server's allowlist. The fence runs before authentication, so the key was never looked at.A host that is not a browser should send no Origin at all. If you are calling from browser code, ask for your origin to be allowed, and consider whether a page should hold a key.
isError: true and the text MCP error -32602: Tool … not foundThe tool name does not exist. Every dot in an operation id becomes an underscore, and nothing else changes.Call tools/list and use a name from it. This is the one refusal that is not JSON, so treat a parse failure as a protocol error rather than an API error.
402 insufficient_secondsThe seconds wallet cannot cover the work. details carries needed and available.Do not retry. No tool buys time. The account holder adds time in the GogoScreen dashboard.
429 rate_limitedToo many calls from this credential this minute, in the rate class named at the end of the tool description. Each rate class has its own budget; the protocol messages count as reads.Back off. details carries class, limit and resetAt, and the HTTP response carries Retry-After and the RateLimit headers, which the bridge passes through too.
503 with details.reason: "api_unreachable"The stdio bridge started but could not reach the API. It answers this itself, so the tools still list.Check GOGOSCREEN_API_URL and that the host can reach it. The bridge prints the base it resolved to on stderr at startup.
503 with details.reason: "api_timeout"The API accepted the connection and did not answer inside the bridge's 30 second budget.Retry with the same idempotencyKey, so a retry cannot duplicate work that may already have started. Raise GOGOSCREEN_API_TIMEOUT_MS only on a slow link.
The host lists no tools at allIt never connected. For the hosted server that is usually a 401 or a 403 the host reported as a connection failure; for the bridge it is usually a wrong path or a missing Node.Run the same call with curl, using the block above. A refusal you can read is faster than a host's connection error.
jsonthe bridge, unable to reach the API
{
  "error": {
    "code": "service_unavailable",
    "message": "That service is temporarily unavailable.",
    "details": {
      "reason": "api_unreachable",
      "url": "http://127.0.0.1:59999/api/v1",
      "cause": "fetch failed"
    },
    "requestId": "d0273c9b-8f69-49b6-87ce-692264213374"
  }
}
A real refusal: the bridge was pointed at http://127.0.0.1:59999, where nothing was listening.