1- import { registerAppResource , registerAppTool , RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server" ;
1+ import {
2+ registerAppResource ,
3+ registerAppTool ,
4+ RESOURCE_MIME_TYPE ,
5+ } from "@modelcontextprotocol/ext-apps/server" ;
26import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" ;
37import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js" ;
4- import type { CallToolResult , ReadResourceResult } from "@modelcontextprotocol/sdk/types.js" ;
8+ import type {
9+ CallToolResult ,
10+ ReadResourceResult ,
11+ } from "@modelcontextprotocol/sdk/types.js" ;
512import fs from "node:fs/promises" ;
13+ import { appendFileSync } from "node:fs" ;
614import path from "node:path" ;
715import { z } from "zod" ;
816import { startServer } from "./server-utils.js" ;
@@ -12,18 +20,50 @@ const DIST_DIR = path.join(import.meta.dirname, "dist");
1220// Track call counter across requests (stateful for demo purposes)
1321let callCounter = 0 ;
1422
23+ // Parse --log-file argument or use default
24+ const DEFAULT_LOG_FILE = "/tmp/mcp-apps-debug-server.log" ;
25+ function getLogFilePath ( ) : string {
26+ const logFileArg = process . argv . find ( ( arg ) => arg . startsWith ( "--log-file=" ) ) ;
27+ if ( logFileArg ) {
28+ return logFileArg . split ( "=" ) [ 1 ] ;
29+ }
30+ return process . env . DEBUG_LOG_FILE ?? DEFAULT_LOG_FILE ;
31+ }
32+
33+ const logFilePath = getLogFilePath ( ) ;
34+
35+ /**
36+ * Append a log entry to the log file
37+ */
38+ function appendToLogFile ( entry : {
39+ timestamp : string ;
40+ type : string ;
41+ payload : unknown ;
42+ } ) : void {
43+ try {
44+ const line = JSON . stringify ( entry ) + "\n" ;
45+ appendFileSync ( logFilePath , line , "utf-8" ) ;
46+ } catch ( e ) {
47+ console . error ( "[debug-server] Failed to write to log file:" , e ) ;
48+ }
49+ }
50+
1551// Minimal 1x1 blue PNG (base64)
16- const BLUE_PNG_1X1 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPj/HwADBwIAMCbHYQAAAABJRU5ErkJggg==" ;
52+ const BLUE_PNG_1X1 =
53+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPj/HwADBwIAMCbHYQAAAABJRU5ErkJggg==" ;
1754
1855// Minimal silent WAV (base64) - 44 byte header + 1 sample
19- const SILENT_WAV = "UklGRiYAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQIAAAAAAA==" ;
56+ const SILENT_WAV =
57+ "UklGRiYAAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQIAAAAAAA==" ;
2058
2159/**
2260 * Input schema for the debug-tool
2361 */
2462const DebugInputSchema = z . object ( {
2563 // Content configuration
26- contentType : z . enum ( [ "text" , "image" , "audio" , "resource" , "resourceLink" , "mixed" ] ) . default ( "text" ) ,
64+ contentType : z
65+ . enum ( [ "text" , "image" , "audio" , "resource" , "resourceLink" , "mixed" ] )
66+ . default ( "text" ) ,
2767 multipleBlocks : z . boolean ( ) . default ( false ) ,
2868 includeStructuredContent : z . boolean ( ) . default ( true ) ,
2969 includeMeta : z . boolean ( ) . default ( false ) ,
@@ -63,10 +103,18 @@ function buildContent(args: DebugInput): CallToolResult["content"] {
63103 content . push ( { type : "text" , text : `Debug text content${ suffix } ` } ) ;
64104 break ;
65105 case "image" :
66- content . push ( { type : "image" , data : BLUE_PNG_1X1 , mimeType : "image/png" } ) ;
106+ content . push ( {
107+ type : "image" ,
108+ data : BLUE_PNG_1X1 ,
109+ mimeType : "image/png" ,
110+ } ) ;
67111 break ;
68112 case "audio" :
69- content . push ( { type : "audio" , data : SILENT_WAV , mimeType : "audio/wav" } ) ;
113+ content . push ( {
114+ type : "audio" ,
115+ data : SILENT_WAV ,
116+ mimeType : "audio/wav" ,
117+ } ) ;
70118 break ;
71119 case "resource" :
72120 content . push ( {
@@ -111,19 +159,21 @@ export function createServer(): McpServer {
111159 const resourceUri = "ui://debug-tool/mcp-app.html" ;
112160
113161 // Main debug tool - exercises all result variations
114- registerAppTool ( server ,
162+ registerAppTool (
163+ server ,
115164 "debug-tool" ,
116165 {
117166 title : "Debug Tool" ,
118- description : "Comprehensive debug tool for testing MCP Apps SDK. Configure content types, error simulation, delays, and more." ,
167+ description :
168+ "Comprehensive debug tool for testing MCP Apps SDK. Configure content types, error simulation, delays, and more." ,
119169 inputSchema : DebugInputSchema ,
120170 outputSchema : DebugOutputSchema ,
121171 _meta : { ui : { resourceUri } } ,
122172 } ,
123173 async ( args ) : Promise < CallToolResult > => {
124174 // Apply delay if requested
125175 if ( args . delayMs && args . delayMs > 0 ) {
126- await new Promise ( resolve => setTimeout ( resolve , args . delayMs ) ) ;
176+ await new Promise ( ( resolve ) => setTimeout ( resolve , args . delayMs ) ) ;
127177 }
128178
129179 // Build content based on config
@@ -138,7 +188,9 @@ export function createServer(): McpServer {
138188 config : args ,
139189 timestamp : new Date ( ) . toISOString ( ) ,
140190 counter : ++ callCounter ,
141- ...( args . largeInput ? { largeInputLength : args . largeInput . length } : { } ) ,
191+ ...( args . largeInput
192+ ? { largeInputLength : args . largeInput . length }
193+ : { } ) ,
142194 } ;
143195 }
144196
@@ -162,11 +214,13 @@ export function createServer(): McpServer {
162214 ) ;
163215
164216 // App-only refresh tool (hidden from model)
165- registerAppTool ( server ,
217+ registerAppTool (
218+ server ,
166219 "debug-refresh" ,
167220 {
168221 title : "Refresh Debug Info" ,
169- description : "App-only tool for polling server state. Not visible to the model." ,
222+ description :
223+ "App-only tool for polling server state. Not visible to the model." ,
170224 inputSchema : z . object ( { } ) ,
171225 outputSchema : z . object ( { timestamp : z . string ( ) , counter : z . number ( ) } ) ,
172226 _meta : {
@@ -185,13 +239,47 @@ export function createServer(): McpServer {
185239 } ,
186240 ) ;
187241
242+ // App-only log tool - writes events to log file
243+ registerAppTool (
244+ server ,
245+ "debug-log" ,
246+ {
247+ title : "Log to File" ,
248+ description :
249+ "App-only tool for logging events to the server log file. Not visible to the model." ,
250+ inputSchema : z . object ( {
251+ type : z . string ( ) ,
252+ payload : z . unknown ( ) ,
253+ } ) ,
254+ outputSchema : z . object ( { logged : z . boolean ( ) , logFile : z . string ( ) } ) ,
255+ _meta : {
256+ ui : {
257+ resourceUri,
258+ visibility : [ "app" ] ,
259+ } ,
260+ } ,
261+ } ,
262+ async ( args ) : Promise < CallToolResult > => {
263+ const timestamp = new Date ( ) . toISOString ( ) ;
264+ appendToLogFile ( { timestamp, type : args . type , payload : args . payload } ) ;
265+ return {
266+ content : [ { type : "text" , text : `Logged to ${ logFilePath } ` } ] ,
267+ structuredContent : { logged : true , logFile : logFilePath } ,
268+ } ;
269+ } ,
270+ ) ;
271+
188272 // Register the resource which returns the bundled HTML/JavaScript for the UI
189- registerAppResource ( server ,
273+ registerAppResource (
274+ server ,
190275 resourceUri ,
191276 resourceUri ,
192277 { mimeType : RESOURCE_MIME_TYPE } ,
193278 async ( ) : Promise < ReadResourceResult > => {
194- const html = await fs . readFile ( path . join ( DIST_DIR , "mcp-app.html" ) , "utf-8" ) ;
279+ const html = await fs . readFile (
280+ path . join ( DIST_DIR , "mcp-app.html" ) ,
281+ "utf-8" ,
282+ ) ;
195283
196284 return {
197285 contents : [
@@ -205,6 +293,13 @@ export function createServer(): McpServer {
205293}
206294
207295async function main ( ) {
296+ console . log ( `[debug-server] Log file: ${ logFilePath } ` ) ;
297+ appendToLogFile ( {
298+ timestamp : new Date ( ) . toISOString ( ) ,
299+ type : "server-start" ,
300+ payload : { logFilePath, pid : process . pid } ,
301+ } ) ;
302+
208303 if ( process . argv . includes ( "--stdio" ) ) {
209304 await createServer ( ) . connect ( new StdioServerTransport ( ) ) ;
210305 } else {
0 commit comments