Convilyn developers

Runtime

Serve your tools over the JSON-RPC /mcp runtime, verify inbound HMAC, and test in-process.

serve

serve runs the JSON-RPC /mcp runtime — /health, /manifest, and an HMAC-verified POST /mcp — interchangeable with the Python/Go author SDKs behind the same gateway.

import {
  defineTool,
  ToolServer,
  ToolResult,
  serve,
} from "@convilyn/sdk-author";
import { z } from "zod";
 
const echo = defineTool({
  name: "echo",
  description: "Echo the input text back.",
  input: z.object({ text: z.string().min(1) }),
  idempotent: true,
  handler: (args) =>
    ToolResult.ok({ echoed: args.text }, `Echoed ${args.text.length} chars`),
});
 
const server = new ToolServer({
  name: "echo-server",
  version: "1.0.0",
  description: "demo",
});
server.register(echo);
 
serve(server, { port: 8080 }); // CONVILYN_HMAC_SECRET / CONVILYN_PORT come from env via SDKConfig
EndpointPurpose
GET /healthLiveness probe
GET /manifestServes the server manifest
POST /mcpHMAC-verified JSON-RPC tool invocation

Inbound HMAC verification

The gateway signs every call it makes to your tool server; verify the signature to reject forgeries. The built-in runtime (serve) does this for you — use verifySignature directly only if you run your own HTTP framework.

import { verifySignature, InvalidSignatureError } from "@convilyn/sdk-author";
 
try {
  // `verifySignature` reads the `x-convilyn-signature` / `-timestamp` headers.
  verifySignature(
    process.env.CONVILYN_HMAC_SECRET!,
    rawBodyBytes,
    req.headers as Record<string, string | undefined>,
  );
} catch (err) {
  if (err instanceof InvalidSignatureError) {
    // err.reason ∈ missing_secret | missing_header | invalid_timestamp | timestamp_out_of_range | signature_mismatch
    res.writeHead(401).end();
  }
}

InvalidSignatureError.reason is one of missing_secret | missing_header | invalid_timestamp | timestamp_out_of_range | signature_mismatch.

signRequest

signRequest(secret, body, unixSeconds) produces the matching { signature, timestamp } pair — for tests, or for calling a developer-hosted server directly.

import { signRequest } from "@convilyn/sdk-author";
 
const { signature, timestamp } = signRequest(
  secret,
  body,
  Math.floor(Date.now() / 1000),
);

Local testing harness

invokeTool drives a ToolServer in-process (no HTTP), returning the same wire envelope the gateway would receive — ideal for unit tests.

import { invokeTool } from "@convilyn/sdk-author";
 
const wire = await invokeTool(server, "echo", { text: "hi" });
expect(wire.status).toBe("ok");
expect(wire.data).toEqual({ echoed: "hi" });

Authentication at a glance

Secret (env)Used for
CONVILYN_API_KEYBearer auth to the platform API (ConvilynClient)
CONVILYN_HMAC_SECRETVerifying inbound POST /mcp calls from the gateway (serve / verifySignature)
CONVILYN_TOOL_CONFIRMATION_SECRETMinting / verifying confirmation tokens

Where to go next