Posts

Creating and Publishing MCP Kernel

Why I built MCP Kernel, how it composes TypeScript MCP servers, and the verification and release work behind publishing the package.

September 18, 2026 8 min read 1679 words

In this article

An MCP server needs more than a collection of tool handlers. Once you add service clients, shared context, authorization rules, and cleanup, you have an application to maintain.

I created and published MCP Kernel to handle that repeated application code. It gives TypeScript MCP servers reusable plugins, typed context, shared request policies, and lifecycle management so you can spend more time building the tools your application needs.

You can find it on GitHub and npm.

The kernel handles tool registration, request policies, startup, and shutdown. Your application supplies the API clients and the code that does the actual work.

Before publishing, the verification suite checks that an MCP client can call the tools and that another TypeScript project can import and use the built package.

Give the Repeated Work a Home

Consider a server that exposes tools for reading project records and updating their status. Each handler has its own business logic, but several questions repeat:

  • Where does the service client come from?
  • Who checks the caller’s scopes?
  • What happens when a request times out?
  • Who releases dependencies when startup fails halfway through?
  • How do we test what an MCP client actually receives?

Copy those answers into every handler and you have several implementations of the same application rules. Eventually, one gets fixed and another gets forgotten. Copy-paste has excellent distribution and questionable maintenance support.

MCP Kernel gives that shared behavior a defined home. Features describe capabilities. Plugins group them. The application manages their lifecycle and invocation. The official MCP SDK handles protocol framing and transport contracts. The architecture guide spells out those responsibilities.

MCP client
  <-> official SDK transport
  <-> kernel application
       -> middleware and tool policies
       -> feature handler
       -> your service client

Your application supplies credentials, service clients, and business behavior through its context.

What MCP Kernel Adds to the Official SDK

MCP Kernel reduces the application code you need to write around your handlers. You get a consistent way to group features, share service clients, apply request policies, and manage startup and cleanup across your MCP servers.

It builds on the official TypeScript SDK’s McpServer, schema validation, tools, resources, prompts, clients, and transports. The SDK handles the protocol; the kernel connects those capabilities to reusable application behavior. The SDK documentation describes the underlying APIs.

TaskUsing the SDK directlyWhat MCP Kernel adds
Register tools, resources, and promptsRegister them with McpServer, using the SDK’s schema support.Define features separately, group them into plugins, and validate their composition before startup.
Share service clientsPass dependencies into your registration functions or capture them in handlers.Create a typed application context once and supply it to handlers through the same interface.
Manage application resourcesCoordinate your service initialization and disposal with the SDK connection lifecycle.Ordered plugin setup, reverse-order disposal, and cleanup of successfully initialized resources when startup fails.
Apply tool policiesImplement or integrate the scope checks, local cache, rate limits, and invalidation your application needs.Configure those policies on tool definitions and execute them through a shared request path.
Test through MCPConnect the SDK client and server using its transports and manage the test lifecycle.connectTestClient starts a fresh application on linked in-memory SDK transports and provides cleanup.
Inspect the configured feature setMaintain a description or query a connected server’s discovery APIs.Generate app.manifest() from the feature definitions without starting a transport.

For a new tool, that means you can supply its schema, handler, and policies using the same structure as the rest of the application. Shared logging and lifecycle behavior are already wired in. The architecture guide explains how the kernel composes those pieces.

Consider two tools: one reads project records, and another updates them. You want reads cached for 30 seconds, a successful update to invalidate those cached reads, and every read to require the caller’s records:read scope.

With a direct SDK implementation, you connect those behaviors in application code: check access, look up the cache, call the service when necessary, store the result, and remove affected entries after a write. MCP Kernel provides that execution path, so you configure the behavior alongside the tool.

In MCP Kernel, the read tool can include these fields in its definition:

annotations: { readOnlyHint: true },
policy: {
  requiredScopes: ['records:read'],
  cache: { ttlMs: 30_000, tags: ['records'] },
},

The writer declares policy: { invalidates: ['records'] }. The kernel checks scopes before serving cached data and invalidates tagged results after a successful write. Your handlers can concentrate on reading and updating records. See the published runtime policy contract.

When several tools or servers need those rules, the same implementation handles them. A fix to the shared behavior can be tested in the kernel and delivered to its consumers through a package update.

That is the benefit I wanted from this project: less repeated setup, smaller handlers, and one place to maintain the behavior they share. Using the SDK directly gives you control over your own application structure; MCP Kernel gives you a ready-made structure for these common needs.

A Tool Should Explain Its Contract

Here is a small feature definition using the API documented for the published 0.1.1 release:

import { defineTool, jsonResult } from '@coderrob/mcp-kernel';
import { z } from 'zod';

