Vyriy MCP: calm interface between packages and AI agents

MCP is often presented as something special: a new protocol, a new runtime, a new integration layer for AI tools.

But from the architecture point of view, MCP can be much simpler.

For Vyriy, MCP is just another HTTP boundary.

A request comes in.
A router finds the route.
A handler prepares the response.
A server runs the handler.
Tools are registered explicitly.
Each tool is a small package with a clear contract.

No magic.
No hidden runtime.
No framework lock-in.

Just a calm HTTP service that exposes Vyriy knowledge to AI agents.

Why MCP matters for Vyriy

Vyriy is built around small packages and explicit boundaries.

Each package should be understandable on its own.
Each function should have a clear purpose.
Each runtime layer should be boring enough to debug.

That makes MCP a natural fit.

AI agents do not need access to a whole repository with every internal detail. They need a stable interface:

  • list available packages;
  • search documentation;
  • read package documentation;
  • get install commands;
  • inspect server information;
  • check whether the server is alive.

That is exactly what a Vyriy MCP server can expose.

Instead of asking an AI agent to guess how the ecosystem works, we can give it a small, typed, documented tool surface.

Install the Vyriy packages used by this example

The server example uses a few small Vyriy packages for the HTTP boundary.

The MCP server can return the same install command through get_vyriy_install_command, but the package set is intentionally plain:

yarn add @vyriy/handler @vyriy/router @vyriy/server

Or with npm:

npm install @vyriy/handler @vyriy/router @vyriy/server

Each Vyriy package keeps one part of the server explicit:

  • @vyriy/handler builds the HTTP API wrapper and shared response behavior;
  • @vyriy/router defines the /mcp route and fallback route;
  • @vyriy/server runs the handler on Node HTTP;
  • @vyriy/server/body reads the request body from the same server package.

The protocol layer still needs the official MCP SDK, and the tool contracts in this example use Zod schemas:

yarn add @modelcontextprotocol/sdk zod

The example also imports local tool packages such as @p/ping, @p/search-docs, and @p/get-install-command. Those are workspace packages in the MCP server source, not external packages to install from npm.

If the same project also serves static documentation locally, add @vyriy/static beside the server stack:

yarn add @vyriy/static

That package is not required for the MCP endpoint itself. It is useful when documentation pages and the MCP server are developed together.

MCP as a normal HTTP endpoint

The HTTP server is intentionally simple.

The /mcp route accepts MCP requests over Streamable HTTP. Everything else goes through the regular Vyriy HTTP flow.

import type { IncomingMessage, ServerResponse } from 'node:http';
import type { AddressInfo } from 'node:net';

import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { create } from '@vyriy/handler';
import { createHttpRouter } from '@vyriy/router';
import { httpServer } from '@vyriy/server';
import { getBody } from '@vyriy/server/body';

import { createMcpServer } from './mcp.js';

const HTTP_HEADERS = {
  'access-control-allow-headers': 'content-type, mcp-protocol-version',
  'access-control-allow-methods': 'GET, POST, OPTIONS',
  'access-control-allow-origin': '*',
  'x-content-type-options': 'nosniff',
};

There are no special global assumptions here.

Vyriy still owns the HTTP boundary:

export const createHttpHandler = () => {
  const router = createHttpRouter();

  router.all('/mcp', handleMcpRequest);

  router.fallback((_req, res) => {
    json(res, 404, {
      message: 'Not found.',
    });
  });

  return create.httpApi({
    headers: HTTP_HEADERS,
    healthcheck: {
      body: {
        ok: true,
        name: '@vyriy/mcp',
        transport: 'streamable-http',
      },
    },
  })(router.handle());
};

This is the important part.

MCP is not replacing the application architecture.
It is mounted into it.

The same Vyriy primitives still apply:

  • @vyriy/router defines the route;
  • @vyriy/handler wraps the HTTP API behavior;
  • @vyriy/server starts the Node HTTP server;
  • @vyriy/server/body reads the request body.

MCP becomes a protocol layer inside a normal Vyriy service.

One request, one server instance, one clear lifecycle

For the first version, the MCP server is created per request:

const handleMcpRequest = async (req: IncomingMessage, res: ServerResponse) => {
  if ((req.method ?? 'GET') !== 'POST') {
    jsonRpcError(res, 405, -32000, 'Method not allowed.');
    return;
  }

  const server = createMcpServer();
  const transport = new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });

  try {
    const body = await readJsonBody(req);

    await server.connect(transport);
    await transport.handleRequest(req, res, body);
  } catch (error) {
    console.error(error);

    if (!res.headersSent) {
      jsonRpcError(res, 500, -32603, 'Internal server error.');
    }
  } finally {
    await transport.close();
    await server.close();
  }
};

This keeps the lifecycle explicit.

The request creates the server.
The server connects to the transport.
The transport handles the request.
The transport closes.
The server closes.

For a small documentation-focused MCP server, this is a good trade-off. It is predictable, easy to reason about, and simple to debug.

