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

ItemPlanned value
Maven coordinatesio.spacefix:sdk (core), with optional modules
JDK17 and newer; virtual threads are used automatically on 21 and newer
Build toolsMaven and Gradle (a plain JAR with a module-info.java)
Core dependencyjackson-databind. The HTTP client is the JDK's own java.net.http.HttpClient
Optional modulessvc-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>
java
// Gradle (Kotlin DSL)
implementation("io.spacefix:sdk:0.1.0")
implementation("io.spacefix:sdk-otel:0.1.0")   // optional

A minimal agent

java
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
    }
}
bash
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.

java
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:

java
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

java
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();
OptionDefaultMeaning
apiKeynone, requiredThe agent key. build() throws IllegalArgumentException("apiKey is required.") for null or empty
urlSPACEFIX_SERVICE_URL, then https://api.spacefix.ioThe gateway base URL. Set it per environment
otelonOtelOptions.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 implements AutoCloseable) disconnects and then waits for running handlers to post their results. agent.installShutdownHook() registers a JVM shutdown hook.
  • Health. agent.state() returns CONNECTING, CONNECTED, RETRYING or STOPPED; agent.isConnected() is also available.
  • Spring Boot. svc-sdk-spring-boot-starter configures an Agent bean from properties, registers every bean with @Tool, @Resource or @Prompt methods, and starts and stops the agent with the application context:
yaml
# 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:

java
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());
OptionDefaultMeaning
interval10 secondsHow often to sample and report
reportbuilt inA Consumer<Metrics>; replaces the gateway transport
oteltrueAlso 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