Get started
Concepts
The mental model behind Spacefix: projects, agents, replicas, keys, how a call travels, what runs where, and what we store.
This page gives you the model that the rest of the documentation assumes. Read it once after the Quickstart, and come back when a word is unclear.
The model in one picture
- An account owns projects.
- A project is one MCP endpoint,
https://api.spacefix.io/mcp/<project id>, with its own keys, settings, connected agents and tools. - Your service runs an SDK. Each running copy is an agent: it holds one outbound connection to the project, authenticated with an agent key.
- An MCP client calls the project's endpoint with a client key.
- The gateway sends each call to one connected, enabled agent and returns the answer.
The running agent is the source of truth for what a project offers. The gateway does not hold your tools; the dashboard shows what the connected agent registered right now.
Terms
| Term | Meaning |
|---|---|
| Project | One MCP endpoint. Has a project id (a UUID, the last segment of the endpoint URL), a name, keys, settings and, while one is connected, agents. The project id is not secret and the endpoint alone grants nothing: every request needs a key. |
| Agent | One running process holding one open connection to a project. A project with three copies of your service has three agents. An agent exists only while connected; its agent id (ten hex characters, such as 47377b2f4e) is made when it connects and is gone when it disconnects. |
| Replica | One of several copies of your service, each running the SDK, so each its own agent of the same project. See Running replicas. |
| Tool | A function a client calls with arguments, such as get_order. Registered with a name, a description and a JSON Schema for its arguments. |
| Resource | Data a client reads by URI, such as orders://open, with no arguments. |
| Prompt | A message template a client lists in its menu and fills in. Its arguments are always strings. |
| Call | One request to run a tool, read a resource or render a prompt (tools/call, resources/read, prompts/get). Listing (tools/list and so on) is not a call. |
| Key | A secret that authenticates an agent or a client to one project. It looks like sf_live_ followed by 22 characters. It is shown once and stored only as a hash. |
| Allowance | The number of billable calls your plan includes each UTC month. See Plans and limits. |
| Client | Anything that calls the endpoint: Claude Code, Claude Desktop, Cursor, VS Code, the MCP Inspector, or your own code. |
Key kinds
Every key has a kind, a label, an optional expiry and, for kinds that can call tools, an optional tool allow-list and an access level: read and write (the default), or read only, which limits the key to tools that read.
| Kind | Dashboard label | Connects an agent (SDK) | Calls tools (MCP client) | Allow-list |
|---|---|---|---|---|
agent | Agent | Yes | No | Not applicable |
client | Client | No | Yes | Yes |
full | Full | Yes | Yes | Yes |
A genuine key used on the wrong door is refused with 403:
- An agent key on the MCP endpoint:
This is an agent key. MCP clients connect with a client key. - A client key where an agent connects:
This is a client key. Agents connect with an agent key.
A tool reads when your agent sets readOnlyHint: true on it, or when you mark it Read in the dashboard. Any other tool counts as a write. A project's Client access setting can limit every client to reads. See Read-only keys.
Every new project starts with one full key labelled Default. For real use, create one agent key for each deployment and one client key for each client or person. Details are in Projects and keys.
What runs where
| Runs in your infrastructure | Runs on the gateway |
|---|---|
| Your service, the SDK and every handler you write | The MCP endpoint your clients call |
| Your database, secrets and network access | Key checks, plan and rate checks, and validation of arguments against your schema |
| The decision of what a tool does | Routing a call to one of your agents, timeouts, the optional result cache |
| The one outbound HTTPS connection per replica | The dashboard: agents, logs, traces, usage, keys, alerts |
No code of yours runs on the gateway, and no inbound port is opened on your side. The agent dials out; the gateway pushes calls down that connection, and the agent returns each result with an ordinary HTTPS request.
The life of a call
- Your service starts. The SDK connects out to the gateway with an agent key and announces its tools, resources and prompts. The connection stays open, and the SDK reconnects by itself if it drops.
- A client calls your project's endpoint with a client key, for example
tools/callwithget_orderand{"id":"1"}. - The gateway checks, in order: the key (valid, right kind, not expired or revoked), whether the project is suspended, the client address allow-list if you set one, the project's request rate, the monthly allowance, and whether any agent is connected.
- The gateway validates the arguments against the JSON Schema your tool registered. A call that does not match never reaches your code.
- The gateway picks one agent that has the tool, in turn across your enabled agents, and sends the call down its connection.
- Your handler runs in your process and returns a value (or throws).
- The result goes back: the SDK posts it, the gateway returns it to the client as the tool's result, and records the call: tool, time, duration, outcome and sizes.
If a tool takes longer than the project's call timeout (30 seconds by default, never more), the client gets an error result and your handler's late result is discarded.
At-most-once delivery
The gateway sends a call to one agent and does not retry it. If that agent dies while running the call, the call fails with Agent disconnected before returning a result.; the gateway does not send it to another replica, because it cannot know whether your handler already did its work. The same goes for a call that times out: your handler may still have run.
Design for it. A tool that writes should accept a caller-supplied request id and ignore a repeat (see Idempotency), so a model or client that retries after a failure does not do the work twice. The SDK does retry posting a result back to the gateway after a network failure; that is separate from running the call again.
Timeouts
- The default wait for a tool, a resource read or a prompt is 30 seconds.
- In Project settings you can set Call timeout (seconds) to any whole number from 1 up to 30. You can lower it, not raise it.
- When it expires the client gets a result with
isErrorandRequest for tool "<name>" timed out after <ms>ms., and the call is logged as Timed out. It counts as a billable call because it reached your agent.
For work that takes longer, return a job reference and add a second tool to poll it.
Why state must be shared
When you run more than one copy of your service, calls are spread across them and each copy has its own memory. A tool that keeps data in a variable gives different answers depending on which copy received the call.
This is a real result from the SDK's example agent, run as two replicas (A and B) of one project and called through the gateway:
add_item {"item":"x"} -> added x on A
get_all_items -> {"replica":"B","items":[]} <- served by B, which never saw the write
get_all_items -> {"replica":"A","items":["x"]} <- served by A
The write landed on A, the next read was served by B, and the list looked empty. The tool did nothing wrong; the data was not shared. Keep state where every replica can see it: a database, a cache or a queue, and treat handlers as stateless. There is no affinity: do not rely on the same agent answering next time. See Running replicas for a worked example and the fix.
What we store
The gateway keeps what it needs to route, bill and show you what happened, and nothing of the content of your calls.
| We store | We do not store |
|---|---|
| The names, descriptions and schemas of your tools, resources and prompts, only while an agent that registered them is connected | Your code, your data or your secrets |
| A hash of each API key, with its prefix, label, kind, expiry and last-used time | The secret of a key, after it is shown once |
Call metadata: tool name, kind (tool, resource or prompt), time, duration, outcome, the size in bytes of the arguments and the result, the label of the key used, the MCP client's name and version as it reported them and the caller's User-Agent, the runtime your agent runs on, a request id and a trace id | The values of the arguments or the result |
| The names of the arguments passed (up to 20 names per call) | Argument values |
| For a failed call, the error message, shortened to 300 characters | |
| Your settings: tool controls, project settings, webhooks, account details |
Arguments and results travel through the gateway in memory on their way to and from your agent. The only time a result is held longer is the opt-in result cache, in the gateway's memory for the number of seconds you set.
Four switches that stop traffic
| What | Where you set it | Effect | Survives an agent restart? |
|---|---|---|---|
| Disabled agent | Agents page, Disable | The agent stays connected but is sent no new calls, and its tools are left out of the lists clients see. Calls already running on it finish. | No: a reconnecting agent is a new connection and starts enabled |
| Disabled tool | Project page, tool Edit | The tool disappears from tools/list for every client and cannot be called. Your agent is unaffected. | Yes |
| Revoked key | Project page, Revoke | The key stops working on the next request, and an agent connected with it is disconnected. | Yes (permanent) |
| Suspended project or account | The service operator | Agents and clients are refused with the reason shown. | Until reinstated |
Next steps
- Connecting clients: the endpoint, the auth header and a configuration for each client.
- Building tools: write tools a model uses correctly.
- Running replicas: how calls are shared and how to deploy safely.