@mcp-apps-kit/core
Advanced tools
+4
-4
| { | ||
| "name": "@mcp-apps-kit/core", | ||
| "version": "0.4.0", | ||
| "version": "0.5.0", | ||
| "description": "Server-side framework for building MCP applications", | ||
@@ -45,7 +45,7 @@ "type": "module", | ||
| "dependencies": { | ||
| "@modelcontextprotocol/sdk": "^1.25.1", | ||
| "@modelcontextprotocol/sdk": "^1.25.2", | ||
| "express": "^5.1.0", | ||
| "jsonwebtoken": "^9.0.3", | ||
| "jwks-rsa": "^3.2.0", | ||
| "zod": "^4.0.0" | ||
| "zod": "^4.3.5" | ||
| }, | ||
@@ -57,3 +57,3 @@ "devDependencies": { | ||
| "peerDependencies": { | ||
| "zod": "^4.0.0" | ||
| "zod": "^4.3.5" | ||
| }, | ||
@@ -60,0 +60,0 @@ "engines": { |
+327
-18
@@ -42,3 +42,3 @@ # @mcp-apps-kit/core | ||
| - Node.js: `>= 18` | ||
| - Node.js: `>= 20` | ||
| - Zod: `^4.0.0` (peer dependency) | ||
@@ -58,3 +58,3 @@ - MCP SDK: uses `@modelcontextprotocol/sdk` | ||
| ```ts | ||
| import { createApp, defineTool } from "@mcp-apps-kit/core"; | ||
| import { createApp, tool } from "@mcp-apps-kit/core"; | ||
| import { z } from "zod"; | ||
@@ -67,12 +67,11 @@ | ||
| tools: { | ||
| greet: defineTool({ | ||
| description: "Greet a user", | ||
| input: z.object({ | ||
| greet: tool("greet") | ||
| .describe("Greet a user") | ||
| .input({ | ||
| name: z.string().describe("Name to greet"), | ||
| }), | ||
| output: z.object({ message: z.string() }), | ||
| handler: async (input) => { | ||
| return { message: `Hello, ${input.name}!` }; | ||
| }, | ||
| }), | ||
| }) | ||
| .handle(async ({ name }) => { | ||
| return { message: `Hello, ${name}!` }; | ||
| }) | ||
| .build(), | ||
| }, | ||
@@ -122,9 +121,51 @@ }); | ||
| ## Type-Safe Tool Definitions | ||
| ## Defining Tools | ||
| ### The `defineTool` helper | ||
| Core provides two ways to define tools: the **Fluent Builder API** (recommended) and the **Legacy `defineTool` Helper**. Both ensure full type inference for Zod schemas. | ||
| Use `defineTool` to get automatic type inference in your handlers: | ||
| ### Fluent Tool Builder (Recommended) | ||
| The `tool()` factory provides a progressive, discoverable API for defining tools. It guides you through the definition process with type safety at every step. | ||
| ```ts | ||
| import { tool } from "@mcp-apps-kit/core"; | ||
| tools: { | ||
| search: tool("search") | ||
| .describe("Search database") | ||
| .input({ | ||
| query: z.string(), | ||
| limit: z.number().optional(), | ||
| }) | ||
| .handle(async ({ query, limit }) => { | ||
| // Types inferred automatically | ||
| return { results: await db.search(query, limit) }; | ||
| }) | ||
| .build(), | ||
| } | ||
| ``` | ||
| #### Builder Features | ||
| - **Progressive Discovery**: Chain methods to see available options (`describe`, `input`, `handle`). | ||
| - **Shortcuts**: Use `.readOnly()`, `.destructive()`, `.expensive()` for common annotations. | ||
| - **UI Attachment**: Attach UI definitions directly with `.ui()`. | ||
| - **Validation**: Ensures all required steps (description, input, handler) are completed. | ||
| ```ts | ||
| tool("deleteUser") | ||
| .describe("Delete a user account") | ||
| .input({ userId: z.string() }) | ||
| .destructive() // Adds warning annotation | ||
| .handle(async ({ userId }) => { | ||
| /* ... */ | ||
| }) | ||
| .build(); | ||
| ``` | ||
| ### Legacy: The `defineTool` helper | ||
| Use `defineTool` to get automatic type inference in your handlers with a configuration object: | ||
| ```ts | ||
| import { defineTool } from "@mcp-apps-kit/core"; | ||
@@ -145,6 +186,4 @@ | ||
| Why `defineTool`? | ||
| Why helpers? With Zod v4, TypeScript cannot infer concrete schema types across module boundaries when using generic `z.ZodType`. Both `tool()` and `defineTool` capture specific schema types at the call site enabling proper inference. | ||
| With Zod v4, TypeScript cannot infer concrete schema types across module boundaries when using generic `z.ZodType`. The `defineTool` helper captures specific schema types at the call site, enabling proper type inference without manual type assertions. | ||
| ### Alternative: object syntax with type assertions | ||
@@ -483,2 +522,272 @@ | ||
| ### Middleware Best Practices | ||
| The framework provides `defineMiddleware` helpers to prevent common mistakes like forgetting `await next()`. Two patterns are available: **basic** (void-based) for simple use cases, and **result-passing** for advanced scenarios where middleware needs to inspect or transform tool results. | ||
| #### Pattern Overview | ||
| **Basic Pattern** - Simplified middleware without result access: | ||
| - Use for logging, timing, validation, auth checks | ||
| - Automatically calls `next()` with before/after hooks | ||
| - Type-enforced `Promise<void>` return in wrap pattern | ||
| **Result-Passing Pattern** - Advanced middleware with result access: | ||
| - Use for caching, result transformation, result validation, sanitization | ||
| - Can inspect and modify tool results | ||
| - Type-enforced `Promise<TResult>` return in wrap pattern | ||
| #### Pattern Selection Guide | ||
| | Pattern | Use Cases | Result Access | Auto next()? | | ||
| | ------------------------------------- | ------------------------------------------- | ------------- | ------------ | | ||
| | `defineMiddleware({ before, after })` | Logging, metrics, timing, setup/teardown | ❌ No | ✅ Yes | | ||
| | `.before(hook)` | Validation, state initialization | ❌ No | ✅ Yes | | ||
| | `.after(hook)` | Cleanup, final logging | ❌ No | ✅ Yes | | ||
| | `.wrap(wrapper)` | Auth, conditional execution, error handling | ❌ No | ❌ Manual | | ||
| | `.withResult({ before, after })` | Result inspection, enrichment | ✅ After only | ✅ Yes | | ||
| | `.withResult.after(hook)` | Result transformation, enrichment | ✅ Yes | ✅ Yes | | ||
| | `.withResult.wrap(wrapper)` | Caching, validation, retry, sanitization | ✅ Yes | ❌ Manual | | ||
| #### Example 1: Simple Logging (Basic Pattern) | ||
| ```ts | ||
| import { defineMiddleware } from "@mcp-apps-kit/core"; | ||
| const logging = defineMiddleware({ | ||
| before: async (context) => { | ||
| console.log(`[${new Date().toISOString()}] Tool: ${context.toolName}`); | ||
| }, | ||
| after: async (context) => { | ||
| console.log(`Completed: ${context.toolName}`); | ||
| }, | ||
| }); | ||
| app.use(logging); | ||
| ``` | ||
| #### Example 2: Timing with State (Basic Pattern) | ||
| ```ts | ||
| const timing = defineMiddleware({ | ||
| before: async (context) => { | ||
| context.state.set("startTime", performance.now()); | ||
| }, | ||
| after: async (context) => { | ||
| const duration = performance.now() - (context.state.get("startTime") as number); | ||
| console.log(`${context.toolName} took ${duration.toFixed(2)}ms`); | ||
| }, | ||
| }); | ||
| app.use(timing); | ||
| ``` | ||
| #### Example 3: Authentication (Wrap Pattern) | ||
| ```ts | ||
| const auth = defineMiddleware.wrap(async (context, next) => { | ||
| const token = context.metadata.raw?.["authorization"]; | ||
| if (!token) { | ||
| throw new Error("Unauthorized"); | ||
| } | ||
| const user = await validateToken(token); | ||
| context.state.set("user", user); | ||
| return next(); | ||
| }); | ||
| app.use(auth); | ||
| ``` | ||
| #### Example 4: Result Caching (Result-Passing with Short-Circuit) | ||
| ```ts | ||
| import { defineMiddleware } from "@mcp-apps-kit/core"; | ||
| interface ToolResult { | ||
| data: unknown; | ||
| _meta?: Record<string, unknown>; | ||
| } | ||
| const cache = new Map<string, ToolResult>(); | ||
| const caching = defineMiddleware.withResult.wrap<ToolResult>(async (context, next) => { | ||
| const cacheKey = `${context.toolName}:${JSON.stringify(context.input)}`; | ||
| const cached = cache.get(cacheKey); | ||
| if (cached) { | ||
| console.log("Cache hit:", cacheKey); | ||
| return cached; // Short-circuit, don't call next() | ||
| } | ||
| console.log("Cache miss:", cacheKey); | ||
| const result = await next(); | ||
| cache.set(cacheKey, result); | ||
| return result; | ||
| }); | ||
| app.use(caching); | ||
| ``` | ||
| #### Example 5: Result Transformation (Result-Passing After Hook) | ||
| ```ts | ||
| interface ToolResult { | ||
| data: unknown; | ||
| _meta?: Record<string, unknown>; | ||
| } | ||
| const addTimestamp = defineMiddleware.withResult.after<ToolResult>(async (context, result) => { | ||
| return { | ||
| ...result, | ||
| _meta: { | ||
| ...result._meta, | ||
| processedAt: new Date().toISOString(), | ||
| toolName: context.toolName, | ||
| }, | ||
| }; | ||
| }); | ||
| app.use(addTimestamp); | ||
| ``` | ||
| #### Example 6: Result Enrichment from State | ||
| ```ts | ||
| const enrichWithUser = defineMiddleware.withResult<ToolResult>({ | ||
| before: async (context) => { | ||
| const userId = context.metadata.subject; | ||
| if (userId) { | ||
| const userProfile = await fetchUserProfile(userId); | ||
| context.state.set("userProfile", userProfile); | ||
| } | ||
| }, | ||
| after: async (context, result) => { | ||
| const userProfile = context.state.get("userProfile"); | ||
| if (userProfile) { | ||
| return { | ||
| ...result, | ||
| _meta: { | ||
| ...result._meta, | ||
| user: userProfile, | ||
| }, | ||
| }; | ||
| } | ||
| return result; // Keep original if no user | ||
| }, | ||
| }); | ||
| app.use(enrichWithUser); | ||
| ``` | ||
| #### Example 7: Result Validation & Retry | ||
| ```ts | ||
| const validateAndRetry = defineMiddleware.withResult.wrap<ToolResult>(async (context, next) => { | ||
| let attempts = 0; | ||
| const maxAttempts = 3; | ||
| while (attempts < maxAttempts) { | ||
| try { | ||
| const result = await next(); | ||
| // Validate result structure | ||
| if (!result.data) { | ||
| throw new Error("Invalid result: missing data field"); | ||
| } | ||
| return result; | ||
| } catch (error) { | ||
| attempts++; | ||
| if (attempts >= maxAttempts) throw error; | ||
| console.log(`Retry ${attempts}/${maxAttempts} for ${context.toolName}`); | ||
| await new Promise((resolve) => setTimeout(resolve, 100 * attempts)); | ||
| } | ||
| } | ||
| throw new Error("Max retries exceeded"); | ||
| }); | ||
| app.use(validateAndRetry); | ||
| ``` | ||
| #### Example 8: Result Sanitization | ||
| ```ts | ||
| interface SensitiveResult extends ToolResult { | ||
| apiKey?: string; | ||
| internalId?: string; | ||
| } | ||
| const sanitizeForPublic = defineMiddleware.withResult.wrap<SensitiveResult>( | ||
| async (context, next) => { | ||
| const result = await next(); | ||
| // Remove sensitive fields for non-admin users | ||
| const isAdmin = context.state.get("isAdmin"); | ||
| if (!isAdmin) { | ||
| const { apiKey, internalId, ...sanitized } = result; | ||
| return sanitized as SensitiveResult; | ||
| } | ||
| return result; | ||
| } | ||
| ); | ||
| app.use(sanitizeForPublic); | ||
| ``` | ||
| #### Migration from Raw Middleware | ||
| Old style (still works, but not recommended): | ||
| ```ts | ||
| app.use(async (context, next) => { | ||
| console.log("before"); | ||
| await next(); // Easy to forget await! | ||
| console.log("after"); | ||
| }); | ||
| ``` | ||
| New style (recommended): | ||
| ```ts | ||
| app.use( | ||
| defineMiddleware({ | ||
| before: async (context) => console.log("before"), | ||
| after: async (context) => console.log("after"), | ||
| }) | ||
| ); | ||
| ``` | ||
| #### Performance Considerations | ||
| - **Basic pattern**: Minimal overhead (<1% compared to raw middleware) | ||
| - **Result-passing pattern**: Small overhead (<5% for result passing through chain) | ||
| - **Caching middleware**: Can dramatically improve performance by avoiding expensive operations | ||
| - **State sharing**: Use `context.state` instead of closures for better memory efficiency | ||
| #### Type Inference | ||
| TypeScript automatically infers types when using `defineMiddleware`: | ||
| ```ts | ||
| // Type of result is inferred from generic parameter | ||
| const typed = defineMiddleware.withResult<{ count: number }>({ | ||
| after: async (context, result) => { | ||
| // result.count is typed as number | ||
| return { count: result.count + 1 }; | ||
| }, | ||
| }); | ||
| // Or explicit return type annotation | ||
| const explicit = defineMiddleware.withResult.after<ToolResult>( | ||
| async (context, result): Promise<ToolResult> => { | ||
| return { ...result, modified: true }; | ||
| } | ||
| ); | ||
| ``` | ||
| ## Debug Logging | ||
@@ -696,3 +1005,3 @@ | ||
| - `createApp`, `defineTool`, `defineUI` | ||
| - `createApp`, `tool`, `defineTool`, `defineUI` | ||
| - `createPlugin`, `loggingPlugin` | ||
@@ -699,0 +1008,0 @@ - `debugLogger`, `ClientToolsFromCore` |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
1164391
16.54%10428
14.64%1013
43.89%29
-6.45%Updated