Get started
Quickstart
From a new account to your first successful tool call from an MCP client, in about ten minutes.
You will create an account and a project, run a small agent that registers one tool, watch the project turn Connected, create a key for an MCP client, call the tool, and find the call in Logs. You need a terminal, Node.js 20 or later or Go 1.22 or later, and, for the last steps, an MCP client such as Claude Code (or just curl).
1. Create an account
- Open Get started. The page is titled Create your account.
- Fill in Name or organization, Email and Password (at least 8 characters).
- Tick I agree to the Terms of Service, Privacy Policy and Acceptable Use Policy. Sign-up is refused without it. The three documents open in a new tab: Terms, Privacy and Acceptable Use.
- Press Create account. You land on the Dashboard.
2. Create a project
A project is one MCP endpoint with its own keys, agents and tools.
- Press New project in the top bar (or on the Projects page).
- Enter a Name, for example
Orders API(1 to 60 characters), and press Create project. - The project page opens with a Copy your key now dialog. This is the project's first key, a full key labelled Default. It is shown once. We keep only a hash, so a lost key is replaced, never recovered. Copy it, then press I have copied it.
The dialog shows the two settings your service needs:
SPACEFIX_API_KEY=sf_live_xxxxxxxxxxxxxxxxxxxxxx
SPACEFIX_SERVICE_URL=https://api.spacefix.io
SPACEFIX_SERVICE_URL is the gateway's address, https://api.spacefix.io. The project page also shows your MCP endpoint, https://api.spacefix.io/mcp/<project id>, which is what MCP clients call.
3. Install the SDK
Pick your language. The language you choose here is remembered on every page of these docs.
mkdir orders-agent && cd orders-agent
npm init -y
npm install @spacefix/core zod
mkdir orders-agent && cd orders-agent
go mod init orders-agent
go get github.com/softmode/spacefix-go
pip install svc-sdk
io.spacefix:sdk:0.1.0
dotnet add package Spacefix.Sdk
cargo add svc-sdk
cargo add tokio --features macros,rt-multi-thread,signal
composer require spacefix/sdk
4. Write and run an agent
An agent is your service with the SDK in it. This one offers a single tool, get_order. The key comes from SPACEFIX_API_KEY and the gateway address from SPACEFIX_SERVICE_URL.
import { Agent } from '@spacefix/core';
import { z } from 'zod';
// SPACEFIX_API_KEY is the agent key; SPACEFIX_SERVICE_URL is the gateway address.
const agent = new Agent({ apiKey: process.env.SPACEFIX_API_KEY });
const orders = {
'1': { id: '1', item: 'Widget', total: 4200 },
'2': { id: '2', item: 'Gadget', total: 1950 }
};
agent.tool(
'get_order',
{
description: 'Look up a single order by its id. Returns an error if the order does not exist.',
inputSchema: z.object({ id: z.string().describe('The order id, for example "1"') })
},
({ id }) => {
const order = orders[id];
if (!order) throw new Error(`No order with id ${id}. Ask the user to confirm the id.`);
return order;
}
);
void agent.connect(); // dials out; there is no port to open
async function shutdown() {
await agent.disconnect(); // waits up to 5 seconds for running calls
process.exit(0);
}
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
package main
import (
"context"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"github.com/softmode/spacefix-go/core"
)
type GetOrderInput struct {
ID string `json:"id" desc:"The order id, for example \"1\""`
}
type Order struct {
ID string `json:"id"`
Item string `json:"item"`
Total int `json:"total"`
}
var orders = map[string]Order{
"1": {ID: "1", Item: "Widget", Total: 4200},
"2": {ID: "2", Item: "Gadget", Total: 1950},
}
func main() {
// SPACEFIX_API_KEY is the agent key; SPACEFIX_SERVICE_URL is the gateway address.
agent, err := core.New(core.Options{APIKey: os.Getenv("SPACEFIX_API_KEY")})
if err != nil {
log.Fatal(err)
}
err = core.AddTool(agent, "get_order",
"Look up a single order by its id. Returns an error if the order does not exist.",
func(ctx context.Context, in GetOrderInput) (Order, error) {
order, ok := orders[in.ID]
if !ok {
return Order{}, fmt.Errorf("no order with id %s: ask the user to confirm the id", in.ID)
}
return order, nil
})
if err != nil {
log.Fatal(err)
}
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
if err := agent.Run(ctx); err != nil { // blocks; reconnects by itself; returns nil on Ctrl-C
log.Fatal(err)
}
}
from svc_sdk import Agent
agent = Agent(api_key=os.environ["SPACEFIX_API_KEY"]) # URL comes from SPACEFIX_SERVICE_URL
@agent.tool(description="Look up a single order by its id. Returns null if it does not exist.")
async def get_order(id: str) -> dict | None:
return await orders.find_by_id(id)
asyncio.run(agent.run()) # connects, reconnects, and shuts down cleanly on SIGINT/SIGTERM
public final class OrdersAgent {
@Tool(description = "Look up a single order by its id. Returns null if it does not exist.")
public Order getOrder(@Param(description = "Order id, for example o_123") String id) {
return orders.findById(id);
}
public static void main(String[] args) throws Exception {
Agent agent = Agent.builder().apiKey(System.getenv("SPACEFIX_API_KEY")).build();
agent.register(new OrdersAgent());
Runtime.getRuntime().addShutdownHook(new Thread(agent::disconnect));
agent.connect(); // blocks until disconnect() is called
}
}
var agent = new Agent(new AgentOptions
{
ApiKey = Environment.GetEnvironmentVariable("SPACEFIX_API_KEY")!,
});
agent.Tool("get_order",
description: "Look up a single order by its id. Returns null if it does not exist.",
handler: async (GetOrderArgs args, CancellationToken ct) => await orders.FindByIdAsync(args.Id, ct));
await agent.ConnectAsync(stop.Token); // returns when the token is cancelled
public sealed record GetOrderArgs(string Id);
/// Look up a single order by its id. Returns null if it does not exist.
#[tool(name = "get_order")]
async fn get_order(args: GetOrderArgs) -> Result<Option<Order>, ToolError> {
Ok(orders::find_by_id(&args.id).await?)
}
#[tokio::main]
async fn main() -> Result<(), svc_sdk::Error> {
let agent = Agent::builder()
.api_key(std::env::var("SPACEFIX_API_KEY").expect("SPACEFIX_API_KEY"))
.tool(get_order)
.build()?;
agent.run_until_signal().await
}
final class OrderTools
{
#[Tool(description: 'Look up a single order by its id. Returns null if it does not exist.')]
public function getOrder(#[Param('Order id, for example o_123')] string $id): ?array
{
return $this->orders->find($id);
}
}
$agent = Agent::create(apiKey: getenv('SPACEFIX_API_KEY') ?: '');
$agent->register(new OrderTools(new OrderRepository()));
exit($agent->run());
Run it with the values from the key dialog:
SPACEFIX_API_KEY=sf_live_xxxxxxxxxxxxxxxxxxxxxx \
SPACEFIX_SERVICE_URL=https://api.spacefix.io \
node agent.mjs
SPACEFIX_API_KEY=sf_live_xxxxxxxxxxxxxxxxxxxxxx \
SPACEFIX_SERVICE_URL=https://api.spacefix.io \
go run .
The agent prints one line when the gateway accepts it, and keeps running. Leave it running.
[spacefix] connected with 1 tool(s)
2026/10/31 02:53:03 INFO [spacefix] connected with 1 tool(s)
The SDK accepts plain JSON Schema or a schema library such as Zod (Node.js), and the Go SDK builds the schema from the struct's tags. Building tools covers both.
5. Watch the project connect
Go back to the project page. Within a few seconds the Get started pill changes from Waiting for your agent… to Connected · 1 tool, step 2 gets a tick, and the page header shows a green pill reading 1 agent.
Further down, the Connected agents panel lists your process with a green dot, its agent id, the key it used (Default), how long it has been connected, and Registered 1 · 0 · 0 (tools, resources, prompts). The Tools table lists get_order.
Open Agents in the sidebar to see the same process across all your projects. Agents appear when they connect and disappear when they disconnect.
6. Create a client key
Your agent used the Default key, which can both register tools and call them. A client should have its own client key: it can call tools but cannot register any, and you can revoke it without touching your agent.
- In the API keys panel press Create key. The dialog is titled Create an API key.
- Set Label to
claude-code, Type to Client: for an AI app only, and Expires to Never (or a date you prefer). - Leave Allowed tools (optional) empty to allow every tool, or enter patterns such as
get_*to limit what this key can see and call. - Press Create key. Copy the secret from Copy your key now, then press I have copied it.
7. Connect a client and call the tool
The project page has a Connect a client panel with one tab per client (Claude Code, Claude Desktop, Cursor, VS Code, MCP Inspector), with your endpoint already filled in. Only the key is a placeholder, because a secret is shown once.
For Claude Code, run this once in a terminal, with your client key and project id:
claude mcp add --transport http orders-api https://api.spacefix.io/mcp/<project id> \
--header "Authorization: Bearer <your client key>"
Then ask Claude Code something that needs the tool, for example "Use get_order to look up order 1", and approve the tool when asked. You get back:
{"id":"1","item":"Widget","total":4200}
No client at hand? Make the same call with curl:
curl -s -X POST https://api.spacefix.io/mcp/<project id> \
-H "Authorization: Bearer <your client key>" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_order","arguments":{"id":"1"}}}'
The reply is a one-event stream:
event: message
data: {"result":{"content":[{"type":"text","text":"{\"id\":\"1\",\"item\":\"Widget\",\"total\":4200}"}]},"jsonrpc":"2.0","id":1}
You can also run the tool from the dashboard with no client at all: use Try a tool on the project page, or the Playground. Playground runs are logged but not billed.
Try an unknown id ("id":"9") to see what a model sees when a tool fails: the result has "isError":true and the text of the error you threw.
8. See the call in Logs
Open Logs in the sidebar. The newest row is get_order, status ok, with its duration. Click the row to expand it:
- Request id and Trace id identify this call on both sides.
- Credential is the label of the key that made the call (
claude-code). - Platform is the runtime your agent runs on, as the SDK reported it, such as
Node.js 22.14.0. - Environment is what the call was made from: the
User-Agentthe caller sent, such asnode. - Client is the MCP client that made the call, by the name and version it reported (for example
claude-code 2.1.4). It is shown when the client reported one. - Arguments passed lists the argument names (
id). The values and the result are never stored. - Size in → out is the size of the arguments and the result.
The Agents page now shows your process with Calls 1, and the Usage page counts the call (allow a couple of seconds). The project's Get started panel says 1 call so far.
If something does not work
On the project page use Check connection. Leave A key to check empty, or paste a key to find out whether it is valid, the wrong kind, revoked, expired or from another project, then press Run checks.
| Symptom | Likely cause |
|---|---|
| The project stays on Waiting for your agent… | Wrong SPACEFIX_API_KEY or SPACEFIX_SERVICE_URL; the process cannot make an outbound HTTPS request to the gateway; or the key is a client key (This is a client key. Agents connect with an agent key.) |
The client gets 401 Invalid API key. | A truncated or wrong key, or the key was revoked or has expired |
The client gets 403 This is an agent key. MCP clients connect with a client key. | You used the agent's key in the client; create a client key |
The client gets 503 No agent connected for server "<project id>". | The agent is not running, or has not connected |
The client gets 503 Every agent for server "<project id>" is disabled. | Every agent is disabled on the Agents page |
The client gets -32004 | The project has a client address allow-list and your address is not on it |
| The client connects but sees fewer tools, or none | The key is Read only (or the project's Client access is): it sees only tools with readOnlyHint: true or marked Read under Tools |
More in Connecting clients and the Troubleshooting guide.
Next steps
- Concepts: the mental model of projects, agents, replicas and keys.
- Connecting clients: Claude Desktop, Cursor, VS Code and the Inspector.
- Building tools: names, descriptions, schemas, errors and idempotency.
- Projects and keys: scoped and read-only keys, expiry and rotation without downtime.
- Running replicas: more than one copy of your service, and deploys.