MCP server
Thirteen tools so an agent can check its data, train, predict, forecast and close the loop without leaving its conversation. The server is hosted by Spavik: there is nothing to install and nothing to run on your side.
Connect
The server lives at https://mcp.spavik.co. It speaks Streamable HTTP, the transport of the current protocol revision (2026-07-28): a single entry point, POST /, with no session to keep. Each request stands on its own, which is why a restart on either side never produces an "unknown session" error.
Every request carries your API key in the Authorization header:
Authorization: Bearer sk-spv-api-...That is the whole authentication. The server checks the key with a free read against the API, then uses it as is for the tools you call, on your own workspace. It stores nothing. A request without the header, or with an admin key, is answered 401, with a body that says what to send. There is no OAuth flow: a client that tries one gets that same answer.
What changes from one client to the next is where the URL and the header are written. Keep the key in the environment rather than in a file.
claude mcp add --transport http spavik https://mcp.spavik.co \
--header "Authorization: Bearer sk-spv-api-..."// Claude Code, checked in with the project
{
"mcpServers": {
"spavik": {
"type": "http",
"url": "https://mcp.spavik.co",
"headers": { "Authorization": "Bearer sk-spv-api-..." }
}
}
}// .cursor/mcp.json for one project, ~/.cursor/mcp.json for all of them
{
"mcpServers": {
"spavik": {
"url": "https://mcp.spavik.co",
"headers": { "Authorization": "Bearer ${env:SPAVIK_API_KEY}" }
}
}
}// .vscode/mcp.json: the root key is "servers", the key is asked for once
{
"inputs": [
{ "type": "promptString", "id": "spavik-key", "description": "Spavik API key", "password": true }
],
"servers": {
"spavik": {
"type": "http",
"url": "https://mcp.spavik.co",
"headers": { "Authorization": "Bearer ${input:spavik-key}" }
}
}
}# ~/.codex/config.toml, or .codex/config.toml for a trusted project
[mcp_servers.spavik]
url = "https://mcp.spavik.co"
bearer_token_env_var = "SPAVIK_API_KEY"Keep the key out of the file
Cursor reads ${env:SPAVIK_API_KEY} from your environment, VS Code prompts for a ${input:...} value and stores it in its own secret storage, and Codex names the variable to read with bearer_token_env_var. Prefer those forms over pasting the key into a file you might commit.
Claude.ai and Claude Desktop
The custom connectors screen takes a server URL and, optionally, OAuth client credentials. It offers no field for a static Authorization header, so the Spavik server cannot be added there today. Use one of the clients above.
The distribution page, get.spavik.co/mcp, gives the same snippets ready to copy.
What the agent is told
At the first exchange, the server sends its instructions, which the client adds to the model's context: what Spavik does, the journey, where the money goes. An agent therefore knows, before its first tool call, that a table is checked before it is trained on, that a prediction is billed per row, and that a real value is reported with the request_id kept at prediction time.
The journey those instructions describe:
validate_dataon the table, free: does the data hold, what to fix first, and does the engine find a signal.train_modelon the same table, free. Keep the model identifier and the feature columns it returns.predicton new rows, billed per row. Keep eachrequest_id.- Later,
submit_outcomeswith what really happened, thenmodel_performancefor the verdict, thenrefresh_modelif the model holds.
For a series: backtest_forecast first, free, then forecast, billed.
The thirteen tools
| Tool | What it does | Cost |
|---|---|---|
validate_data | checks a table before training, with the engine's verdict | free |
train_model | creates a model and trains it on a table | free |
predict | scores rows with a trained model | the engine's credits per row, 1 on the default engine |
backtest_forecast | says whether a series is worth forecasting, replaying its history | free |
forecast | forecasts a time series, with no model kept | horizon x series |
submit_outcomes | reports what actually happened, as actual per outcome | free |
model_performance | compares real performance with training, with a verdict | free |
refresh_model | folds reported outcomes into a new version | free |
prediction_history | lists past predictions with their request_id | free |
list_models | lists the workspace models, active or archived | free |
account_usage | reads the plan, the balance and the consumption | free |
archive_model | stops serving a model and frees its slot in the plan | free |
restore_model | brings an archived model back into service | free |
Every billable tool announces its cost in its description, where the model reads it before choosing, and repeats it in the result along with the remaining balance. predict and forecast also take dry_run: true: the exact same call then sends nothing, charges nothing, and answers with what it would cost against what remains. A dry run also checks that the rows carry the model's feature columns, when the API records them.
Data travels inside the call
The server runs remotely and cannot read a file on your machine. A path is refused, with that explanation. train_model, validate_data, forecast and backtest_forecast take the content of the file in data: CSV text with a header row, or a JSON array of objects. The agent reads the file and passes the text. predict and submit_outcomes accept up to fifty rows inline (rows, outcomes) and take the same data content beyond that.
Parameters, in short
train_model and validate_data take data and target, the name of the column to predict; name, engine and auto_update are optional. The result of train_model names the task, the target, the feature columns and the engine used; the API fills the default engine in when none is given.
predict takes model_id and the rows, keyed by the feature columns. Each prediction comes back with its request_id.
forecast and backtest_forecast take data, target and horizon; timestamp names the time column and item_id the column that tells several series apart. Several series in one call cost the same as separate calls and give the same values. forecast also takes quantiles, the levels to return.
submit_outcomes takes model_id and the outcomes: the observed value in actual, plus the request_id of the original prediction or the full row in features. refresh_model, model_performance (with days, 30 by default), prediction_history (with limit), archive_model and restore_model take model_id. list_models takes archived: true to list what can be restored.
What the results say
Each result leads with the answer in plain words, then the details. A verdict comes first when there is one: validate_data says whether the data is usable, suspiciously good (a column probably gives the answer away) or without signal; backtest_forecast says whether forecasting is worth paying for; model_performance says whether the model is holding, weak or has dropped, with the move to make. The scores stay in the body for whoever wants them.
A refused call comes back with isError: true, so the model knows it has to correct and try again, rather than as text that would pass for a success. The text says why: data refused before anything was sent, columns missing, plan quota reached with the way out, insufficient credits with the balance and the amount required.
Resources and prompts
Three resources round this out without consuming tool budget: spavik://operations lists the tools in the order of the journey with their cost, spavik://models the workspace catalogue, spavik://engines the engines a model can be built on. Three prompts offer the journey as entries the user picks: build_model, score_rows, close_the_loop. Several clients ignore resources and prompts, so nothing essential depends on them: the tools and the instructions carry everything.
Models, quota and archiving
Plans cap the number of active models. When train_model is refused for that reason, the refusal says what to do: list_models shows the active models, archive_model frees a slot. An archived model stops serving but keeps everything, and restore_model brings it back, taking a slot again. Nothing here deletes a model for good.
What the server does by the book
- Every tool carries its annotations: none is destructive, none opens onto the outside world, six are read-only.
- The tool list comes out in a stable order, so clients can cache it.
- Requests are stateless: no session identifier, one
POSTendpoint, JSON responses. OriginandHostare validated.
What is not exposed, and why
No account deletion, no model deletion, no key management, no billing, no administration, no webhooks. An agent that gets it wrong must not be able to do irreversible damage. The server cannot open a web session either: an agent has no business handling a sign-in link.
Why thirteen and not fifty
Past about forty tools, selection degrades noticeably: the model chooses less well, and some clients cap at that number anyway. Exposing one tool per route would make the server unusable. The thirteen kept cover the engine scope, where an agent actually has something to do, with one tool per concept: a tool never mixes two things to keep the count down.