Build MCP servers without reading the spec.

CLQ is a TypeScript framework that turns defineTool + server.start() into a fully wired MCP server — protocol, validation, transport, and error formatting handled entirely by the framework.

import { createServer, defineTool } from "@clq-sh/core"
import { z } from "zod"

const getWeather = defineTool({
  name: "get_weather",
  description: "Get current weather for a city.",
  input: z.object({ city: z.string() }),
  handler: async ({ input }) => ({ temperature: 22, condition: "sunny" }),
})

createServer({ name: "my-server", version: "1.0.0" }).tool(getWeather).start()

That's a working MCP server. It handles the JSON-RPC wire protocol, Zod input validation, structured error responses, and stdio transport. You write the tool logic.


Install

$ npm install -g @clq-sh/cli
$ clq init my-server
Scaffolded my-server
$ cd my-server && pnpm install && pnpm build
$ clq inspect
Inspector running at http://127.0.0.1:7317/?token=…

Opens a browser UI where you can call your tools, inspect responses, and see call logs — all before connecting to any AI client.


What is MCP?

MCP (Model Context Protocol) is the standard wire protocol that lets AI assistants like Claude call external tools — APIs, databases, scripts, anything that can run code. It defines how an AI client discovers available tools, sends inputs, and receives structured results. Learn more →

Why does this matter as a developer? If you want Claude (or any MCP-compatible agent) to call your code, your code needs to speak MCP. That means implementing JSON-RPC over stdio, declaring tool schemas in a specific format, handling the protocol handshake, and formatting errors correctly. CLQ does all of that so you don't have to.


Why CLQ?

Why not the official MCP TypeScript SDK?

The official SDK is the right choice if you need full control over every protocol detail or you're building something non-standard. It's low-level by design. With it you manually wire the stdio transport, write your own input validation, format your own error responses, and handle the protocol handshake yourself. CLQ wraps all of that with Zod schemas, structured CLQErrors, and a dev CLI — so you write tool logic instead of protocol glue. If you outgrow CLQ, the underlying MCP SDK is still there.

Why not mcp-use or similar orchestration tools?

mcp-use and similar projects are about connecting AI agents to MCP servers — the client side. They help you build an agent that calls existing tools. CLQ is about building the MCP server itself — the server side. These are different problems. If you're orchestrating an agent that calls tools someone else built, look at mcp-use. If you're writing the tools themselves, that's what CLQ is for. The two can coexist: build your server with CLQ, then connect to it with whatever agent orchestration layer you prefer.


Core API

defineTool

Defines a named tool with Zod schemas for input and output, and an async handler. Both boundaries are validated: input is checked before the handler runs; output is checked before the result leaves the framework. A schema mismatch throws a structured CLQError — never a raw crash.

const getWeather = defineTool({
  name: "get_weather",
  description: "Get current weather for a city.",  // required — agents read this
  input: z.object({ city: z.string().describe("City name") }),
  output: z.object({ temperature: z.number(), condition: z.string() }),
  handler: async ({ input }) => {
    const data = await fetchWeatherApi(input.city)
    return { temperature: data.temp_c, condition: data.description }
  },
})

The output schema is optional but recommended: it lets AI clients trust tool responses without re-validating them, which reduces token usage and tightens agent feedback loops. The description is mandatory — AI agents read it to decide when to call the tool, so a missing or vague description causes agents to call tools incorrectly or not at all. CLQ throws immediately if it's empty.

createServer

A chainable builder that collects tools (and optionally middleware), then starts the MCP stdio transport. Duplicate tool names throw at registration time, not at runtime.

const server = createServer({ name: "my-server", version: "1.0.0" })
server.tool(getWeather).tool(listCities)
server.start()

defineConfig

Declares required environment variables with types, descriptions, and a secret flag. Missing or mistyped vars throw at startup — never silently mid-request. The secret flag ensures that if the value leaks into an error message, only its shape is revealed, not its content.

// clq.config.ts
import { defineConfig } from "@clq-sh/core"

export default defineConfig({
  name: "my-server",
  version: "1.0.0",
  env: {
    GITHUB_TOKEN: {
      type: "string",
      description: "GitHub personal access token",
      secret: true,
    },
    MAX_RESULTS: {
      type: "number",
      description: "Maximum results to return per call",
      default: 10,
    },
  },
})

Connect to Claude Desktop

After pnpm build, add this to claude_desktop_config.json:

{
  "mcpServers": {
    "my-server": {
      "command": "node",
      "args": ["<absolute-path>/my-server/dist/index.js"]
    }
  }
}

Restart Claude Desktop after editing the config. Your tools appear in Claude's tool list immediately.


Security & Quality

CLQ underwent a real security audit before the v0.2.0 release. Two confirmed vulnerabilities were found and fixed: a credential leak in clq doctor and a redaction bypass in clq inspect. A subsequent fix attempt introduced a regex regression that was caught and corrected. The full disclosed trail is on the Security page. The suite passes 211 tests across 18 test files.