@happyvertical/smrt-app-mcp

App-runtime MCP server scaffolding — expose your deployed SMRT app's Tier 1 runtime MCP surface (live CRUD / list / AI tools) over HTTP, then drive it from an MCP client.

v0.29.34MCPTier 1 RuntimeApp Server

Overview

@happyvertical/smrt-app-mcp wraps the Tier 1 (Runtime) MCP surface that smrt-core generates from your @smrt() classes and exposes it over HTTP from your deployed application. Where the Tier 1 tools themselves (one <class>_list / <class>_get / <class>_create / ... per object) come from MCPGenerator, this package decides which of them an app publishes, who may call them, and what server-trusted fields get injected on the way through.

It provides two things:

  • CorecreateMcpAppServer(...) returns a framework-agnostic { listTools, callTool } server bound to your database and an allow-list of class names.
  • SvelteKit adapters (./sveltekit) — mountMcpToolsRoute / mountMcpCallRoute turn that server into the GET/POST handlers a SvelteKit +server.ts expects. The core is deliberately transport-neutral, so additional adapters (standalone Node HTTP, serverless, Express/Hono) can be added as sibling subpaths without touching it.

Talk to your app from an MCP client

Once the two routes below are live, your deployed app speaks MCP over HTTP at /api/mcp/tools and /api/mcp/call. Point any MCP client that can reach those endpoints at them — for example a desktop assistant via a stdio-to-HTTP bridge — and it can list and invoke your app's published tools (subject to the same auth your routes enforce).

Installation

bash
pnpm add @happyvertical/smrt-app-mcp

Define the server

createMcpAppServer binds the generator to your database, an allow-list of class names, a public-tool policy, and optional per-tool workflow assertions:

src/lib/server/mcp.ts
typescript
// src/lib/server/mcp.ts
import { createMcpAppServer, McpAccessError } from '@happyvertical/smrt-app-mcp';
import { adminResources } from '$lib/admin/resources';
import { getDbConfig } from './db';

export const mcpServer = createMcpAppServer({
  // SMRT context bag (db, etc.), resolved lazily per call.
  smrtOptions: () => ({ db: getDbConfig() }),
  serverInfo: { name: 'my-app', version: '0.1.0' },
  // Only publish a subset of your @smrt() classes, not everything.
  allowedClassNames: adminResources.map((r) => r.className),
  // Read-only tools unauthenticated callers may use (default: none).
  publicToolPatterns: () =>
    (process.env.MY_APP_PUBLIC_MCP_TOOLS ?? '')
      .split(',')
      .map((s) => s.trim())
      .filter(Boolean),
  // Per-tool guards. Throw McpAccessError to reject; mutate args to
  // inject server-trusted fields.
  workflowAssertions: {
    application_update: (args, user) => {
      if (!user?.id) throw new McpAccessError(401, 'sign in first');
      args.approvedByUserId = user.id;
    },
  },
});

Options

OptionTypeRequiredDescription
smrtOptions() => Record<string, unknown>YesThunk returning the SMRT context bag (db, etc.) passed to the generator per call. A function so env vars resolve lazily at call time.
serverInfo{ name, version, description? }YesServer identity surfaced in the MCP protocol.
allowedClassNamesreadonly string[]YesSMRT class names the app publishes. Tools whose name does not start with one of these classes (lowercased + _) are filtered out, even if SMRT generated them.
publicToolPatterns() => readonly string[]NoThunk of glob-ish patterns (* wildcards) for read-only tools anonymous callers may use. Defaults to empty — everything requires auth.
workflowAssertionsRecord<string, McpWorkflowAssertion>NoPer-tool guards keyed by tool name. Each runs after tool resolution and before the call; throw McpAccessError to reject, or mutate args to inject trusted fields.

Mount the SvelteKit routes

Two thin route files turn the server into HTTP endpoints:

src/routes/api/mcp/tools/+server.ts
typescript
// src/routes/api/mcp/tools/+server.ts
import { mountMcpToolsRoute } from '@happyvertical/smrt-app-mcp/sveltekit';
import { mcpServer } from '$lib/server/mcp';

export const GET = mountMcpToolsRoute(mcpServer);
src/routes/api/mcp/call/+server.ts
typescript
// src/routes/api/mcp/call/+server.ts
import { mountMcpCallRoute } from '@happyvertical/smrt-app-mcp/sveltekit';
import { mcpServer } from '$lib/server/mcp';

export const POST = mountMcpCallRoute(mcpServer);

By default both adapters read the authenticated user from event.locals.user; pass resolveUser / resolveAuthenticated in the mount options to source it elsewhere.

typescript
// GET /api/mcp/tools        -> { tools: MCPTool[] }
// POST /api/mcp/call        body: { name, arguments }
//
// Authenticated callers see every allow-listed tool; anonymous callers
// see only read-only tools matching publicToolPatterns. A call to a
// non-public tool without a user throws McpAccessError(401).
fetch('/api/mcp/call', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    name: 'application_list',
    arguments: { limit: 20 },
  }),
});

Access model

The server enforces a three-layer policy on every request:

  • Allow-list — only tools whose name starts with an allowedClassNames prefix are ever returned or callable, so decorating a class with @smrt() does not automatically expose it through this server.
  • Public-tool policy — unauthenticated callers see only read-only tools (names ending in _list / _get) that also match a publicToolPatterns entry. A call to any non-public tool without a user throws McpAccessError(401); an unknown tool throws McpAccessError(404).
  • Workflow assertions — per-tool hooks run last and can reject the call or clamp arguments (e.g. force approvedByUserId to the authenticated user's id), keeping that policy in your app rather than in the framework.

Exports

ExportFromDescription
createMcpAppServer.Build the framework-agnostic server core.
McpAccessError.Error carrying an HTTP status; thrown to reject a call.
matchesToolPattern, isReadOnlyToolName, isPublicToolName, isAllowedCoreTool, classNamePrefixes.Tool-name policy helpers (also used internally) for custom transports.
mountMcpToolsRoute, mountMcpCallRoute./sveltekitSvelteKit GET/POST adapters for an McpAppServer.

Types: CreateMcpAppServerOptions, McpAppServer, McpAppUser, McpWorkflowAssertion, CallToolInput, ListToolsInput, and the two thunk aliases are all exported from the root entry.

Related Modules