SDKs
Java SDK
The planned Java SDK (io.spacefix:sdk): what it will look like, and what to use until it ships.
This page previews the planned API so you can see how a Java agent will feel. Nothing below can be installed yet.
What it will be
| Item | Planned value |
|---|---|
| Maven coordinates | io.spacefix:sdk (core), with optional modules |
| JDK | 17 and newer; virtual threads are used automatically on 21 and newer |
| Build tools | Maven and Gradle (a plain JAR with a module-info.java) |
| Core dependency | jackson-databind. The HTTP client is the JDK's own java.net.http.HttpClient |
| Optional modules | svc-sdk-otel, svc-sdk-spring-boot-starter, svc-sdk-micronaut, svc-sdk-schema-victools, svc-sdk-test |
Like every SDK, it connects out to the gateway (no inbound port), registers your tools, resources and prompts, runs your handlers when calls arrive, reconnects with backoff, and can report telemetry. See the SDK overview.
Install
<dependency>
<groupId>com.spacefix</groupId>
<artifactId>svc-sdk</artifactId>
<version>0.1.0</version>
</dependency>
// Gradle (Kotlin DSL)
implementation("io.spacefix:sdk:0.1.0")
implementation("io.spacefix:sdk-otel:0.1.0") // optional
A minimal agent
import io.spacefix.Agent;
import io.spacefix.annotations.Param;
import io.spacefix.annotations.Tool;
public final class OrdersAgent {
public record Order(String id, String status, long totalCents) {}
@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")) // URL comes from SPACEFIX_SERVICE_URL
.build();
agent.register(new OrdersAgent()); // scans @Tool, @Resource, @Prompt methods
Runtime.getRuntime().addShutdownHook(new Thread(agent::disconnect));
agent.connect(); // blocks until disconnect() is called
}
}
export SPACEFIX_API_KEY=agent_key_from_the_dashboard
export SPACEFIX_SERVICE_URL=https://api.spacefix.io # optional; defaults to the production URL
java -jar orders-agent.jar
# [svc-sdk] connected with 1 tool(s)
connect() blocks the calling thread until disconnect(); connectAsync() returns a CompletableFuture<Void> that completes when the agent stops, for applications that own their threads.
Defining a tool
The planned model is annotations on methods, with records as argument types. A JSON Schema is generated from them.
public final class OrderTools {
public record SearchArgs(
@Param(description = "Customer id, for example cus_123") String customerId,
@Param(description = "Order status") Status status,
@Param(description = "Maximum orders to return", min = 1, max = 100) Integer limit
) {
public enum Status { OPEN, SHIPPED, CANCELLED }
}
@Tool(name = "search_orders",
description = "Search a customer's orders by status. Returns at most `limit` orders, newest first.")
public List<Order> search(SearchArgs args) {
return orders.search(args.customerId(), args.status(), args.limit() == null ? 20 : args.limit());
}
}
A tool method takes either one record parameter (the argument object) or several simple parameters (each becomes a property, which needs -parameters at compile time). It may return a value or a CompletableFuture<T>. Names default to the method name in snake_case (getOrder becomes get_order) and must match ^[a-zA-Z0-9_-]{1,64}$. A lambda API exists for code that wants no reflection:
agent.tool("get_order",
ToolConfig.builder()
.description("Look up a single order by id.")
.inputSchema(Schemas.of(GetOrderArgs.class)) // or Schemas.fromJson("{...}")
.build(),
(GetOrderArgs args) -> orders.findById(args.id()));
Resources and prompts use @Resource and @Prompt; prompt arguments are always String.
Configuration
Agent agent = Agent.builder()
.apiKey(System.getenv("SPACEFIX_API_KEY")) // required
.url("https://api.spacefix.io") // optional
.otel(OtelOptions.defaults().argKeys(true)) // optional; OtelOptions.off() disables
.build();
| Option | Default | Meaning |
|---|---|---|
apiKey | none, required | The agent key. build() throws IllegalArgumentException("apiKey is required.") for null or empty |
url | SPACEFIX_SERVICE_URL, then https://api.spacefix.io | The gateway base URL. Set it per environment |
otel | on | OtelOptions.off() disables the bridge. argKeys(true) also records argument names, never values. Only effective when svc-sdk-otel is on the classpath |
The design also proposes executor, maxConcurrentCalls, httpClient, logger, maxResultBytes and onStateChange.
Running in production
Run the agent as an always-on process, in its own deployment so it scales by replica independent of your HTTP service. Request-scoped serverless runtimes freeze the process between requests and break the connection; they are not supported.
- Concurrency. Each call runs on its own thread (a virtual thread on JDK 21 and newer), so calls run concurrently.
- Scaling. Run more JVMs. Each opens its own connection and the gateway spreads calls across them, so keep state in a database.
- Shutdown.
disconnect()is idempotent.agent.close()(the SDK implementsAutoCloseable) disconnects and then waits for running handlers to post their results.agent.installShutdownHook()registers a JVM shutdown hook. - Health.
agent.state()returnsCONNECTING,CONNECTED,RETRYINGorSTOPPED;agent.isConnected()is also available. - Spring Boot.
svc-sdk-spring-boot-starterconfigures anAgentbean from properties, registers every bean with@Tool,@Resourceor@Promptmethods, and starts and stops the agent with the application context:
# application.yml
svc:
api-key: ${SPACEFIX_API_KEY}
url: ${SPACEFIX_SERVICE_URL:} # empty falls back to the SDK default
otel:
arg-keys: false
telemetry:
enabled: true
interval: 10s
Telemetry
An optional package reports process health to the Agents page, the counterpart of @spacefix/telemetry:
agent.use(Telemetry.create()); // every 10s over the agent's connection
agent.use(Telemetry.builder().interval(Duration.ofSeconds(30))
.report(m -> statsd.gauge("heap", m.heapUsed())).build());
| Option | Default | Meaning |
|---|---|---|
interval | 10 seconds | How often to sample and report |
report | built in | A Consumer<Metrics>; replaces the gateway transport |
otel | true | Also publish through OpenTelemetry (needs svc-sdk-otel) |
OpenTelemetry
Provided by svc-sdk-otel, which depends only on the OpenTelemetry API. The SDK never creates an OpenTelemetry SDK: it uses the global instance, or one you pass with .openTelemetry(...). Without the module on the classpath the bridge is off. Each call that reaches your agent produces a server span (tools/call <name>, resources/read <uri> or prompts/get <name>) that continues the caller's trace, plus the duration histogram mcp.server.operation.duration, the counter spacefix.agent.reconnects, the gauge spacefix.agent.connected and connection logs. Argument values and results are never recorded. The attributes and metric names are the same in every SDK; see OpenTelemetry.
Next steps
- Node.js SDK and Go SDK: available now
- The agent protocol: connect from Java today without an SDK
- SDK overview: every language and its status