Skip to content

Commit a3b3f80

Browse files
docs: document runtime model, ASYNC semantics, and MCP SDK milestone
Clarify singleton-per-class concurrency, pseudo-async Mono.fromCallable behavior, and 2.0.0-M3 stability expectations in README, site docs, and llms files. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent aaf86c3 commit a3b3f80

8 files changed

Lines changed: 125 additions & 7 deletions

File tree

README.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,36 @@ streamable:
266266
port: 8080
267267
```
268268

269+
### Runtime model and stability
270+
271+
#### SYNC vs ASYNC (`type`)
272+
273+
The `type` setting selects which **MCP Java SDK server API** the framework uses. It does **not** turn your component methods into reactive code.
274+
275+
| `type` | What happens |
276+
|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
277+
| `SYNC` | Handlers invoke your `@McpTool` / `@McpPrompt` / `@McpResource` methods on the request thread. |
278+
| `ASYNC` | Handlers return Reactor `Mono` values for MCP SDK compatibility. The SDK wraps each call in `Mono.fromCallable(...)` — your method body is still a normal **blocking** Java invocation. |
279+
280+
**ASYNC is not a non-blocking or Project Reactor programming model.** You do not implement `Mono`/`Flux` in annotated methods. Long-running or CPU-heavy work still occupies a Reactor worker thread. Use **SYNC** unless your deployment specifically requires the async MCP server API. For high concurrency, keep handlers short and tune `request-timeout`.
281+
282+
#### Component instances and concurrency
283+
284+
The SDK creates **one instance per component class** (no-arg constructor) and reuses it for every MCP request to methods on that class. Concurrent calls share the same object.
285+
286+
- Prefer **stateless** component classes, or **thread-safe** mutable state only.
287+
- Do not store per-request data in instance fields without proper synchronization.
288+
- Delegate shared mutable state to thread-safe services when needed.
289+
290+
`McpApplicationContext.from(...)` currently uses this default singleton-per-class factory. There is no built-in Spring/CDI wiring in the public API today.
291+
292+
#### MCP Java SDK 2.x (milestone)
293+
294+
This project builds on the official [MCP Java SDK](https://github.com/modelcontextprotocol/java-sdk) **2.0.0-M3**, a **pre-release milestone**. APIs may change before 2.0 GA — pin dependency versions and re-run tests when upgrading.
295+
296+
- **STREAMABLE** is the recommended HTTP transport for new projects.
297+
- **SSE** is still supported for compatibility but is **deprecated** in MCP SDK 2.x; avoid new SSE deployments.
298+
269299
### Multilingual Support (i18n)
270300

271301
Enable i18n for your MCP components:
@@ -353,7 +383,15 @@ Run the test suite:
353383

354384
### Q: Can I use this in production?
355385

356-
**A:** This project is currently in active development. While it's stable for development and testing, we recommend thorough testing before production use.
386+
**A:** The annotated layer is stable for development and testing, but it depends on the official MCP Java SDK **2.0.0-M3** (a milestone release). Pin versions, run your own integration tests, and expect possible SDK API changes before 2.0 GA before relying on it in production.
387+
388+
### Q: What does `type: ASYNC` mean?
389+
390+
**A:** It selects the async MCP server API from the underlying SDK. Your `@McpTool` / `@McpPrompt` / `@McpResource` methods remain ordinary blocking Java code; the framework wraps them in `Mono.fromCallable(...)`. Use SYNC unless you specifically need the async server API.
391+
392+
### Q: Are component classes singletons?
393+
394+
**A:** Yes. The SDK creates one instance per component class and reuses it for all requests. Keep components stateless or thread-safe; do not keep per-request mutable state on the instance without synchronization.
357395

358396
### Q: What Java version is required?
359397

README.zh-CN.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,36 @@ streamable:
268268
port: 8080
269269
```
270270

