The three pieces

Everything in Silkweave is a composition of three concepts. Learn these and the rest of the toolkit is just adapters that follow the same contract.

  • 1
    Action

    What your code does. A Zod input schema, an optional output, and an async run() - and nothing about transports.

  • 2
    Adapter

    The door to one transport. It takes your actions and speaks the protocol - MCP tools, REST routes, CLI flags - so your logic never has to.

  • 3
    Silkweave

    The fluent builder that wires actions to adapters and carries every action's types forward to type-aware adapters like tRPC.

1. Action - what your code does

An Action is a named operation with a Zod input schema, an optional output schema, and an async run(input, context) function. Actions are deliberately transport-agnostic: they never import an HTTP framework, never read a socket, never format an MCP result. They receive a typed context (logger, auth, request metadata) and return plain data.

export const GreetAction = createAction({
  name: 'greet',
  description: 'Greet a person by name',
  input: z.object({ name: z.string() }),
  output: z.object({ message: z.string() }),
  run: async ({ name }, { logger }) => {
    logger.info('greeting', { name })
    return { message: `Hello, ${name}!` }
  }
})

Because the schema is Zod, one declaration drives MCP tool definitions, OpenAPI specs, tRPC procedure types, and CLI option parsing - validation and types for free, no second source of truth.

2. Adapter - the door to a transport

An Adapter translates actions into one specific transport. It takes config, receives your actions at start(), and owns all the protocol-specific work - registering MCP tools in PascalCase, building Fastify routes, mapping Zod types to CLI flags. Each adapter is a separate package, so you only install the transports you actually ship. See all seven adapters →

3. Silkweave - the fluent builder

Silkweave is the builder that wires actions to adapters:

silkweave({ name: 'my-server', version: '1.0.0' })
  .adapter(stdio())
  .action(GreetAction)
  .start()

The builder is generic over the actions you add, so typeof server carries every action's name and type forward. That is what lets type-aware adapters like tRPC infer a fully typed router with zero hand-written glue.

Why decouple logic from transport?

Agents need services, and services need many doors: an MCP tool for a coding agent, a REST endpoint for a dashboard, a tRPC procedure for your own frontend, a CLI for local scripting. Built the usual way, each door is a fresh re-implementation of the same logic, drifting out of sync over time.

Silkweave collapses that to one definition and N adapters. Validation, types, auth, logging, and context are shared; only the wire format differs. Add a transport later and your existing actions light up on it for free.

Streaming actions

An action can also stream. Declare a chunk schema and make run an async function* that yields chunks. Adapters detect this automatically and switch to per-chunk delivery: MCP progress notifications, SSE or NDJSON over REST, or a tRPC subscription - same action, transport-appropriate streaming. See streaming in the docs.

Keep exploring