Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,31 @@

- Meta-tools return JSON with `success` and `message` fields. Read `message` to adapt decisions (e.g., policy denial, already active, limits exceeded).

## HTTP (debugging only)
## HTTP endpoints

- Endpoints: `GET /healthz`, `GET /tools`, `POST/GET/DELETE /mcp`, `GET /.well-known/mcp-config`.
- Headers: use `mcp-client-id` to reuse per-client server bundles; `mcp-session-id` is managed by the MCP transport after initialize.
### Built-in MCP endpoints

- `GET /healthz` - Health check
- `GET /tools` - List available toolsets and tools
- `POST /mcp` - MCP JSON-RPC requests
- `GET /mcp` - Server-sent events stream
- `DELETE /mcp` - Close session
- `GET /.well-known/mcp-config` - Configuration schema

### Custom HTTP endpoints

Servers may expose custom REST-like endpoints alongside MCP protocol endpoints. These are defined by the server implementer and provide direct HTTP access to functionality.

- Custom endpoints use standard HTTP methods (GET, POST, PUT, DELETE, PATCH)
- Request/response validation via Zod schemas
- Access to client ID via `mcp-client-id` header
- Permission-aware endpoints receive client's allowed toolsets
- Standard error format with `VALIDATION_ERROR`, `INTERNAL_ERROR`, or `RESPONSE_VALIDATION_ERROR` codes

Check `GET /tools` or server documentation to discover available custom endpoints.

### Headers

