Skip to content

Commit 9aaf706

Browse files
feat((versioning): Implemented versioned route apps (#63)
* feat: Implement API versioning in minimal example with enhanced greet tools - Updated README to reflect versioning support and added API endpoint details. - Refactored index.ts to define separate greet tools for v1 (name only) and v2 (name + surname). - Removed deprecated GreetingWidget component and created version-specific UI components. - Enhanced styles.css to support version badges and improved UI layout. - Updated core to support multi-version app configurations and validation. * docs: Update README and improve code formatting for clarity - Enhanced README with clearer API endpoint descriptions and input/output specifications. - Improved formatting in GreetingWidget components for better readability. - Refactored createApp.ts and server/index.ts for consistent code style and lazy initialization of JWKS client. - Updated OAuth middleware to support JWKS client as a getter function. * feat: Add configuration support for protocol in minimal example app - Introduced a new configuration option for the app to specify the protocol as "openai". - Updated createApp.ts to manage a shared HTTP server for multi-version applications, enhancing server instance accessibility. - Improved comments for clarity regarding server instance handling and debug logger configuration. * docs: Enhance README with API versioning details and examples - Added a new section on API versioning, explaining how to expose multiple versions from a single app. - Included code examples demonstrating version-specific tools, configuration overrides, and middleware. - Updated existing examples to reflect API versioning capabilities. * docs: Update README and core documentation for improved clarity and formatting - Added blank lines for better readability in README and core documentation. - Reformatted tool definitions in the core README for consistency. - Ensured consistent code style across examples in the documentation. * refactor: Simplify GreetingWidgetV2 input handling and improve createApp comments - Removed unnecessary surname check in GreetingWidgetV2 onKeyDown event. - Updated comments in createApp.ts for clarity on version-specific config merging and server instance handling. - Cleaned up versioning test file by removing unused server cleanup logic. * feat: Add health check and OpenAI domain challenge endpoints to createApp - Implemented a health check endpoint that returns the app status and available versions. - Added support for handling OpenAI domain verification challenge requests. - Updated routing logic to return 404 for unmatched routes, ensuring consistency with Express behavior. - Enhanced comments for clarity on event handling and memory management in the request processing flow. * feat: Enhance configuration validation and deep merge functionality - Updated `validateGlobalConfig` to accept `VersionSpecificConfig`, allowing null values to disable properties. - Introduced `deepMerge` function for recursively merging global and version-specific configurations, with support for null to remove properties. - Updated `mergeVersionConfig` to utilize deep merging for nested objects, ensuring proper handling of undefined and null values. - Added tests for deep merging behavior, including scenarios for overriding, disabling, and inheriting configurations. - Enhanced type definitions for better clarity on configuration structures. * docs: Remove example curl commands from index.ts for cleaner documentation - Eliminated example curl commands from the index.ts file to streamline the documentation. - Focused on providing a clearer overview of available endpoints without cluttering the content. * refactor: Improve readability and consistency in createApp.ts - Reformatted conditional checks in validateGlobalConfig for better clarity. - Updated mergeVersionConfig to enhance readability by using parentheses for nested expressions. - Ensured consistent handling of null and undefined values in configuration merging logic. * refactor: Improve deep merge logic in createApp.ts for better property handling - Updated deepMerge function to build the result object without null properties. - Replaced direct property deletion with Reflect.deleteProperty to comply with ESLint rules. - Enhanced comments for clarity on the merging process and runtime validation in versioning tests.
1 parent c4d50c8 commit 9aaf706

File tree

14 files changed

+2688
-201
lines changed

14 files changed

+2688
-201
lines changed

README.md

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ This project may be a poor fit if you:
6262
## Features
6363

6464
- Single `createApp()` entry point to define tools and UI once
65+
- **API Versioning**: Expose multiple API versions from a single app (e.g., `/v1/mcp`, `/v2/mcp`)
6566
- Type-safe tool bindings with full TypeScript inference for inputs, outputs, and UI access
6667
- Protocol abstraction so UI code works identically on both platforms
6768
- OAuth 2.1 security with JWT validation and JWKS discovery (RFC 6750, RFC 8414)
@@ -181,6 +182,70 @@ export type AppTools = typeof app.tools;
181182
export type AppClientTools = ClientToolsFromCore<AppTools>;
182183
```
183184

185+
### API Versioning
186+
187+
Expose multiple API versions from a single application, each with its own tools and UI:
188+
189+
```typescript
190+
const app = createApp({
191+
name: "my-app",
192+
193+
// Shared config across all versions
194+
config: {
195+
cors: { origin: true },
196+
oauth: { authorizationServer: "https://auth.example.com" },
197+
},
198+
199+
// Version-specific tools and config
200+
versions: {
201+
v1: {
202+
version: "1.0.0",
203+
tools: {
204+
greet: defineTool({
205+
description: "Greet v1",
206+
input: z.object({ name: z.string() }),
207+
output: z.object({ message: z.string() }),
208+
handler: async ({ name }) => ({ message: `Hello, ${name}!` }),
209+
}),
210+
},
211+
},
212+
v2: {
213+
version: "2.0.0",
214+
tools: {
215+
greet: defineTool({
216+
description: "Greet v2",
217+
input: z.object({ name: z.string(), surname: z.string().optional() }),
218+
output: z.object({ message: z.string() }),
219+
handler: async ({ name, surname }) => ({
220+
message: `Hello, ${name} ${surname || ""}!`.trim(),
221+
}),
222+
}),
223+
},
224+
// Version-specific config overrides global config
225+
config: {
226+
protocol: "openai",
227+
},
228+
},
229+
},
230+
});
231+
232+
// Access version info programmatically
233+
console.log(app.getVersions()); // ["v1", "v2"]
234+
const v2App = app.getVersion("v2");
235+
236+
// Each version is exposed at its dedicated route
237+
// - v1: http://localhost:3000/v1/mcp
238+
// - v2: http://localhost:3000/v2/mcp
239+
```
240+
241+
Each version can have:
242+
243+
- Different tools and tool schemas
244+
- Version-specific UI components
245+
- Config overrides (merged with global config)
246+
- Version-specific plugins (merged with global plugins)
247+
- Shared middleware and OAuth configuration
248+
184249
### UI Setup (React)
185250

186251
```typescript
@@ -455,7 +520,7 @@ npm run dev
455520

456521
Local examples:
457522

458-
- [examples/minimal](examples/minimal/) - minimal server and UI widget
523+
- [examples/minimal](examples/minimal/) - minimal server with API versioning (v1 and v2)
459524
- [examples/restaurant-finder](examples/restaurant-finder/) - end-to-end app with search functionality
460525

461526
## API

examples/minimal/README.md

Lines changed: 91 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
# Minimal Example
1+
# Minimal Example with Versioning
22

3-
A simple "hello world" example demonstrating basic @mcp-apps-kit/core usage.
3+
A simple example demonstrating @mcp-apps-kit/core versioning support - exposing multiple API versions from a single application.
44

55
## Features
66

7-
- Single tool definition with Zod schema validation
8-
- Simple UI widget showing greeting messages
9-
- Basic server setup
7+
- **API Versioning**: Two API versions exposed at different routes
8+
- `v1`: Simple greet tool (name only)
9+
- `v2`: Enhanced greet tool (name + optional surname)
10+
- **Shared Configuration**: CORS, debug settings shared across versions
11+
- **Type-Safe Tools**: Full TypeScript support for each version's tools
12+
- **React UI Widgets**: Version-specific UI components
1013

1114
## Quick Start
1215

@@ -22,16 +25,42 @@ pnpm build
2225
pnpm start
2326
```
2427

28+
## API Endpoints
29+
30+
Once running, the server exposes:
31+
32+
| Endpoint | Description |
33+
| -------------- | --------------------------- |
34+
| `GET /health` | Health check |
35+
| `POST /v1/mcp` | MCP v1 API (name only) |
36+
| `POST /v2/mcp` | MCP v2 API (name + surname) |
37+
38+
## Testing the API
39+
40+
```bash
41+
# v1: Greet with name only
42+
curl -X POST http://localhost:3000/v1/mcp \
43+
-H "Content-Type: application/json" \
44+
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"greet","arguments":{"name":"World"}},"id":1}'
45+
46+
# v2: Greet with name and surname
47+
curl -X POST http://localhost:3000/v2/mcp \
48+
-H "Content-Type: application/json" \
49+
-d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"greet","arguments":{"name":"John","surname":"Doe"}},"id":1}'
50+
```
51+
2552
## Connecting to Claude Desktop
2653

2754
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
2855

2956
```json
3057
{
3158
"mcpServers": {
32-
"minimal-app": {
33-
"command": "npx",
34-
"args": ["tsx", "path/to/examples/minimal/src/index.ts"]
59+
"minimal-app-v1": {
60+
"url": "http://localhost:3000/v1/mcp"
61+
},
62+
"minimal-app-v2": {
63+
"url": "http://localhost:3000/v2/mcp"
3564
}
3665
}
3766
}
@@ -42,18 +71,20 @@ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_
4271
```
4372
minimal/
4473
src/
45-
index.ts # Server with tool definition
74+
index.ts # Server with versioned app setup
4675
ui/
47-
index.html # Widget HTML entry
48-
main.ts # Widget TypeScript
76+
GreetingWidgetV1.tsx # V1 UI widget (name only)
77+
GreetingWidgetV2.tsx # V2 UI widget (name + surname)
78+
styles.css # Shared styles
79+
dist/ # Built UI HTML files
4980
package.json
5081
tsconfig.json
5182
vite.config.ts
5283
```
5384

54-
## Tool
85+
## Tools
5586

56-
### `greet`
87+
### V1: `greet`
5788

5889
Greet someone by name.
5990

@@ -65,3 +96,50 @@ Greet someone by name.
6596

6697
- `message` (string): Greeting message
6798
- `timestamp` (string): ISO timestamp
99+
100+
### V2: `greet`
101+
102+
Greet someone by name and optional surname.
103+
104+
**Input:**
105+
106+
- `name` (string): First name to greet
107+
- `surname` (string, optional): Surname
108+
109+
**Output:**
110+
111+
- `message` (string): Greeting message
112+
- `fullName` (string): The full name used in greeting
113+
- `timestamp` (string): ISO timestamp
114+
115+
## Versioning Configuration
116+
117+
The app uses the `createApp` versioning feature:
118+
119+
```typescript
120+
const app = createApp({
121+
name: "minimal-app",
122+
123+
// Shared config across all versions
124+
config: {
125+
cors: { origin: true },
126+
debug: { logTool: true, level: "info" },
127+
},
128+
129+
// Version-specific tools and config
130+
versions: {
131+
v1: {
132+
version: "1.0.0",
133+
tools: { greet: greetToolV1 },
134+
},
135+
v2: {
136+
version: "2.0.0",
137+
tools: { greet: greetToolV2 },
138+
},
139+
},
140+
});
141+
142+
// Access version info programmatically
143+
console.log(app.getVersions()); // ["v1", "v2"]
144+
const v2App = app.getVersion("v2");
145+
```

0 commit comments

Comments
 (0)