The TypeScript-first way to build production-grade MCP servers.
Made with ❤️ for TypeScript developers
FrontMCP is a TypeScript-first framework for the Model Context Protocol (MCP). You describe servers, apps, tools, resources, and prompts with decorators; FrontMCP handles protocol, transport, DI, session/auth, and execution flow.
// src/main.ts
import {FrontMcp, LogLevel} from '@frontmcp/sdk';
import HelloApp from './hello.app';
@FrontMcp({
info: {name: 'Demo 🚀', version: '0.1.0'},
apps: [HelloApp],
http: {port: 3001},
logging: {level: LogLevel.Info},
})
export default class Server {
}- Why FrontMCP?
- Installation
- Quickstart
- Core Concepts
- Authentication
- Sessions & Transport
- Logging Transports
- Deployment
- Version Alignment
- Contributing
- License
- TypeScript-native DX — decorators, Zod validation, strong typing end-to-end
- Spec-aligned transports — Streamable HTTP (GET/POST), streaming, sessions
- Scoped invoker + DI — secure, composable execution with hooks
- Adapters & Plugins — generate tools from OpenAPI; add cross-cutting behavior
- Auth — remote OAuth (external IdP) or built-in local OAuth
- Logging — pluggable log transports (console, JSONL, HTTP batch, …)
Choose your package manager:
# npm
npm i -E @frontmcp/sdk @frontmcp/core zod reflect-metadata
npm i -D typescript tsx @types/node rimraf @modelcontextprotocol/inspector
# yarn
yarn add -E @frontmcp/sdk @frontmcp/core zod reflect-metadata
yarn add -D typescript tsx @types/node rimraf @modelcontextprotocol/inspector
# pnpm
pnpm add -E @frontmcp/sdk @frontmcp/core zod reflect-metadata
pnpm add -D typescript tsx @types/node rimraf @modelcontextprotocol/inspectorRequires Node 20+.
src/
main.ts
hello.app.ts
tools/
greet.tool.ts
src/main.ts
import 'reflect-metadata';
import {FrontMcp, LogLevel} from '@frontmcp/sdk';
import HelloApp from './hello.app';
@FrontMcp({
info: {name: 'Hello MCP', version: '0.1.0'},
apps: [HelloApp],
http: {port: Number(process.env.PORT) || 3001},
logging: {level: LogLevel.Info},
})
export default class Server {
}src/hello.app.ts
import {App} from '@frontmcp/sdk';
import GreetTool from './tools/greet.tool';
@App({id: 'hello', name: 'Hello', tools: [GreetTool]})
export default class HelloApp {
}Function tool
import {tool} from '@frontmcp/sdk';
import {z} from 'zod';
export default tool({
name: 'greet',
description: 'Greets a user by name',
inputSchema: z.object({name: z.string()}),
})(({name}) => `Hello, ${name}!`);Class tool
import {Tool} from '@frontmcp/sdk';
import {z} from 'zod';
@Tool({
name: 'add',
description: 'Add two numbers',
inputSchema: z.object({a: z.number(), b: z.number()}),
})
export default class AddTool {
execute({a, b}: { a: number; b: number }) {
return a + b;
}
}tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"lib": [
"ES2020"
],
"rootDir": "src",
"outDir": "dist",
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"**/*.test.ts",
"**/__tests__/**"
]
}tsconfig.build.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"declaration": true,
"sourceMap": false
},
"exclude": [
"**/*.test.ts",
"**/__tests__/**",
"src/**/*.dev.ts"
]
}package.json (scripts)
{
"scripts": {
"dev": "tsx watch src/main.ts",
"start:dev": "tsx src/main.ts",
"build": "tsc -p tsconfig.build.json",
"start": "node dist/main.js",
"typecheck": "tsc --noEmit -p tsconfig.json",
"clean": "rimraf dist",
"inspect:dev": "npx @modelcontextprotocol/inspector tsx src/main.ts",
"inspect:dist": "npx @modelcontextprotocol/inspector node dist/main.js"
}
}Always import
reflect-metadatafirst in your entry to enable decorator metadata.
Debug your server with a browser UI:
# Dev (runs TS)
npm run inspect:dev
# Dist (runs built JS)
npm run inspect:distThe decorated entry (@FrontMcp) defines server info, apps, http, logging, session, optional **auth
**, and shared providers.
Use @App to group tools, resources, prompts, providers, adapters, and plugins. With
splitByApp: true, each app has its own scope/base path and (optionally) its own auth.
Active actions with input/output schemas. Use class tools (@Tool) or function tools (tool({...})(handler)).
Expose read-only data by URI. Define with @Resource or resource(...) (see docs).
Reusable prompt templates (@Prompt / prompt(...)) supplying arguments for LLM interactions.
Dependency-injected singletons for config/DB/Redis/KMS/etc., with scopes: GLOBAL, SESSION, REQUEST.
Generate tools/resources/prompts from external definitions (e.g., OpenAPI).
Cross-cutting behavior (caching, tracing, policy). Plugins can contribute providers/adapters/tools/resources/prompts.
You can configure auth on the server (multi-app shared) or per app (isolated scopes).
auth: {
type: 'remote',
name: 'frontegg',
baseUrl: 'https://idp.example.com',
dcrEnabled ? : boolean,
clientId ? : string | ((info: { clientId: string }) => string),
mode ? : 'orchestrated' | 'transparent',
allowAnonymous ? : boolean,
consent ? : boolean,
scopes ? : string[],
grantTypes ? : ('authorization_code' | 'refresh_token')[],
authEndpoint ? : string,
tokenEndpoint ? : string,
registrationEndpoint ? : string,
userInfoEndpoint ? : string,
jwks ? : JSONWebKeySet,
jwksUri ? : string
}{
auth: {
type: 'local',
id: 'local',
name: 'Local Auth',
scopes?: string[],
grantTypes?: ('authorization_code' | 'refresh_token')[],
allowAnonymous?: boolean, // default true
consent?: boolean,
jwks?: JSONWebKeySet,
signKey?: JWK | Uint8Array
}With
splitByApp: true, define auth per@App(server-levelauthis disallowed).
session: {
sessionMode ? : 'stateful' | 'stateless' | ((issuer) =>
...), // default 'stateless'
transportIdMode ? : 'uuid' | 'jwt' | ((issuer) =>
...), // default 'uuid'
}- Stateful: server-side store for tokens; enables refresh; recommended for short-lived upstream tokens.
- Stateless: tokens embedded in JWT; simpler but no silent refresh.
- Transport IDs:
uuid(per node) orjwt(signed; distributed setups).
Add custom log sinks via @LogTransport:
import {LogTransport, LogTransportInterface, LogRecord} from '@frontmcp/sdk';
@LogTransport({name: 'StructuredJson', description: 'JSONL to stdout'})
export class StructuredJsonTransport extends LogTransportInterface {
log(rec: LogRecord): void {
try {
process.stdout.write(JSON.stringify({
ts: rec.timestamp.toISOString(),
level: rec.levelName,
msg: String(rec.message),
prefix: rec.prefix || undefined,
args: (rec.args || []).map(String),
}) + '\n');
} catch {
}
}
}Register:
logging: {
level: LogLevel.Info,
enableConsole: false,
transports : [StructuredJsonTransport],
}# npm
npm run dev
# yarn
yarn dev
# pnpm
pnpm dev- HTTP default:
http.port(e.g., 3001) http.entryPathdefaults to''(set to/mcpif you prefer)
If versions drift, the runtime may throw a "version mismatch" error at boot. Keep @frontmcp/sdk and @frontmcp/core
on the same version across your workspace.
PRs welcome! Please:
- Keep changes focused and tested
- Run
typecheck,build, and try MCP Inspector locally - Align
@frontmcp/*versions in examples
See LICENSE.