Skip to content
Open
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
28 changes: 25 additions & 3 deletions packages/middleware/hono/src/hono.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Context } from 'hono';
import { Hono } from 'hono';
import type { BlankEnv } from 'hono/types';

import { hostHeaderValidation, localhostHostValidation } from './middleware/hostHeaderValidation.js';

Expand Down Expand Up @@ -37,14 +38,35 @@ export interface CreateMcpHonoAppOptions {
*
* @param options - Configuration options
* @returns A configured Hono application
*
* @example
* ```typescript
* // With custom Env type for Cloudflare Workers
* type Env = {
* Bindings: {
* DB: D1Database;
* };
* Variables: {
* user: User;
* };
* };
*
* const app = createMcpHonoApp<Env>();
*
* app.get('/data', async (c) => {
* const db = c.env.DB; // typed as D1Database
* const user = c.get('user'); // typed as User
* // ...
* });
* ```
*/
export function createMcpHonoApp(options: CreateMcpHonoAppOptions = {}): Hono {
export function createMcpHonoApp<E extends BlankEnv = BlankEnv>(options: CreateMcpHonoAppOptions = {}): Hono<E> {
const { host = '127.0.0.1', allowedHosts } = options;

const app = new Hono();
const app = new Hono<E>();

// Similar to `express.json()`: parse JSON bodies and make them available to MCP adapters via `parsedBody`.
app.use('*', async (c: Context, next) => {
app.use('*', async (c: Context<E>, next) => {
// If an upstream middleware already set parsedBody, keep it.
if (c.get('parsedBody') !== undefined) {
return await next();
Expand Down
43 changes: 43 additions & 0 deletions packages/middleware/hono/test/hono.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,46 @@ describe('@modelcontextprotocol/hono', () => {
expect(await res.json()).toEqual({ preset: true });
});
});

describe('createMcpHonoApp generic type support', () => {
test('createMcpHonoApp accepts generic Env type parameter', () => {
// Define a custom Env type with Bindings and Variables
type CustomEnv = {
Bindings: {
DB: { query: (sql: string) => Promise<unknown[]> };
};
Variables: {
userId: string;
};
};

// Create app with custom Env type
const app = createMcpHonoApp<CustomEnv>();

// Add a route that uses the typed env and variables
app.get('/test', async (c) => {
// c.env.DB should be typed
const db = c.env.DB;

// c.get('userId') should be typed as string
const userId = c.get('userId');

// TypeScript should allow setting a string variable
c.set('userId', '123');

return c.json({ db: typeof db, userId: typeof userId });
});

// App should be created successfully
expect(app).toBeInstanceOf(Hono);
});

test('createMcpHonoApp works without generic parameter (backward compatibility)', () => {
// Should work without any generic parameter (uses BlankEnv default)
const app = createMcpHonoApp();

app.get('/health', c => c.text('ok'));

expect(app).toBeInstanceOf(Hono);
});
});
Loading