271+
### 运行时模型与稳定性
272+
273+
#### SYNC 与 ASYNC(`type`)
274+
275+
`type` 配置项决定框架使用哪一种 **MCP Java SDK 服务端 API**,**不会**把你的组件方法变成响应式代码。
276+
277+
| `type` | 行为 |
278+
|---------|---------------------------------------------------------------------------------------------------------------|
279+
| `SYNC` | 在请求线程上直接调用 `@McpTool` / `@McpPrompt` / `@McpResource` 方法。 |
280+
| `ASYNC` | 为兼容 MCP SDK 的异步 API,handler 返回 Reactor `Mono`。SDK 用 `Mono.fromCallable(...)` 包装调用 — 方法体仍是普通的 **阻塞式** Java 调用。 |
281+
282+
**ASYNC 不是非阻塞或 Project Reactor 编程模型。** 注解方法中无需也不应返回 `Mono`/`Flux`。耗时或 CPU 密集的逻辑仍会占用 Reactor 工作线程。除非部署环境明确要求 async MCP 服务端 API,否则优先使用 **SYNC**。高并发场景请保持 handler 简短,并合理设置 `request-timeout`。
283+
284+
#### 组件实例与并发
285+
286+
SDK 为每个组件类创建 **唯一实例**(无参构造),该类上所有 MCP 请求共享该对象。
287+
288+
- 组件类应 **无状态**,或仅持有 **线程安全** 的可变状态。
289+
- 不要在实例字段中存放未同步的 per-request 数据。
290+
- 需要共享可变状态时,请委托给线程安全的服务层。
291+
292+
当前 `McpApplicationContext.from(...)` 使用默认的单例-per-class 工厂;公开 API 尚未内置 Spring/CDI 集成。
293+
294+
#### MCP Java SDK 2.x(里程碑版本)
295+
296+
本项目基于官方 [MCP Java SDK](https://github.com/modelcontextprotocol/java-sdk) **2.0.0-M3**,属于 **预发布里程碑**。2.0 正式版发布前 API 可能变更 — 请锁定依赖版本,升级后重新跑集成测试。
297+
298+
- 新项目 HTTP 传输推荐 **STREAMABLE**。
299+
- **SSE** 仍可兼容使用,但在 MCP SDK 2.x 中已 **弃用**,不建议新部署采用。
300+
271301
### 多语言支持(i18n)
272302

273303
为 MCP 组件启用国际化:
@@ -355,7 +385,15 @@ Windows 下可使用 `mvnw.cmd clean test`。
355385

356386
### 问:可以用于生产环境吗?
357387

358-
**答:** 项目仍在积极开发中。用于开发与测试已较稳定,生产使用前建议充分验证。
388+
**答:** 注解层用于开发与测试已较稳定,但依赖官方 MCP Java SDK **2.0.0-M3**(里程碑版本)。生产使用前请锁定依赖版本、跑完自己的集成测试,并关注 2.0 正式版发布前可能的 SDK API 变更。
389+
390+
### 问:`type: ASYNC` 是什么意思?
391+
392+
**答:** 表示使用底层 SDK 的 async MCP 服务端 API。你的 `@McpTool` / `@McpPrompt` / `@McpResource` 方法仍是普通阻塞 Java 代码,框架会用 `Mono.fromCallable(...)` 包装。无特殊需求时请优先使用 SYNC。
393+
394+
### 问:组件类是单例吗?
395+
396+
**答:** 是。SDK 为每个组件类创建一个实例并在所有请求间复用。请保持组件无状态或线程安全,不要在实例字段中存放未同步的 per-request 可变状态。
359397

360398
### 问:需要什么 Java 版本?
361399

docs/components.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,18 @@ public int add(
267267

268268
After defining MCP components, they will be automatically registered to the server. You just need to ensure that the component classes are in the package scanning path of the server application.
269269

270+
### One instance per component class
271+
272+
The SDK creates a single object per component class (via its no-arg constructor) and invokes all annotated methods on that class through the same instance. **Concurrent requests share one object**, so:
273+
274+
- Keep component classes stateless when possible.
275+
- Any mutable instance fields must be thread-safe, or you must synchronize access.
276+
- Do not treat instance fields as per-request storage.
277+
278+
### SYNC vs ASYNC server type
279+
280+
Setting `type: ASYNC` in `mcp-server.yml` uses the async MCP server API from the underlying SDK. Handlers are implemented with `Mono.fromCallable(...)` around your blocking method — **ASYNC mode is not Project Reactor**. Your `@McpTool`, `@McpPrompt`, and `@McpResource` methods remain ordinary synchronous Java code.
281+
270282
### Specify Package Path
271283

272284
If you need to specify a specific package path, you can use the following methods:

docs/getting-started.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,25 @@ streamable:
188188
| `streamable.keep-alive-interval` | Keep-alive interval (ms) | `20000` |
189189
| `streamable.port` | HTTP port for STREAMABLE mode | `8080` |
190190

191+
## Runtime model and stability
192+
193+
### SYNC vs ASYNC (`type`)
194+
195+
The `type` property selects the MCP Java SDK server API (`SYNC` or `ASYNC`). It does **not** make your annotated methods reactive.
196+
197+
- **SYNC** — methods run on the request thread.
198+
- **ASYNC** — the SDK exposes async handlers that wrap your method in `Mono.fromCallable(...)`. Your code is still **blocking** Java; you do not return `Mono` from `@McpTool` / `@McpPrompt` / `@McpResource` methods.
199+
200+
Use **SYNC** by default. Choose **ASYNC** only when your deployment requires the async MCP server API. Long work still blocks a Reactor worker thread under ASYNC.
201+
202+
### Component instances and concurrency
203+
204+
The SDK creates **one instance per component class** and reuses it for all requests. Concurrent MCP calls share that object. Keep components **stateless** or **thread-safe**; avoid unsynchronized per-request instance fields.
205+
206+
### MCP Java SDK 2.x (milestone)
207+
208+
This SDK depends on MCP Java SDK **2.0.0-M3** (pre-release). Pin versions and retest when upgrading. Prefer **STREAMABLE** over deprecated **SSE** for new HTTP deployments.
209+
191210
## Profile-based Configuration
192211

193212
You can use profiles for different environments:

docs/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ This SDK is especially suitable for the following scenarios:
5555
| **SSE** | Server-Sent Events (HTTP-based) | Real-time web applications (deprecated) |
5656
| **STREAMABLE** | HTTP streaming | Web applications, recommended for production |
5757

58+
## Runtime notes
59+
60+
- **ASYNC vs SYNC**`type: ASYNC` selects the async MCP server API; your annotated methods stay blocking Java wrapped in `Mono.fromCallable(...)`. See [Getting Started — Runtime model](./getting-started.md#runtime-model-and-stability).
61+
- **Singleton components** — one instance per component class, shared across concurrent requests; keep handlers stateless or thread-safe.
62+
- **MCP SDK 2.0.0-M3** — built on a pre-release milestone; pin versions and prefer **STREAMABLE** over deprecated **SSE**.
63+
5864
## 📖 Getting Started
5965

6066
Want to get started quickly? Check out the [Getting Started Guide](./getting-started.md) to learn how to build your first MCP server in 5 minutes.

llms-full.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,8 +517,11 @@ public String createUser(
517517
## Important Notes
518518

519519
1. **Deprecated API**: The `McpServers` class is deprecated. Use `McpApplication.run()` instead.
520-
2. **Deprecated Mode**: SSE mode is deprecated. Use STREAMABLE mode for new projects.
521-
3. **Default Required**: The default `required` value for `@McpToolParam`, `@McpPromptParam`, and `@McpJsonSchemaProperty` is `true`.
520+
2. **Deprecated Mode**: SSE mode is deprecated in MCP SDK 2.x. Use STREAMABLE mode for new HTTP projects.
521+
3. **MCP SDK milestone**: This project depends on MCP Java SDK **2.0.0-M3** (pre-release). Pin versions and retest when upgrading.
522+
4. **ASYNC is not reactive**: `type: ASYNC` selects the async MCP server API; handlers wrap blocking Java methods in `Mono.fromCallable(...)`. Annotated methods do not return `Mono`/`Flux`.
523+
5. **Singleton components**: One instance per component class is shared across concurrent requests. Keep components stateless or thread-safe.
524+
6. **Default Required**: The default `required` value for `@McpToolParam`, `@McpPromptParam`, and `@McpJsonSchemaProperty` is `true`.
522525

523526
## Links
524527

llms.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,10 @@ change-notification:
128128
## Important Notes
129129

130130
- The `McpServers` class is deprecated, use `McpApplication.run()` instead
131-
- SSE mode is deprecated, use STREAMABLE for production
131+
- SSE mode is deprecated in MCP SDK 2.x; use STREAMABLE for new HTTP deployments
132+
- Built on MCP Java SDK **2.0.0-M3** (milestone) — pin versions and retest when upgrading
133+
- `type: ASYNC` uses the async MCP server API; annotated methods stay blocking Java wrapped in `Mono.fromCallable(...)` — not Project Reactor
134+
- One instance per component class is created and shared across concurrent requests — keep components stateless or thread-safe
132135
- Components are auto-registered via package scanning
133136

134137
## Links

src/main/java/com/github/thought2code/mcp/annotated/server/component/DefaultMcpComponentInstanceFactory.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
* Default component instance factory used by the SDK.
1010
*
1111
* <p>The factory creates component instances lazily through their no-argument constructor and
12-
* caches one instance per component class. Callers may also register prebuilt instances to preserve
13-
* application state or bridge into another object lifecycle.
12+
* caches one instance per component class for the lifetime of the application context.
1413
*
1514
* @author codeboyzhou
1615
*/

0 commit comments

Comments
 (0)