captchakraken-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,105 @@
1
+ CaptchaKraken Source-Available License v1.0
2
+ Copyright (c) 2026 JobHarvest / JWriter20. All rights reserved.
3
+
4
+ This license governs use of the CaptchaKraken software in this repository and of
5
+ the associated machine-learning model weights and LoRA adapters published by the
6
+ copyright holder (collectively, the "Software"). By using the Software you agree
7
+ to these terms. If you do not agree, do not use the Software.
8
+
9
+ ================================================================================
10
+ 1. GRANT
11
+ ================================================================================
12
+ Subject to the restrictions below, you are granted a worldwide, royalty-free,
13
+ non-exclusive license to use, copy, modify, and distribute the Software, and to
14
+ incorporate it into your own products and services.
15
+
16
+ ================================================================================
17
+ 2. PERMITTED USES
18
+ ================================================================================
19
+ You MAY use the Software for:
20
+
21
+ (a) Personal, non-commercial use, research, and education.
22
+
23
+ (b) Commercial use INSIDE a larger product or service that delivers
24
+ substantial value BEYOND captcha solving itself — that is, where captcha
25
+ solving is an internal, enabling component rather than the product.
26
+
27
+ Illustrative (non-exhaustive) examples of PERMITTED commercial use:
28
+ - Web scrapers and data-collection pipelines.
29
+ - Stealth / anti-detection browsers and browser automation frameworks
30
+ that use the Software as one internal capability.
31
+ - QA, testing, and accessibility tooling.
32
+ - Any application where the captcha solve is a means to an end the user
33
+ is actually paying for.
34
+
35
+ ================================================================================
36
+ 3. PROHIBITED USES
37
+ ================================================================================
38
+ You MAY NOT, without a separate written commercial agreement from the copyright
39
+ holder:
40
+
41
+ (a) Sell, resell, rent, or otherwise offer for a fee any captcha-SOLVING
42
+ service, product, or API whose primary value is solving captchas,
43
+ where that service is powered by the Software or its model outputs.
44
+
45
+ (b) Distribute "thin wrappers" around the Software whose primary purpose is
46
+ captcha solving — including but not limited to browser extensions,
47
+ hosted endpoints, SaaS products, or CLIs that simply expose the
48
+ Software's solving capability to end users.
49
+
50
+ (c) Expose, proxy, or relay the responses or outputs of the Software's LoRA
51
+ models (e.g. bounding boxes, tile selections, click plans) through a paid
52
+ or public API as a captcha-solving service.
53
+
54
+ In short: you may BUILD WITH this Software, but you may not SELL THE SOLVE.
55
+
56
+ ================================================================================
57
+ 4. MODEL OUTPUTS
58
+ ================================================================================
59
+ Outputs produced by the model weights/adapters are subject to Sections 2 and 3
60
+ to the same extent as the Software itself. Reselling or API-relaying those
61
+ outputs as a captcha-solving service is prohibited under Section 3(c).
62
+
63
+ ================================================================================
64
+ 5. REDISTRIBUTION
65
+ ================================================================================
66
+ If you redistribute the Software (modified or not), you must retain this license
67
+ and this notice, and you must not remove or alter the restrictions in Section 3.
68
+
69
+ ================================================================================
70
+ 6. TRADEMARKS
71
+ ================================================================================
72
+ This license does not grant permission to use the trade names, trademarks, or
73
+ product names of the copyright holder, except as required to describe the origin
74
+ of the Software.
75
+
76
+ ================================================================================
77
+ 7. NO WARRANTY
78
+ ================================================================================
79
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
80
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
81
+ FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
82
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER
83
+ IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
84
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
85
+
86
+ ================================================================================
87
+ 8. RESPONSIBLE USE
88
+ ================================================================================
89
+ You are responsible for ensuring your use of the Software complies with all
90
+ applicable laws, the terms of service of any site you interact with, and
91
+ applicable anti-fraud and computer-access statutes. The copyright holder does
92
+ not endorse using the Software to violate any third party's terms or rights.
93
+
94
+ ================================================================================
95
+ 9. COMMERCIAL LICENSING
96
+ ================================================================================
97
+ For uses prohibited under Section 3 — including selling captcha-solving as a
98
+ service — contact the copyright holder to discuss a separate commercial license.
99
+
100
+ --------------------------------------------------------------------------------
101
+ This text is a license, not legal advice. If the boundary between a permitted
102
+ "value-add" product and a prohibited "thin wrapper" is unclear for your use
103
+ case, ask by opening an issue or messaging the maintainer on GitHub:
104
+ https://github.com/JWriter20/CaptchaKraken
105
+ --------------------------------------------------------------------------------
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # captchakraken-mcp
2
+
3
+ The CaptchaKraken account, driven from an MCP client. It signs you in through
4
+ GitHub, mints and revokes API keys for the solving endpoint, and reads back what
5
+ you have spent.
6
+
7
+ **It does not solve captchas.** The key it mints is what does that, against the
8
+ OpenAI-compatible endpoint at `api.captchakraken.com/v1`.
9
+
10
+ ```bash
11
+ claude mcp add captchakraken -- npx -y captchakraken-mcp
12
+ ```
13
+
14
+ ## Tools
15
+
16
+ | Tool | What it does |
17
+ | --- | --- |
18
+ | `sign_in` | Prints a code and a link; the human approves in a browser. Creates the account if it does not exist, with trial credits and a first key. |
19
+ | `sign_out` | Revokes this client's token on the server and deletes it from disk. |
20
+ | `get_account` | Who is signed in, the balance, and the base URL to point a solver at. |
21
+ | `get_balance` | The balance, and nothing else. |
22
+ | `get_usage` | Billable responses and credits, per day and in total, plus purchases. |
23
+ | `list_api_keys` | The account's solving keys, masked. |
24
+ | `create_api_key` | Mints one and **saves it to disk; the secret is never shown.** |
25
+ | `revoke_api_key` | Kills one by id. Effective within ~30 seconds. |
26
+ | `get_topup_link` | A Stripe URL for the human to open. Charges nothing. |
27
+ | `get_pricing` | The rate card, and how many responses a captcha typically takes. |
28
+ | `get_models` | The lineup, so an agent can decide whether to self-host instead. |
29
+
30
+ ## Signing in
31
+
32
+ The MCP server runs on a laptop, inside an editor, with no browser it controls
33
+ and nowhere for an OAuth redirect to land. It also cannot hold our GitHub client
34
+ secret — it is distributed to customers, so anything baked into it is public.
35
+
36
+ So it uses the device flow (RFC 8628):
37
+
38
+ ```
39
+ sign_in ──▶ "Open http://…/device?code=ABCD-EFGH, code ABCD-EFGH"
40
+
41
+ ├─ the human opens it, signs in with GitHub, approves
42
+
43
+ sign_in ──▶ "Signed in as <login>. Balance: $0.50"
44
+ ```
45
+
46
+ `sign_in` waits about 25 seconds and then hands control back rather than
47
+ blocking — MCP clients time tool calls out, and a call killed mid-wait would
48
+ strand the code. **Calling it again resumes the same request**; it does not
49
+ print a second code at a human who is already looking at the first.
50
+
51
+ ## Two credentials, two blast radii
52
+
53
+ The token this server holds (`ckm_…`) manages the account: it reads the balance
54
+ and usage, mints and revokes API keys, and opens a checkout page. **It cannot
55
+ solve a captcha.**
56
+
57
+ An API key (`ck_live_…`) solves captchas. **It cannot manage the account** — the
58
+ account API returns 401 for one, exactly as it does for a random string.
59
+
60
+ That split is the point. If they were one credential, a key leaked from a
61
+ scraper could mint its own replacements faster than anyone could revoke them,
62
+ and the balance would stop being a bound on the damage. Either can be revoked
63
+ from the dashboard.
64
+
65
+ Nothing here can spend money. `get_topup_link` returns a URL; a person clicks
66
+ it, on a page Stripe hosts. There is no tool that charges a card.
67
+
68
+ ## Where the credentials live
69
+
70
+ Two files, because they are two credentials.
71
+
72
+ **The management token** (`ckm_…`) is in `~/.config/captchakraken/mcp.json`,
73
+ mode 0600 in a 0700 directory, keyed by the control-plane origin — so
74
+ rehearsing against a staging deployment and then running against production
75
+ switches identity cleanly instead of presenting a staging token to production
76
+ and 401ing with no explanation.
77
+
78
+ **The solving key** (`ck_live_…`) is in `~/.captchakraken/credentials`, also
79
+ 0600, written by `create_api_key` and read by the solver itself. It carries the
80
+ endpoint alongside the key, so after `create_api_key` a solve works with **no
81
+ environment variables set at all**.
82
+
83
+ The secret is never returned through a tool result. It goes straight to that
84
+ file and the tool reports the path, because a key in a tool result is a key in
85
+ the transcript the moment an agent repeats it back in a summary — and asking an
86
+ agent nicely not to do that is not a control. If you need the key in an env var
87
+ instead, read it out of the file yourself; nothing in this server will print it.
88
+
89
+ It is not encrypted. A local secret encrypted with a local key is ceremony, not
90
+ protection: whoever can read the file can read the key. The real defences are
91
+ the file mode, the token's one-year expiry, and the dashboard's disconnect
92
+ button.
93
+
94
+ Delete the file to forget everything, or run `sign_out`, which also revokes the
95
+ token server-side. Prefer `sign_out` — deleting the file alone leaves a live
96
+ credential that nothing can reach to revoke.
97
+
98
+ ## Configuration
99
+
100
+ | Variable | Default | Why |
101
+ | --- | --- | --- |
102
+ | `CAPTCHAKRAKEN_BASE_URL` | `https://captchakraken.com` | Point at a staging or self-hosted control plane. |
103
+ | `CAPTCHAKRAKEN_CLIENT_NAME` | `MCP client` | Shown on the approval page. A person approving a code should see something they recognise. |
104
+
105
+ ## Development
106
+
107
+ ```bash
108
+ npm install
109
+ npm run typecheck
110
+ npm run build
111
+ CAPTCHAKRAKEN_BASE_URL=http://127.0.0.1:3000 node dist/index.js
112
+ ```
113
+
114
+ The server it talks to is `captchakraken-cloud`; the endpoints are documented in
115
+ that package's README under **The MCP surface**, and covered by
116
+ `test/mcp-api.test.ts` there. Nothing on stdout but MCP frames — stdout *is* the
117
+ protocol channel on this transport, and one stray `console.log` corrupts the
118
+ stream and presents as a broken server.
package/dist/api.js ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * The HTTP client for `/api/v1` on the control plane.
3
+ *
4
+ * ONE PLACE THAT TALKS TO THE NETWORK, so the timeout, the error shape and the
5
+ * "your token stopped working" case are decided once. Every tool in server.ts
6
+ * goes through here and none of them calls `fetch`.
7
+ *
8
+ * ERRORS ARE THROWN AS `ApiError` WITH THE SERVER'S CODE INTACT. The control
9
+ * plane deliberately returns a stable `error` string and a human `message`; the
10
+ * message is what an agent shows a person, and the code is what the tools
11
+ * branch on. Flattening them into one string would mean parsing English to
12
+ * decide whether to prompt for a sign-in.
13
+ */
14
+ import { loadCredential, saveCredential } from './credentials.js';
15
+ /** Every request gets this long. A hung control plane must not hang the editor. */
16
+ const TIMEOUT_MS = 20_000;
17
+ export class ApiError extends Error {
18
+ status;
19
+ code;
20
+ constructor(status, code, message) {
21
+ super(message);
22
+ this.name = 'ApiError';
23
+ this.status = status;
24
+ this.code = code;
25
+ }
26
+ }
27
+ export class ControlPlane {
28
+ baseUrl;
29
+ constructor(baseUrl) {
30
+ this.baseUrl = baseUrl.replace(/\/$/, '');
31
+ }
32
+ async request(path, options = {}) {
33
+ const { method = 'GET', body, authenticated = true } = options;
34
+ const headers = {
35
+ accept: 'application/json',
36
+ // Named so a support conversation can tell an MCP client apart from curl
37
+ // in an access log. It carries no authority.
38
+ 'user-agent': 'captchakraken-mcp',
39
+ };
40
+ if (body !== undefined)
41
+ headers['content-type'] = 'application/json';
42
+ if (authenticated) {
43
+ const credential = loadCredential(this.baseUrl);
44
+ if (!credential.accessToken) {
45
+ throw new ApiError(401, 'not_signed_in', 'Not signed in. Run the sign_in tool first.');
46
+ }
47
+ headers.authorization = `Bearer ${credential.accessToken}`;
48
+ }
49
+ const controller = new AbortController();
50
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
51
+ let response;
52
+ try {
53
+ response = await fetch(`${this.baseUrl}${path}`, {
54
+ method,
55
+ headers,
56
+ ...(body === undefined ? {} : { body: JSON.stringify(body) }),
57
+ signal: controller.signal,
58
+ });
59
+ }
60
+ catch (error) {
61
+ // A DNS failure, a refused connection and our own abort all land here, and
62
+ // they mean the same thing to the person reading it: we could not reach
63
+ // the service. The cause is included because "which one" is the first
64
+ // thing anyone debugging asks.
65
+ throw new ApiError(0, 'network_error', `Could not reach ${this.baseUrl}: ${error instanceof Error ? error.message : String(error)}`);
66
+ }
67
+ finally {
68
+ clearTimeout(timer);
69
+ }
70
+ const payload = await readJson(response);
71
+ if (!response.ok) {
72
+ const code = typeof payload?.error === 'string' ? payload.error : `http_${response.status}`;
73
+ const message = typeof payload?.message === 'string'
74
+ ? payload.message
75
+ : `${this.baseUrl}${path} returned ${response.status}`;
76
+ /*
77
+ * A 401 on an authenticated call means the token we hold is dead —
78
+ * revoked from the dashboard, expired, or from a deployment this is no
79
+ * longer pointed at. Dropping it here is what turns the next `sign_in`
80
+ * into a clean new flow instead of a confusing "already signed in".
81
+ *
82
+ * Only on authenticated calls: the device endpoints answer 400/401 for
83
+ * reasons that have nothing to do with a stored token.
84
+ */
85
+ if (response.status === 401 && authenticated) {
86
+ const credential = loadCredential(this.baseUrl);
87
+ delete credential.accessToken;
88
+ delete credential.expiresAt;
89
+ saveCredential(this.baseUrl, credential);
90
+ }
91
+ throw new ApiError(response.status, code, message);
92
+ }
93
+ return payload;
94
+ }
95
+ }
96
+ async function readJson(response) {
97
+ try {
98
+ const parsed = await response.json();
99
+ return parsed && typeof parsed === 'object' ? parsed : null;
100
+ }
101
+ catch {
102
+ // An HTML error page from a proxy in front of the app, most likely. The
103
+ // caller turns this into "returned <status>", which is more use than a JSON
104
+ // parse error nobody can act on.
105
+ return null;
106
+ }
107
+ }
108
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Where the token lives on disk.
3
+ *
4
+ * THIS FILE IS A CREDENTIAL, and it is written with that in mind: the directory
5
+ * is 0700, the file is 0600, and it is created with those modes rather than
6
+ * chmod'ed afterwards — a file that is briefly world-readable on a shared box is
7
+ * a file that was world-readable.
8
+ *
9
+ * IT IS KEYED BY BASE URL. Someone rehearsing against dev.captchakraken.com and
10
+ * then running against production must not have the dev token silently
11
+ * presented to production, where it is not a token at all and every call 401s
12
+ * with no explanation. So the store is a map from origin to what we hold for it,
13
+ * and switching `CAPTCHAKRAKEN_BASE_URL` switches identity cleanly.
14
+ *
15
+ * NOTHING HERE IS ENCRYPTED. A local secret encrypted with a local key is
16
+ * ceremony, not protection — whoever can read the file can read the key. The
17
+ * real defences are the file mode, the token's one-year expiry, and the
18
+ * dashboard's disconnect button.
19
+ */
20
+ import { chmodSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
21
+ import { homedir } from 'node:os';
22
+ import { dirname, join } from 'node:path';
23
+ function configPath() {
24
+ // XDG first, because someone who has set it has done so on purpose. The
25
+ // fallback is ~/.config on every platform rather than %APPDATA% on Windows —
26
+ // one path is easier to tell a person to delete than three.
27
+ const base = process.env.XDG_CONFIG_HOME?.trim() || join(homedir(), '.config');
28
+ return join(base, 'captchakraken', 'mcp.json');
29
+ }
30
+ function readStore() {
31
+ try {
32
+ const parsed = JSON.parse(readFileSync(configPath(), 'utf8'));
33
+ return parsed && typeof parsed === 'object' ? parsed : {};
34
+ }
35
+ catch {
36
+ // Missing, empty, or corrupt all mean the same thing to every caller: we
37
+ // hold nothing for this origin, so sign in. Refusing to start because the
38
+ // file has a stray comma would strand someone with no obvious way out.
39
+ return {};
40
+ }
41
+ }
42
+ function writeStore(store) {
43
+ const path = configPath();
44
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
45
+ /*
46
+ * Write-then-rename, so a crash mid-write cannot leave a truncated file where
47
+ * a valid one was — the failure that would cost someone their token for no
48
+ * reason. The temporary file is created 0600 so the secret is never on disk
49
+ * under a laxer mode, even for the instant before the rename.
50
+ */
51
+ const temporary = `${path}.${process.pid}.tmp`;
52
+ writeFileSync(temporary, `${JSON.stringify(store, null, 2)}\n`, { mode: 0o600 });
53
+ renameSync(temporary, path);
54
+ // renameSync preserves the temporary file's mode, but an existing file's mode
55
+ // wins on some filesystems. Cheap to be sure.
56
+ chmodSync(path, 0o600);
57
+ }
58
+ export function credentialPath() {
59
+ return configPath();
60
+ }
61
+ /*
62
+ * ───────────────────────────────────────────────────────────────────────────
63
+ * The solver credential — a SECOND, deliberately separate store.
64
+ *
65
+ * Two credentials exist and they must never meet:
66
+ *
67
+ * ckm_… management token. Talks to the control plane's /api/v1. Lives in
68
+ * mcp.json, above. Can mint and revoke keys.
69
+ * ck_live_… inference key. Talks to the gateway. Lives here. Can spend money
70
+ * but cannot mint anything.
71
+ *
72
+ * Keeping them in one file would mean whatever reads the solver key at solve
73
+ * time is also holding a token that can issue more keys — a strictly worse
74
+ * blast radius for no gain. So this is a separate file in a separate directory,
75
+ * and the only thing that crosses between them is that `create_api_key` writes
76
+ * this one as a side effect.
77
+ *
78
+ * WHY A FILE AT ALL: `create_api_key` used to return the secret in its tool
79
+ * result, which puts a live inference key into the agent transcript — the
80
+ * exact thing this handoff exists to prevent. The key goes to disk at 0600 and
81
+ * the agent is told the path.
82
+ *
83
+ * The Python client reads this in `config.py` (`_read_credentials_file`), which
84
+ * accepts either name for each value; the env-file form is used because it is
85
+ * also directly `source`-able by a human.
86
+ * ───────────────────────────────────────────────────────────────────────────
87
+ */
88
+ /**
89
+ * Where the solver looks for its key.
90
+ *
91
+ * `CAPTCHA_KRAKEN_STATE_DIR` is honoured because the Python client honours it —
92
+ * if the two disagreed, someone who had redirected their state dir would get a
93
+ * file written where nothing reads it and a 401 with no explanation.
94
+ */
95
+ export function solverCredentialPath() {
96
+ const stateDir = process.env.CAPTCHA_KRAKEN_STATE_DIR?.trim() || join(homedir(), '.captchakraken');
97
+ return join(stateDir, 'credentials');
98
+ }
99
+ /**
100
+ * Write the inference key and its endpoint where the solver will find them.
101
+ *
102
+ * The endpoint travels WITH the key on purpose: a hosted user who has never run
103
+ * vLLM has no reason to know what `VLLM_BASE_URL` is, and a key without an
104
+ * endpoint authenticates flawlessly against a local port with nothing behind
105
+ * it. Writing both means signup configures the solve path completely.
106
+ *
107
+ * Returns the path, which is what the caller reports in place of the secret.
108
+ */
109
+ export function writeSolverCredential(options) {
110
+ const path = solverCredentialPath();
111
+ // 0700 on the directory, created with the mode rather than chmod'ed into it:
112
+ // a directory that is briefly world-readable is a directory that was
113
+ // world-readable, and the file inside it is a live credential.
114
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
115
+ const lines = [
116
+ '# CaptchaKraken inference key — written by captchakraken-mcp.',
117
+ '# Keep this file private (0600). Revoke the key from the dashboard or the',
118
+ '# MCP `revoke_api_key` tool if it is ever exposed.',
119
+ `CAPTCHA_KRAKEN_API_KEY=${options.apiKey}`,
120
+ ];
121
+ if (options.baseUrl)
122
+ lines.push(`VLLM_BASE_URL=${options.baseUrl}`);
123
+ // Write-then-rename through a 0600 temp file, so a crash mid-write cannot
124
+ // leave a truncated credential where a valid one was.
125
+ const temporary = `${path}.${process.pid}.tmp`;
126
+ writeFileSync(temporary, `${lines.join('\n')}\n`, { mode: 0o600 });
127
+ renameSync(temporary, path);
128
+ // renameSync preserves the temp file's mode, but an existing file's mode wins
129
+ // on some filesystems — and this path is very likely to already exist, since
130
+ // minting a second key overwrites the first. Cheap to be sure.
131
+ chmodSync(path, 0o600);
132
+ return path;
133
+ }
134
+ export function loadCredential(baseUrl) {
135
+ return readStore()[baseUrl] ?? {};
136
+ }
137
+ export function saveCredential(baseUrl, credential) {
138
+ const store = readStore();
139
+ store[baseUrl] = credential;
140
+ writeStore(store);
141
+ }
142
+ export function clearCredential(baseUrl) {
143
+ const store = readStore();
144
+ delete store[baseUrl];
145
+ writeStore(store);
146
+ }
147
+ /**
148
+ * Is there a token, and is it still in date?
149
+ *
150
+ * The expiry is checked locally as a courtesy — the server checks it too, and
151
+ * the server is the one that decides. This exists so `sign_in` can say "your
152
+ * token expired" instead of the agent discovering it through a 401 on whatever
153
+ * it was actually trying to do.
154
+ */
155
+ export function hasLiveToken(credential) {
156
+ if (!credential.accessToken)
157
+ return false;
158
+ if (!credential.expiresAt)
159
+ return true;
160
+ const expiry = Date.parse(credential.expiresAt);
161
+ return Number.isNaN(expiry) || expiry > Date.now();
162
+ }
163
+ //# sourceMappingURL=credentials.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The entry point. Stdio transport, because that is what an editor speaks.
4
+ *
5
+ * NOTHING MAY BE WRITTEN TO STDOUT except MCP frames. Stdout *is* the protocol
6
+ * channel on this transport, and one stray `console.log` — a debug line, a
7
+ * deprecation notice, a banner — corrupts the stream and the client reports the
8
+ * server as broken rather than as chatty. Every diagnostic in this package goes
9
+ * to stderr, which the client shows in its logs.
10
+ */
11
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
12
+ import { createServer } from './server.js';
13
+ /**
14
+ * Which deployment to talk to.
15
+ *
16
+ * Overridable so the same package can be pointed at a staging box, and so a
17
+ * self-hosted control plane is a configuration line rather than a fork. The
18
+ * default is production, because that is what `npx captchakraken-mcp` with no
19
+ * arguments should mean.
20
+ */
21
+ const BASE_URL = (process.env.CAPTCHAKRAKEN_BASE_URL ?? 'https://captchakraken.com').replace(/\/$/, '');
22
+ /**
23
+ * What the approval page calls this client.
24
+ *
25
+ * A person approving a code should see something they recognise, and "the thing
26
+ * I am typing in" is more recognisable than "captchakraken-mcp". Editors that
27
+ * set this are rare, so the fallback matters more than the variable.
28
+ */
29
+ const CLIENT_NAME = process.env.CAPTCHAKRAKEN_CLIENT_NAME?.trim() || 'MCP client';
30
+ async function main() {
31
+ const server = createServer(BASE_URL, CLIENT_NAME);
32
+ await server.connect(new StdioServerTransport());
33
+ console.error(`[captchakraken-mcp] connected, talking to ${BASE_URL}`);
34
+ }
35
+ main().catch((error) => {
36
+ console.error('[captchakraken-mcp] fatal:', error instanceof Error ? error.stack : error);
37
+ process.exit(1);
38
+ });
39
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,QAAQ,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,2BAA2B,CAAC,CAAC,OAAO,CAC1F,KAAK,EACL,EAAE,CACH,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,GACf,OAAO,CAAC,GAAG,CAAC,yBAAyB,EAAE,IAAI,EAAE,IAAI,YAAY,CAAC;AAEhE,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IACnD,MAAM,MAAM,CAAC,OAAO,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IACjD,OAAO,CAAC,KAAK,CAAC,6CAA6C,QAAQ,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,OAAO,CAAC,KAAK,CAAC,4BAA4B,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC1F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
package/dist/server.js ADDED
@@ -0,0 +1,518 @@
1
+ /**
2
+ * The tools.
3
+ *
4
+ * WHAT THIS SERVER IS FOR: letting an agent provision and watch its own captcha
5
+ * solving. Sign the human in, mint a key for the Abyss model, report what has
6
+ * been spent, and hand over a payment link when the balance runs low. It does
7
+ * not solve captchas — that is the gateway's OpenAI-compatible endpoint, and
8
+ * the key minted here is what talks to it.
9
+ *
10
+ * TWO RULES SHAPE EVERY TOOL BELOW.
11
+ *
12
+ * 1. NOTHING SPENDS MONEY WITHOUT A HUMAN. `get_topup_link` returns a URL. It
13
+ * does not charge a card, and there is no tool that can. An agent may
14
+ * *offer* to spend; a person clicks.
15
+ *
16
+ * 2. NO TOOL OUTPUT EVER CONTAINS A LIVE CREDENTIAL. `create_api_key` writes
17
+ * the minted key straight to a 0600 file and reports the PATH; the solver
18
+ * reads it from there. Returning the secret — which this tool used to do —
19
+ * puts it in the transcript the moment an agent repeats it in a summary,
20
+ * and asking the agent nicely not to is not a control. The management
21
+ * token obtained by `sign_in` is likewise written to disk and never
22
+ * printed.
23
+ *
24
+ * Read-only tools are annotated as such so a client can decide what needs
25
+ * confirming. `create_api_key` and `revoke_api_key` are not read-only and are
26
+ * marked destructive where they are.
27
+ */
28
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
29
+ import { z } from 'zod';
30
+ import { ApiError, ControlPlane } from './api.js';
31
+ import { clearCredential, credentialPath, hasLiveToken, loadCredential, saveCredential, solverCredentialPath, writeSolverCredential, } from './credentials.js';
32
+ /**
33
+ * How long `sign_in` waits before handing control back.
34
+ *
35
+ * Deliberately short. MCP clients time tool calls out — 60 seconds is a common
36
+ * default — and a sign-in that blocks for two minutes gets killed with the
37
+ * device code stranded. So this polls for well under any of them, then returns
38
+ * "still waiting, call me again", and the pending code is on disk so the next
39
+ * call resumes the same flow rather than printing a second code at a human who
40
+ * is already looking at the first one.
41
+ */
42
+ const SIGN_IN_WAIT_MS = 25_000;
43
+ function text(body) {
44
+ return { content: [{ type: 'text', text: body }] };
45
+ }
46
+ function failure(body) {
47
+ return { content: [{ type: 'text', text: body }], isError: true };
48
+ }
49
+ /**
50
+ * Turn a thrown ApiError into something an agent can act on.
51
+ *
52
+ * `not_signed_in` gets the instruction rather than the raw message, because it
53
+ * is the one failure with an obvious next step and an agent that is told the
54
+ * step will take it instead of reporting an error to the human.
55
+ */
56
+ function describe(error) {
57
+ if (error instanceof ApiError) {
58
+ if (error.code === 'not_signed_in' || error.status === 401) {
59
+ return failure('Not signed in to CaptchaKraken. Run the `sign_in` tool first.');
60
+ }
61
+ return failure(`${error.code}: ${error.message}`);
62
+ }
63
+ return failure(error instanceof Error ? error.message : String(error));
64
+ }
65
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
66
+ export function createServer(baseUrl, clientName) {
67
+ const api = new ControlPlane(baseUrl);
68
+ const server = new McpServer({ name: 'captchakraken', version: '0.1.0' }, {
69
+ instructions: 'CaptchaKraken account management. Use sign_in once to connect a GitHub account, ' +
70
+ 'create_api_key to get a key for the captcha-solving endpoint, and get_usage / ' +
71
+ 'get_balance to see what has been spent. This server does not solve captchas itself — ' +
72
+ 'the key it mints is used against the OpenAI-compatible endpoint reported by get_account.',
73
+ });
74
+ // ── signing in ────────────────────────────────────────────────────────────
75
+ server.registerTool('sign_in', {
76
+ title: 'Sign in to CaptchaKraken',
77
+ description: 'Connect a CaptchaKraken account using GitHub, creating one if it does not exist. ' +
78
+ 'Prints a short code and a link for the human to approve in a browser, then waits ' +
79
+ 'briefly. If it returns still-waiting, call it again to resume the same sign-in — it ' +
80
+ 'will not start a second one. New accounts get free trial credits and a first API key.',
81
+ inputSchema: {
82
+ client_name: z
83
+ .string()
84
+ .optional()
85
+ .describe('Name shown on the approval page, e.g. the editor this is running in.'),
86
+ },
87
+ annotations: { title: 'Sign in to CaptchaKraken', openWorldHint: true },
88
+ }, async ({ client_name }) => {
89
+ try {
90
+ const credential = loadCredential(baseUrl);
91
+ if (hasLiveToken(credential)) {
92
+ const who = credential.account?.githubLogin ?? `account ${credential.account?.userId}`;
93
+ return text(`Already signed in as ${who} on ${baseUrl}.\n` +
94
+ 'Run `sign_out` first if you want to connect a different account.');
95
+ }
96
+ // Resume a code that is still live rather than printing a second one at
97
+ // a human who is already looking at the first.
98
+ let pending = credential.pending;
99
+ if (!pending || pending.expiresAtMs <= Date.now()) {
100
+ const started = await api.request('/api/v1/device/start', {
101
+ method: 'POST',
102
+ authenticated: false,
103
+ body: { client_name: client_name ?? clientName },
104
+ });
105
+ pending = {
106
+ deviceCode: started.device_code,
107
+ userCode: started.user_code,
108
+ verificationUri: started.verification_uri,
109
+ verificationUriComplete: started.verification_uri_complete,
110
+ expiresAtMs: Date.now() + started.expires_in * 1000,
111
+ intervalSeconds: started.interval,
112
+ };
113
+ saveCredential(baseUrl, { ...credential, pending });
114
+ }
115
+ const outcome = await pollForToken(api, pending);
116
+ if (outcome.kind === 'granted') {
117
+ saveCredential(baseUrl, {
118
+ accessToken: outcome.grant.access_token,
119
+ expiresAt: outcome.grant.expires_at,
120
+ account: {
121
+ userId: outcome.grant.account.user_id,
122
+ githubLogin: outcome.grant.account.github_login,
123
+ email: outcome.grant.account.email,
124
+ },
125
+ });
126
+ const who = outcome.grant.account.github_login ?? `account ${outcome.grant.account.user_id}`;
127
+ return text(`Signed in as ${who}.\n` +
128
+ `Balance: ${outcome.grant.account.balance_usd} (${outcome.grant.account.balance_credits.toLocaleString('en-US')} credits).\n` +
129
+ `The token is stored at ${credentialPath()} and can be revoked from the dashboard at any time.\n\n` +
130
+ 'Next: `create_api_key` mints a key for the solving endpoint.');
131
+ }
132
+ if (outcome.kind === 'waiting') {
133
+ return text(`Waiting for approval.\n\n` +
134
+ ` Open: ${pending.verificationUriComplete}\n` +
135
+ ` Code: ${pending.userCode}\n\n` +
136
+ 'Ask the human to open that link and approve the code. Then call `sign_in` again ' +
137
+ 'to resume — it will pick up this same request, not start a new one.');
138
+ }
139
+ // Denied or dead. Drop the pending code so the next call starts clean.
140
+ const { pending: _dropped, ...withoutPending } = credential;
141
+ saveCredential(baseUrl, withoutPending);
142
+ return failure(outcome.kind === 'denied'
143
+ ? 'The sign-in was declined in the browser. Nothing was connected.'
144
+ : 'That sign-in code expired or was already used. Call `sign_in` again for a new one.');
145
+ }
146
+ catch (error) {
147
+ return describe(error);
148
+ }
149
+ });
150
+ server.registerTool('sign_out', {
151
+ title: 'Sign out',
152
+ description: 'Revoke this client’s access token on the server and delete it from disk. API keys ' +
153
+ 'already minted keep working — this disconnects the management client, not the solving.',
154
+ annotations: { title: 'Sign out', destructiveHint: true, openWorldHint: true },
155
+ }, async () => {
156
+ try {
157
+ // Revoke server-side first. If that fails we still have the token, and
158
+ // deleting it locally would leave a live credential nobody can reach to
159
+ // revoke — the file is the only copy of it.
160
+ await api.request('/api/v1/signout', { method: 'POST' });
161
+ clearCredential(baseUrl);
162
+ return text('Signed out. The token has been revoked on the server and removed from disk.');
163
+ }
164
+ catch (error) {
165
+ if (error instanceof ApiError && (error.status === 401 || error.code === 'not_signed_in')) {
166
+ clearCredential(baseUrl);
167
+ return text('There was no live token. Local state cleared.');
168
+ }
169
+ return describe(error);
170
+ }
171
+ });
172
+ // ── the account ───────────────────────────────────────────────────────────
173
+ server.registerTool('get_account', {
174
+ title: 'Account and endpoint',
175
+ description: 'Who is signed in, the credit balance, and the base URL to point an OpenAI-compatible ' +
176
+ 'client at for solving.',
177
+ annotations: { title: 'Account and endpoint', readOnlyHint: true, openWorldHint: true },
178
+ }, async () => {
179
+ try {
180
+ const me = await api.request('/api/v1/me');
181
+ const lines = [
182
+ `Account: ${me.account.github_login ?? `#${me.account.user_id}`}`,
183
+ `Email: ${me.account.email ?? '(none on file)'}`,
184
+ `Balance: ${me.account.balance_usd} (${me.account.balance_credits.toLocaleString('en-US')} credits)`,
185
+ `Endpoint: ${me.endpoint.base_url}`,
186
+ `Dashboard: ${me.endpoint.dashboard_url}`,
187
+ ];
188
+ if (me.account.low_balance) {
189
+ lines.push('', 'Balance is low. `get_topup_link` returns a payment link for the human.');
190
+ }
191
+ if (me.endpoint.requires_dev_header) {
192
+ lines.push('', 'This is a development deployment: its gateway also requires the shared X-CK-Dev-Auth header in front of the key.');
193
+ }
194
+ return text(lines.join('\n'));
195
+ }
196
+ catch (error) {
197
+ return describe(error);
198
+ }
199
+ });
200
+ server.registerTool('get_balance', {
201
+ title: 'Credit balance',
202
+ description: 'The remaining credit balance, in credits and in dollars. Nothing else.',
203
+ annotations: { title: 'Credit balance', readOnlyHint: true, openWorldHint: true },
204
+ }, async () => {
205
+ try {
206
+ const me = await api.request('/api/v1/me');
207
+ return text(`${me.account.balance_usd} (${me.account.balance_credits.toLocaleString('en-US')} credits)` +
208
+ (me.account.low_balance ? ' — low' : ''));
209
+ }
210
+ catch (error) {
211
+ return describe(error);
212
+ }
213
+ });
214
+ server.registerTool('get_usage', {
215
+ title: 'Usage and spending',
216
+ description: 'Billable inference responses and credits spent, per day and in total, plus recent ' +
217
+ 'purchases. The server keeps a rolling 30-day window; `days` narrows what is reported.',
218
+ inputSchema: {
219
+ days: z
220
+ .number()
221
+ .int()
222
+ .min(1)
223
+ .max(30)
224
+ .optional()
225
+ .describe('How many of the last 30 days to report. Defaults to all 30.'),
226
+ },
227
+ annotations: { title: 'Usage and spending', readOnlyHint: true, openWorldHint: true },
228
+ }, async ({ days }) => {
229
+ try {
230
+ const usage = await api.request('/api/v1/usage');
231
+ // Sliced here rather than asked for: the window is fixed server-side
232
+ // because that is what makes the read cheap. See the note on the route.
233
+ const window = days ?? usage.window_days;
234
+ const daily = usage.daily.slice(-window);
235
+ const credits = daily.reduce((sum, day) => sum + day.credits, 0);
236
+ const rounds = daily.reduce((sum, day) => sum + day.billable_rounds, 0);
237
+ const lines = [
238
+ `Last ${window} day${window === 1 ? '' : 's'}:`,
239
+ ` ${rounds.toLocaleString('en-US')} billable responses`,
240
+ ` ${credits.toLocaleString('en-US')} credits spent`,
241
+ `Balance now: ${usage.balance_usd} (${usage.balance_credits.toLocaleString('en-US')} credits)`,
242
+ ];
243
+ if (usage.totals.waived_rounds > 0) {
244
+ lines.push('', `${usage.totals.waived_rounds} response(s) in the full window were served free — our own retries and the per-attempt cap. Those are not billing errors.`);
245
+ }
246
+ if (usage.totals.by_client.length > 1) {
247
+ lines.push('', 'By integration (full window):');
248
+ for (const row of usage.totals.by_client) {
249
+ lines.push(` ${row.client ?? 'direct'}: ${row.billable_rounds.toLocaleString('en-US')} rounds, ${row.credits.toLocaleString('en-US')} credits`);
250
+ }
251
+ }
252
+ const spending = daily.filter((day) => day.credits > 0);
253
+ if (spending.length > 0) {
254
+ lines.push('', 'Per day:');
255
+ for (const day of spending) {
256
+ lines.push(` ${day.day} ${day.credits.toLocaleString('en-US')} credits`);
257
+ }
258
+ }
259
+ if (usage.ledger.length > 0) {
260
+ lines.push('', 'Purchases and grants:');
261
+ for (const entry of usage.ledger.slice(0, 10)) {
262
+ const paid = entry.usd_amount_cents === null ? '' : ` ($${(entry.usd_amount_cents / 100).toFixed(2)})`;
263
+ lines.push(` ${entry.at.slice(0, 10)} ${entry.kind} ${entry.delta_credits > 0 ? '+' : ''}${entry.delta_credits.toLocaleString('en-US')}${paid}`);
264
+ }
265
+ }
266
+ return text(lines.join('\n'));
267
+ }
268
+ catch (error) {
269
+ return describe(error);
270
+ }
271
+ });
272
+ // ── keys ──────────────────────────────────────────────────────────────────
273
+ server.registerTool('list_api_keys', {
274
+ title: 'List API keys',
275
+ description: 'The account’s solving keys, masked. The secret half is not stored anywhere and cannot ' +
276
+ 'be listed — only minted.',
277
+ annotations: { title: 'List API keys', readOnlyHint: true, openWorldHint: true },
278
+ }, async () => {
279
+ try {
280
+ const listing = await api.request('/api/v1/keys');
281
+ if (listing.keys.length === 0) {
282
+ return text('No keys yet. `create_api_key` mints one.');
283
+ }
284
+ const rows = listing.keys.map((key) => {
285
+ const state = key.revoked ? 'revoked' : 'live';
286
+ const used = key.last_used_at ? `last used ${key.last_used_at.slice(0, 10)}` : 'never used';
287
+ return ` #${key.id} ${key.masked} ${key.name ?? '(unnamed)'} [${state}, ${used}]`;
288
+ });
289
+ const live = listing.keys.filter((key) => !key.revoked).length;
290
+ return text([`${live} live of ${listing.max_active_keys} allowed:`, ...rows].join('\n'));
291
+ }
292
+ catch (error) {
293
+ return describe(error);
294
+ }
295
+ });
296
+ server.registerTool('create_api_key', {
297
+ title: 'Create an API key',
298
+ description: 'Mint a key for the captcha-solving endpoint (the Abyss model) and save it to disk where ' +
299
+ 'the solver will find it. THE SECRET IS NEVER RETURNED — it is written straight to a ' +
300
+ '0600 file and only the path is reported back, so it cannot leak through this ' +
301
+ 'conversation. After this runs, a solve works with no environment variables set.',
302
+ inputSchema: {
303
+ name: z
304
+ .string()
305
+ .max(60)
306
+ .optional()
307
+ .describe('A label so this key is identifiable in the dashboard, e.g. "scraper-prod".'),
308
+ },
309
+ annotations: { title: 'Create an API key', readOnlyHint: false, openWorldHint: true },
310
+ }, async ({ name }) => {
311
+ try {
312
+ const created = await api.request('/api/v1/keys', {
313
+ method: 'POST',
314
+ body: { name: name ?? null },
315
+ });
316
+ // The secret goes to disk and NOT into this tool's result. Everything
317
+ // returned below is safe to repeat: an id, a masked form, and a path.
318
+ //
319
+ // The write is the last thing that can fail, and if it does the key
320
+ // already exists server-side — so say so plainly rather than pretending
321
+ // nothing happened, and point at revoke. Silently swallowing this would
322
+ // strand a live key that the user does not know they own.
323
+ let credentialFile;
324
+ try {
325
+ credentialFile = writeSolverCredential({
326
+ apiKey: created.api_key,
327
+ baseUrl: created.base_url,
328
+ });
329
+ }
330
+ catch (writeError) {
331
+ const reason = writeError instanceof Error ? writeError.message : String(writeError);
332
+ return text([
333
+ `Key #${created.id} was created (${created.masked}), but saving it to ` +
334
+ `${solverCredentialPath()} failed: ${reason}`,
335
+ '',
336
+ 'The secret is NOT being printed here — it would end up in this transcript.',
337
+ 'The key exists server-side and cannot be retrieved again, so either fix the',
338
+ `permissions on that path and mint a new key, or revoke key #${created.id} with`,
339
+ 'revoke_api_key so nothing untracked is left live.',
340
+ ].join('\n'));
341
+ }
342
+ return text([
343
+ `Key #${created.id} created${created.name ? ` (${created.name})` : ''}, shown as ${created.masked} in the dashboard.`,
344
+ '',
345
+ `Saved to ${credentialFile} (0600), pointing at ${created.base_url}.`,
346
+ '',
347
+ 'The secret itself is deliberately not shown — it went straight to that file so it',
348
+ 'never enters this conversation, and only a SHA-256 is stored server-side. Solving',
349
+ 'now works with no environment variables set at all.',
350
+ ].join('\n'));
351
+ }
352
+ catch (error) {
353
+ return describe(error);
354
+ }
355
+ });
356
+ server.registerTool('revoke_api_key', {
357
+ title: 'Revoke an API key',
358
+ description: 'Kill a key by its id. It stops working within about 30 seconds — the gateway’s refresh ' +
359
+ 'interval. This cannot be undone; mint a new key instead of trying to restore one.',
360
+ inputSchema: {
361
+ id: z.number().int().positive().describe('The key id from list_api_keys.'),
362
+ },
363
+ annotations: { title: 'Revoke an API key', readOnlyHint: false, destructiveHint: true, openWorldHint: true },
364
+ }, async ({ id }) => {
365
+ try {
366
+ await api.request(`/api/v1/keys/${id}/revoke`, { method: 'POST' });
367
+ return text(`Key #${id} revoked. It stops working within 30 seconds.`);
368
+ }
369
+ catch (error) {
370
+ return describe(error);
371
+ }
372
+ });
373
+ // ── money ─────────────────────────────────────────────────────────────────
374
+ server.registerTool('get_topup_link', {
375
+ title: 'Get a top-up link',
376
+ description: 'A URL the human opens to add credits. THIS DOES NOT CHARGE ANYTHING — it returns a ' +
377
+ 'link; a person completes the payment on Stripe. Omit `usd` to let them choose the ' +
378
+ 'amount, which is the right default when the agent has not been told what to spend.',
379
+ inputSchema: {
380
+ usd: z
381
+ .number()
382
+ .positive()
383
+ .optional()
384
+ .describe('A published pack amount. Omit to return the chooser page instead.'),
385
+ },
386
+ annotations: { title: 'Get a top-up link', readOnlyHint: false, openWorldHint: true },
387
+ }, async ({ usd }) => {
388
+ try {
389
+ const result = await api.request('/api/v1/billing/checkout', {
390
+ method: 'POST',
391
+ body: usd === undefined ? {} : { usd },
392
+ });
393
+ if (result.kind === 'checkout') {
394
+ return text(`Stripe checkout for $${result.usd} (${result.credits?.toLocaleString('en-US')} credits):\n\n ${result.url}\n\n` +
395
+ 'Nothing has been charged. The human completes the payment on that page, and the credits land within seconds of it succeeding.');
396
+ }
397
+ const packs = (result.packs ?? [])
398
+ .map((pack) => ` $${pack.usd} → ${pack.credits.toLocaleString('en-US')} credits`)
399
+ .join('\n');
400
+ return text(`Top-up page:\n\n ${result.url}\n\nAvailable packs:\n${packs}\n\nNothing has been charged.`);
401
+ }
402
+ catch (error) {
403
+ return describe(error);
404
+ }
405
+ });
406
+ server.registerTool('get_pricing', {
407
+ title: 'Pricing',
408
+ description: 'The rate card: dollars per 1,000 inference responses for images and for video, how many ' +
409
+ 'responses a captcha typically takes, and the per-captcha billing ceiling. Use this to ' +
410
+ 'estimate a job before running it.',
411
+ annotations: { title: 'Pricing', readOnlyHint: true, openWorldHint: true },
412
+ }, async () => {
413
+ try {
414
+ const pricing = await api.request('/api/v1/pricing', { authenticated: false });
415
+ const lines = [
416
+ `Billed per inference response. $1.00 = ${pricing.credits_per_usd.toLocaleString('en-US')} credits.`,
417
+ '',
418
+ ];
419
+ for (const row of pricing.classes) {
420
+ lines.push(`${row.label}: $${row.usd_per_1000_responses.toFixed(2)} per 1,000 (${row.credits_per_response} credits each)`, ` ${row.covers}`,
421
+ // The number that turns a rate into a cost. Without it an agent
422
+ // estimating a job would quietly assume one response per captcha.
423
+ ` Typically ${row.typical_responses_per_captcha} response(s) per captcha`);
424
+ }
425
+ lines.push('', `Free (never reach the model): ${pricing.free_challenges.join(', ')}`,
426
+ // The ceiling an agent should actually plan against: one captcha can
427
+ // never cost more than this, however badly it goes.
428
+ `One captcha costs at most ${pricing.max_billable_responses_per_session} billable responses ` +
429
+ `($${((pricing.classes.find((row) => row.puzzle_class === 'image')?.usd_per_1000_responses ?? 0) * pricing.max_billable_responses_per_session).toFixed(2)} per 1,000 for images). ` +
430
+ `Responses past that are served free, and the attempt is abandoned after ${pricing.max_responses_per_session}.`, `Packs: ${pricing.credit_packs.map((pack) => `$${pack.usd}`).join(', ')}`);
431
+ return text(lines.join('\n'));
432
+ }
433
+ catch (error) {
434
+ return describe(error);
435
+ }
436
+ });
437
+ server.registerTool('get_models', {
438
+ title: 'Models',
439
+ description: 'The model lineup: which weights can be self-hosted, and which one the hosted endpoint ' +
440
+ 'runs. Useful for deciding whether to pay per solve or run it yourself.',
441
+ annotations: { title: 'Models', readOnlyHint: true, openWorldHint: true },
442
+ }, async () => {
443
+ try {
444
+ const listing = await api.request('/api/v1/models', { authenticated: false });
445
+ const lines = [`Hosted endpoint: ${listing.base_url}`, ''];
446
+ for (const model of listing.models) {
447
+ lines.push(`${model.name} — ${model.zone}`);
448
+ lines.push(` ${model.tagline}`);
449
+ lines.push(model.hosted
450
+ ? ' Hosted only; not downloadable.'
451
+ : model.published
452
+ ? ` Weights: ${model.hugging_face_id} (~${model.weights_gb} GB, needs ${model.min_vram})`
453
+ : ` Weights: ${model.hugging_face_id} — reserved, not uploaded yet`);
454
+ if (model.accuracy !== null) {
455
+ lines.push(` Measured: ${(model.accuracy * 100).toFixed(1)}% exact match`);
456
+ }
457
+ if (model.video)
458
+ lines.push(' Handles video challenges.');
459
+ lines.push('');
460
+ }
461
+ return text(lines.join('\n').trimEnd());
462
+ }
463
+ catch (error) {
464
+ return describe(error);
465
+ }
466
+ });
467
+ return server;
468
+ }
469
+ /**
470
+ * Poll until the token arrives or the budget runs out.
471
+ *
472
+ * `slow_down` doubles the interval, permanently for this attempt, which is what
473
+ * RFC 8628 §3.5 asks for. It should not happen — we honour the interval the
474
+ * server gave us — but a clock that jumps or a retried call can produce it, and
475
+ * an implementation that ignores it is one that hammers an unauthenticated
476
+ * endpoint whenever it does.
477
+ */
478
+ async function pollForToken(api, pending) {
479
+ const deadline = Date.now() + SIGN_IN_WAIT_MS;
480
+ let interval = Math.max(1, pending.intervalSeconds) * 1000;
481
+ while (Date.now() < deadline) {
482
+ if (pending.expiresAtMs <= Date.now())
483
+ return { kind: 'dead' };
484
+ try {
485
+ const grant = await api.request('/api/v1/device/token', {
486
+ method: 'POST',
487
+ authenticated: false,
488
+ body: { device_code: pending.deviceCode },
489
+ });
490
+ return { kind: 'granted', grant };
491
+ }
492
+ catch (error) {
493
+ if (!(error instanceof ApiError))
494
+ throw error;
495
+ switch (error.code) {
496
+ case 'authorization_pending':
497
+ break;
498
+ case 'slow_down':
499
+ interval *= 2;
500
+ break;
501
+ case 'access_denied':
502
+ return { kind: 'denied' };
503
+ case 'expired_token':
504
+ return { kind: 'dead' };
505
+ default:
506
+ throw error;
507
+ }
508
+ }
509
+ // Do not overshoot the deadline sleeping: returning a moment early with
510
+ // "call me again" is better than being killed by the client's timeout.
511
+ const remaining = deadline - Date.now();
512
+ if (remaining <= 0)
513
+ break;
514
+ await sleep(Math.min(interval, remaining));
515
+ }
516
+ return { kind: 'waiting' };
517
+ }
518
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "captchakraken-mcp",
3
+ "version": "0.1.0",
4
+ "description": "MCP server for CaptchaKraken: sign in with GitHub, mint API keys for the Abyss model, and read your usage and spending.",
5
+ "type": "module",
6
+ "author": "Jake Writer",
7
+ "license": "SEE LICENSE IN LICENSE",
8
+ "bin": {
9
+ "captchakraken-mcp": "dist/index.js"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/JWriter20/CaptchaKraken.git",
22
+ "directory": "mcp"
23
+ },
24
+ "keywords": [
25
+ "mcp",
26
+ "model-context-protocol",
27
+ "captcha",
28
+ "captchakraken"
29
+ ],
30
+ "bugs": {
31
+ "url": "https://github.com/JWriter20/CaptchaKraken/issues"
32
+ },
33
+ "homepage": "https://github.com/JWriter20/CaptchaKraken#readme",
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.json",
36
+ "typecheck": "tsc --noEmit",
37
+ "prepublishOnly": "npm run build",
38
+ "start": "node dist/index.js"
39
+ },
40
+ "dependencies": {
41
+ "@modelcontextprotocol/sdk": "^1.30.0",
42
+ "zod": "^3.25.76"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.20.1",
46
+ "typescript": "^5.9.3"
47
+ }
48
+ }