You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@mcp-apps-kit/core

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mcp-apps-kit/core - npm Package Compare versions

Comparing version
0.4.0
to
0.5.0
+4
-4
package.json
{
"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