Later, this can evolve into a shared server or a session-aware transport. But the first version should be boring and correct.

That is the Vyriy way.

Tools as packages

The MCP server itself does not contain much logic.

It only registers tools.

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

import { getInstallCommandTool } from '@p/get-install-command';
import { getPackageDocTool } from '@p/get-package-doc';
import { listPackagesTool } from '@p/list-packages';
import { pingTool } from '@p/ping';
import { searchDocsTool } from '@p/search-docs';
import { serverInfoTool } from '@p/server-info';

This is where the package-based structure becomes useful.

Each tool can live as a small package with its own contract:

  • @p/ping;
  • @p/server-info;
  • @p/list-packages;
  • @p/search-docs;
  • @p/get-package-doc;
  • @p/get-install-command.

The MCP layer does not need to know how documentation search works.
It does not need to know how package metadata is stored.
It does not need to know how install commands are generated.

It only knows that a tool has a name, description, input schema, and handler.

const registerTool = <Input extends z.ZodRawShape>(server: McpServer, tool: ToolDefinition<Input>) => {
  const callback = ((args: unknown) => tool.handler(args as z.infer<z.ZodObject<Input>>)) as ToolCallback<Input>;

  server.registerTool(
    tool.name,
    {
      description: tool.description,
      inputSchema: tool.inputSchema,
    },
    callback,
  );
};

This keeps the MCP server small.

The actual behavior stays close to the package that owns it.

Explicit registration instead of discovery magic

The server registers every tool manually:

export const createMcpServer = () => {
  const server = new McpServer({
    name: '@vyriy/mcp',
    version: '0.0.0',
  });

  registerTool(server, pingTool);
  registerTool(server, serverInfoTool);
  registerTool(server, listPackagesTool);
  registerTool(server, searchDocsTool);
  registerTool(server, getPackageDocTool);
  registerTool(server, getInstallCommandTool);

  return server;
};

This is intentional.

Automatic discovery can be useful later. But explicit registration is better for the first version because it makes the public AI-facing interface obvious.

When an AI agent connects to this MCP server, we know exactly what it can call.

No hidden tools.
No accidental exports.
No surprising behavior.

The tool list becomes part of the architecture contract.

What the first Vyriy MCP server can do

The first practical goal is simple: make Vyriy easier for AI agents to understand.

An agent should be able to ask:

  • What packages exist?
  • Which package should I install for this task?
  • How do I install it?
  • What does this package do?
  • Which docs mention this concept?
  • Is the MCP server alive?

That is enough to make AI-assisted development more grounded.

Instead of guessing from package names or searching through a repository manually, the agent gets a curated interface to the ecosystem.

For example:

searchDocs("static server")
getPackageDoc("@vyriy/static")
getInstallCommand("@vyriy/static")

This is not only useful for external AI tools. It is also useful for the future Vyriy CLI.

A command like this becomes possible:

vyriy ask "how do I add a static server with SPA fallback?"

The CLI could call the same MCP tools internally or expose them to a local model.

MCP as documentation infrastructure

For Vyriy, MCP is not only an AI integration.

It is documentation infrastructure.

The same package documentation that powers the website can also power the MCP tools. The same package metadata that appears in /docs/ can be used by an AI agent. The same install examples can be returned as structured tool output.

This gives the documentation a second life.

Documentation is no longer only something humans read in a browser.
It becomes something tools can query.
It becomes something agents can use.
It becomes part of the development flow.

That matches the broader Vyriy idea:

Architecture should be readable.
Documentation should be executable enough to guide tools.
CI should be explicit enough for agents to trust.
Deployment should be boring enough to repeat.

MCP fits into that story naturally.

Why package boundaries matter even more with AI

When humans use a library, unclear boundaries are annoying.

When AI agents use a library, unclear boundaries become dangerous.

An agent can easily make wrong assumptions if packages are too large, responsibilities are mixed, or documentation is scattered.

That is why the Vyriy package model matters.

Small packages are easier to describe.
Typed tools are easier to call.
Explicit docs are easier to search.
Stable install commands are easier to recommend.

The MCP server becomes better because the ecosystem behind it is already structured.

Good MCP is not only about the protocol.
It is about the shape of the system behind the protocol.

A calm interface for AI agents

The first version of @vyriy/mcp is intentionally small.

It does not try to automate everything.
It does not try to become a full AI framework.
It does not try to hide the architecture behind prompts.

It gives agents a calm interface:

  • a single HTTP endpoint;
  • a clear server lifecycle;
  • a small set of tools;
  • typed input schemas;
  • package-based implementation;
  • documentation-first behavior.

That is enough.

Because the goal is not to make AI magical.

The goal is to make AI useful inside a system that remains understandable.

Final thought

MCP gives AI agents a way to interact with tools.

Vyriy gives those tools structure.

Together, they create a simple idea:

AI agents should not guess your architecture.
They should use the same explicit contracts as everyone else.

That is why @vyriy/mcp is not just an integration package.

It is another step toward calm architecture for humans, tools, and AI agents.