Quick Start โ
lynq is a framework for building MCP servers with middleware and session-scoped tool visibility.
Install โ
pnpm add @lynq/lynq zodnpm install @lynq/lynq zodyarn add @lynq/lynq zodbun add @lynq/lynq zodMinimal Server โ
// server.ts
import { createMCPServer } from "@lynq/lynq";
import { z } from "zod";
const server = createMCPServer({ name: "my-server", version: "1.0.0" });
server.tool(
"greet",
{
description: "Say hello",
input: z.object({ name: z.string() }),
},
async (args, c) => c.text(`Hello, ${args.name}!`),
);
await server.stdio();Run it:
npx tsx server.tsThat's it. One tool, stdio transport, zero config.
Connect to Claude Code โ
Add to your Claude Code config (claude_code_config.json):
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["tsx", "server.ts"]
}
}
}Restart Claude Code. The greet tool appears in the tool list.
Under the hood
server.stdio() creates an StdioServerTransport from the MCP SDK and connects it to the internal server. The client spawns your process, then communicates over stdin/stdout using JSON-RPC. All MCP protocol negotiation (capabilities, initialization) is handled by the SDK.
Add Auth-Protected Tools โ
lynq's core feature is session-scoped tool visibility. Tools guarded by middleware are hidden until the session is authorized. Here's an example using the built-in guard() middleware:
import { createMCPServer } from "@lynq/lynq";
import { guard } from "@lynq/lynq/guard";
import { z } from "zod";
const server = createMCPServer({ name: "my-server", version: "1.0.0" });
// Always visible -- the entry point
server.tool(
"login",
{
description: "Login with credentials",
input: z.object({
username: z.string(),
password: z.string(),
}),
},
async (args, c) => {
if (args.username === "admin" && args.password === "1234") {
c.session.set("user", { name: args.username });
c.session.authorize("guard");
return c.text(`Welcome, ${args.username}.`);
}
return c.error("Invalid credentials.");
},
);
// Hidden until c.session.authorize("guard") is called
server.tool(
"get_weather",
guard(),
{
description: "Get current weather for a city",
input: z.object({ city: z.string() }),
},
async (args, c) => c.text(`${args.city}: 22C, Sunny`),
);
await server.stdio();
guard()is a middleware that demonstrates the visibility pattern. For production use cases, write your own middleware tailored to your auth system -- see Custom Middleware.
Verify Visibility โ
Before login, the client sees:
| Tool | Visible |
|---|---|
login | Yes |
get_weather | No |
After calling login with valid credentials:
| Tool | Visible |
|---|---|
login | Yes |
get_weather | Yes |
The client receives the notification automatically. No manual sendToolListChanged call needed.
Next Steps โ
- Middleware -- understand the middleware model
- Session & Visibility -- lynq's core concept
- Hono Adapter -- deploy over HTTP