splera-mcp 0.1.3 → 0.1.4
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 +21 -0
- package/README.md +14 -14
- package/dist/apiClient.js +3 -0
- package/dist/config.d.ts +7 -4
- package/dist/config.js +11 -8
- package/dist/errors.js +6 -5
- package/dist/index.js +13 -7
- package/dist/tools/createExperiment.js +2 -2
- package/dist/tools/getResults.js +2 -2
- package/dist/tools/shipWinner.js +1 -1
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sohaib Thiab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -7,8 +7,8 @@ A/B testing for vibe coders, exposed as a Model Context Protocol (MCP) server. Y
|
|
|
7
7
|
Three native tools your agent gets once installed: `create_experiment`, `get_results`, `ship_winner`. The agent calls them, edits your code to wire the SDK and variant, and reports stats back in plain English. No dashboard tab, no copy-paste, no test infrastructure to learn.
|
|
8
8
|
|
|
9
9
|
- npm: [`splera-mcp`](https://www.npmjs.com/package/splera-mcp)
|
|
10
|
-
- Web docs: [splera.
|
|
11
|
-
- Issues: [github.com/sohaibt/splera/issues](https://github.com/sohaibt/splera/issues)
|
|
10
|
+
- Web docs: [splera.ai/docs/mcp](https://splera.ai/docs/mcp)
|
|
11
|
+
- Issues: [github.com/sohaibt/splera-mcp/issues](https://github.com/sohaibt/splera-mcp/issues)
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -30,7 +30,7 @@ The agent picks the right tool, makes the call, and either edits your code to in
|
|
|
30
30
|
|
|
31
31
|
### 1. Generate an API key
|
|
32
32
|
|
|
33
|
-
Go to [splera.
|
|
33
|
+
Go to [splera.ai/settings](https://splera.ai/settings), click **Generate new key**, name it something like `claude-code-macbook`. Copy the key from the one-time modal. It starts with `splera_live_`. You will not see the full key again. If you close the modal without copying, revoke and regenerate.
|
|
34
34
|
|
|
35
35
|
### 2. Add the config to your agent
|
|
36
36
|
|
|
@@ -100,7 +100,7 @@ Restart Zed.
|
|
|
100
100
|
curl -H "Authorization: Bearer splera_live_your_key_here" https://splera.app/api/me
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
`200 OK` with a JSON body means the key works. `401` means the key is bad or revoked, regenerate at [splera.
|
|
103
|
+
`200 OK` with a JSON body means the key works. `401` means the key is bad or revoked, regenerate at [splera.ai/settings](https://splera.ai/settings).
|
|
104
104
|
|
|
105
105
|
### Option B: check the agent's MCP panel after restart
|
|
106
106
|
|
|
@@ -121,17 +121,17 @@ If those lines are missing, the MCP isn't loading. Check your config file path a
|
|
|
121
121
|
|
|
122
122
|
The MCP config isn't being read. Check that (1) you edited the right file for your agent (see install table above), (2) the JSON is valid, no trailing commas, (3) you restarted the agent after editing.
|
|
123
123
|
|
|
124
|
-
### `SPLERA_API_KEY not set`
|
|
124
|
+
### `SPLERA_API_KEY not set` / `SPLERA_API_KEY_MISSING` on every call
|
|
125
125
|
|
|
126
|
-
The `env` block in your MCP config is missing or malformed. The key must start with `splera_live_`. Re-check the examples above.
|
|
126
|
+
The server starts without a key (so your agent still lists the tools), but every call fails until the key is set. The `env` block in your MCP config is missing or malformed. The key must start with `splera_live_`. Re-check the examples above.
|
|
127
127
|
|
|
128
128
|
### 401 / `SPLERA_INVALID_API_KEY` on every call
|
|
129
129
|
|
|
130
|
-
The key is wrong, revoked, or truncated during copy. Go to [splera.
|
|
130
|
+
The key is wrong, revoked, or truncated during copy. Go to [splera.ai/settings](https://splera.ai/settings), revoke the existing key, generate a new one, update the config, restart the agent.
|
|
131
131
|
|
|
132
132
|
### `SPLERA_PLAN_LIMIT_EXCEEDED` on create_experiment
|
|
133
133
|
|
|
134
|
-
You've hit your plan's monthly experiment cap. Options: [upgrade](https://splera.
|
|
134
|
+
You've hit your plan's monthly experiment cap. Options: [upgrade](https://splera.ai/pricing) or ship / stop an existing experiment to free a slot. The error `hint` field tells you which plan and how many slots are used.
|
|
135
135
|
|
|
136
136
|
### `get_results` says NOT_READY forever
|
|
137
137
|
|
|
@@ -145,7 +145,7 @@ The SDK isn't reaching the page being tested. Verify the script tag is in place:
|
|
|
145
145
|
|
|
146
146
|
`data-api-url` is required: without it the SDK tracks nothing. If your experiment was created with splera-mcp 0.1.1, its install code was missing this attribute and used a callback form of `getVariant` that never runs. Re-copy the snippet from the experiment page on the dashboard.
|
|
147
147
|
|
|
148
|
-
Also confirm the page is receiving real traffic. The dashboard shows live exposure counts at [splera.
|
|
148
|
+
Also confirm the page is receiving real traffic. The dashboard shows live exposure counts at [splera.ai/experiments/:id](https://splera.ai).
|
|
149
149
|
|
|
150
150
|
### Agent doesn't use the Splera tool even when I ask
|
|
151
151
|
|
|
@@ -196,15 +196,15 @@ Ship the winning variant to 100% of traffic. The SDK flips server-side within 30
|
|
|
196
196
|
- `splera-mcp` makes HTTPS calls to `splera.app` only. No local telemetry, no third-party analytics from this package.
|
|
197
197
|
- Your API key stays on your machine in your agent's MCP config. It's never logged server-side.
|
|
198
198
|
- Keys are stored as `sha256` hashes in our database. Even if our DB were compromised, the raw keys are not recoverable.
|
|
199
|
-
- Revoke any key instantly at [splera.
|
|
199
|
+
- Revoke any key instantly at [splera.ai/settings](https://splera.ai/settings). Revocation takes effect within 60 seconds.
|
|
200
200
|
|
|
201
201
|
---
|
|
202
202
|
|
|
203
203
|
## Links
|
|
204
204
|
|
|
205
|
-
- [Settings / API keys](https://splera.
|
|
206
|
-
- [Pricing](https://splera.
|
|
207
|
-
- [Web docs](https://splera.
|
|
205
|
+
- [Settings / API keys](https://splera.ai/settings) — generate, name, revoke
|
|
206
|
+
- [Pricing](https://splera.ai/pricing) — plan limits and upgrade
|
|
207
|
+
- [Web docs](https://splera.ai/docs/mcp) — longer-form setup guide with screenshots
|
|
208
208
|
- [GitHub issues](https://github.com/sohaibt/splera/issues) — bug reports and feature requests
|
|
209
209
|
|
|
210
|
-
Built by [Splera](https://splera.
|
|
210
|
+
Built by [Splera](https://splera.ai). A/B testing for vibe coders.
|
package/dist/apiClient.js
CHANGED
|
@@ -13,6 +13,9 @@ const { apiKey, apiUrl } = loadConfig();
|
|
|
13
13
|
* so tool handlers never see raw HTTP failures.
|
|
14
14
|
*/
|
|
15
15
|
export async function apiFetch(path, init = {}) {
|
|
16
|
+
if (!apiKey) {
|
|
17
|
+
throw new SpleraError('SPLERA_API_KEY_MISSING', 'SPLERA_API_KEY is not set.', HINTS.SPLERA_API_KEY_MISSING);
|
|
18
|
+
}
|
|
16
19
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
17
20
|
...init,
|
|
18
21
|
headers: {
|
package/dist/config.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Env var parsing + validation. Loaded once at startup.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A missing SPLERA_API_KEY does NOT exit: the server still starts and lists
|
|
5
|
+
* its tools (directories like Glama and MCP inspectors introspect without a
|
|
6
|
+
* key), logs the fix to the host's server-logs panel, and every tool call
|
|
7
|
+
* returns SPLERA_API_KEY_MISSING with the same fix (apiClient.ts).
|
|
8
|
+
* A malformed SPLERA_API_URL still fails fast (process.exit(1)) — that is a
|
|
9
|
+
* developer override, never set by directories.
|
|
7
10
|
*/
|
|
8
11
|
export interface Config {
|
|
9
12
|
apiKey: string;
|
|
@@ -17,4 +20,4 @@ export declare function loadConfig(): Config;
|
|
|
17
20
|
* Pre-release / build metadata is NOT supported — we don't ship those for MCP.
|
|
18
21
|
*/
|
|
19
22
|
export declare function isBelow(current: string, minimum: string): boolean;
|
|
20
|
-
export declare const CURRENT_MCP_VERSION = "0.1.
|
|
23
|
+
export declare const CURRENT_MCP_VERSION = "0.1.4";
|
package/dist/config.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Env var parsing + validation. Loaded once at startup.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* A missing SPLERA_API_KEY does NOT exit: the server still starts and lists
|
|
5
|
+
* its tools (directories like Glama and MCP inspectors introspect without a
|
|
6
|
+
* key), logs the fix to the host's server-logs panel, and every tool call
|
|
7
|
+
* returns SPLERA_API_KEY_MISSING with the same fix (apiClient.ts).
|
|
8
|
+
* A malformed SPLERA_API_URL still fails fast (process.exit(1)) — that is a
|
|
9
|
+
* developer override, never set by directories.
|
|
7
10
|
*/
|
|
8
11
|
export function loadConfig() {
|
|
9
12
|
const apiKey = process.env.SPLERA_API_KEY;
|
|
10
13
|
if (!apiKey) {
|
|
11
14
|
console.error('splera-mcp: SPLERA_API_KEY not set.\n' +
|
|
12
|
-
'Generate a key at https://splera.
|
|
15
|
+
'Generate a key at https://splera.ai/settings.\n' +
|
|
13
16
|
'Your MCP config env block should look like:\n' +
|
|
14
17
|
' { "splera": { "command": "npx", "args": ["-y", "splera-mcp@latest"], ' +
|
|
15
|
-
'"env": { "SPLERA_API_KEY": "splera_live_..." } } }'
|
|
16
|
-
|
|
18
|
+
'"env": { "SPLERA_API_KEY": "splera_live_..." } } }\n' +
|
|
19
|
+
'Starting anyway: tools will list, but every call fails until the key is set.');
|
|
17
20
|
}
|
|
18
21
|
const rawUrl = process.env.SPLERA_API_URL ?? 'https://splera.app';
|
|
19
22
|
let parsed;
|
|
@@ -30,7 +33,7 @@ export function loadConfig() {
|
|
|
30
33
|
'Plain http is only allowed for localhost/127.0.0.1 (local dev).');
|
|
31
34
|
process.exit(1);
|
|
32
35
|
}
|
|
33
|
-
return { apiKey, apiUrl: parsed.origin };
|
|
36
|
+
return { apiKey: apiKey ?? '', apiUrl: parsed.origin };
|
|
34
37
|
}
|
|
35
38
|
/**
|
|
36
39
|
* Semver comparison used by the X-Splera-MCP-Min-Version guard in apiClient.
|
|
@@ -51,4 +54,4 @@ export function isBelow(current, minimum) {
|
|
|
51
54
|
}
|
|
52
55
|
return false;
|
|
53
56
|
}
|
|
54
|
-
export const CURRENT_MCP_VERSION = '0.1.
|
|
57
|
+
export const CURRENT_MCP_VERSION = '0.1.4';
|
package/dist/errors.js
CHANGED
|
@@ -23,15 +23,16 @@ export class SpleraError extends Error {
|
|
|
23
23
|
* Keep them actionable — every hint ends in a thing the user can DO.
|
|
24
24
|
*/
|
|
25
25
|
export const HINTS = {
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
SPLERA_API_KEY_MISSING: 'Set SPLERA_API_KEY in the env block of your MCP config (generate a key at https://splera.ai/settings), then restart your agent.',
|
|
27
|
+
SPLERA_INVALID_API_KEY: 'Check SPLERA_API_KEY or regenerate at https://splera.ai/settings.',
|
|
28
|
+
SPLERA_PLAN_LIMIT_EXCEEDED: 'Upgrade at https://splera.ai/pricing, or ship/stop an existing experiment to free a slot.',
|
|
28
29
|
SPLERA_DOMAIN_NOT_ALLOWED: 'This experiment is locked to specific domains. Add the requesting origin at the experiment\'s /settings.',
|
|
29
|
-
SPLERA_EXPERIMENT_NOT_FOUND: 'Experiment
|
|
30
|
+
SPLERA_EXPERIMENT_NOT_FOUND: 'Experiment not found — it may have been deleted. Confirm the UUID from create_experiment.',
|
|
30
31
|
SPLERA_VALIDATION_ERROR: 'Check the tool input — see server message for details.',
|
|
31
32
|
SPLERA_MCP_VERSION_TOO_OLD: 'splera-mcp client is below the minimum supported version. Run: npm update -g splera-mcp',
|
|
32
|
-
SPLERA_HTTP_ERROR: 'Transient Splera API error. Retry in a moment, or check https://splera.
|
|
33
|
+
SPLERA_HTTP_ERROR: 'Transient Splera API error. Retry in a moment, or check https://splera.ai for status.',
|
|
33
34
|
SPLERA_UNKNOWN_TOOL: 'Unknown tool name. Expected one of: create_experiment, get_results, ship_winner.',
|
|
34
|
-
SPLERA_JWT_REQUIRED: 'This action requires a browser session, not an API key. Sign in at https://splera.
|
|
35
|
+
SPLERA_JWT_REQUIRED: 'This action requires a browser session, not an API key. Sign in at https://splera.ai/settings.',
|
|
35
36
|
};
|
|
36
37
|
/**
|
|
37
38
|
* Maps an HTTP status (+ optional server-returned code) to a SPLERA_* code
|
package/dist/index.js
CHANGED
|
@@ -15,8 +15,10 @@ console.error(`splera-mcp: connecting to ${apiUrl}`);
|
|
|
15
15
|
// Install verification with a 5s timeout. Slow networks should not hang
|
|
16
16
|
// the MCP host — hosts typically kill a server that doesn't respond in ~10s.
|
|
17
17
|
// On timeout or network error: soft-fail (warn + continue). The first real
|
|
18
|
-
// tool call will re-surface the error with full context.
|
|
19
|
-
//
|
|
18
|
+
// tool call will re-surface the error with full context. A missing or
|
|
19
|
+
// rejected key warns but does NOT exit, so the server still answers
|
|
20
|
+
// tools/list (directories like Glama introspect without a key); every tool
|
|
21
|
+
// call then returns SPLERA_API_KEY_MISSING / SPLERA_INVALID_API_KEY.
|
|
20
22
|
const VERIFY_TIMEOUT_MS = 5_000;
|
|
21
23
|
try {
|
|
22
24
|
const ctrl = new AbortController();
|
|
@@ -30,13 +32,17 @@ try {
|
|
|
30
32
|
catch (err) {
|
|
31
33
|
const msg = err.message ?? String(err);
|
|
32
34
|
const code = err.code ?? '';
|
|
33
|
-
if (code === '
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
if (code === 'SPLERA_API_KEY_MISSING') {
|
|
36
|
+
// config.ts already logged the fix.
|
|
37
|
+
}
|
|
38
|
+
else if (code === 'SPLERA_INVALID_API_KEY') {
|
|
39
|
+
console.error('splera-mcp: API key rejected (401). Tools will list, but every call fails until it is fixed.\n' +
|
|
40
|
+
'Regenerate at https://splera.ai/settings or verify with:\n' +
|
|
36
41
|
' curl -H "Authorization: Bearer $SPLERA_API_KEY" https://splera.app/api/me');
|
|
37
|
-
process.exit(1);
|
|
38
42
|
}
|
|
39
|
-
|
|
43
|
+
else {
|
|
44
|
+
console.error(`splera-mcp: startup verification skipped (${msg}). Will retry on first tool call.`);
|
|
45
|
+
}
|
|
40
46
|
}
|
|
41
47
|
const tools = [createExperimentTool, getResultsTool, shipWinnerTool];
|
|
42
48
|
const server = new Server({ name: 'splera-mcp', version: CURRENT_MCP_VERSION }, { capabilities: { tools: {} } });
|
|
@@ -117,7 +117,7 @@ export const createExperimentTool = {
|
|
|
117
117
|
});
|
|
118
118
|
const exp = res.experiment;
|
|
119
119
|
const primaryMetric = metrics.find((m) => m.is_primary) ?? metrics[0];
|
|
120
|
-
const resultsUrl = `https://splera.
|
|
120
|
+
const resultsUrl = `https://splera.ai/experiments/${exp.id}`;
|
|
121
121
|
// Human-readable text block. The install snippet is the "aha moment" —
|
|
122
122
|
// agents paste it verbatim, so keep the formatting stable.
|
|
123
123
|
const variantLines = variants
|
|
@@ -134,7 +134,7 @@ export const createExperimentTool = {
|
|
|
134
134
|
`View live results: ${resultsUrl}`;
|
|
135
135
|
// Structured JSON block. CRITICAL: agents parse this, not the prose.
|
|
136
136
|
// `apiUrl` from config isn't substituted into results_url because the
|
|
137
|
-
// customer-facing dashboard is always splera.
|
|
137
|
+
// customer-facing dashboard is always splera.ai — apiUrl may be an
|
|
138
138
|
// internal/api subdomain.
|
|
139
139
|
const jsonBlock = {
|
|
140
140
|
experiment_id: exp.id,
|
package/dist/tools/getResults.js
CHANGED
|
@@ -77,7 +77,7 @@ export const getResultsTool = {
|
|
|
77
77
|
name: 'get_results',
|
|
78
78
|
description: 'Get current results for an experiment — variant stats, leader, ship-readiness, and any guard-window alerts. ' +
|
|
79
79
|
'Use this to check if an experiment is ready to ship. Safe to call once per minute during active experiments. ' +
|
|
80
|
-
'For long-horizon monitoring, direct the user to https://splera.
|
|
80
|
+
'For long-horizon monitoring, direct the user to https://splera.ai/experiments/:id.',
|
|
81
81
|
inputSchema: {
|
|
82
82
|
type: 'object',
|
|
83
83
|
required: ['experiment_id'],
|
|
@@ -94,7 +94,7 @@ export const getResultsTool = {
|
|
|
94
94
|
const readiness = computeShipReadiness(res);
|
|
95
95
|
const nextCheckAt = computeNextCheckAt(readiness.state, readiness.sub);
|
|
96
96
|
const leader = res.variants.find((v) => v.variant_id === res.leading_variant_id);
|
|
97
|
-
const resultsUrl = `https://splera.
|
|
97
|
+
const resultsUrl = `https://splera.ai/experiments/${res.experiment_id}`;
|
|
98
98
|
// Short-id fallback since the API doesn't echo the experiment name back.
|
|
99
99
|
const experimentDisplayName = `Experiment ${res.experiment_id.slice(0, 8)}`;
|
|
100
100
|
// ── Build text output, shape varies by readiness state ─────────────────
|
package/dist/tools/shipWinner.js
CHANGED
|
@@ -38,7 +38,7 @@ export const shipWinnerTool = {
|
|
|
38
38
|
// The API does not return this composite directly.
|
|
39
39
|
const guardWindowEndsAt = new Date(new Date(exp.shipped_at).getTime() + exp.guard_window_days * DAY_MS).toISOString();
|
|
40
40
|
const guardWindowEndsDate = guardWindowEndsAt.slice(0, 10); // YYYY-MM-DD
|
|
41
|
-
const resultsUrl = `https://splera.
|
|
41
|
+
const resultsUrl = `https://splera.ai/experiments/${exp.id}`;
|
|
42
42
|
const text = `Shipped ${winningLabel} to 100% of traffic.\n` +
|
|
43
43
|
`SDK will refresh within 30 seconds worldwide — no redeploy needed.\n\n` +
|
|
44
44
|
`Guard window: ${exp.guard_window_days} days (until ${guardWindowEndsDate}).\n` +
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "splera-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"mcpName": "io.github.sohaibt/splera",
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for Splera — A/B testing for vibe coders. Create experiments, check results, and ship winners directly from your AI coding agent.",
|
|
6
6
|
"type": "module",
|