diff --git a/README.md b/README.md index 5ea6197..72c3e02 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![License](https://img.shields.io/badge/license-MIT-111827?style=flat-square)](LICENSE) -> **Published package — `@theorvane/type-mcp@0.2.2`:** provides standard decorators, definition validation, explicit instance resolution, MCP SDK compilation, stdio, `@theorvane/type-mcp/http` Streamable HTTP, and the tools-only `@theorvane/type-mcp/langchain` adapter. +> **Published package — `@theorvane/type-mcp@0.3.0`:** provides standard decorators, definition validation, explicit instance resolution, MCP SDK compilation, stdio, `@theorvane/type-mcp/http` Streamable HTTP, and the tools-only `@theorvane/type-mcp/langchain` adapter. > > **Integration boundary:** LangGraph `ToolNode` composition, graph topology, model choice, authorization, state, persistence, and deployment remain consumer responsibilities. @@ -50,7 +50,7 @@ The package is ESM-first and also exposes a CommonJS root export. TypeScript pro } ``` -Do not enable TypeScript's legacy `experimentalDecorators` mode for these standard decorator examples. See [configuration and compatibility](docs/guides/configuration.md) for ESM, CommonJS, and decorator details. +Do not enable TypeScript's legacy `experimentalDecorators` mode for these standard decorator examples. For a CommonJS legacy-decorator consumer, use the separate `@theorvane/type-mcp/legacy` entrypoint with Node16 module resolution; its supported surface and constraints are documented in the [Decorator API contract](docs/api/decorator-api.md#legacy-cjs-decorators). See [configuration and compatibility](docs/guides/configuration.md) for ESM, CommonJS, and decorator details. ## Define and inspect a server declaration @@ -98,11 +98,11 @@ console.log(definition?.tools[0]?.name); // "findProduct" `getMcpServerDefinition()` returns `undefined` for a class without `@McpServer`. For a decorated class, it returns a newly allocated frozen metadata container on every call. Zod schemas retain their original identity, so treat a schema passed to a decorator as immutable after declaration. -The methods above are ordinary application methods. In `0.2.2`, use `createMcpServer()` to validate and compile this declaration through an explicit resolver; choose an adapter exported by the installed package only when the application owns its hosting, authorization, and lifecycle policy. Follow the [getting-started guide](docs/guides/getting-started.md) for the complete version boundary. +The methods above are ordinary application methods. In `0.3.0`, use `createMcpServer()` to validate and compile this declaration through an explicit resolver; choose an adapter exported by the installed package only when the application owns its hosting, authorization, and lifecycle policy. Follow the [getting-started guide](docs/guides/getting-started.md) for the complete version boundary. ## Capability map -| Surface | `@theorvane/type-mcp@0.2.2` | What it does | +| Surface | `@theorvane/type-mcp@0.3.0` | What it does | | --- | --- | --- | | `@McpServer` | Available | Records server name and version metadata. | | `@McpTool` | Available | Records a method name, optional public name/description, and Zod object schema. | diff --git a/docs/README.md b/docs/README.md index 1b35d76..4fae21e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,6 @@ # TypeMCP documentation -TypeMCP is a decorator-first TypeScript package for describing an MCP server and compiling that description at an explicit application boundary. The published package is [`@theorvane/type-mcp@0.2.2`](https://www.npmjs.com/package/@theorvane/type-mcp). +TypeMCP is a decorator-first TypeScript package for describing an MCP server and compiling that description at an explicit application boundary. The published package is [`@theorvane/type-mcp@0.3.0`](https://www.npmjs.com/package/@theorvane/type-mcp). > **Published boundary:** TypeMCP provides declaration metadata, definition validation, MCP SDK compilation, an explicit resolver seam, a stdio helper, a Fetch Streamable HTTP adapter, and a tools-only LangChain adapter. Applications retain ownership of **hosting, authorization, persistence, models, LangGraph composition, and deployment**. diff --git a/docs/api/decorator-api.md b/docs/api/decorator-api.md index 38da200..8cd7dc8 100644 --- a/docs/api/decorator-api.md +++ b/docs/api/decorator-api.md @@ -1,6 +1,6 @@ # Decorator API contract -**Public package:** [`@theorvane/type-mcp@0.2.2`](https://www.npmjs.com/package/@theorvane/type-mcp) provides decorator declarations, definition validation, MCP SDK compilation for tools/static resources/prompts, a Node stdio helper, and a Fetch Streamable HTTP adapter. LangChain interoperability is isolated at `@theorvane/type-mcp/langchain`. +**Public package:** [`@theorvane/type-mcp@0.3.0`](https://www.npmjs.com/package/@theorvane/type-mcp) provides decorator declarations, definition validation, MCP SDK compilation for tools/static resources/prompts, a Node stdio helper, and a Fetch Streamable HTTP adapter. LangChain interoperability is isolated at `@theorvane/type-mcp/langchain`. ## Server declaration @@ -117,6 +117,33 @@ export { handler as GET, handler as POST, handler as DELETE }; `getMcpServerDefinition()` returns a newly allocated, frozen server definition, component arrays, and component records on every read. Tool `input` schemas retain the caller-supplied Zod object-schema identity: schemas are executable mutable objects and are not cloned or frozen by TypeMCP. Consumers should treat a schema supplied to a decorator as immutable after declaration. +## Legacy CJS decorators + +`@theorvane/type-mcp/legacy` is the compatibility entrypoint for TypeScript's +legacy `experimentalDecorators` emit in CommonJS applications. +It exposes `McpServer`, `McpTool`, `McpResource`, and `McpPrompt` with the same +options and definition-reader/compiler contracts as the root Stage 3 API. + +```ts +import { z } from "zod"; +import { McpServer, McpTool } from "@theorvane/type-mcp/legacy"; + +@McpServer({ name: "catalog", version: "1.0.0" }) +class CatalogServer { + @McpTool({ input: z.object({ sku: z.string() }) }) + findProduct({ sku }: { readonly sku: string }) { + return { sku }; + } +} +``` + +Use `"module": "Node16"`, `"moduleResolution": "Node16"`, and +`"experimentalDecorators": true` for a CommonJS consumer so TypeScript selects +the package's CJS declaration condition. The legacy entrypoint supports public +instance methods with string names only; parameter, accessor, field, private, +and symbol-named decorators are excluded. Do not mix Stage 3 and legacy +decorators in one TypeScript compilation unit. + ## Compatibility policy Public decorator option names, exported definitions, `InstanceResolver`, compiler and transport entry points, and handler signatures are semver-governed. Any breaking change requires an ADR, migration note, and a major release decision. diff --git a/docs/architecture/overview.md b/docs/architecture/overview.md index dc48b27..bdc76c8 100644 --- a/docs/architecture/overview.md +++ b/docs/architecture/overview.md @@ -1,6 +1,6 @@ # Architecture overview -> **Public release:** [`@theorvane/type-mcp@0.2.2`](https://www.npmjs.com/package/@theorvane/type-mcp) implements the metadata, validation, resolver, compiler, stdio, HTTP, and LangChain adapter surfaces described here. Applications remain responsible for hosting and lifecycle policy. +> **Public release:** [`@theorvane/type-mcp@0.3.0`](https://www.npmjs.com/package/@theorvane/type-mcp) implements the metadata, validation, resolver, compiler, stdio, HTTP, and LangChain adapter surfaces described here. Applications remain responsible for hosting and lifecycle policy. ## Package surface diff --git a/docs/guides/agent-integration.md b/docs/guides/agent-integration.md index 0b55cb1..77369df 100644 --- a/docs/guides/agent-integration.md +++ b/docs/guides/agent-integration.md @@ -1,6 +1,6 @@ # Agent integration guide -This guide gives coding agents a deterministic procedure for adding TypeMCP declarations without inventing application-owned policy. It applies to the published `@theorvane/type-mcp@0.2.2` package. +This guide gives coding agents a deterministic procedure for adding TypeMCP declarations without inventing application-owned policy. It applies to the published `@theorvane/type-mcp@0.3.0` package. ## Capability contract agents must honor diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index bad720c..5cbe02d 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -1,13 +1,13 @@ # Configuration and compatibility -`@theorvane/type-mcp@0.2.2` is the published TypeScript declaration and runtime package. Configuration determines whether TypeScript emits standard decorators and whether the runtime can resolve the package's ESM/CJS exports; applications configure their own hosting and transport lifecycle around installed MCP adapters. +`@theorvane/type-mcp@0.3.0` is the published TypeScript declaration and runtime package. Configuration determines whether TypeScript emits standard decorators and whether the runtime can resolve the package's ESM/CJS exports; applications configure their own hosting and transport lifecycle around installed MCP adapters. ## Runtime and package manager -Use Node.js 20 or later. After `npm view @theorvane/type-mcp@0.2.2 version` succeeds, install TypeMCP and Zod as application dependencies: +Use Node.js 20 or later. After `npm view @theorvane/type-mcp@0.3.0 version` succeeds, install TypeMCP and Zod as application dependencies: ```bash -npm install @theorvane/type-mcp@0.2.2 zod +npm install @theorvane/type-mcp@0.3.0 zod ``` The package name and import are scoped to Theorvane: @@ -16,7 +16,7 @@ The package name and import are scoped to Theorvane: import { McpServer, McpTool } from "@theorvane/type-mcp"; ``` -The public `@theorvane/type-mcp@0.2.2` package exports `@theorvane/type-mcp/http`. Add it where the application owns Fetch route hosting, durable session policy, and authorization; the adapter owns in-process MCP session routing around the SDK transport. +The public `@theorvane/type-mcp@0.3.0` package exports `@theorvane/type-mcp/http`. Add it where the application owns Fetch route hosting, durable session policy, and authorization; the adapter owns in-process MCP session routing around the SDK transport. ## TypeScript decorators @@ -67,10 +67,10 @@ find({ id }: z.infer) { } ``` -A missing component `name` defaults to the method name. `0.2.2` validates the decorated definition before compilation; application tests should still protect domain naming conventions. +A missing component `name` defaults to the method name. `0.3.0` validates the decorated definition before compilation; application tests should still protect domain naming conventions. ## Registry release versus repository development -The published `@theorvane/type-mcp@0.2.2` root exports `McpServer`, `McpTool`, `McpResource`, `McpPrompt`, `getMcpServerDefinition`, `readMcpServerDefinition`, `TypeMcpDefinitionError`, `InstanceResolver`, `resolveMcpServerInstance`, `createMcpServer`, and `startStdioServer`. The `@theorvane/type-mcp/http` and `@theorvane/type-mcp/langchain` subpaths expose their respective adapters. +The published `@theorvane/type-mcp@0.3.0` root exports `McpServer`, `McpTool`, `McpResource`, `McpPrompt`, `getMcpServerDefinition`, `readMcpServerDefinition`, `TypeMcpDefinitionError`, `InstanceResolver`, `resolveMcpServerInstance`, `createMcpServer`, and `startStdioServer`. The `@theorvane/type-mcp/http` and `@theorvane/type-mcp/langchain` subpaths expose their respective adapters. Before upgrading, read the release notes and inspect the package's generated type declarations. Treat a feature as available only when a released version documents it and the installed package exports it. diff --git a/docs/guides/core-concepts.md b/docs/guides/core-concepts.md index 2093045..6069cca 100644 --- a/docs/guides/core-concepts.md +++ b/docs/guides/core-concepts.md @@ -1,6 +1,6 @@ # Core concepts -This page explains the published [`@theorvane/type-mcp@0.2.2`](https://www.npmjs.com/package/@theorvane/type-mcp) model before you choose a runtime boundary. +This page explains the published [`@theorvane/type-mcp@0.3.0`](https://www.npmjs.com/package/@theorvane/type-mcp) model before you choose a runtime boundary. > **Responsibility boundary:** TypeMCP provides declaration metadata, validation, MCP SDK compilation, and selected adapters. Applications retain ownership of **hosting, authorization, persistence, models, LangGraph composition, and deployment**. diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index 6a376e3..9b5e465 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -1,13 +1,13 @@ -# Getting started with `@theorvane/type-mcp@0.2.2` +# Getting started with `@theorvane/type-mcp@0.3.0` -This guide creates and inspects an MCP **declaration** using the published `@theorvane/type-mcp@0.2.2` package. It also validates and compiles decorated definitions through `createMcpServer()`; the [HTTP guide](http-and-nextjs.md) and [LangChain guide](langchain-langgraph.md) cover their focused adapter boundaries. +This guide creates and inspects an MCP **declaration** using the published `@theorvane/type-mcp@0.3.0` package. It also validates and compiles decorated definitions through `createMcpServer()`; the [HTTP guide](http-and-nextjs.md) and [LangChain guide](langchain-langgraph.md) cover their focused adapter boundaries. ## Install the package and configure TypeScript Install the package and import Zod directly in the application that owns its schemas: ```bash -npm install @theorvane/type-mcp@0.2.2 zod +npm install @theorvane/type-mcp@0.3.0 zod ``` Run on Node.js 20 or later. Use standard TypeScript decorators with Node-aware ESM settings. A minimal `tsconfig.json` is: @@ -71,7 +71,7 @@ export class NotesServer { } ``` -The public component name defaults to the method name when `name` is omitted. TypeMCP records these options as metadata and `0.2.2` validates the decorated definition before compilation; use application tests for domain-specific naming conventions. +The public component name defaults to the method name when `name` is omitted. TypeMCP records these options as metadata and `0.3.0` validates the decorated definition before compilation; use application tests for domain-specific naming conventions. ## Inspect metadata at an application boundary @@ -100,6 +100,6 @@ The function returns `undefined` for a class without `@McpServer`. For a decorat ## Continue through the runtime boundary -The published `@theorvane/type-mcp@0.2.2` package contains `createMcpServer()`, `startStdioServer()`, `@theorvane/type-mcp/http`, and `@theorvane/type-mcp/langchain`. TypeMCP validates and compiles decorated definitions through an explicit `InstanceResolver`; it does not choose a web host, authorization model, session store, LangGraph topology, model, or persistence policy for the application. +The published `@theorvane/type-mcp@0.3.0` package contains `createMcpServer()`, `startStdioServer()`, `@theorvane/type-mcp/http`, and `@theorvane/type-mcp/langchain`. TypeMCP validates and compiles decorated definitions through an explicit `InstanceResolver`; it does not choose a web host, authorization model, session store, LangGraph topology, model, or persistence policy for the application. The declaration created above remains useful for application-owned inspection. Read [core concepts](core-concepts.md) for the definition/compiler model, then follow the [Petstore walkthrough](petstore-walkthrough.md) to select stdio, HTTP, or LangChain reuse. Consult the [configuration guide](configuration.md), [HTTP guide](http-and-nextjs.md), [LangChain guide](langchain-langgraph.md), and [agent guide](agent-integration.md) before automating a change. diff --git a/docs/guides/http-and-nextjs.md b/docs/guides/http-and-nextjs.md index 5a5d5f5..cb541e5 100644 --- a/docs/guides/http-and-nextjs.md +++ b/docs/guides/http-and-nextjs.md @@ -35,4 +35,4 @@ This is a route integration shape, not a full Next.js scaffold. It intentionally ## Published package boundary -The published `@theorvane/type-mcp@0.2.2` package includes `createMcpServer()` and `@theorvane/type-mcp/http`. This guide demonstrates the package API, while hosting, authentication, persistence, and authorization remain application-owned responsibilities. +The published `@theorvane/type-mcp@0.3.0` package includes `createMcpServer()` and `@theorvane/type-mcp/http`. This guide demonstrates the package API, while hosting, authentication, persistence, and authorization remain application-owned responsibilities. diff --git a/docs/guides/langchain-langgraph.md b/docs/guides/langchain-langgraph.md index 2f7cb3c..022fc4d 100644 --- a/docs/guides/langchain-langgraph.md +++ b/docs/guides/langchain-langgraph.md @@ -1,6 +1,6 @@ # LangChain and LangGraph integration -> **Published boundary:** `@theorvane/type-mcp/langchain` is part of the published `@theorvane/type-mcp@0.2.2` package. It is tools-only; LangGraph remains a consumer-owned composition choice. +> **Published boundary:** `@theorvane/type-mcp/langchain` is part of the published `@theorvane/type-mcp@0.3.0` package. It is tools-only; LangGraph remains a consumer-owned composition choice. ## Scope @@ -16,7 +16,7 @@ The core package and `@theorvane/type-mcp/http` remain independent of agent fram The adapter has an optional peer dependency on `@langchain/core`. A consumer that imports the adapter must install a compatible peer: ```bash -npm install @theorvane/type-mcp@0.2.2 @langchain/core zod +npm install @theorvane/type-mcp@0.3.0 @langchain/core zod ``` LangGraph is a consumer choice, not an adapter dependency. Install it only when using a graph: diff --git a/docs/guides/petstore-project-setup.md b/docs/guides/petstore-project-setup.md index 291fcb1..26c2619 100644 --- a/docs/guides/petstore-project-setup.md +++ b/docs/guides/petstore-project-setup.md @@ -2,7 +2,7 @@ This is the first chapter of the TypeMCP Petstore curriculum. It creates a small local project that can compile a decorated server before the application selects a runtime boundary. -> **Published version:** The examples target [`@theorvane/type-mcp@0.2.2`](https://www.npmjs.com/package/@theorvane/type-mcp). They use standard TypeScript decorators, not legacy `experimentalDecorators`. +> **Published version:** The examples target [`@theorvane/type-mcp@0.3.0`](https://www.npmjs.com/package/@theorvane/type-mcp). They use standard TypeScript decorators, not legacy `experimentalDecorators`. ## Before you start @@ -33,7 +33,7 @@ mkdir petstore-workspace cd petstore-workspace npm init -y npm pkg set type=module -npm install @theorvane/type-mcp@0.2.2 zod +npm install @theorvane/type-mcp@0.3.0 zod npm install --save-dev typescript tsx @types/node npm pkg set scripts.check="tsc --noEmit" ``` diff --git a/docs/guides/petstore-typemcp-foundation.md b/docs/guides/petstore-typemcp-foundation.md index 422a0dd..b86b713 100644 --- a/docs/guides/petstore-typemcp-foundation.md +++ b/docs/guides/petstore-typemcp-foundation.md @@ -5,7 +5,7 @@ This chapter continues the [Petstore project setup](petstore-project-setup.md). ## Before you start - Complete [Petstore project setup](petstore-project-setup.md), including strict NodeNext TypeScript configuration. -- Node.js 20 or later and the released `@theorvane/type-mcp@0.2.2` and `zod` dependencies. +- Node.js 20 or later and the released `@theorvane/type-mcp@0.3.0` and `zod` dependencies. - An MCP-capable local client only if you plan to connect to the stdio process after verifying the project locally. ## Workspace checkpoint @@ -29,7 +29,7 @@ The local script connects a compiled server to stdio. It does not register the p Confirm the project contains the released package and local commands: ```bash -npm install @theorvane/type-mcp@0.2.2 zod +npm install @theorvane/type-mcp@0.3.0 zod npm install --save-dev typescript tsx @types/node npm pkg set scripts.check="tsc --noEmit" npm pkg set scripts.inspect-server="tsx src/inspect-server.ts" @@ -70,7 +70,7 @@ export class PetstoreServer { } ``` -The decorated class deliberately has no constructor parameters because that is the published `@McpServer` decorator contract in `0.2.2`. The explicit resolver returns an application-owned configured instance before compilation; replace `configure()` with your own composition-root wiring while preserving a zero-argument decorated constructor. +The decorated class deliberately has no constructor parameters because that is the published `@McpServer` decorator contract in `0.3.0`. The explicit resolver returns an application-owned configured instance before compilation; replace `configure()` with your own composition-root wiring while preserving a zero-argument decorated constructor. ## Inspect the declaration @@ -175,7 +175,7 @@ The last command intentionally remains running while its stdio transport waits f - **`PetstoreServer is missing @McpServer metadata`:** confirm the class is decorated and imported from the `.js` ESM path in the inspecting file. - **A TypeScript error around decorators:** use the NodeNext/`ESNext.Decorators` configuration from [project setup](petstore-project-setup.md); do not enable `experimentalDecorators`. -- **A TypeScript error says the decorated constructor is incompatible:** keep the `@McpServer` class zero-argument and configure application dependencies in the explicit resolver. The published 0.2.2 decorator contract does not accept a constructor-parameter class. +- **A TypeScript error says the decorated constructor is incompatible:** keep the `@McpServer` class zero-argument and configure application dependencies in the explicit resolver. The published 0.3.0 decorator contract does not accept a constructor-parameter class. - **The process exits immediately:** inspect application startup errors and the real client/dependency configuration. `startStdioServer()` connects an already compiled server; it does not validate your environment or provision a client. - **A local MCP client cannot discover the tool:** verify that the client launches the documented executable from the project directory and that its own process/access policy permits it. TypeMCP does not register desktop client configuration. diff --git a/docs/guides/petstore-walkthrough.md b/docs/guides/petstore-walkthrough.md index dca1d7b..0e60ee8 100644 --- a/docs/guides/petstore-walkthrough.md +++ b/docs/guides/petstore-walkthrough.md @@ -1,6 +1,6 @@ # Petstore walkthrough: from declaration to a selected runtime -This walkthrough uses one read-only Petstore catalog tool to show the published [`@theorvane/type-mcp@0.2.2`](https://www.npmjs.com/package/@theorvane/type-mcp) flow: declare a server, inspect or compile it, then select the smallest supported runtime boundary. +This walkthrough uses one read-only Petstore catalog tool to show the published [`@theorvane/type-mcp@0.3.0`](https://www.npmjs.com/package/@theorvane/type-mcp) flow: declare a server, inspect or compile it, then select the smallest supported runtime boundary. > **What this does not do:** TypeMCP does not choose hosting, authorization, persistence, models, LangGraph composition, or deployment. Those decisions remain in the application. @@ -19,7 +19,7 @@ For a project-starting route, complete [Petstore project setup](petstore-project Install the package and Zod: ```bash -npm install @theorvane/type-mcp@0.2.2 zod +npm install @theorvane/type-mcp@0.3.0 zod ``` Use a Node-aware TypeScript configuration: @@ -58,7 +58,7 @@ export class PetstoreServer { } ``` -The decorated class must keep a zero-argument constructor under the published `0.2.2` `@McpServer` contract. Configure a real catalog service through the explicit resolver before it creates `PetstoreServer`; TypeMCP does not construct or authorize that dependency for you. +The decorated class must keep a zero-argument constructor under the published `0.3.0` `@McpServer` contract. Configure a real catalog service through the explicit resolver before it creates `PetstoreServer`; TypeMCP does not construct or authorize that dependency for you. ## 2. Inspect, then compile through an explicit resolver @@ -122,7 +122,7 @@ A Fetch host can call `handler(request)`. In a Next.js route, re-export it for ` Install the optional peer only when you select this path: ```bash -npm install @theorvane/type-mcp@0.2.2 @langchain/core zod +npm install @theorvane/type-mcp@0.3.0 @langchain/core zod ``` Create `src/langchain-tools.ts`: diff --git a/docs/guides/runtime-selection.md b/docs/guides/runtime-selection.md index 9c3b953..bc1810f 100644 --- a/docs/guides/runtime-selection.md +++ b/docs/guides/runtime-selection.md @@ -1,6 +1,6 @@ # Choose a TypeMCP runtime boundary -> **Release status:** This guide documents the published `@theorvane/type-mcp@0.2.2` package. Version `0.2.2` is an audit-remediation release; the runtime boundaries below are the released `0.2.x` surface. +> **Release status:** This guide documents the published `@theorvane/type-mcp@0.3.0` package. Version `0.3.0` adds the explicit `@theorvane/type-mcp/legacy` compatibility entrypoint for TypeScript `experimentalDecorators` and CommonJS consumers; the standard decorator/runtime boundaries below remain the released `0.3.x` surface. A TypeMCP class is a declaration plus ordinary application methods. Choose the package entry point from the way the application needs to expose that declaration, then keep hosting and policy at the application boundary. @@ -9,7 +9,7 @@ A TypeMCP class is a declaration plus ordinary application methods. Choose the p Install the root package and Zod when the application declares MCP tools, resources, or prompts: ```bash -npm install @theorvane/type-mcp@0.2.2 zod +npm install @theorvane/type-mcp@0.3.0 zod ``` The root entry point provides decorators, definition inspection, compilation through `createMcpServer()`, an explicit `InstanceResolver`, and `startStdioServer()`. It does not select a web framework, model, authorization scheme, session store, persistence layer, or deployment target. @@ -97,7 +97,7 @@ The adapter handles MCP HTTP session routing, protocol negotiation, and JSON-RPC Install the optional LangChain peer only when importing the tools-only adapter: ```bash -npm install @theorvane/type-mcp@0.2.2 @langchain/core zod +npm install @theorvane/type-mcp@0.3.0 @langchain/core zod ``` ```ts diff --git a/docs/ko/guides/core-concepts.md b/docs/ko/guides/core-concepts.md new file mode 100644 index 0000000..f923823 --- /dev/null +++ b/docs/ko/guides/core-concepts.md @@ -0,0 +1,85 @@ +# 핵심 개념 + +이 문서는 런타임 경계를 고르기 전에 배포된 [`@theorvane/type-mcp@0.3.0`](https://www.npmjs.com/package/@theorvane/type-mcp) 모델을 설명합니다. + +> **책임 경계:** TypeMCP는 선언 메타데이터, 검증, MCP SDK 컴파일, 그리고 선별된 어댑터를 제공합니다. **호스팅, 인가, 영속화, 모델, LangGraph 구성, 배포**의 소유권은 애플리케이션에 남습니다. + +## 선언은 배포가 아니다 + +데코레이터는 평범한 애플리케이션 메서드에 MCP를 향한 계약을 기술합니다. + +```ts +import { z } from "zod"; +import { McpServer, McpTool } from "@theorvane/type-mcp"; + +@McpServer({ name: "petstore", version: "1.0.0" }) +export class PetstoreServer { + @McpTool({ + name: "find-product", + description: "Find a Petstore product by SKU.", + input: z.object({ sku: z.string().min(1) }), + }) + findProduct({ sku }: { readonly sku: string }) { + return { sku, available: true }; + } +} +``` + +`@McpServer`는 서버 신원을 기록합니다. `@McpTool`은 공개 이름, 설명, Zod 객체 입력 스키마를 기록합니다. 리소스와 프롬프트 데코레이터도 각자 지원하는 정적 계약에 대해 같은 방식으로 동작합니다. 데코레이터는 프로세스를 시작하지 않고, 네트워크 리스너를 열지 않으며, 웹 프레임워크를 고르지 않고, 자격 증명을 읽지 않으며, 호출자를 인가하지 않습니다. + +## 애플리케이션 경계에서 정의 읽기 + +애플리케이션 코드가 계약을 노출하기 전에 확인해야 할 때 `getMcpServerDefinition()`을 사용합니다. + +```ts +import { getMcpServerDefinition } from "@theorvane/type-mcp"; +import { PetstoreServer } from "./petstore-server.js"; + +const definition = getMcpServerDefinition(PetstoreServer); +if (definition === undefined) { + throw new Error("PetstoreServer is missing @McpServer metadata."); +} + +console.log(definition.tools.map((tool) => tool.name)); +// ["find-product"] +``` + +반환된 정의 컨테이너, 컴포넌트 배열, 컴포넌트 레코드는 모두 동결된 스냅샷입니다. 실행 가능한 스키마는 안전하게 복제하거나 동결할 수 없으므로 도구의 Zod 스키마는 원래의 동일성을 유지합니다. 데코레이터에 넘긴 스키마는 불변으로 취급하세요. + +## 명시적 인스턴스 리졸버로 컴파일하기 + +`createMcpServer()`는 선언된 정의를 검증하고, 인스턴스를 해석한 뒤, 공식 MCP SDK 서버를 만듭니다. 리졸버는 애플리케이션이 서비스·리포지토리·API 클라이언트를 공급할 수 있는 경계입니다. + +```ts +import { + createMcpServer, + type InstanceResolver, +} from "@theorvane/type-mcp"; +import { PetstoreServer } from "./petstore-server.js"; + +const resolver: InstanceResolver = { + resolve: () => new PetstoreServer(), +}; + +const server = await createMcpServer(PetstoreServer, resolver); +``` + +인자가 없는 클래스에는 기본 리졸버를 쓸 수 있습니다. 의존성이 필요한 클래스에는 명시적 `InstanceResolver`를 공급하세요. TypeMCP는 애플리케이션 컨테이너를 탐색하지 않고, 의존성을 대신 생성하지도 않습니다. + +## 가장 작은 런타임 경계 고르기 + +| 필요한 것 | 배포된 진입점 | TypeMCP가 제공 | 애플리케이션이 제공 | +| --- | --- | --- | --- | +| 서버를 기술·확인·컴파일 | `@theorvane/type-mcp` | 선언, 검증, 리졸버 이음새, MCP SDK 컴파일 | 의존성, 메서드 동작, 인가 | +| 로컬 MCP 클라이언트와 통신 | `@theorvane/type-mcp` | `startStdioServer()` | 실행 파일 패키징, 프로세스 수명주기, 환경 검증, 접근 제어 | +| Fetch로 MCP 요청 수신 | `@theorvane/type-mcp/http` | Streamable HTTP 프레이밍과 인프로세스 세션 라우팅 | 라우트, origin 정책, 인증, 지속 세션 정책, 배포 | +| 선언된 도구를 LangChain에서 재사용 | `@theorvane/type-mcp/langchain` | LangChain structured tools | 모델, 에이전트/그래프 토폴로지, 상태, 정책, 영속화 | + +각 경계의 전체 코드는 [런타임 경계 고르기](../../guides/runtime-selection.md)에서 읽으세요. [Petstore 워크스루](petstore-walkthrough.md)는 같은 선언으로 그 선택을 구체적으로 보여 줍니다. + +## 다음에 읽을 것 + +- [Petstore 워크스루](petstore-walkthrough.md) — 선언, 리졸버, 그리고 지원되는 세 가지 경로. +- [데코레이터 API 계약](../../api/decorator-api.md) — 모든 공개 옵션과 제외 항목. +- [HTTP 프레임워크 통합](../../guides/http-and-nextjs.md) — Fetch/Next.js 라우트 형태. +- [LangChain과 LangGraph 통합](../../guides/langchain-langgraph.md) — 도구 전용 어댑터와 소비자가 소유하는 그래프 구성. diff --git a/docs/ko/guides/getting-started.md b/docs/ko/guides/getting-started.md new file mode 100644 index 0000000..871c01f --- /dev/null +++ b/docs/ko/guides/getting-started.md @@ -0,0 +1,105 @@ +# `@theorvane/type-mcp@0.3.0` 시작하기 + +이 가이드는 배포된 `@theorvane/type-mcp@0.3.0` 패키지로 MCP **선언(declaration)** 을 만들고 확인합니다. 또한 `createMcpServer()`를 통해 데코레이터로 선언된 정의를 검증하고 컴파일합니다. 각 어댑터 경계는 [HTTP 가이드](../../guides/http-and-nextjs.md)와 [LangChain 가이드](../../guides/langchain-langgraph.md)에서 따로 다룹니다. + +## 패키지 설치와 TypeScript 설정 + +패키지를 설치하고, 스키마를 소유하는 애플리케이션에서 Zod를 직접 임포트합니다. + +```bash +npm install @theorvane/type-mcp@0.3.0 zod +``` + +Node.js 20 이상에서 실행합니다. 표준 TypeScript 데코레이터와 Node를 인식하는 ESM 설정을 사용하세요. 최소 `tsconfig.json`은 다음과 같습니다. + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "ESNext.Decorators"], + "strict": true, + "verbatimModuleSyntax": true + } +} +``` + +이 예제들은 현재의 표준 데코레이터 제안을 사용합니다. 여기에 레거시 `experimentalDecorators`를 켜지 마세요. 프로젝트가 CommonJS이거나 이미 다른 데코레이터 프레임워크를 쓰고 있다면 [설정과 호환성](../../guides/configuration.md)을 참고하세요. + +## 서버 클래스 하나 선언하기 + +선언은 그것이 설명하는 애플리케이션 기능 옆에 두세요. `@McpServer`는 클래스를 장식하고, `@McpTool`·`@McpResource`·`@McpPrompt`는 인스턴스 메서드를 장식합니다. + +```ts +// src/notes-server.ts +import { z } from "zod"; +import { + McpPrompt, + McpResource, + McpServer, + McpTool, +} from "@theorvane/type-mcp"; + +@McpServer({ name: "notes", version: "0.2.0" }) +export class NotesServer { + @McpTool({ + name: "find-note", + description: "Describe the note lookup operation.", + input: z.object({ id: z.string().min(1) }), + }) + findNote({ id }: { id: string }) { + return { id, title: "Example note" }; + } + + @McpResource({ + name: "notes-config", + uri: "config://notes", + mimeType: "application/json", + }) + readConfig() { + return { locale: "en" }; + } + + @McpPrompt({ + name: "summarize-note", + description: "Describe a note-summary prompt.", + }) + summarizeNote() { + return "Summarize the supplied note in three bullets."; + } +} +``` + +`name`을 생략하면 공개 컴포넌트 이름은 메서드 이름을 기본값으로 씁니다. TypeMCP는 이 옵션들을 메타데이터로 기록하고, `0.3.0`는 컴파일 전에 선언된 정의를 검증합니다. 도메인에 특화된 이름 규칙은 애플리케이션 테스트로 확인하세요. + +## 애플리케이션 경계에서 메타데이터 확인하기 + +`getMcpServerDefinition()`을 호출해 데코레이터가 적용된 클래스에 연결된 선언을 가져옵니다. + +```ts +// src/inspect-notes-server.ts +import { getMcpServerDefinition } from "@theorvane/type-mcp"; +import { NotesServer } from "./notes-server.js"; + +const definition = getMcpServerDefinition(NotesServer); + +if (definition === undefined) { + throw new Error("NotesServer is missing @McpServer metadata."); +} + +console.log({ + server: definition.name, + tools: definition.tools.map((tool) => tool.name), + resources: definition.resources.map((resource) => resource.uri), + prompts: definition.prompts.map((prompt) => prompt.name), +}); +``` + +`@McpServer`가 없는 클래스에는 `undefined`를 반환합니다. 데코레이터가 적용된 클래스에는 새로 할당된 동결(frozen) 컨테이너를 반환합니다. 실행 가능한 스키마는 안전하게 복제하거나 동결할 수 없으므로, 도구 입력 스키마는 원래의 Zod 객체 동일성을 유지합니다. `@McpTool`에 전달한 뒤에는 스키마를 변경하지 마세요. + +## 런타임 경계로 이어가기 + +배포된 `@theorvane/type-mcp@0.3.0` 패키지에는 `createMcpServer()`, `startStdioServer()`, `@theorvane/type-mcp/http`, `@theorvane/type-mcp/langchain`이 들어 있습니다. TypeMCP는 명시적인 `InstanceResolver`를 통해 선언된 정의를 검증하고 컴파일합니다. 웹 호스트, 인가 모델, 세션 저장소, LangGraph 토폴로지, 모델, 영속화 정책을 애플리케이션 대신 고르지는 않습니다. + +위에서 만든 선언은 애플리케이션이 소유하는 확인 작업에 계속 유용합니다. 정의/컴파일러 모델은 [핵심 개념](core-concepts.md)에서 읽고, 그다음 [Petstore 워크스루](petstore-walkthrough.md)를 따라 stdio·HTTP·LangChain 중 무엇을 재사용할지 고르세요. 변경을 자동화하기 전에 [설정 가이드](../../guides/configuration.md), [HTTP 가이드](../../guides/http-and-nextjs.md), [LangChain 가이드](../../guides/langchain-langgraph.md), [에이전트 가이드](../../guides/agent-integration.md)를 확인하세요. diff --git a/docs/ko/guides/petstore-project-setup.md b/docs/ko/guides/petstore-project-setup.md new file mode 100644 index 0000000..ac65859 --- /dev/null +++ b/docs/ko/guides/petstore-project-setup.md @@ -0,0 +1,107 @@ +# Petstore 프로젝트 설정: 엄격한 TypeScript 워크스페이스 + +TypeMCP Petstore 커리큘럼의 첫 장입니다. 애플리케이션이 런타임 경계를 고르기 전에, 데코레이터가 적용된 서버를 컴파일할 수 있는 작은 로컬 프로젝트를 만듭니다. + +> **배포 버전:** 예제는 [`@theorvane/type-mcp@0.3.0`](https://www.npmjs.com/package/@theorvane/type-mcp)를 대상으로 합니다. 레거시 `experimentalDecorators`가 아니라 표준 TypeScript 데코레이터를 사용합니다. + +## 시작하기 전에 + +- Node.js 20 이상과 npm. +- 새 프로젝트를 만들 수 있는 디렉터리의 터미널. +- 앞으로 애플리케이션이 소유할 Petstore 클라이언트 또는 서비스. 이 장에서는 의도적으로 자격 증명을 만들지 않고, API를 고르지 않으며, 네트워크 요청도 하지 않습니다. + +## 워크스페이스 체크포인트 + +이 장을 마치면 워크스페이스는 다음 형태가 됩니다. + +```text +petstore-workspace/ +├── package.json +├── tsconfig.json +└── src/ + └── petstore-client.ts +``` + +프로젝트는 타입 검사를 통과합니다. 아직 MCP 서버를 노출하지는 않습니다. 그것은 다음 장입니다. `DOM`/`DOM.Iterable`은 배포된 MCP SDK의 Web API 타입을 커버하고, `@types/node`는 `console` 같은 로컬 Node 전역을 커버합니다. + +## 설치 + +워크스페이스를 만들고, ESM으로 표시한 뒤, 릴리스된 패키지와 이 가이드에서 쓰는 도구를 설치합니다. + +```bash +mkdir petstore-workspace +cd petstore-workspace +npm init -y +npm pkg set type=module +npm install @theorvane/type-mcp@0.3.0 zod +npm install --save-dev typescript tsx @types/node +npm pkg set scripts.check="tsc --noEmit" +``` + +`@theorvane/type-mcp`는 데코레이터, 정의 확인, 컴파일, stdio 헬퍼를 제공합니다. 도구 입력 스키마는 애플리케이션이 공급하므로 `zod`는 애플리케이션 의존성입니다. `tsx`는 뒤의 stdio 장에서 로컬 TypeScript 진입점을 실행하는 데 쓰이며, TypeMCP 런타임 요구사항은 아닙니다. + +## TypeScript 설정 + +`tsconfig.json`을 만듭니다. + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "ESNext.Decorators", "DOM", "DOM.Iterable"], + "types": ["node"], + "strict": true, + "skipLibCheck": true, + "verbatimModuleSyntax": true, + "rootDir": "src", + "outDir": "dist" + }, + "include": ["src/**/*.ts"] +} +``` + +`experimentalDecorators`는 켜지 **마세요**. 이 예제들은 현재의 표준 데코레이터 제안을 사용합니다. 기존 CommonJS 프로젝트나 다른 데코레이터 프레임워크에 이 설정을 적용하기 전에 [설정과 호환성](../../guides/configuration.md)을 확인하세요. + +## 애플리케이션 이음새 만들기 + +`src/petstore-client.ts`를 만듭니다. + +```ts +export interface PetstoreClient { + findProduct(sku: string): Promise<{ + readonly sku: string; + readonly name: string; + readonly available: boolean; + }>; +} +``` + +이 인터페이스는 의도적으로 애플리케이션 소유입니다. 실제 프로젝트에서는 그 구현이 데이터베이스, 도메인 서비스, 또는 자격 증명이 필요한 HTTP API를 호출할 수 있습니다. TypeMCP는 그 의존성을 고르지도, 생성하지도 않습니다. + +## 실행과 확인 + +타입 검사를 실행합니다. + +```bash +npm run check +``` + +## 예상 동작 + +TypeScript는 리스너, 네트워크 요청, 모델 호출, MCP 세션을 만들지 않고 정상 종료합니다. 이제 워크스페이스는 엄격한 ESM과 데코레이터 지원, 그리고 애플리케이션의 카탈로그 의존성을 위한 타입이 붙은 이음새를 갖췄습니다. + +## 실패 가이드 + +- **`Cannot find name 'console'` 또는 Node 전역 오류:** 이 최소 구성 장에서는 Node 전역을 쓰지 않습니다. 애플리케이션 진입점이 Node API를 필요로 한다면, 데코레이터 설정을 바꾸지 말고 해당 애플리케이션에 적절한 Node 타입 설정을 추가하세요. +- **`Cannot use import statement outside a module`:** `package.json`에 `"type": "module"`이 있는지 확인하고, `module`과 `moduleResolution`을 `NodeNext`로 유지하세요. +- **다음 장을 복사한 뒤 데코레이터 오류:** 레거시 `experimentalDecorators`를 제거하세요. Stage 3 데코레이터 타이핑에는 이 가이드의 `ESNext.Decorators` 라이브러리 항목이 필요합니다. + +## 책임 경계 + +워크스페이스, 패키지 매니저, 의존성 버전, 자격 증명, Petstore 클라이언트 구현, 프로젝트 수명주기는 사용자가 소유합니다. TypeMCP는 프로젝트를 만들지 않고, 배포를 설정하지 않으며, 엔드포인트를 호스팅하지 않고, 호출자를 인가하지 않으며, 상태를 영속화하지 않고, 모델을 고르지 않으며, Petstore 요청을 보내지 않습니다. + +## 다음 단계 + +[Petstore TypeMCP 기반](petstore-typemcp-foundation.md)으로 이어가서 서버를 선언하고, 정의를 확인하고, 명시적 리졸버로 컴파일하고, 로컬 stdio 경계를 시작하세요. diff --git a/docs/ko/guides/petstore-typemcp-foundation.md b/docs/ko/guides/petstore-typemcp-foundation.md new file mode 100644 index 0000000..95aad74 --- /dev/null +++ b/docs/ko/guides/petstore-typemcp-foundation.md @@ -0,0 +1,188 @@ +# Petstore TypeMCP 기반: 선언, 확인, 컴파일, stdio 실행 + +이 장은 [Petstore 프로젝트 설정](petstore-project-setup.md)에서 이어집니다. 애플리케이션이 소유한 Petstore 클라이언트 이음새를 데코레이터가 적용된 MCP 서버 하나로 바꾸고, 정의를 검증하고, 명시적 리졸버로 컴파일한 뒤, 로컬 stdio 프로세스를 시작합니다. + +## 시작하기 전에 + +- 엄격한 NodeNext TypeScript 설정을 포함해 [Petstore 프로젝트 설정](petstore-project-setup.md)을 마치세요. +- Node.js 20 이상, 그리고 릴리스된 `@theorvane/type-mcp@0.3.0`와 `zod` 의존성. +- 프로젝트를 로컬에서 확인한 뒤 stdio 프로세스에 연결할 계획이라면, 그때만 MCP를 지원하는 로컬 클라이언트가 필요합니다. + +## 워크스페이스 체크포인트 + +이 장을 마치면 워크스페이스에는 다음이 포함됩니다. + +```text +petstore-workspace/ +└── src/ + ├── inspect-server.ts + ├── petstore-client.ts + ├── petstore-server.ts + ├── server.ts + └── run-stdio.ts +``` + +로컬 스크립트는 컴파일된 서버를 stdio에 연결합니다. 프로그램을 데스크톱 클라이언트에 등록하지 않고, 튜토리얼의 인메모리 카탈로그를 프로덕션 서비스로 만들지도 않습니다. + +## 설치 + +프로젝트에 릴리스된 패키지와 로컬 명령이 있는지 확인합니다. + +```bash +npm install @theorvane/type-mcp@0.3.0 zod +npm install --save-dev typescript tsx @types/node +npm pkg set scripts.check="tsc --noEmit" +npm pkg set scripts.inspect-server="tsx src/inspect-server.ts" +npm pkg set scripts.stdio="tsx src/run-stdio.ts" +``` + +## 서버 선언 + +`src/petstore-server.ts`를 만듭니다. + +```ts +import { z } from "zod"; +import { McpServer, McpTool } from "@theorvane/type-mcp"; + +import type { PetstoreClient } from "./petstore-client.js"; + +@McpServer({ name: "petstore", version: "1.0.0" }) +export class PetstoreServer { + @McpTool({ + name: "find-product", + description: "Find a Petstore product by SKU.", + input: z.object({ sku: z.string().min(1) }), + }) + findProduct({ sku }: { readonly sku: string }) { + if (this.client === undefined) { + throw new Error("Petstore client was not configured by the application."); + } + + return this.client.findProduct(sku); + } + + private client: PetstoreClient | undefined; + + configure(client: PetstoreClient) { + this.client = client; + return this; + } +} +``` + +데코레이터가 적용된 클래스에 생성자 매개변수가 없는 것은 의도된 것입니다. `0.3.0`에 배포된 `@McpServer` 데코레이터 계약이 그렇게 정의되어 있습니다. 명시적 리졸버가 컴파일 전에 애플리케이션이 소유한, 구성이 끝난 인스턴스를 반환합니다. `configure()`는 여러분의 컴포지션 루트 배선으로 바꾸되, 데코레이터가 적용된 생성자는 인자 없는 형태로 유지하세요. + +## 선언 확인 + +`src/inspect-server.ts`를 만듭니다. + +```ts +import { getMcpServerDefinition } from "@theorvane/type-mcp"; + +import { PetstoreServer } from "./petstore-server.js"; + +const definition = getMcpServerDefinition(PetstoreServer); +if (definition === undefined) { + throw new Error("PetstoreServer is missing @McpServer metadata."); +} + +console.log({ + server: definition.name, + tools: definition.tools.map((tool) => tool.name), +}); +``` + +`getMcpServerDefinition()`은 애플리케이션이 확인할 수 있도록 선언을 제공합니다. 서버를 인스턴스화하거나 전송을 열지는 않습니다. + +## 명시적 리졸버로 컴파일 + +`src/server.ts`를 만듭니다. + +```ts +import { + createMcpServer, + type InstanceResolver, +} from "@theorvane/type-mcp"; + +import type { PetstoreClient } from "./petstore-client.js"; +import { PetstoreServer } from "./petstore-server.js"; + +export function createPetstoreMcpServer(petstoreClient: PetstoreClient) { + const resolver: InstanceResolver = { + resolve: () => new PetstoreServer().configure(petstoreClient), + }; + + return createMcpServer(PetstoreServer, resolver); +} +``` + +명시적 형태인 `createMcpServer(PetstoreServer, resolver)`는 선언된 정의를 검증하고, 애플리케이션이 소유한 인스턴스를 해석한 뒤, 지원되는 표면을 MCP SDK 서버로 컴파일합니다. + +규모가 큰 애플리케이션에서는 팩토리를 호출하기 전에 컴포지션 루트가 자신의 소유권을 명시할 수 있습니다. + +```ts +import type { PetstoreClient } from "./petstore-client.js"; + +declare const petstoreClient: PetstoreClient; +``` + +이 선언은 타이핑 이음새이며 자격 증명 구현이 아닙니다. 실제 의존성은 애플리케이션 시작 코드에서 생성하고 인가하세요. + +## 실행과 확인 + +`src/run-stdio.ts`를 만듭니다. + +```ts +import { startStdioServer } from "@theorvane/type-mcp"; + +import type { PetstoreClient } from "./petstore-client.js"; +import { createPetstoreMcpServer } from "./server.js"; + +const localPetstoreClient: PetstoreClient = { + findProduct: async (sku) => ({ + sku, + name: "Petstore starter product", + available: true, + }), +}; + +const server = await createPetstoreMcpServer(localPetstoreClient); +await startStdioServer(server); +``` + +그다음 실행합니다. + +```bash +npm run check +npm run inspect-server +npm run stdio +``` + +마지막 명령은 stdio 전송이 MCP 프로토콜 메시지를 기다리는 동안 의도적으로 계속 실행됩니다. 프로세스가 시작되는 것을 확인한 뒤 `Ctrl+C`로 중지하세요. MCP를 지원하는 클라이언트는 애플리케이션이 패키징한 실행 명령으로 별도로 설정하세요. 그 클라이언트 설정은 TypeMCP가 만들어 주지 않습니다. + +## 예상 동작 + +- `npm run check`가 성공합니다. +- `npm run inspect-server`가 다음과 동등한 값을 출력합니다. + + ```text + { server: 'petstore', tools: [ 'find-product' ] } + ``` + +- `npm run stdio`는 HTTP 리스너, 브라우저, 모델 호출, 자격 증명 없이 애플리케이션이 소유한 로컬 프로세스를 시작합니다. 연결된 MCP 클라이언트는 자신의 설정에 따라 컴파일된 `find-product` 도구를 발견할 수 있습니다. + +## 실패 가이드 + +- **`PetstoreServer is missing @McpServer metadata`:** 클래스에 데코레이터가 적용되어 있는지, 확인 파일에서 `.js` ESM 경로로 임포트했는지 확인하세요. +- **데코레이터 관련 TypeScript 오류:** [프로젝트 설정](petstore-project-setup.md)의 NodeNext/`ESNext.Decorators` 설정을 사용하고, `experimentalDecorators`는 켜지 마세요. +- **데코레이터가 적용된 생성자가 호환되지 않는다는 TypeScript 오류:** `@McpServer` 클래스를 인자 없는 형태로 유지하고, 애플리케이션 의존성은 명시적 리졸버에서 구성하세요. 배포된 0.3.0 데코레이터 계약은 생성자 매개변수를 받는 클래스를 허용하지 않습니다. +- **프로세스가 즉시 종료됨:** 애플리케이션 시작 오류와 실제 클라이언트/의존성 설정을 확인하세요. `startStdioServer()`는 이미 컴파일된 서버를 연결할 뿐, 환경을 검증하거나 클라이언트를 준비해 주지는 않습니다. +- **로컬 MCP 클라이언트가 도구를 발견하지 못함:** 클라이언트가 프로젝트 디렉터리에서 문서화된 실행 파일을 실행하는지, 그리고 클라이언트 자신의 프로세스/접근 정책이 이를 허용하는지 확인하세요. TypeMCP는 데스크톱 클라이언트 설정을 등록하지 않습니다. + +## 책임 경계 + +TypeMCP는 선언을 검증하고, MCP 서버를 컴파일하고, 그 서버를 stdio에 연결합니다. Petstore 데이터 접근, 리졸버 의존성, 프로세스 수명주기, 실행 파일 패키징, 환경 검증, 인가, 로깅 정책, 호스팅, 영속화, 모델, LangGraph 구성, 배포는 사용자가 소유합니다. + +## 다음 단계 + +선택적인 Fetch/Next.js HTTP 또는 도구 전용 LangChain 재사용은 [Petstore 런타임 선택](petstore-walkthrough.md)으로 이어가세요. 전체 경계 표는 [런타임 선택](../../guides/runtime-selection.md), 정확한 공개 계약은 [데코레이터 API](../../api/decorator-api.md)에서 읽으세요. diff --git a/docs/ko/guides/petstore-walkthrough.md b/docs/ko/guides/petstore-walkthrough.md new file mode 100644 index 0000000..2e1d727 --- /dev/null +++ b/docs/ko/guides/petstore-walkthrough.md @@ -0,0 +1,189 @@ +# Petstore 워크스루: 선언에서 선택된 런타임까지 + +이 워크스루는 읽기 전용 Petstore 카탈로그 도구 하나로 배포된 [`@theorvane/type-mcp@0.3.0`](https://www.npmjs.com/package/@theorvane/type-mcp)의 흐름을 보여 줍니다. 서버를 선언하고, 확인하거나 컴파일한 뒤, 지원되는 가장 작은 런타임 경계를 고릅니다. + +> **이것이 하지 않는 일:** TypeMCP는 호스팅, 인가, 영속화, 모델, LangGraph 구성, 배포를 고르지 않습니다. 그 결정은 애플리케이션에 남습니다. + +## 시작하기 전에 + +- Node.js 20 이상 +- 표준 데코레이터를 쓰는 TypeScript. 레거시 `experimentalDecorators`는 켜지 마세요 +- 실제 도구가 데이터나 API 클라이언트를 필요로 한다면, 애플리케이션이 소유하는 카탈로그 서비스 + +## 워크스페이스 체크포인트 + +프로젝트를 처음부터 시작하는 경로라면 [Petstore 프로젝트 설정](petstore-project-setup.md)과 [Petstore TypeMCP 기반](petstore-typemcp-foundation.md)을 먼저 마치세요. 이 워크스루는 선언, 명시적 리졸버, 로컬 stdio 경로가 이미 컴파일되는 상태에서 런타임 경계 하나를 고르는 후속 단계입니다. + +## 설치 + +패키지와 Zod를 설치합니다. + +```bash +npm install @theorvane/type-mcp@0.3.0 zod +``` + +Node를 인식하는 TypeScript 설정을 사용합니다. + +```json +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022", "ESNext.Decorators", "DOM", "DOM.Iterable"], + "strict": true, + "verbatimModuleSyntax": true + } +} +``` + +## 1. Petstore 서버 선언 + +`src/petstore-server.ts`를 만듭니다. + +```ts +import { z } from "zod"; +import { McpServer, McpTool } from "@theorvane/type-mcp"; + +@McpServer({ name: "petstore", version: "1.0.0" }) +export class PetstoreServer { + @McpTool({ + name: "find-product", + description: "Find a Petstore product by SKU.", + input: z.object({ sku: z.string().min(1) }), + }) + findProduct({ sku }: { readonly sku: string }) { + return { sku, available: true }; + } +} +``` + +배포된 `0.3.0`의 `@McpServer` 계약에서는 데코레이터가 적용된 클래스가 인자 없는 생성자를 유지해야 합니다. 실제 카탈로그 서비스는 리졸버가 `PetstoreServer`를 생성하기 전에 명시적 리졸버를 통해 구성하세요. TypeMCP는 그 의존성을 대신 생성하거나 인가하지 않습니다. + +## 2. 확인한 뒤 명시적 리졸버로 컴파일 + +`src/server.ts`를 만듭니다. + +```ts +import { + createMcpServer, + getMcpServerDefinition, + type InstanceResolver, +} from "@theorvane/type-mcp"; +import { PetstoreServer } from "./petstore-server.js"; + +const definition = getMcpServerDefinition(PetstoreServer); +if (definition === undefined) { + throw new Error("PetstoreServer is missing its declaration."); +} + +const resolver: InstanceResolver = { + resolve: () => new PetstoreServer(), +}; + +export const server = await createMcpServer(PetstoreServer, resolver); +``` + +이 시점에서 TypeMCP는 선언을 검증하고, 애플리케이션 인스턴스 하나를 해석하고, MCP SDK 서버를 컴파일했습니다. 요청이 그 서버에 어떻게 도달하는지와 각 호출에 어떤 정책을 적용할지는 여전히 애플리케이션이 결정합니다. + +## 3. 경계 하나 고르기 + +### stdio로 로컬 실행 + +MCP를 지원하는 데스크톱 또는 로컬 클라이언트가 여러분의 프로세스를 실행한다면 `src/stdio.ts`를 추가합니다. + +```ts +import { startStdioServer } from "@theorvane/type-mcp"; +import { server } from "./server.js"; + +await startStdioServer(server); +``` + +TypeMCP는 컴파일된 서버를 SDK stdio 전송에 연결합니다. 실행 파일 패키징, 환경 검증, 프로세스 수명주기, 접근 제어는 애플리케이션이 소유합니다. [런타임 선택](../../guides/runtime-selection.md#connect-stdio-when-the-process-is-the-boundary)으로 이어가세요. + +### Fetch 또는 Next.js 호스트에서 Streamable HTTP 제공 + +추가 TypeMCP 패키지를 설치할 필요 없이, 배포된 HTTP 하위 경로를 임포트합니다. `src/mcp-handler.ts`를 만듭니다. + +```ts +import { createMcpServer } from "@theorvane/type-mcp"; +import { createMcpHandler } from "@theorvane/type-mcp/http"; +import { PetstoreServer } from "./petstore-server.js"; + +export const handler = createMcpHandler(() => + createMcpServer(PetstoreServer, { resolve: () => new PetstoreServer() }), +); +``` + +Fetch 호스트는 `handler(request)`를 호출할 수 있습니다. Next.js 라우트에서는 `GET`, `POST`, `DELETE`로 다시 내보내세요. 어댑터는 JSON-RPC 프레이밍, 프로토콜 협상, 인프로세스 MCP 세션 라우팅을 소유합니다. 호스트는 URL, 인증, origin 제어, 지속 세션 정책, 텔레메트리, 배포를 소유합니다. [HTTP 프레임워크 통합](../../guides/http-and-nextjs.md)과 [실행 가능한 독립형 HTTP 예제](../../../examples/standalone-http/README.md)를 참고하세요. + +### LangChain에서 도구 재사용 + +이 경로를 고를 때에만 선택적 피어 의존성을 설치합니다. + +```bash +npm install @theorvane/type-mcp@0.3.0 @langchain/core zod +``` + +`src/langchain-tools.ts`를 만듭니다. + +```ts +import { createLangChainTools } from "@theorvane/type-mcp/langchain"; +import { PetstoreServer } from "./petstore-server.js"; + +export const tools = await createLangChainTools(PetstoreServer, { + resolver: { resolve: () => new PetstoreServer() }, +}); +``` + +어댑터는 데코레이터가 적용된 `@McpTool` 메서드로 LangChain structured tools를 만듭니다. MCP 전송을 시작하거나, 에이전트를 만들거나, 모델을 고르거나, LangGraph 그래프를 만들지는 않습니다. `tools`를 여러분의 LangChain 또는 LangGraph 구성에 넘기고, 정책/상태 결정은 그쪽에 남겨 두세요. [LangChain과 LangGraph 통합](../../guides/langchain-langgraph.md)과 [인메모리 ToolNode 예제](../../../examples/langgraph-tools/README.md)를 참고하세요. + +## 실행과 확인 + +후속 경로를 정확히 하나만 고르고, 프로젝트 루트에서 그에 맞는 검사를 실행하세요. + +```bash +# stdio: 먼저 컴파일한 뒤 프로세스를 시작합니다. stdin/stdout에 계속 붙어 있습니다. +npm run check +npm run inspect-server +npm run stdio + +# HTTP: 위와 같이 src/mcp-handler.ts를 만든 뒤 소비자 프로젝트를 컴파일합니다. +npm run check + +# LangChain: 위와 같이 src/langchain-tools.ts를 만든 뒤 소비자 프로젝트를 컴파일합니다. +npm run check +``` + +`npm run check`는 소비자 프로젝트 검증입니다. 선택한 각 소스 파일을 설치된 배포 패키지에 대해 타입 검사합니다. stdio의 경우 성공한 프로세스는 완료 줄을 출력하는 대신 MCP 클라이언트를 위해 열린 상태로 남습니다. 클라이언트가 연결된 뒤 `Ctrl+C`로 중지하세요. HTTP와 LangChain 컴파일은 리스너를 배포하거나 모델을 호출하지 않습니다. + +## 예상 동작 + +선언은 공개 도구 이름 `find-product`를 만듭니다. 선택한 stdio 경로는 이미 컴파일된 서버를 로컬 프로세스에 연결하고, HTTP 경로는 Fetch 호환 핸들러를 반환하며, LangChain 경로는 structured tools를 반환합니다. 이 선택들 중 어느 것도 호스트를 만들거나, 호출자를 인가하거나, 모델을 고르거나, 애플리케이션 상태를 영속화하지 않습니다. + +## 패턴 검증 + +**저장소 관리자 전용:** 아래 명령은 이 저장소 체크아웃이 필요합니다(복사한 소비자 워크스페이스용 명령이 아닙니다). 실제 리스너, 모델, 자격 증명, 공개 Petstore 요청 없이 배포된 HTTP와 LangChain 경계를 증명합니다. + +```bash +npm test -- --run test/standalone-http-example.test.ts +npm test -- --run test/langgraph-tool-node.test.ts +``` + +이 스모크 테스트는 기존 카탈로그 예제를 실행합니다. 여러분의 애플리케이션에서는 리졸버, 도구의 도메인 결과, 그리고 TypeMCP가 의도적으로 남겨 둔 인가 정책에 대한 집중된 테스트를 추가하세요. + +## 실패 가이드 + +- **데코레이터 또는 ESM 컴파일 실패:** [프로젝트 설정](petstore-project-setup.md)의 엄격한 `NodeNext`와 `ESNext.Decorators` 설정으로 시작하세요. 레거시 `experimentalDecorators`는 켜지 마세요. +- **의존성이나 자격 증명을 쓸 수 없음:** 리졸버가 `PetstoreServer`를 생성하기 전에 애플리케이션 컴포지션 루트에서 생성하고 검증하세요. TypeMCP는 자격 증명이나 재시도 정책을 공급하지 않습니다. +- **클라이언트가 다른 프로세스를 필요로 함:** 애플리케이션이 고른 stdio 또는 HTTP 호스트를 사용하세요. LangChain 경로는 도구만 적응시키며 클라이언트/전송이 아닙니다. + +## 책임 경계 + +TypeMCP는 선언을 검증하고, 리졸버를 통해 컴파일하고, 컴파일된 서버를 선택된 배포 경계에 연결할 수 있습니다. 카탈로그 클라이언트, 인가, 정책, 프로세스와 호스트 수명주기, 영속화, 모델, LangGraph 구성, 텔레메트리, 배포는 애플리케이션이 소유합니다. + +## 다음 단계 + +- [핵심 개념](core-concepts.md) — 선언, 정의, 컴파일러, 리졸버 모델. +- [런타임 경계 고르기](../../guides/runtime-selection.md) — 루트, stdio, HTTP, LangChain 결정 표. +- [데코레이터 API 계약](../../api/decorator-api.md) — 정확한 공개 API와 제외 항목. diff --git a/docs/product/mvp-scope.md b/docs/product/mvp-scope.md index 89929df..162910f 100644 --- a/docs/product/mvp-scope.md +++ b/docs/product/mvp-scope.md @@ -1,8 +1,8 @@ # MVP scope -> **Published package:** `@theorvane/type-mcp@0.2.2` includes this MVP's metadata, validation, resolver, compiler, stdio, HTTP, and tools-only LangChain adapter. Start with the [README](../../README.md) and [getting-started guide](../guides/getting-started.md) for exact exports and boundaries. +> **Published package:** `@theorvane/type-mcp@0.3.0` includes this MVP's metadata, validation, resolver, compiler, stdio, HTTP, and tools-only LangChain adapter. Start with the [README](../../README.md) and [getting-started guide](../guides/getting-started.md) for exact exports and boundaries. -**Status:** Implemented and published in `@theorvane/type-mcp@0.2.2`: decorator metadata storage, definition validation, the instance resolver seam, compiler behavior, the Node stdio helper, the Fetch Streamable HTTP adapter, and the tools-only LangChain adapter. +**Status:** Implemented and published in `@theorvane/type-mcp@0.3.0`: decorator metadata storage, definition validation, the instance resolver seam, compiler behavior, the Node stdio helper, the Fetch Streamable HTTP adapter, and the tools-only LangChain adapter. ## Included @@ -33,7 +33,7 @@ ## Constraints -- Public distribution: `@theorvane/type-mcp@0.2.2` on npm; the repository is `Theorvane/type-mcp`. +- Public distribution: `@theorvane/type-mcp@0.3.0` on npm; the repository is `Theorvane/type-mcp`. - Runtime protocol behavior comes from the official MCP SDK. - Core and HTTP have no agent-framework runtime or peer dependency; `@theorvane/type-mcp/langchain` has an isolated optional LangChain peer. - Public types are strict and runtime input is validated before handler invocation. diff --git a/examples/langgraph-tools/README.md b/examples/langgraph-tools/README.md index 14aeef2..7ef910d 100644 --- a/examples/langgraph-tools/README.md +++ b/examples/langgraph-tools/README.md @@ -2,7 +2,7 @@ This repository-source example turns a decorated TypeMCP tool into a standard LangChain tool and passes it to LangGraph's `ToolNode`. TypeMCP supplies the tool declaration and adapter only; the application retains ownership of graph topology, models, authorization, persistence, and deployment. -> **Published package example:** `@theorvane/type-mcp@0.2.2` provides `@theorvane/type-mcp/langchain`. It creates tools only; applications own their LangGraph graph, model, state, and policy. +> **Published package example:** `@theorvane/type-mcp@0.3.0` provides `@theorvane/type-mcp/langchain`. It creates tools only; applications own their LangGraph graph, model, state, and policy. ## Build from a checkout diff --git a/examples/standalone-http/README.md b/examples/standalone-http/README.md index af54c77..5612f41 100644 --- a/examples/standalone-http/README.md +++ b/examples/standalone-http/README.md @@ -2,7 +2,7 @@ This example is a minimal TypeScript server declaration compiled into a Fetch-compatible MCP Streamable HTTP handler. It deliberately does **not** start a Node listener or bind to a web framework: a host framework supplies a Web `Request` and returns the handler's `Response`. -> **Published package example:** `@theorvane/type-mcp@0.2.2` provides `createMcpServer()` and `@theorvane/type-mcp/http`. This example leaves route hosting, durable session policy, and authorization to the application; the adapter owns in-process MCP session routing. +> **Published package example:** `@theorvane/type-mcp@0.3.0` provides `createMcpServer()` and `@theorvane/type-mcp/http`. This example leaves route hosting, durable session policy, and authorization to the application; the adapter owns in-process MCP session routing. ## Build from a checkout diff --git a/package-lock.json b/package-lock.json index 79be134..97dde23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@theorvane/type-mcp", - "version": "0.2.2", + "version": "0.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@theorvane/type-mcp", - "version": "0.2.2", + "version": "0.3.0", "license": "MIT", "dependencies": { "@hono/node-server": "2.0.12", diff --git a/package.json b/package.json index 2ddd4d3..3d72a0e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@theorvane/type-mcp", - "version": "0.2.2", + "version": "0.3.0", "description": "Decorator-first TypeScript framework for Model Context Protocol servers", "repository": { "type": "git", @@ -30,6 +30,16 @@ "types": "./dist/langchain.d.ts", "import": "./dist/langchain.js", "require": "./dist/langchain.cjs" + }, + "./legacy": { + "import": { + "types": "./dist/legacy.d.ts", + "default": "./dist/legacy.js" + }, + "require": { + "types": "./dist/legacy.d.cts", + "default": "./dist/legacy.cjs" + } } }, "files": [ @@ -51,7 +61,7 @@ "lint": "biome check .", "build": "tsup", "verify:package": "node scripts/verify-package-exports.mjs", - "verify:consumer": "node scripts/verify-documentation-consumer.mjs", + "verify:consumer": "node scripts/verify-documentation-consumer.mjs && node scripts/verify-legacy-consumer.mjs", "verify:publish": "npm run build && node scripts/verify-publish-readiness.mjs && npm run verify:consumer", "example:standalone-http": "npm run build && npm --prefix examples/standalone-http ci && npm --prefix examples/standalone-http run build", "example:langgraph-tools": "npm run build && npm --prefix examples/langgraph-tools ci && npm --prefix examples/langgraph-tools run build", diff --git a/scripts/verify-legacy-consumer.mjs b/scripts/verify-legacy-consumer.mjs new file mode 100644 index 0000000..9f13f69 --- /dev/null +++ b/scripts/verify-legacy-consumer.mjs @@ -0,0 +1,75 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, resolve } from "node:path"; + +const packageRoot = process.cwd(); +let consumer; +let tarballPath; + +function run(command, args, cwd = packageRoot) { + return execFileSync(command, args, { + cwd, + encoding: "utf8", + stdio: ["ignore", "pipe", "pipe"], + }); +} + +try { + run("npm", ["run", "build"]); + const [packed] = JSON.parse( + run("npm", ["pack", "--json", "--ignore-scripts"]), + ); + tarballPath = resolve(packageRoot, packed.filename); + consumer = mkdtempSync(join(tmpdir(), "type-mcp-legacy-consumer-")); + run("npm", ["init", "--yes"], consumer); + run( + "npm", + [ + "install", + "--ignore-scripts", + "--no-audit", + "--no-fund", + tarballPath, + "zod", + "@types/node", + ], + consumer, + ); + writeFileSync( + join(consumer, "tsconfig.json"), + JSON.stringify( + { + compilerOptions: { + target: "ES2022", + module: "Node16", + moduleResolution: "Node16", + experimentalDecorators: true, + strict: true, + skipLibCheck: true, + outDir: "dist", + }, + include: ["server.ts"], + }, + null, + 2, + ), + ); + writeFileSync( + join(consumer, "server.ts"), + `import { z } from "zod";\nimport { getMcpServerDefinition, McpServer, McpTool } from "@theorvane/type-mcp/legacy";\n\n@McpServer({ name: "legacy-catalog", version: "1.0.0" })\nclass LegacyCatalog {\n @McpTool({ name: "find_product", description: "Finds a product.", input: z.object({ sku: z.string() }) })\n findProduct({ sku }: { readonly sku: string }) { return { sku }; }\n}\n\nconst definition = getMcpServerDefinition(LegacyCatalog);\nif (definition?.tools[0]?.name !== "find_product") throw new Error("Legacy MCP definition was not registered.");\n`, + ); + run( + resolve(packageRoot, "node_modules/typescript/bin/tsc"), + ["--project", "tsconfig.json"], + consumer, + ); + run("node", ["dist/server.js"], consumer); + console.log( + "Verified packed CommonJS consumer with legacy TypeScript decorators.", + ); +} finally { + if (consumer !== undefined) + rmSync(consumer, { force: true, recursive: true }); + if (tarballPath !== undefined) rmSync(tarballPath, { force: true }); +} diff --git a/scripts/verify-package-exports.mjs b/scripts/verify-package-exports.mjs index 32adad1..5c415b6 100644 --- a/scripts/verify-package-exports.mjs +++ b/scripts/verify-package-exports.mjs @@ -25,6 +25,14 @@ const exportsToVerify = [ key: "./langchain", symbols: [{ name: "createLangChainTools", runtimeType: "function" }], }, + { + key: "./legacy", + symbols: [ + { name: "McpServer", runtimeType: "function" }, + { name: "McpTool", runtimeType: "function" }, + { name: "getMcpServerDefinition", runtimeType: "function" }, + ], + }, ]; for (const { key, symbols } of exportsToVerify) { @@ -32,17 +40,34 @@ for (const { key, symbols } of exportsToVerify) { if ( exportMap === undefined || typeof exportMap !== "object" || - exportMap === null || - typeof exportMap.import !== "string" || - typeof exportMap.require !== "string" || - typeof exportMap.types !== "string" + exportMap === null + ) { + throw new Error(`${manifest.name}: invalid ${key} export map`); + } + + const importExport = + typeof exportMap.import === "string" + ? { default: exportMap.import, types: exportMap.types } + : exportMap.import; + const requireExport = + typeof exportMap.require === "string" + ? { default: exportMap.require, types: exportMap.types } + : exportMap.require; + if ( + typeof importExport !== "object" || + importExport === null || + typeof importExport.default !== "string" || + typeof importExport.types !== "string" || + typeof requireExport !== "object" || + requireExport === null || + typeof requireExport.default !== "string" ) { throw new Error(`${manifest.name}: invalid ${key} export map`); } - const esmPath = resolve(root, exportMap.import); - const cjsPath = resolve(root, exportMap.require); - const typesPath = resolve(root, exportMap.types); + const esmPath = resolve(root, importExport.default); + const cjsPath = resolve(root, requireExport.default); + const typesPath = resolve(root, importExport.types); await Promise.all([access(esmPath), access(cjsPath), access(typesPath)]); const [esm, typeDeclarations] = await Promise.all([ diff --git a/src/legacy.ts b/src/legacy.ts new file mode 100644 index 0000000..74a6c1b --- /dev/null +++ b/src/legacy.ts @@ -0,0 +1,129 @@ +import { storeMcpServerDefinition } from "./metadata/definitions.js"; +import type { + McpPromptDefinition, + McpPromptOptions, + McpResourceDefinition, + McpResourceOptions, + McpServerConstructor, + McpServerOptions, + McpToolDefinition, + McpToolOptions, +} from "./types.js"; + +export { createMcpServer } from "./compiler/create-mcp-server.js"; +export { TypeMcpDefinitionError } from "./errors.js"; +export { getMcpServerDefinition } from "./metadata/definitions.js"; +export { readMcpServerDefinition } from "./metadata/read-server-definition.js"; +export { defaultInstanceResolver } from "./resolver/default-instance-resolver.js"; +export type { InstanceResolver } from "./resolver/instance-resolver.js"; +export { resolveMcpServerInstance } from "./resolver/resolve-server-instance.js"; +export type { + McpPromptDefinition, + McpPromptOptions, + McpResourceDefinition, + McpResourceOptions, + McpServerConstructor, + McpServerOptions, + McpToolDefinition, + McpToolOptions, +} from "./types.js"; + +export type LegacyClassDecorator = ( + target: T, +) => void; + +export type LegacyMethodDecorator = ( + target: object, + propertyKey: string | symbol, + descriptor: PropertyDescriptor, +) => void; + +interface PendingDefinitions { + readonly tools: McpToolDefinition[]; + readonly resources: McpResourceDefinition[]; + readonly prompts: McpPromptDefinition[]; +} + +const pendingDefinitions = new WeakMap(); + +export function McpServer(options: McpServerOptions): LegacyClassDecorator { + return (target): void => { + const pending = pendingDefinitions.get(target) ?? emptyDefinitions(); + storeMcpServerDefinition(target, { + name: options.name, + version: options.version, + tools: pending.tools, + resources: pending.resources, + prompts: pending.prompts, + }); + }; +} + +export function McpTool(options: McpToolOptions): LegacyMethodDecorator { + return (target, propertyKey, descriptor): void => { + const methodName = requireMethodName(propertyKey, descriptor); + const pending = getPendingDefinitions(target.constructor); + pending.tools.push({ + name: options.name ?? methodName, + methodName, + description: options.description, + input: options.input, + }); + }; +} + +export function McpResource( + options: McpResourceOptions, +): LegacyMethodDecorator { + return (target, propertyKey, descriptor): void => { + const methodName = requireMethodName(propertyKey, descriptor); + const pending = getPendingDefinitions(target.constructor); + pending.resources.push({ + name: options.name ?? methodName, + methodName, + uri: options.uri, + mimeType: options.mimeType, + description: options.description, + }); + }; +} + +export function McpPrompt(options: McpPromptOptions): LegacyMethodDecorator { + return (target, propertyKey, descriptor): void => { + const methodName = requireMethodName(propertyKey, descriptor); + const pending = getPendingDefinitions(target.constructor); + pending.prompts.push({ + name: options.name ?? methodName, + methodName, + description: options.description, + }); + }; +} + +function getPendingDefinitions(target: object): PendingDefinitions { + const existing = pendingDefinitions.get(target); + if (existing !== undefined) { + return existing; + } + + const pending = emptyDefinitions(); + pendingDefinitions.set(target, pending); + return pending; +} + +function emptyDefinitions(): PendingDefinitions { + return { tools: [], resources: [], prompts: [] }; +} + +function requireMethodName( + propertyKey: string | symbol, + descriptor: PropertyDescriptor, +): string { + if (typeof propertyKey !== "string") { + throw new TypeError("MCP decorators require string-named methods"); + } + if (typeof descriptor.value !== "function") { + throw new TypeError("MCP decorators can decorate methods only"); + } + return propertyKey; +} diff --git a/test/langchain-documentation-contract.test.ts b/test/langchain-documentation-contract.test.ts index b114eff..48dd0cc 100644 --- a/test/langchain-documentation-contract.test.ts +++ b/test/langchain-documentation-contract.test.ts @@ -76,7 +76,7 @@ describe("LangChain current-facing documentation contract", () => { .flat() .join("\n"); - expect(combined).toContain("@theorvane/type-mcp@0.2.2"); + expect(combined).toContain("@theorvane/type-mcp@0.3.0"); expect(combined).toContain( "strict declarations, validation, MCP SDK compilation, stdio, or Streamable HTTP", ); diff --git a/test/legacy-decorators.test.ts b/test/legacy-decorators.test.ts new file mode 100644 index 0000000..7b2145b --- /dev/null +++ b/test/legacy-decorators.test.ts @@ -0,0 +1,82 @@ +import { describe, expect, it } from "vitest"; +import { z } from "zod"; +import { + getMcpServerDefinition, + McpPrompt, + McpResource, + McpServer, + McpTool, +} from "../src/legacy.js"; + +describe("legacy MCP decorators", () => { + it("registers public instance components in the existing server definition format", () => { + const input = z.object({ query: z.string() }); + + class LegacyServer { + search({ query }: { readonly query: string }) { + return { query }; + } + + config() { + return { region: "ap-northeast-2" }; + } + + prompt() { + return "Search the catalog."; + } + } + + const prototype = LegacyServer.prototype; + McpTool({ name: "search", description: "Searches the catalog.", input })( + prototype, + "search", + Object.getOwnPropertyDescriptor(prototype, "search") ?? {}, + ); + McpResource({ uri: "config://legacy" })( + prototype, + "config", + Object.getOwnPropertyDescriptor(prototype, "config") ?? {}, + ); + McpPrompt({ description: "Creates a search prompt." })( + prototype, + "prompt", + Object.getOwnPropertyDescriptor(prototype, "prompt") ?? {}, + ); + McpServer({ name: "legacy", version: "1.0.0" })(LegacyServer); + + expect(getMcpServerDefinition(LegacyServer)).toEqual({ + name: "legacy", + version: "1.0.0", + tools: [ + { + name: "search", + methodName: "search", + description: "Searches the catalog.", + input, + }, + ], + resources: [ + { name: "config", methodName: "config", uri: "config://legacy" }, + ], + prompts: [ + { + name: "prompt", + methodName: "prompt", + description: "Creates a search prompt.", + }, + ], + }); + }); + + it("rejects non-method and symbol-named declarations", () => { + const input = z.object({}); + const symbol = Symbol("legacy"); + + expect(() => + McpTool({ input })({}, symbol, { value: () => undefined }), + ).toThrow("MCP decorators require string-named methods"); + expect(() => McpTool({ input })({}, "field", {})).toThrow( + "MCP decorators can decorate methods only", + ); + }); +}); diff --git a/test/reference-documentation-contract.test.ts b/test/reference-documentation-contract.test.ts index 1398f9f..51a854d 100644 --- a/test/reference-documentation-contract.test.ts +++ b/test/reference-documentation-contract.test.ts @@ -14,7 +14,7 @@ describe("reference-first TypeMCP documentation", () => { await Promise.all(documents.map((path) => readFile(path, "utf8"))) ).join("\n"); - expect(content).toContain("@theorvane/type-mcp@0.2.2"); + expect(content).toContain("@theorvane/type-mcp@0.3.0"); expect(content).toContain("Inspect a declaration"); expect(content).toContain("Run over stdio"); expect(content).toContain("Serve Streamable HTTP"); @@ -59,7 +59,7 @@ describe("reference-first TypeMCP documentation", () => { expect(content, path).toMatch(/## Next steps/); } - expect(allContent).toContain("@theorvane/type-mcp@0.2.2"); + expect(allContent).toContain("@theorvane/type-mcp@0.3.0"); expect(allContent).not.toMatch(/npm install @theorvane\/type-mcp(?:\s|$)/); expect(allContent).toContain("npm run stdio"); expect(consumerScript).toContain( diff --git a/tsup.config.ts b/tsup.config.ts index 88ef4f6..70b2751 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -5,6 +5,7 @@ export default defineConfig({ index: "src/index.ts", http: "src/http.ts", langchain: "src/langchain.ts", + legacy: "src/legacy.ts", }, format: ["esm", "cjs"], dts: true,