modality-mcp-kit 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,345 @@
1
+ /**
2
+ * MCP Middleware for Hono Integration
3
+ *
4
+ * This middleware integrates the Model Context Protocol (MCP) into a Hono web server.
5
+ * It delegates all MCP protocol handling to FastMCP's built-in capabilities, avoiding
6
+ * any custom protocol implementation.
7
+ *
8
+ * Architecture:
9
+ * - Hono serves as the primary and only HTTP server (port 8800)
10
+ * - FastMCP handles all MCP protocol logic, schema conversion, and responses
11
+ * - Middleware acts as a simple proxy to FastMCP's stateless request handling
12
+ * - No manual JSON-RPC method handling - FastMCP does everything
13
+ *
14
+ * Key Principle:
15
+ * - Always reuse FastMCP's built-in functions
16
+ * - Never implement custom MCP protocol logic
17
+ * - Let FastMCP handle initialize, tools/list, tools/call, etc.
18
+ *
19
+ * Usage:
20
+ * app.use('/mcp', mcpMiddleware);
21
+ * app.use('/mcp/*', mcpMiddleware);
22
+ *
23
+ * https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema
24
+ * https://modelcontextprotocol.io/specification/2025-11-25/schema
25
+ */
26
+ import { ModalityFastMCP } from "modality-mcp-kit";
27
+ import { JSONRPCManager, getLoggerInstance, } from "modality-kit";
28
+ import { sseNotification, sseError, SSE_HEADERS, createSSEStream, } from "./sse-wrapper.js";
29
+ import { McpSessionManager } from "./McpSessionManager.js";
30
+ // Initialize FastMCP instance for internal use (NO SERVER)
31
+ export class FastHonoMcp extends ModalityFastMCP {
32
+ logger;
33
+ config;
34
+ sessions = new McpSessionManager();
35
+ currentSessionId = "";
36
+ constructor(config) {
37
+ super();
38
+ this.config = config;
39
+ }
40
+ /**
41
+ * Disconnect and cleanup a session
42
+ */
43
+ disconnect(sessionId) {
44
+ const targetSession = sessionId || this.currentSessionId;
45
+ const disconnected = this.sessions.disconnect(targetSession);
46
+ if (disconnected) {
47
+ this.logger?.info(`Session disconnected: ${targetSession}`);
48
+ }
49
+ return disconnected;
50
+ }
51
+ /**
52
+ * Ensure session exists, create if needed
53
+ */
54
+ ensureSession() {
55
+ if (!this.sessions.has(this.currentSessionId)) {
56
+ const session = this.sessions.create();
57
+ this.currentSessionId = session.id;
58
+ this.logger?.info(`Session connected: ${this.currentSessionId}`);
59
+ }
60
+ else {
61
+ this.sessions.touch(this.currentSessionId);
62
+ }
63
+ }
64
+ handler() {
65
+ return async (c, next) => {
66
+ this.logger =
67
+ this.logger || getLoggerInstance("HonoMcpMiddleware", "debug");
68
+ const url = new URL(c.req.url);
69
+ // Only handle MCP routes
70
+ if (!url.pathname.startsWith("/mcp")) {
71
+ return next();
72
+ }
73
+ try {
74
+ // Handle DELETE for session disconnect
75
+ if (c.req.method === "DELETE" && url.pathname === "/mcp") {
76
+ const requestSessionId = c.req.header("mcp-session-id");
77
+ // Validate session ID matches
78
+ if (requestSessionId && requestSessionId !== this.currentSessionId) {
79
+ return c.json({ error: "Session ID mismatch" }, 400);
80
+ }
81
+ const disconnected = this.disconnect(requestSessionId || this.currentSessionId);
82
+ if (disconnected) {
83
+ return c.body(null, 204);
84
+ }
85
+ return c.json({ error: "Session not found" }, 404);
86
+ }
87
+ // Handle main MCP endpoint
88
+ if (c.req.method === "POST" && url.pathname === "/mcp") {
89
+ // Ensure session exists (creates new one if disconnected)
90
+ this.ensureSession();
91
+ const headers = {
92
+ "mcp-session-id": this.currentSessionId,
93
+ };
94
+ const bodyText = await c.req.text();
95
+ this.logger.info("MCP Middleware Received Body", { bodyText });
96
+ // Handle notifications/initialized locally (no response needed for notifications)
97
+ try {
98
+ const requestData = JSON.parse(bodyText);
99
+ if (requestData?.method === "notifications/initialized") {
100
+ return c.text(sseNotification(), 200, {
101
+ ...SSE_HEADERS,
102
+ ...headers,
103
+ });
104
+ }
105
+ }
106
+ catch {
107
+ // Not valid JSON, continue with normal processing
108
+ }
109
+ // Use streaming SSE response
110
+ return createSSEStream(async (writer) => {
111
+ const result = await createJsonRpcManager(this).validateMessage(bodyText);
112
+ writer.send(result);
113
+ }, headers);
114
+ }
115
+ return c.json({ error: "MCP endpoint not implemented" }, 501);
116
+ }
117
+ catch (error) {
118
+ this.logger.error("MCP Middleware Error", error);
119
+ const message = error instanceof Error ? error.message : "Internal error";
120
+ return c.text(sseError(null, -32603, message), 500, SSE_HEADERS);
121
+ }
122
+ };
123
+ }
124
+ initHono(app, path = "mcp") {
125
+ const middlewareHandler = this.handler();
126
+ app.use(`/${path}`, middlewareHandler);
127
+ app.use(`/${path}/*`, middlewareHandler);
128
+ return this;
129
+ }
130
+ }
131
+ class HonoJSONRPCManager extends JSONRPCManager {
132
+ async sendMessage(message) {
133
+ return message;
134
+ }
135
+ }
136
+ function createJsonRpcManager(middleware) {
137
+ const mcpTools = middleware.getTools();
138
+ const mcpPrompts = middleware.getPrompts();
139
+ const jsonrpc = new HonoJSONRPCManager();
140
+ jsonrpc.registerMethod("initialize", {
141
+ handler(params) {
142
+ // Validate required request parameters
143
+ if (!params.capabilities) {
144
+ throw new Error("Missing required parameter: capabilities");
145
+ }
146
+ if (!params.clientInfo) {
147
+ throw new Error("Missing required parameter: clientInfo");
148
+ }
149
+ if (!params.protocolVersion) {
150
+ throw new Error("Missing required parameter: protocolVersion");
151
+ }
152
+ // Return valid InitializeResult
153
+ return {
154
+ protocolVersion: "2025-11-25",
155
+ capabilities: {
156
+ tools: { listChanged: true },
157
+ ...(mcpPrompts.length > 0 && { prompts: { listChanged: true } }),
158
+ completions: {},
159
+ logging: {},
160
+ },
161
+ serverInfo: {
162
+ name: middleware.config.name,
163
+ version: middleware.config.version,
164
+ },
165
+ };
166
+ },
167
+ });
168
+ jsonrpc.registerMethod("tools/list", {
169
+ async handler() {
170
+ const { toJsonSchema } = await import("xsschema");
171
+ const tools = await Promise.all(mcpTools.map(async (tool) => ({
172
+ name: tool.name,
173
+ description: tool.description,
174
+ inputSchema: await toJsonSchema(tool.parameters), // Simplified for example
175
+ })));
176
+ return {
177
+ tools,
178
+ };
179
+ },
180
+ });
181
+ jsonrpc.registerMethod("tools/call", {
182
+ async handler(params) {
183
+ const { ERROR_METHOD_NOT_FOUND } = await import("modality-kit");
184
+ const { name, arguments: args } = params;
185
+ const tool = mcpTools.find((t) => t.name === name);
186
+ if (!tool) {
187
+ throw new ERROR_METHOD_NOT_FOUND(`Tool not found: ${name}`);
188
+ }
189
+ return {
190
+ content: [
191
+ {
192
+ text: await tool.execute(args),
193
+ type: "text",
194
+ },
195
+ ],
196
+ };
197
+ },
198
+ });
199
+ jsonrpc.registerMethod("prompts/list", {
200
+ async handler() {
201
+ const prompts = mcpPrompts.map((prompt) => ({
202
+ name: prompt.name,
203
+ ...(prompt.description && { description: prompt.description }),
204
+ ...(prompt.title && { title: prompt.title }),
205
+ ...(prompt.arguments && {
206
+ arguments: prompt.arguments.map((arg) => ({
207
+ name: arg.name,
208
+ ...(arg.description && { description: arg.description }),
209
+ ...(arg.required !== undefined && { required: arg.required }),
210
+ ...(arg.title && { title: arg.title }),
211
+ ...(arg.enum && { enum: Array.from(arg.enum) }),
212
+ })),
213
+ }),
214
+ }));
215
+ return { prompts };
216
+ },
217
+ });
218
+ jsonrpc.registerMethod("prompts/get", {
219
+ async handler(params) {
220
+ const { ERROR_METHOD_NOT_FOUND } = await import("modality-kit");
221
+ const { name, arguments: args } = params;
222
+ const prompt = mcpPrompts.find((p) => p.name === name);
223
+ if (!prompt) {
224
+ throw new ERROR_METHOD_NOT_FOUND(`Prompt not found: ${name}`);
225
+ }
226
+ const text = await prompt.load(args || {});
227
+ return {
228
+ ...(prompt.description && { description: prompt.description }),
229
+ messages: [
230
+ {
231
+ role: "user",
232
+ content: {
233
+ type: "text",
234
+ text,
235
+ },
236
+ },
237
+ ],
238
+ };
239
+ },
240
+ });
241
+ jsonrpc.registerMethod("completion/complete", {
242
+ async handler(params) {
243
+ const { ref, argument } = params;
244
+ // Only handle prompt references
245
+ if (ref?.type !== "ref/prompt") {
246
+ return { completion: { values: [], total: 0 } };
247
+ }
248
+ const prompt = mcpPrompts.find((p) => p.name === ref.name);
249
+ if (!prompt || !prompt.arguments) {
250
+ return { completion: { values: [], total: 0 } };
251
+ }
252
+ const arg = prompt.arguments.find((a) => a.name === argument.name);
253
+ if (!arg || !arg.enum) {
254
+ return { completion: { values: [], total: 0 } };
255
+ }
256
+ const enumValues = Array.from(arg.enum);
257
+ const inputValue = (argument.value || "").trim().toLowerCase();
258
+ const completionLimit = prompt.completionLimit ?? 10;
259
+ // Empty input: return first N items
260
+ if (!inputValue) {
261
+ const limit = Math.min(completionLimit, enumValues.length);
262
+ return {
263
+ completion: {
264
+ values: enumValues.slice(0, limit),
265
+ total: enumValues.length,
266
+ hasMore: enumValues.length > limit,
267
+ },
268
+ };
269
+ }
270
+ // Filter enum values by the partial input
271
+ const matchingValues = enumValues.filter((v) => v.toLowerCase().startsWith(inputValue));
272
+ return {
273
+ completion: {
274
+ values: matchingValues.slice(0, 100),
275
+ total: matchingValues.length,
276
+ hasMore: matchingValues.length > 100,
277
+ },
278
+ };
279
+ },
280
+ });
281
+ // Notification - no response needed (return empty result)
282
+ jsonrpc.registerMethod("notifications/initialized", {
283
+ handler() {
284
+ return {};
285
+ },
286
+ });
287
+ // notifications/cancelled - client requests cancellation of in-flight request
288
+ jsonrpc.registerMethod("notifications/cancelled", {
289
+ handler(params) {
290
+ const { requestId, reason } = params;
291
+ middleware.logger.info(`Request cancelled: ${requestId}`, { reason });
292
+ // Note: Stateless HTTP cannot cancel in-flight requests
293
+ // This handler acknowledges the notification for spec compliance
294
+ return {};
295
+ },
296
+ });
297
+ // ping - keep-alive check per MCP spec
298
+ jsonrpc.registerMethod("ping", {
299
+ handler() {
300
+ return {};
301
+ },
302
+ });
303
+ // logging/setLevel - validate and return empty result per MCP spec
304
+ jsonrpc.registerMethod("logging/setLevel", {
305
+ handler(params) {
306
+ const VALID_LOG_LEVELS = [
307
+ "debug",
308
+ "info",
309
+ "notice",
310
+ "warning",
311
+ "error",
312
+ "critical",
313
+ "alert",
314
+ "emergency",
315
+ ];
316
+ // Map MCP log levels to modality logger levels
317
+ const LOG_LEVEL_MAP = {
318
+ debug: "debug",
319
+ info: "info",
320
+ notice: "info",
321
+ warning: "warn",
322
+ error: "error",
323
+ critical: "error",
324
+ alert: "error",
325
+ emergency: "error",
326
+ };
327
+ const { level } = params;
328
+ // Validate level parameter exists
329
+ if (!level) {
330
+ throw new Error("Missing required parameter: level");
331
+ }
332
+ // Validate level is a valid LoggingLevel
333
+ if (!VALID_LOG_LEVELS.includes(level)) {
334
+ throw new Error(`Invalid log level: ${level}. Must be one of: ${VALID_LOG_LEVELS.join(", ")}`);
335
+ }
336
+ // Map and apply log level to modality logger
337
+ const modalityLogLevel = LOG_LEVEL_MAP[level];
338
+ middleware.logger.setLogLevel(modalityLogLevel);
339
+ middleware.logger.info(`Log level set to: ${level}`);
340
+ // Return empty result per MCP spec
341
+ return {};
342
+ },
343
+ });
344
+ return jsonrpc;
345
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * MCP Session Manager - Simple session lifecycle management
3
+ */
4
+ export class McpSessionManager {
5
+ sessions = new Map();
6
+ /**
7
+ * Create a new session
8
+ */
9
+ create() {
10
+ const now = new Date();
11
+ const session = {
12
+ id: crypto.randomUUID(),
13
+ createdAt: now,
14
+ lastActivity: now,
15
+ };
16
+ this.sessions.set(session.id, session);
17
+ return session;
18
+ }
19
+ /**
20
+ * Get session by ID
21
+ */
22
+ get(sessionId) {
23
+ return this.sessions.get(sessionId);
24
+ }
25
+ /**
26
+ * Update last activity timestamp
27
+ */
28
+ touch(sessionId) {
29
+ const session = this.sessions.get(sessionId);
30
+ if (session) {
31
+ session.lastActivity = new Date();
32
+ }
33
+ }
34
+ /**
35
+ * Remove a session
36
+ */
37
+ disconnect(sessionId) {
38
+ return this.sessions.delete(sessionId);
39
+ }
40
+ /**
41
+ * Check if session exists
42
+ */
43
+ has(sessionId) {
44
+ return this.sessions.has(sessionId);
45
+ }
46
+ }
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { toJsonSchema } from "xsschema";
2
2
  export { setupAITools, ModalityFastMCP } from "./util_mcp_tools_converter";
3
+ export { FastHonoMcp } from "./FastHonoMcp";
@@ -0,0 +1,206 @@
1
+ /**
2
+ * SSE (Server-Sent Events) Wrapper Utility
3
+ *
4
+ * Wraps JSON-RPC responses in SSE format as per MCP specification.
5
+ * SSE format:
6
+ * event: message
7
+ * id: <unique-id>
8
+ * data: <json-rpc-response>
9
+ *
10
+ * Supports both single-response and streaming modes.
11
+ * Uses modality-kit for JSON-RPC types and error codes.
12
+ */
13
+ // ============================================
14
+ // SSE HEADERS
15
+ // ============================================
16
+ /**
17
+ * Standard SSE response headers for MCP
18
+ */
19
+ export const SSE_HEADERS = {
20
+ "Content-Type": "text/event-stream",
21
+ "Cache-Control": "no-cache",
22
+ "Connection": "keep-alive",
23
+ };
24
+ // ============================================
25
+ // CORE SSE FUNCTIONS
26
+ // ============================================
27
+ /**
28
+ * Generate a unique ID for SSE messages
29
+ */
30
+ function generateSSEId() {
31
+ const timestamp = Date.now();
32
+ const random = Math.random().toString(36).substring(2, 9);
33
+ return `${timestamp}_${random}`;
34
+ }
35
+ /**
36
+ * Wrap a JSON-RPC response in SSE format
37
+ */
38
+ export function wrapSSE(jsonrpcResponse) {
39
+ return {
40
+ event: "message",
41
+ id: generateSSEId(),
42
+ data: jsonrpcResponse,
43
+ };
44
+ }
45
+ /**
46
+ * Format SSE message as text for transmission
47
+ */
48
+ export function formatSSE(sseMessage) {
49
+ return `event: ${sseMessage.event}\nid: ${sseMessage.id}\ndata: ${JSON.stringify(sseMessage.data)}\n\n`;
50
+ }
51
+ /**
52
+ * Wrap and format JSON-RPC response in one step
53
+ */
54
+ export function wrapAndFormatSSE(jsonrpcResponse) {
55
+ const sseMessage = wrapSSE(jsonrpcResponse);
56
+ return formatSSE(sseMessage);
57
+ }
58
+ // ============================================
59
+ // CONVENIENCE SSE FORMATTERS
60
+ // ============================================
61
+ /**
62
+ * Create SSE-formatted success response
63
+ */
64
+ export function sseSuccess(id, result = {}) {
65
+ return wrapAndFormatSSE({
66
+ jsonrpc: "2.0",
67
+ id,
68
+ result,
69
+ });
70
+ }
71
+ /**
72
+ * Create SSE-formatted error response
73
+ */
74
+ export function sseError(id, code, message, data) {
75
+ return wrapAndFormatSSE({
76
+ jsonrpc: "2.0",
77
+ id,
78
+ error: { code, message, ...(data !== undefined && { data }) },
79
+ });
80
+ }
81
+ /**
82
+ * Create SSE-formatted notification response (id: null, empty result)
83
+ */
84
+ export function sseNotification() {
85
+ return wrapAndFormatSSE({
86
+ jsonrpc: "2.0",
87
+ id: null,
88
+ result: {},
89
+ });
90
+ }
91
+ // ============================================
92
+ // STREAMING SSE SUPPORT
93
+ // ============================================
94
+ /**
95
+ * SSE Stream writer for true streaming support
96
+ */
97
+ export class SSEStreamWriter {
98
+ controller = null;
99
+ encoder = new TextEncoder();
100
+ closed = false;
101
+ /**
102
+ * Create a ReadableStream for SSE responses
103
+ */
104
+ createStream() {
105
+ return new ReadableStream({
106
+ start: (controller) => {
107
+ this.controller = controller;
108
+ },
109
+ cancel: () => {
110
+ this.closed = true;
111
+ this.controller = null;
112
+ },
113
+ });
114
+ }
115
+ /**
116
+ * Send a JSON-RPC response as SSE message
117
+ */
118
+ send(response) {
119
+ if (this.closed || !this.controller)
120
+ return;
121
+ const formatted = wrapAndFormatSSE(response);
122
+ this.controller.enqueue(this.encoder.encode(formatted));
123
+ }
124
+ /**
125
+ * Send a progress notification
126
+ */
127
+ sendProgress(progressToken, progress, total) {
128
+ if (this.closed || !this.controller)
129
+ return;
130
+ const notification = {
131
+ jsonrpc: "2.0",
132
+ id: null,
133
+ result: {
134
+ method: "notifications/progress",
135
+ params: {
136
+ progressToken,
137
+ progress,
138
+ ...(total !== undefined && { total }),
139
+ },
140
+ },
141
+ };
142
+ const formatted = wrapAndFormatSSE(notification);
143
+ this.controller.enqueue(this.encoder.encode(formatted));
144
+ }
145
+ /**
146
+ * Send a keep-alive ping (SSE comment)
147
+ */
148
+ ping() {
149
+ if (this.closed || !this.controller)
150
+ return;
151
+ this.controller.enqueue(this.encoder.encode(": ping\n\n"));
152
+ }
153
+ /**
154
+ * Send raw SSE event
155
+ */
156
+ sendEvent(event, data, id) {
157
+ if (this.closed || !this.controller)
158
+ return;
159
+ const sseId = id || generateSSEId();
160
+ const formatted = `event: ${event}\nid: ${sseId}\ndata: ${JSON.stringify(data)}\n\n`;
161
+ this.controller.enqueue(this.encoder.encode(formatted));
162
+ }
163
+ /**
164
+ * Close the stream
165
+ */
166
+ close() {
167
+ if (this.closed || !this.controller)
168
+ return;
169
+ this.closed = true;
170
+ this.controller.close();
171
+ this.controller = null;
172
+ }
173
+ /**
174
+ * Check if stream is still open
175
+ */
176
+ get isOpen() {
177
+ return !this.closed && this.controller !== null;
178
+ }
179
+ }
180
+ /**
181
+ * Create a streaming SSE response
182
+ */
183
+ export function createSSEStream(handler, headers) {
184
+ const writer = new SSEStreamWriter();
185
+ const stream = writer.createStream();
186
+ // Execute handler asynchronously
187
+ handler(writer)
188
+ .catch((error) => {
189
+ if (writer.isOpen) {
190
+ writer.send({
191
+ jsonrpc: "2.0",
192
+ id: null,
193
+ error: {
194
+ code: -32603,
195
+ message: error instanceof Error ? error.message : "Internal error",
196
+ },
197
+ });
198
+ }
199
+ })
200
+ .finally(() => {
201
+ writer.close();
202
+ });
203
+ return new Response(stream, {
204
+ headers: { ...SSE_HEADERS, ...headers },
205
+ });
206
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * MCP Middleware for Hono Integration
3
+ *
4
+ * This middleware integrates the Model Context Protocol (MCP) into a Hono web server.
5
+ * It delegates all MCP protocol handling to FastMCP's built-in capabilities, avoiding
6
+ * any custom protocol implementation.
7
+ *
8
+ * Architecture:
9
+ * - Hono serves as the primary and only HTTP server (port 8800)
10
+ * - FastMCP handles all MCP protocol logic, schema conversion, and responses
11
+ * - Middleware acts as a simple proxy to FastMCP's stateless request handling
12
+ * - No manual JSON-RPC method handling - FastMCP does everything
13
+ *
14
+ * Key Principle:
15
+ * - Always reuse FastMCP's built-in functions
16
+ * - Never implement custom MCP protocol logic
17
+ * - Let FastMCP handle initialize, tools/list, tools/call, etc.
18
+ *
19
+ * Usage:
20
+ * app.use('/mcp', mcpMiddleware);
21
+ * app.use('/mcp/*', mcpMiddleware);
22
+ *
23
+ * https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema
24
+ * https://modelcontextprotocol.io/specification/2025-11-25/schema
25
+ */
26
+ import type { MiddlewareHandler, Hono } from "hono";
27
+ import { ModalityFastMCP } from "modality-mcp-kit";
28
+ import { getLoggerInstance } from "modality-kit";
29
+ import { McpSessionManager } from "./McpSessionManager.js";
30
+ export interface FastHonoMcpConfig extends Record<string, unknown> {
31
+ name: string;
32
+ version: string;
33
+ }
34
+ export declare class FastHonoMcp extends ModalityFastMCP {
35
+ logger: ReturnType<typeof getLoggerInstance>;
36
+ config: FastHonoMcpConfig;
37
+ sessions: McpSessionManager;
38
+ private currentSessionId;
39
+ constructor(config: FastHonoMcpConfig);
40
+ /**
41
+ * Disconnect and cleanup a session
42
+ */
43
+ disconnect(sessionId?: string): boolean;
44
+ /**
45
+ * Ensure session exists, create if needed
46
+ */
47
+ private ensureSession;
48
+ handler(): MiddlewareHandler;
49
+ initHono(app: Hono, path?: string): this;
50
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * MCP Session Manager - Simple session lifecycle management
3
+ */
4
+ export interface McpSession {
5
+ id: string;
6
+ createdAt: Date;
7
+ lastActivity: Date;
8
+ }
9
+ export declare class McpSessionManager {
10
+ private sessions;
11
+ /**
12
+ * Create a new session
13
+ */
14
+ create(): McpSession;
15
+ /**
16
+ * Get session by ID
17
+ */
18
+ get(sessionId: string): McpSession | undefined;
19
+ /**
20
+ * Update last activity timestamp
21
+ */
22
+ touch(sessionId: string): void;
23
+ /**
24
+ * Remove a session
25
+ */
26
+ disconnect(sessionId: string): boolean;
27
+ /**
28
+ * Check if session exists
29
+ */
30
+ has(sessionId: string): boolean;
31
+ }
@@ -2,3 +2,4 @@ export { toJsonSchema } from "xsschema";
2
2
  export { setupAITools, ModalityFastMCP } from "./util_mcp_tools_converter";
3
3
  export type { AITools, AITool, FastMCPTool, } from "./schemas/schemas_tool_config";
4
4
  export type { FastMCPCompatible, Prompt } from "./util_mcp_tools_converter";
5
+ export { FastHonoMcp } from "./FastHonoMcp";
@@ -0,0 +1,91 @@
1
+ /**
2
+ * SSE (Server-Sent Events) Wrapper Utility
3
+ *
4
+ * Wraps JSON-RPC responses in SSE format as per MCP specification.
5
+ * SSE format:
6
+ * event: message
7
+ * id: <unique-id>
8
+ * data: <json-rpc-response>
9
+ *
10
+ * Supports both single-response and streaming modes.
11
+ * Uses modality-kit for JSON-RPC types and error codes.
12
+ */
13
+ import type { JSONRPCResponse, JSONRPCId } from "modality-kit";
14
+ interface SSEMessage {
15
+ event: string;
16
+ id: string;
17
+ data: unknown;
18
+ }
19
+ /**
20
+ * Standard SSE response headers for MCP
21
+ */
22
+ export declare const SSE_HEADERS: {
23
+ readonly "Content-Type": "text/event-stream";
24
+ readonly "Cache-Control": "no-cache";
25
+ readonly Connection: "keep-alive";
26
+ };
27
+ /**
28
+ * Wrap a JSON-RPC response in SSE format
29
+ */
30
+ export declare function wrapSSE(jsonrpcResponse: JSONRPCResponse): SSEMessage;
31
+ /**
32
+ * Format SSE message as text for transmission
33
+ */
34
+ export declare function formatSSE(sseMessage: SSEMessage): string;
35
+ /**
36
+ * Wrap and format JSON-RPC response in one step
37
+ */
38
+ export declare function wrapAndFormatSSE(jsonrpcResponse: JSONRPCResponse): string;
39
+ /**
40
+ * Create SSE-formatted success response
41
+ */
42
+ export declare function sseSuccess(id: JSONRPCId, result?: unknown): string;
43
+ /**
44
+ * Create SSE-formatted error response
45
+ */
46
+ export declare function sseError(id: JSONRPCId, code: number, message: string, data?: unknown): string;
47
+ /**
48
+ * Create SSE-formatted notification response (id: null, empty result)
49
+ */
50
+ export declare function sseNotification(): string;
51
+ /**
52
+ * SSE Stream writer for true streaming support
53
+ */
54
+ export declare class SSEStreamWriter {
55
+ private controller;
56
+ private encoder;
57
+ private closed;
58
+ /**
59
+ * Create a ReadableStream for SSE responses
60
+ */
61
+ createStream(): ReadableStream<Uint8Array>;
62
+ /**
63
+ * Send a JSON-RPC response as SSE message
64
+ */
65
+ send(response: JSONRPCResponse): void;
66
+ /**
67
+ * Send a progress notification
68
+ */
69
+ sendProgress(progressToken: string | number, progress: number, total?: number): void;
70
+ /**
71
+ * Send a keep-alive ping (SSE comment)
72
+ */
73
+ ping(): void;
74
+ /**
75
+ * Send raw SSE event
76
+ */
77
+ sendEvent(event: string, data: unknown, id?: string): void;
78
+ /**
79
+ * Close the stream
80
+ */
81
+ close(): void;
82
+ /**
83
+ * Check if stream is still open
84
+ */
85
+ get isOpen(): boolean;
86
+ }
87
+ /**
88
+ * Create a streaming SSE response
89
+ */
90
+ export declare function createSSEStream(handler: (writer: SSEStreamWriter) => Promise<void>, headers?: Record<string, string>): Response;
91
+ export {};
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.2.0",
2
+ "version": "0.3.0",
3
3
  "name": "modality-mcp-kit",
4
4
  "repository": {
5
5
  "type": "git",
@@ -22,15 +22,18 @@
22
22
  "author": "Hill <hill@kimo.com>",
23
23
  "license": "ISC",
24
24
  "dependencies": {
25
- "modality-kit": "^0.14.5",
25
+ "modality-kit": "^0.14.17",
26
26
  "xsschema": "0.3.5",
27
27
  "zod": "^3.25.76",
28
28
  "zod-to-json-schema": "^3.25.0"
29
29
  },
30
30
  "devDependencies": {
31
- "@types/bun": "^1.3.3",
31
+ "@types/bun": "^1.3.5",
32
32
  "typescript": "^5.9.3"
33
33
  },
34
+ "peerDependencies": {
35
+ "hono": "^4.11.2"
36
+ },
34
37
  "exports": {
35
38
  "types": "./dist/types/index.d.ts",
36
39
  "require": "./dist/index.js",
@@ -42,7 +45,7 @@
42
45
  "scripts": {
43
46
  "build": "bun tsc -p ./",
44
47
  "build:types": "echo 'Starting build validation...' && bun --version && echo 'Running TypeScript type checking...' && bun tsc --noEmit --strict && echo '✅ Build validation successful - no TypeScript errors found' || (echo '❌ Build failed' && exit 1)",
45
- "test": "bun test",
48
+ "test": "bun build:types && bun test",
46
49
  "prepublishOnly": "npm run build && npm run test"
47
50
  },
48
51
  "files": ["package.json", "README.md", "dist"]