interface AppContext {
  greeting: string;
}

const greet = defineTool<AppContext>()({
  name: 'greet_reader',
  description: 'Return a personalized greeting.',
  inputSchema: z.object({ name: z.string().min(1) }),
  outputSchema: z.object({ message: z.string() }),
  annotations: { readOnlyHint: true },
  handler: ({ input, context }) =>
    jsonResult({ message: `${context.greeting}, ${input.name}!` }),
});

The schema describes the input and preserves its TypeScript inference. The context makes the dependency explicit. The output schema declares the shape of successful structured output.

Then, in the same module, compose the feature into an application factory:

import { createMcpServer, definePlugin } from '@coderrob/mcp-kernel';

export function createGreetingApp() {
  return createMcpServer<AppContext>({
    identity: { name: 'reader-greetings', version: '1.0.0' },
    plugins: [
      definePlugin({
        name: 'greetings',
        version: '1.0.0',
        features: [greet],
      }),
    ],
    createContext: () => ({ greeting: 'Hello' }),
  });
}

A process entry point creates the application and calls app.start(stdioTransport()). It also owns calling app.stop() during shutdown. Keep operational logs on stderr: stdout is carrying MCP messages, and your helpful debug statement is otherwise an unexpected protocol participant.

The factory is useful because each test can create a fresh application with its own lifecycle. The package’s connectTestClient helper connects an official SDK client through in-memory transports, allowing a test to discover and invoke tools through MCP. The getting-started guide shows the complete startup and test pattern.

Calling a handler directly can verify its logic. Calling it through the protocol also checks the wiring your consumer depends on.

Apply Shared Rules Consistently

Putting policies in the kernel gives tools a common execution order. Required scopes and rate limits are checked before a cached result is returned, and cache hits count toward the rate limit. You do not have to recreate that ordering in each handler.

The application supplies authenticated caller identity and stable principal IDs; the kernel uses them for scope checks and per-principal policy state. Caches and rate limits are local to the application. Handlers receive a cancellation signal for timeouts and shutdown, which they can pass to cancellable work. Resources and prompts can use middleware or handler-level authorization. These details are documented in the runtime guide.

Publishing Means Testing the Package Boundary

MCP Kernel ships ESM, CommonJS, and bundled TypeScript declarations through one supported package root. The release checks exercise those built entry points from a consumer’s perspective, including TypeScript imports and runtime loading.

The repository’s verification workflow covers formatting, linting, source and test types, architecture, circular dependencies, unused dependencies and exports, the build, and coverage. It also checks downstream TypeScript consumption, loads both runtime entry points, and verifies the npm tarball’s file allowlist.

Coverage thresholds are 95% per file across statements, branches, functions, and lines. Alongside that coverage requirement, consumer checks verify the packaged imports and protocol tests exercise client-server interactions. Each check targets a different part of using the library.

This connects to the same concern behind my ESLint Zero-Tolerance rules: turn expectations into checks that can fail before someone else inherits the problem.

The Release Workflow Is Part of the Product

Publishing brought its own practical lesson. The 0.1.1 release notes record a publishing authentication fix and an exact-version tag that superseded an earlier incorrectly tagged release whose validation failed.

The package build and the publishing path have separate failure modes. Both need verification.

The current release process prepares a release PR with the version, changelog, and lockfile changes, then runs verification. After merge, publishing a GitHub release triggers the npm workflow. Its tag includes the version and a seven-character commit suffix, and the workflow checks both against the release contents.

The workflow publishes with provenance after verification. It also has a manual recovery path for a failed publication that needs a newer workflow fix. Those details make the relationship between reviewed code, release metadata, and the package being shipped easier to inspect.

There is a reader-facing consequence, too: a GitHub release and an available npm version are separate facts.

As of September 19, 2026, npm’s latest version is 0.1.1, while GitHub has a 0.2.0 release. For the available npm version, use Node.js 24.15 or newer and the matching peers:

npm install @coderrob/mcp-kernel@0.1.1 @modelcontextprotocol/sdk@^1.30.0 zod@^3.25.0

Those requirements come from the 0.1.1 package manifest. The 0.2.0 documentation moves to separate MCP SDK v2 server and client packages and Zod 4. Match the documentation to the version you install.

What I Want This to Make Easier

I want adding a capability to an MCP server to be a small, understandable change: define its contract, give it the dependencies it needs, place it behind the appropriate rules, and test it through the interface a client will use.

MCP Kernel is available under GPL-3.0-only. If you are building a TypeScript MCP server, take a look at the repository and try composing a few tools into a plugin.

Define the capability, supply its dependencies, configure its policies, and test it through MCP. The kernel handles the shared setup so the code you write can focus on what the tool actually does.

-Rob