diff --git a/examples/server/README.md b/examples/server/README.md index 384e4f2c2..9d58e5371 100644 --- a/examples/server/README.md +++ b/examples/server/README.md @@ -25,19 +25,21 @@ pnpm tsx src/simpleStreamableHttp.ts ## Example index -| Scenario | Description | File | -| ----------------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| Streamable HTTP server (stateful) | Feature-rich server with tools/resources/prompts, logging, tasks, sampling, and optional OAuth. | [`src/simpleStreamableHttp.ts`](src/simpleStreamableHttp.ts) | -| Streamable HTTP server (stateless) | No session tracking; good for simple API-style servers. | [`src/simpleStatelessStreamableHttp.ts`](src/simpleStatelessStreamableHttp.ts) | -| JSON response mode (no SSE) | Streamable HTTP with JSON-only responses and limited notifications. | [`src/jsonResponseStreamableHttp.ts`](src/jsonResponseStreamableHttp.ts) | -| Server notifications over Streamable HTTP | Demonstrates server-initiated notifications via GET+SSE. | [`src/standaloneSseWithGetStreamableHttp.ts`](src/standaloneSseWithGetStreamableHttp.ts) | -| Output schema server | Demonstrates tool output validation with structured output schemas. | [`src/mcpServerOutputSchema.ts`](src/mcpServerOutputSchema.ts) | -| Form elicitation server | Collects **non-sensitive** user input via schema-driven forms. | [`src/elicitationFormExample.ts`](src/elicitationFormExample.ts) | -| URL elicitation server | Secure browser-based flows for **sensitive** input (API keys, OAuth, payments). | [`src/elicitationUrlExample.ts`](src/elicitationUrlExample.ts) | -| Sampling + tasks server | Demonstrates sampling and experimental task-based execution. | [`src/toolWithSampleServer.ts`](src/toolWithSampleServer.ts) | -| Task interactive server | Task-based execution with interactive server→client requests. | [`src/simpleTaskInteractive.ts`](src/simpleTaskInteractive.ts) | -| Hono Streamable HTTP server | Streamable HTTP server built with Hono instead of Express. | [`src/honoWebStandardStreamableHttp.ts`](src/honoWebStandardStreamableHttp.ts) | -| SSE polling demo server | Legacy SSE server intended for polling demos. | [`src/ssePollingExample.ts`](src/ssePollingExample.ts) | +| Scenario | Description | File | +| ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| Streamable HTTP server (stateful) | Feature-rich server with tools/resources/prompts, logging, tasks, sampling, and optional OAuth. | [`src/simpleStreamableHttp.ts`](src/simpleStreamableHttp.ts) | +| Streamable HTTP server (stateless) | No session tracking; good for simple API-style servers. | [`src/simpleStatelessStreamableHttp.ts`](src/simpleStatelessStreamableHttp.ts) | +| JSON response mode (no SSE) | Streamable HTTP with JSON-only responses and limited notifications. | [`src/jsonResponseStreamableHttp.ts`](src/jsonResponseStreamableHttp.ts) | +| Server notifications over Streamable HTTP | Demonstrates server-initiated notifications via GET+SSE. | [`src/standaloneSseWithGetStreamableHttp.ts`](src/standaloneSseWithGetStreamableHttp.ts) | +| Output schema server | Demonstrates tool output validation with structured output schemas. | [`src/mcpServerOutputSchema.ts`](src/mcpServerOutputSchema.ts) | +| Form elicitation server | Collects **non-sensitive** user input via schema-driven forms. | [`src/elicitationFormExample.ts`](src/elicitationFormExample.ts) | +| URL elicitation server | Secure browser-based flows for **sensitive** input (API keys, OAuth, payments). | [`src/elicitationUrlExample.ts`](src/elicitationUrlExample.ts) | +| Sampling + tasks server | Demonstrates sampling and experimental task-based execution. | [`src/toolWithSampleServer.ts`](src/toolWithSampleServer.ts) | +| Task interactive server | Task-based execution with interactive server→client requests. | [`src/simpleTaskInteractive.ts`](src/simpleTaskInteractive.ts) | +| Hono Streamable HTTP server | Streamable HTTP server built with Hono instead of Express. | [`src/honoWebStandardStreamableHttp.ts`](src/honoWebStandardStreamableHttp.ts) | +| SSE polling demo server | Legacy SSE server intended for polling demos. | [`src/ssePollingExample.ts`](src/ssePollingExample.ts) | +| Class-based decorators (legacy) | Class methods decorated with `@McpTool`, `@McpResource`, `@McpResourceTemplate`, `@McpPrompt`. Uses legacy TypeScript decorators (`experimentalDecorators: true`). | [`src/legacyClassDecoratorsExample.ts`](src/legacyClassDecoratorsExample.ts) | +| Class-based decorators (TC39 Stage-3) | Same pattern, built on TC39 Stage-3 decorators and `Symbol.metadata` instead of prototype-attached arrays. Compiles under its own `tsconfig.standard-decorators.json` (`experimentalDecorators: false`). Run with `pnpm tsx --tsconfig=tsconfig.standard-decorators.json src/classDecoratorsExample.ts`. | [`src/classDecoratorsExample.ts`](src/classDecoratorsExample.ts) | ## OAuth demo flags (Streamable HTTP server) diff --git a/examples/server/package.json b/examples/server/package.json index fcff95d9a..a1e5f83aa 100644 --- a/examples/server/package.json +++ b/examples/server/package.json @@ -20,7 +20,7 @@ "mcp" ], "scripts": { - "typecheck": "tsgo -p tsconfig.json --noEmit", + "typecheck": "tsgo -p tsconfig.json --noEmit && tsgo -p tsconfig.standard-decorators.json --noEmit", "build": "tsdown", "build:watch": "tsdown --watch", "prepack": "pnpm run build:esm && pnpm run build:cjs", diff --git a/examples/server/src/classDecoratorsExample.ts b/examples/server/src/classDecoratorsExample.ts new file mode 100644 index 000000000..120063b76 --- /dev/null +++ b/examples/server/src/classDecoratorsExample.ts @@ -0,0 +1,264 @@ +// Run with: +// pnpm tsx --tsconfig=tsconfig.standard-decorators.json src/classDecoratorsExample.ts +// +// TC39 Stage-3 ("standard") decorator version of the class-based MCP +// pattern. Same call sites as `legacyClassDecoratorsExample.ts` — the +// difference is entirely in how each decorator body receives its arguments +// and where metadata is stashed: +// +// - Legacy decorators: `(target, propertyKey, descriptor)`, metadata +// pushed onto `target.constructor.prototype.__mcpTools`. +// - Standard decorators: `(value, context)`, metadata written to the +// `context.metadata` object — which the runtime hangs off the class +// at `Symbol.metadata` after class initialisation. A subclass's +// metadata object inherits from the parent's via the prototype +// chain, so subclasses see base-class entries — but writes must be +// copy-on-write to avoid mutating the parent's arrays in place +// (see `ownArray()` below). +// +// `experimentalDecorators` is a project-wide TypeScript setting, so this +// file compiles under a dedicated `tsconfig.standard-decorators.json` +// that sets it to `false`. The main package tsconfig excludes this file. + +// Stage-3 decorator metadata uses `Symbol.metadata`. Node < 22 does not +// ship it yet, so register it if missing. TypeScript's decorator emit +// writes to `ctor[Symbol.metadata]` during class initialisation and will +// crash without this polyfill on older Node. +((Symbol as { metadata?: symbol }).metadata as symbol | undefined) ??= Symbol.for('Symbol.metadata'); + +import type { + CallToolResult, + GetPromptResult, + Prompt, + PromptCallback, + ReadResourceCallback, + ReadResourceResult, + ReadResourceTemplateCallback, + Resource, + ResourceTemplateType, + ServerContext, + Tool, + ToolCallback, + Variables +} from '@modelcontextprotocol/server'; +import { McpServer, ResourceTemplate, StdioServerTransport } from '@modelcontextprotocol/server'; +import * as z from 'zod/v4'; + +// ─── Decorator config shapes ───────────────────────────────────────────── +// +// Identical to the legacy version — these types are independent of +// decorator mode. See `legacyClassDecoratorsExample.ts` for the full +// derivation commentary. + +type AnyZodObject = z.ZodObject; + +type ToolConfig = { + name: string; + inputSchema?: S; + outputSchema?: AnyZodObject; +} & Omit; + +type PromptConfig = { + name: string; + argsSchema?: S; +} & Omit; + +type ResourceConfig = { name: string; uri: string } & Omit; + +type ResourceTemplateConfig = { name: string; template: ResourceTemplate } & Omit; + +type ToolEntry = { config: ToolConfig; method: ToolCallback }; +type PromptEntry = { config: PromptConfig; method: PromptCallback }; +type ResourceEntry = { config: ResourceConfig; method: ReadResourceCallback }; +type ResourceTemplateEntry = { config: ResourceTemplateConfig; method: ReadResourceTemplateCallback }; + +type McpClassMetadata = { + __mcpTools?: ToolEntry[]; + __mcpResources?: ResourceEntry[]; + __mcpResourceTemplates?: ResourceTemplateEntry[]; + __mcpPrompts?: PromptEntry[]; +}; + +// ─── Decorators (TC39 Stage-3 method decorators) ───────────────────────── +// +// `context.metadata` is a per-class object shared by every decorator on +// the same class. The runtime attaches it to the constructor at +// `Symbol.metadata` once initialisation finishes. For a subclass, the +// runtime creates `Object.create(parent[Symbol.metadata])`, so *reads* +// transparently walk the prototype chain — subclasses see base-class +// entries for free — but *writes* must be own-properties or they silently +// mutate the parent's array. `ownArray()` below enforces copy-on-write: +// on first write in a given class it clones the inherited array, so +// subclasses end up with `[...parentEntries, ...ownEntries]` and the +// parent stays untouched. + +function metaOf(context: ClassMethodDecoratorContext): McpClassMetadata { + return context.metadata as McpClassMetadata; +} + +function ownArray(meta: McpClassMetadata, key: K): NonNullable { + if (!Object.hasOwn(meta, key)) { + meta[key] = [...(meta[key] ?? [])] as McpClassMetadata[K]; + } + return meta[key] as NonNullable; +} + +export function McpTool(config: ToolConfig) { + return (value: ToolCallback, context: ClassMethodDecoratorContext): void => { + ownArray(metaOf(context), '__mcpTools').push({ + config: config as ToolConfig, + method: value as unknown as ToolCallback + }); + }; +} + +export function McpPrompt(config: PromptConfig) { + return (value: PromptCallback, context: ClassMethodDecoratorContext): void => { + ownArray(metaOf(context), '__mcpPrompts').push({ + config: config as PromptConfig, + method: value as unknown as PromptCallback + }); + }; +} + +export function McpResource(config: ResourceConfig) { + return (value: ReadResourceCallback, context: ClassMethodDecoratorContext): void => { + ownArray(metaOf(context), '__mcpResources').push({ config, method: value }); + }; +} + +export function McpResourceTemplate(config: ResourceTemplateConfig) { + return (value: ReadResourceTemplateCallback, context: ClassMethodDecoratorContext): void => { + ownArray(metaOf(context), '__mcpResourceTemplates').push({ config, method: value }); + }; +} + +// ─── Registration helper ───────────────────────────────────────────────── +// +// Pulls the class's metadata object off `Symbol.metadata` and registers +// each decorated method with the given `McpServer`. + +export function registerClass(server: McpServer, instance: object): void { + const ctor = instance.constructor as { [Symbol.metadata]?: McpClassMetadata }; + const meta = ctor[Symbol.metadata]; + if (!meta) return; + + for (const { config, method } of meta.__mcpTools ?? []) { + const { name, ...rest } = config; + server.registerTool(name, rest, method.bind(instance)); + } + for (const { config, method } of meta.__mcpResources ?? []) { + const { name, uri, ...metadata } = config; + server.registerResource(name, uri, metadata, method.bind(instance)); + } + for (const { config, method } of meta.__mcpResourceTemplates ?? []) { + const { name, template, ...metadata } = config; + server.registerResource(name, template, metadata, method.bind(instance)); + } + for (const { config, method } of meta.__mcpPrompts ?? []) { + const { name, ...rest } = config; + server.registerPrompt(name, rest, method.bind(instance)); + } +} + +// ─── Example class ─────────────────────────────────────────────────────── +// +// Call sites are identical to the legacy version — `@McpTool({ ... })` +// and friends look the same regardless of decorator mode. + +class GreetingController { + constructor(private readonly salutation: string) {} + + @McpTool({ + name: 'greet', + description: 'Greet someone by name', + inputSchema: z.object({ + name: z.string().describe('Name to greet') + }) + }) + greet({ name }: { name: string }): CallToolResult { + return { + content: [{ type: 'text', text: `${this.salutation}, ${name}!` }] + }; + } + + @McpTool({ + name: 'whoami', + description: 'Return the current session id, if any' + }) + whoami(ctx: ServerContext): CallToolResult { + return { + content: [{ type: 'text', text: `session: ${ctx.sessionId ?? ''}` }] + }; + } + + @McpResource({ + name: 'greeter-info', + uri: 'greeter://info', + description: 'Static metadata about the greeter', + mimeType: 'text/plain' + }) + getInfo(uri: URL): ReadResourceResult { + return { + contents: [{ uri: uri.href, mimeType: 'text/plain', text: `salutation=${this.salutation}` }] + }; + } + + @McpResourceTemplate({ + name: 'greeting', + template: new ResourceTemplate('greeting://{name}', { list: undefined }), + description: 'Personalised greeting resource', + mimeType: 'text/plain' + }) + readGreeting(uri: URL, variables: Variables): ReadResourceResult { + const raw = variables.name; + const name = Array.isArray(raw) ? raw.join(', ') : (raw ?? 'friend'); + return { + contents: [{ uri: uri.href, mimeType: 'text/plain', text: `${this.salutation}, ${name}!` }] + }; + } + + @McpPrompt({ + name: 'introduce', + description: 'Prompt the LLM to introduce a person', + argsSchema: z.object({ + name: z.string().describe('Who to introduce') + }) + }) + introduce({ name }: { name: string }): GetPromptResult { + return { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `Say "${this.salutation}, ${name}!" and share a one-line fun fact about them.` + } + } + ] + }; + } +} + +// ─── Bootstrap ─────────────────────────────────────────────────────────── + +const server = new McpServer({ + name: 'class-decorators-example', + version: '1.0.0' +}); + +registerClass(server, new GreetingController('Hello')); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('MCP server is running...'); +} + +try { + await main(); +} catch (error) { + console.error('Server error:', error); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); +} diff --git a/examples/server/src/legacyClassDecoratorsExample.ts b/examples/server/src/legacyClassDecoratorsExample.ts new file mode 100644 index 000000000..884688706 --- /dev/null +++ b/examples/server/src/legacyClassDecoratorsExample.ts @@ -0,0 +1,270 @@ +// Run with: pnpm tsx src/legacyClassDecoratorsExample.ts +// +// Demonstrates authoring MCP tools, resources, and prompts as methods on a +// class, using lightweight `@McpTool` / `@McpResource` / `@McpResourceTemplate` +// / `@McpPrompt` decorators plus a `registerClass()` helper that wires an +// instance into an `McpServer`. +// +// This file uses **legacy TypeScript decorators** (the ones enabled by +// `experimentalDecorators: true` in the package tsconfig). For the TC39 +// Stage-3 / "standard" version of the same pattern, see +// `classDecoratorsExample.ts`, which compiles under its own +// `tsconfig.standard-decorators.json`. +// +// Decorator metadata is stored on the class prototype (no reflect-metadata, +// no extra dependency). The file is self-contained — copy the ~60 lines of +// decorator plumbing into your own project to reuse the pattern. + +import type { + CallToolResult, + GetPromptResult, + Prompt, + PromptCallback, + ReadResourceCallback, + ReadResourceResult, + ReadResourceTemplateCallback, + Resource, + ResourceTemplateType, + ServerContext, + Tool, + ToolCallback, + Variables +} from '@modelcontextprotocol/server'; +import { McpServer, ResourceTemplate, StdioServerTransport } from '@modelcontextprotocol/server'; +import * as z from 'zod/v4'; + +// ─── Decorator config shapes ────────────────────────────────────────────── +// +// Each config is derived from the matching MCP spec type — `Tool`, +// `Prompt`, `Resource`, `ResourceTemplateType` — via `Omit<…, identity>`. +// That way any spec-level field (annotations, `_meta`, `size`, …) shows +// up in the decorator config automatically. Caveat: What properties get +// registered will ultimately depend on how the internals of the registerTool, +// registerPrompt, registerResource method work. +// +// Schema fields (`inputSchema` / `outputSchema` / `argsSchema`) are the +// exception: we replace the spec's JSON-Schema shape with a generic +// `S extends AnyZodObject` so `registerTool` / `registerPrompt` can infer +// `InputArgs` / `Args` from a Zod schema at the call site in +// `registerClass`. Keeping them generic also lets the decorator check at +// decoration time that the method signature matches the declared schema. + +type AnyZodObject = z.ZodObject; + +type ToolConfig = { + name: string; + inputSchema?: S; + outputSchema?: AnyZodObject; +} & Omit; + +type PromptConfig = { + name: string; + argsSchema?: S; +} & Omit; + +type ResourceConfig = { name: string; uri: string } & Omit; + +type ResourceTemplateConfig = { name: string; template: ResourceTemplate } & Omit; + +type ToolEntry = { config: ToolConfig; method: ToolCallback }; +type PromptEntry = { config: PromptConfig; method: PromptCallback }; +type ResourceEntry = { config: ResourceConfig; method: ReadResourceCallback }; +type ResourceTemplateEntry = { config: ResourceTemplateConfig; method: ReadResourceTemplateCallback }; + +type ClassMeta = { + __mcpTools?: ToolEntry[]; + __mcpResources?: ResourceEntry[]; + __mcpResourceTemplates?: ResourceTemplateEntry[]; + __mcpPrompts?: PromptEntry[]; +}; + +// ─── Decorators (legacy TS method decorators) ───────────────────────────── +// +// Each decorator is generic over the schema type, so TypeScript can check +// at decoration time that the method signature matches the declared +// schema. We then widen to `AnyZodObject` when storing on the prototype — +// the per-method generic can't survive a heterogeneous array, but the +// check has already happened at the use site. +// +// The per-class arrays are stored as own-properties on the class's +// prototype. Because `B.prototype`'s `[[Prototype]]` is `A.prototype`, a +// naive `proto.__mcpTools ??= []` on a subclass would read through the +// chain, find the parent's array, short-circuit, and mutate it in +// place — leaking subclass tools into the parent. `ownArray()` clones +// the inherited array on first write so each class gets its own copy +// and subclasses end up with `[...parentEntries, ...ownEntries]`. + +function protoOf(target: object): ClassMeta { + return (target as { constructor: { prototype: ClassMeta } }).constructor.prototype; +} + +function ownArray(proto: ClassMeta, key: K): NonNullable { + if (!Object.hasOwn(proto, key)) { + proto[key] = [...(proto[key] ?? [])] as ClassMeta[K]; + } + return proto[key] as NonNullable; +} + +export function McpTool(config: ToolConfig) { + return >(target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor): void => { + if (!descriptor.value) throw new Error(`@McpTool: method is undefined`); + ownArray(protoOf(target), '__mcpTools').push({ + config: config as ToolConfig, + method: descriptor.value as unknown as ToolCallback + }); + }; +} + +export function McpPrompt(config: PromptConfig) { + return >(target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor): void => { + if (!descriptor.value) throw new Error(`@McpPrompt: method is undefined`); + ownArray(protoOf(target), '__mcpPrompts').push({ + config: config as PromptConfig, + method: descriptor.value as unknown as PromptCallback + }); + }; +} + +export function McpResource(config: ResourceConfig) { + return (target: object, _key: string | symbol, descriptor: TypedPropertyDescriptor): void => { + if (!descriptor.value) throw new Error(`@McpResource: method is undefined`); + ownArray(protoOf(target), '__mcpResources').push({ config, method: descriptor.value }); + }; +} + +export function McpResourceTemplate(config: ResourceTemplateConfig) { + return ( + target: object, + _key: string | symbol, + descriptor: TypedPropertyDescriptor + ): void => { + if (!descriptor.value) throw new Error(`@McpResourceTemplate: method is undefined`); + ownArray(protoOf(target), '__mcpResourceTemplates').push({ config, method: descriptor.value }); + }; +} + +// ─── Registration helper ────────────────────────────────────────────────── +// +// Walks one level of the instance prototype and registers every decorated +// method with the given `McpServer`. Methods are bound to the instance so +// `this` works as expected. + +export function registerClass(server: McpServer, instance: object): void { + const proto = Object.getPrototypeOf(instance) as ClassMeta; + + for (const { config, method } of proto.__mcpTools ?? []) { + const { name, ...rest } = config; + server.registerTool(name, rest, method.bind(instance)); + } + for (const { config, method } of proto.__mcpResources ?? []) { + const { name, uri, ...metadata } = config; + server.registerResource(name, uri, metadata, method.bind(instance)); + } + for (const { config, method } of proto.__mcpResourceTemplates ?? []) { + const { name, template, ...metadata } = config; + server.registerResource(name, template, metadata, method.bind(instance)); + } + for (const { config, method } of proto.__mcpPrompts ?? []) { + const { name, ...rest } = config; + server.registerPrompt(name, rest, method.bind(instance)); + } +} + +// ─── Example class ──────────────────────────────────────────────────────── + +class GreetingController { + constructor(private readonly salutation: string) {} + + @McpTool({ + name: 'greet', + description: 'Greet someone by name', + inputSchema: z.object({ + name: z.string().describe('Name to greet') + }) + }) + greet({ name }: { name: string }): CallToolResult { + return { + content: [{ type: 'text', text: `${this.salutation}, ${name}!` }] + }; + } + + @McpTool({ + name: 'whoami', + description: 'Return the current session id, if any' + }) + whoami(ctx: ServerContext): CallToolResult { + return { + content: [{ type: 'text', text: `session: ${ctx.sessionId ?? ''}` }] + }; + } + + @McpResource({ + name: 'greeter-info', + uri: 'greeter://info', + description: 'Static metadata about the greeter', + mimeType: 'text/plain' + }) + getInfo(uri: URL): ReadResourceResult { + return { + contents: [{ uri: uri.href, mimeType: 'text/plain', text: `salutation=${this.salutation}` }] + }; + } + + @McpResourceTemplate({ + name: 'greeting', + template: new ResourceTemplate('greeting://{name}', { list: undefined }), + description: 'Personalised greeting resource', + mimeType: 'text/plain' + }) + readGreeting(uri: URL, variables: Variables): ReadResourceResult { + const raw = variables.name; + const name = Array.isArray(raw) ? raw.join(', ') : (raw ?? 'friend'); + return { + contents: [{ uri: uri.href, mimeType: 'text/plain', text: `${this.salutation}, ${name}!` }] + }; + } + + @McpPrompt({ + name: 'introduce', + description: 'Prompt the LLM to introduce a person', + argsSchema: z.object({ + name: z.string().describe('Who to introduce') + }) + }) + introduce({ name }: { name: string }): GetPromptResult { + return { + messages: [ + { + role: 'user', + content: { + type: 'text', + text: `Say "${this.salutation}, ${name}!" and share a one-line fun fact about them.` + } + } + ] + }; + } +} + +// ─── Bootstrap ──────────────────────────────────────────────────────────── + +const server = new McpServer({ + name: 'class-decorators-example', + version: '1.0.0' +}); + +registerClass(server, new GreetingController('Hello')); + +async function main() { + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error('MCP server is running...'); +} + +try { + await main(); +} catch (error) { + console.error('Server error:', error); + // eslint-disable-next-line unicorn/no-process-exit + process.exit(1); +} diff --git a/examples/server/tsconfig.json b/examples/server/tsconfig.json index e3c0e9477..9e8e3d453 100644 --- a/examples/server/tsconfig.json +++ b/examples/server/tsconfig.json @@ -1,7 +1,8 @@ { "extends": "@modelcontextprotocol/tsconfig", "include": ["./"], - "exclude": ["node_modules", "dist"], + "exclude": ["node_modules", "dist", "src/classDecoratorsExample.ts"], + "references": [{ "path": "./tsconfig.standard-decorators.json" }], "compilerOptions": { "paths": { "*": ["./*"], diff --git a/examples/server/tsconfig.standard-decorators.json b/examples/server/tsconfig.standard-decorators.json new file mode 100644 index 000000000..a8d966612 --- /dev/null +++ b/examples/server/tsconfig.standard-decorators.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/classDecoratorsExample.ts"], + "exclude": ["node_modules", "dist"], + "compilerOptions": { + "composite": true, + "rootDir": ".", + "experimentalDecorators": false, + "emitDecoratorMetadata": false, + "tsBuildInfoFile": "./dist/.tsbuildinfo-standard-decorators" + } +}