Define once.
Deploy everywhere.
Build MCP servers, REST APIs, and CLI tools from a single set of actions. No framework lock-in. No boilerplate. Just composition.
pnpm add @silkweave/core The Problem
Agents need services. Services need many doors.
The agentic landscape demands that services be reachable via MCP, REST, CLI, and more. Building each transport from scratch is unsustainable.
Fragmented Transports
MCP stdio, HTTP, REST, CLI - each requires a different server setup, different libraries, different patterns.
Boilerplate Overload
Every new endpoint means repeating validation, error handling, and serialization. Your business logic drowns in glue code.
Framework Lock-in
Pick Express? Now you're stuck. Pick Fastify? Same story. Your actions are trapped inside your transport choice.
How It Works
One action. Every transport.
Define your business logic once as an Action. Pick an Adapter. Silkweave wires them together.
import { createAction } from '@silkweave/core'
import z from 'zod'
export const GreetAction = createAction({
name: 'greet',
description: 'Greet a user by name',
input: z.object({
name: z.string().describe('Name of the person to greet')
}),
run: async ({ name }) => {
return { message: `Hello, ${name}!` }
}
}) Adapters
Same action. Seven adapters.
Swap one line to change how your action is served. The business logic stays identical.
import { silkweave } from '@silkweave/core'
import { stdio } from '@silkweave/mcp'
import { GreetAction } from './actions/greet.js'
await silkweave({ name: 'my-server', version: '1.0.0' })
.adapter(stdio())
.action(GreetAction)
.start() import { silkweave } from '@silkweave/core'
import { http } from '@silkweave/mcp'
import { GreetAction } from './actions/greet.js'
await silkweave({ name: 'my-server', version: '1.0.0' })
.adapter(http({ port: 8080 }))
.action(GreetAction)
.start() import { silkweave } from '@silkweave/core'
import { fastify } from '@silkweave/fastify'
import { GreetAction } from './actions/greet.js'
await silkweave({ name: 'my-server', version: '1.0.0' })
.adapter(fastify({ port: 8080 }))
.action(GreetAction)
.start() import { silkweave } from '@silkweave/core'
import { trpc, type InferTrpcRouter } from '@silkweave/trpc'
import { GreetAction } from './actions/greet.js'
const server = silkweave({ name: 'my-api', version: '1.0.0' })
.adapter(trpc({ port: 8080 }))
.action(GreetAction)
export type AppRouter = InferTrpcRouter<typeof server>
await server.start() import { silkweave } from '@silkweave/core'
import { cli } from '@silkweave/cli'
import { GreetAction } from './actions/greet.js'
await silkweave({ name: 'my-tool', version: '1.0.0' })
.adapter(cli())
.action(GreetAction)
.start() import { silkweave } from '@silkweave/core'
import { vercel } from '@silkweave/vercel'
import { GreetAction } from './actions/greet.js'
const { adapter, handler } = vercel()
await silkweave({ name: 'my-server', version: '1.0.0' })
.adapter(adapter)
.action(GreetAction)
.start()
export default { fetch: handler } import { silkweave } from '@silkweave/core'
import { typegen } from '@silkweave/typegen'
import { GreetAction } from './actions/greet.js'
await silkweave({ name: 'my-server', version: '1.0.0' })
.adapter(typegen({ path: 'types/actions.d.ts' }))
.action(GreetAction)
.start()
// Generates: export interface GreetInput { name: string } Integrations
Drop into your framework of choice.
Adapters are one transport each. Integrations compose them with the conventions of a host framework — decorators, DI, file-based routing — so Silkweave feels native.
Standalone
StableCompose the fluent builder yourself. Pick whichever adapters fit; run it as a Node process, container, or serverless function.
- Chain unlimited adapters; one action served everywhere
- Per-action
isEnabledfiltering across transports - Drop into Astro / Vercel / Workers via fetch handlers
NestJS
StableDefine actions as method decorators on Nest providers. Mounts as REST, tRPC, and MCP on your running Nest server, with native guards and DI.
@Action/@Actionsdecorators,DiscoveryService-driven@UseGuards()fires on every HTTP-backed transport- Per-action
transportsallowlist (REST, tRPC, MCP)
Next.js
Coming soonApp Router route helpers that mount actions as MCP and tRPC endpoints, sharing the Next request context. Today: use trpcFetch() and vercel() in route handlers.
- File-based MCP routes (planned)
- App-Router-shaped tRPC + MCP helpers (planned)
- Use
trpcFetch/verceltoday
Features
Everything you need. Nothing you don't.
Zod-Powered Schemas
Define inputs with Zod. Get validation, type inference, and auto-generated docs for free.
Learn more →Built-in OAuth 2.1
Full OAuth proxy with PKCE, refresh tokens, and dynamic client registration out of the box.
Learn more →Auto-Generated Swagger
The Fastify adapter produces OpenAPI specs and interactive Swagger UI automatically.
Learn more →Session & Context
Fluent .set() API for per-request context. Actions receive a typed context bag - no globals.
Learn more →Vercel Serverless
Deploy stateless MCP servers to Vercel, Cloudflare Workers, or Bun. Web Standard APIs, zero lock-in.
Learn more →CLI with Prompts
Expose the same actions as a CLI tool with interactive prompts, powered by Commander + Clack.
Learn more →Type Generation
Auto-generate .d.ts interfaces from your Zod schemas at build time. Zero runtime cost, full type safety.
Learn more →Quick Start
Up and running in 3 steps.
Install
pnpm add @silkweave/core @silkweave/mcp zod Define an action
import { createAction } from '@silkweave/core'
import z from 'zod'
export const GreetAction = createAction({
name: 'greet',
description: 'Greet a user by name',
input: z.object({
name: z.string().describe('Name to greet')
}),
run: async ({ name }) => {
return { message: `Hello, ${name}!` }
}
}) Pick an adapter and start
import { silkweave } from '@silkweave/core'
import { stdio } from '@silkweave/mcp'
import { GreetAction } from './actions/greet.js'
await silkweave({ name: 'my-server', version: '1.0.0' })
.adapter(stdio())
.action(GreetAction)
.start()