Skip to content

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.

sh
claude mcp add --transport http spavik https://mcp.spavik.co \
  --header "Authorization: Bearer sk-spv-api-..."
json
// Claude Code, checked in with the project
{
  "mcpServers": {
    "spavik": {
      "type": "http",
      "url": "https://mcp.spavik.co",
      "headers": { "Authorization": "Bearer sk-spv-api-..." }
    }
  }
}
json
// .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}" }
    }
  }
}
json
// .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}" }
    }
  }
}
toml
# ~/.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:

  1. validate_data on the table, free: does the data hold, what to fix first, and does the engine find a signal.
  2. train_model on the same table, free. Keep the model identifier and the feature columns it returns.
  3. predict on new rows, billed per row. Keep each request_id.
  4. Later, submit_outcomes with what really happened, then model_performance for the verdict, then refresh_model if the model holds.

For a series: backtest_forecast first, free, then forecast, billed.

The thirteen tools

ToolWhat it doesCost
validate_datachecks a table before training, with the engine's verdictfree
train_modelcreates a model and trains it on a tablefree
predictscores rows with a trained modelthe engine's credits per row, 1 on the default engine
backtest_forecastsays whether a series is worth forecasting, replaying its historyfree
forecastforecasts a time series, with no model kepthorizon x series
submit_outcomesreports what actually happened, as actual per outcomefree
model_performancecompares real performance with training, with a verdictfree
refresh_modelfolds reported outcomes into a new versionfree
prediction_historylists past predictions with their request_idfree
list_modelslists the workspace models, active or archivedfree
account_usagereads the plan, the balance and the consumptionfree
archive_modelstops serving a model and frees its slot in the planfree
restore_modelbrings an archived model back into servicefree

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 POST endpoint, JSON responses.
  • Origin and Host are 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.

Part of this documentation is generated from the OpenAPI contract.