- `mcp-client-id`: Client identifier (reuse for per-client sessions)
- `mcp-session-id`: Session identifier (managed by MCP transport after initialize)
- `mcp-toolset-permissions`: Comma-separated toolset list (permission-based servers with header-based permissions)
133 changes: 132 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- [Static startup](#static-startup)
- [Permission-based starter guide](#permission-based-starter-guide)
- [Permission configuration approaches](#permission-configuration-approaches)
- [Custom HTTP endpoints](#custom-http-endpoints)
- [API](#api)
- [createMcpServer](#createmcpserveroptions)
- [createPermissionBasedMcpServer](#createpermissionbasedmcpserveroptions)
Expand Down Expand Up @@ -589,6 +590,135 @@ await start();

**Note:** Resolver functions must be synchronous. If you need to fetch permissions from external sources, do so before server creation and cache the results.

## Custom HTTP endpoints

Toolception supports custom HTTP endpoints alongside MCP protocol endpoints, enabling REST-like APIs with Zod validation and type inference.

### Basic usage

```ts
import { createMcpServer, defineEndpoint } from "toolception";
import { z } from "zod";

const { start } = await createMcpServer({
// ... standard options
http: {
port: 3000,
customEndpoints: [
defineEndpoint({
method: "GET",
path: "/api/users",
querySchema: z.object({
limit: z.coerce.number().int().positive().default(10),
role: z.enum(["admin", "user"]).optional(),
}),
responseSchema: z.object({
users: z.array(z.object({ id: z.string(), name: z.string() })),
}),
handler: async (req) => {
// req.query is typed: { limit: number, role?: "admin" | "user" }
// req.clientId is available from mcp-client-id header
return { users: [{ id: "1", name: "Alice" }] };
},
}),
],
},
});
```

### Validation schemas

- **querySchema**: Validates URL query parameters (use `z.coerce` for type conversion)
- **bodySchema**: Validates request body (POST/PUT/PATCH)
- **paramsSchema**: Validates path parameters (e.g., `/users/:userId`)
- **responseSchema**: Validates handler response (prevents invalid data leakage)

### Request context

Handlers receive a typed request object:

```ts
{
body: TBody, // Validated from bodySchema
query: TQuery, // Validated from querySchema
params: TParams, // Validated from paramsSchema
headers: Record<string, string | string[] | undefined>,
clientId: string, // From mcp-client-id header or auto-generated
}
```

### Permission-aware endpoints

Use `definePermissionAwareEndpoint` in permission-based servers to access client permissions:

```ts
import { createPermissionBasedMcpServer, definePermissionAwareEndpoint } from "toolception";

const { start } = await createPermissionBasedMcpServer({
// ... permission config
http: {
customEndpoints: [
definePermissionAwareEndpoint({
method: "GET",
path: "/api/me",
responseSchema: z.object({
clientId: z.string(),
allowedToolsets: z.array(z.string()),
isAdmin: z.boolean(),
}),
handler: async (req) => {
// req.allowedToolsets and req.failedToolsets are available
return {
clientId: req.clientId,
allowedToolsets: req.allowedToolsets,
isAdmin: req.allowedToolsets.includes("admin-tools"),
};
},
}),
],
},
});
```

### Error handling

Validation failures return standardized error responses:

- **400 VALIDATION_ERROR**: Request validation failed (body, query, or params)
- **500 INTERNAL_ERROR**: Handler threw an exception
- **500 RESPONSE_VALIDATION_ERROR**: Response validation failed

Example error response:

```json
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Validation failed for query",
"details": [
{
"code": "invalid_type",
"path": ["limit"],
"message": "Expected number, received string"
}
]
}
}
```

### Reserved paths

Custom endpoints cannot override built-in MCP paths:

- `/mcp` - MCP JSON-RPC endpoint
- `/healthz` - Health check
- `/tools` - Tool listing
- `/.well-known/mcp-config` - Configuration schema

### Complete example

See `examples/custom-endpoints-demo.ts` for a full working example with GET, POST, PUT, DELETE endpoints, pagination, and permission-aware handlers.

## API

### createMcpServer(options)
Expand Down Expand Up @@ -736,9 +866,10 @@ const moduleLoaders = {

#### options.http (optional)

`{ host?: string; port?: number; basePath?: string; cors?: boolean; logger?: boolean }`
`{ host?: string; port?: number; basePath?: string; cors?: boolean; logger?: boolean; customEndpoints?: CustomEndpointDefinition[] }`

- Fastify transport configuration. Defaults: host `0.0.0.0`, port `3000`, basePath `/`, CORS enabled, logger disabled.
- `customEndpoints`: Optional array of custom HTTP endpoints to register alongside MCP protocol endpoints. See [Custom HTTP endpoints](#custom-http-endpoints) for details.

#### options.createServer (optional)

Expand Down
35 changes: 23 additions & 12 deletions examples/custom-endpoints-demo.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
#!/usr/bin/env tsx

/**
* Example server demonstrating custom endpoint functionality.
* Custom Endpoints Demo - Comprehensive examples of HTTP endpoint integration
*
* This example shows:
* - Basic GET/POST/PUT/DELETE endpoints with Zod validation
* - Query parameter validation and coercion
* - Path parameter validation
* - Request body validation
* - Response validation
* - Client ID extraction
* - Permission-aware endpoints
* This example demonstrates:
*
* Run this example with:
* npx tsx examples/custom-endpoints-demo.ts
* VALIDATION:
* - Query parameter validation with Zod (coercion, defaults, enums)
* - Path parameter validation (/users/:userId)
* - Request body validation (POST/PUT)
* - Response schema validation
*
* Then test the endpoints with curl:
* FEATURES:
* - All HTTP methods: GET, POST, PUT, DELETE
* - Client ID extraction from mcp-client-id header
* - Permission-aware endpoints (allowedToolsets, failedToolsets)
* - Type-safe handlers with automatic inference
* - Standard error responses (VALIDATION_ERROR, INTERNAL_ERROR)
*
* EXAMPLES:
* 1. Standard server with custom endpoints (port 3000)
* 2. Permission-based server with permission-aware endpoints (port 3001)
*
* Run this example:
* npx tsx examples/custom-endpoints-demo.ts # Standard server
* npx tsx examples/custom-endpoints-demo.ts permission # Permission-based server
*
* Quick test:
* curl "http://localhost:3000/api/users?limit=5"
* curl -X POST http://localhost:3000/api/users -H 'Content-Type: application/json' -d '{"name":"Alice","email":"alice@example.com"}'
*/
Expand Down