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
72 changes: 70 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"bump": "node scripts/sync-version.js --bump",
"bump:minor": "node scripts/sync-version.js --bump --minor",
"bump:major": "node scripts/sync-version.js --bump --major",
"build": "tsc && shx cp setup-claude-server.js uninstall-claude-server.js track-installation.js dist/ && shx chmod +x dist/*.js && shx mkdir -p dist/data && shx cp src/data/onboarding-prompts.json dist/data/ && shx mkdir -p dist/remote-device/scripts && shx cp src/remote-device/scripts/blocking-offline-update.js dist/remote-device/scripts/",
"build": "tsc && shx cp setup-claude-server.js uninstall-claude-server.js track-installation.js dist/ && shx chmod +x dist/*.js && shx mkdir -p dist/data && shx cp src/data/onboarding-prompts.json dist/data/ && shx mkdir -p dist/remote-device/scripts && shx cp src/remote-device/scripts/blocking-offline-update.js dist/remote-device/scripts/ && node scripts/build-ui-runtime.cjs file-preview",
"watch": "tsc --watch",
"start": "node dist/index.js",
"start:debug": "node --inspect-brk=9229 dist/index.js",
Expand Down Expand Up @@ -90,7 +90,9 @@
"fastest-levenshtein": "^1.0.16",
"file-type": "^21.1.1",
"glob": "^10.3.10",
"highlight.js": "^11.11.1",
"isbinaryfile": "^5.0.4",
"markdown-it": "^14.1.0",
"md-to-pdf": "^5.2.5",
"open": "^10.2.0",
"pdf-lib": "^1.17.1",
Expand All @@ -110,6 +112,7 @@
"@anthropic-ai/mcpb": "^1.2.0",
"@types/node": "^20.17.24",
"commander": "^13.1.0",
"esbuild": "^0.27.2",
"nexe": "^5.0.0-beta.4",
"nodemon": "^3.0.2",
"shx": "^0.3.4",
Expand Down
79 changes: 79 additions & 0 deletions scripts/build-ui-runtime.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env node
/**
* Build script that compiles and stages browser UI assets used by MCP tool pages. It centralizes bundling/runtime generation so UI resources are deterministic in local dev and CI.
*/
const path = require('path');
const fs = require('fs/promises');
const { build } = require('esbuild');

const target = process.argv[2];

const TARGETS = {
'file-preview': {
entry: 'src/ui/file-preview/src/main.ts',
output: 'dist/ui/file-preview/preview-runtime.js',
staticDir: 'src/ui/file-preview',
styleLayers: [
'src/ui/styles/base.css',
'src/ui/styles/components/tool-header.css',
'src/ui/styles/apps/file-preview.css'
]
}
};

const STATIC_FILES = ['index.html'];
const projectRoot = path.resolve(__dirname, '..');

const selectedTargets =
target
? [target]
: Object.keys(TARGETS);

if (target && !TARGETS[target]) {
console.error(
`Usage: node scripts/build-ui-runtime.cjs [${Object.keys(TARGETS).join('|')}]`
);
process.exit(1);
}

async function buildTarget(targetName) {
const { entry, output, staticDir, styleLayers } = TARGETS[targetName];
const outputPath = path.join(projectRoot, output);
const outputDir = path.dirname(outputPath);

await fs.mkdir(outputDir, { recursive: true });

for (const fileName of STATIC_FILES) {
await fs.copyFile(
path.join(projectRoot, staticDir, fileName),
path.join(outputDir, fileName)
);
}

const styles = await Promise.all(
styleLayers.map((layerPath) => fs.readFile(path.join(projectRoot, layerPath), 'utf8'))
);
await fs.writeFile(path.join(outputDir, 'styles.css'), `${styles.join('\n\n')}\n`, 'utf8');

await build({
entryPoints: [path.join(projectRoot, entry)],
bundle: true,
format: 'iife',
platform: 'browser',
target: ['es2020'],
outfile: outputPath,
minify: false,
sourcemap: false
});
}

async function main() {
for (const targetName of selectedTargets) {
await buildTarget(targetName);
}
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
30 changes: 28 additions & 2 deletions src/handlers/filesystem-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ import {
GetFileInfoArgsSchema,
WritePdfArgsSchema
} from '../tools/schemas.js';
import path from 'path';
import { buildUiToolMeta, FILE_PREVIEW_RESOURCE_URI } from '../ui/contracts.js';
import { resolvePreviewFileType } from '../ui/file-preview/shared/preview-file-types.js';

/**
* Helper function to check if path contains an error
Expand Down Expand Up @@ -102,7 +105,14 @@ export async function handleReadFile(args: unknown): Promise<ServerResult> {
text: `PDF file: ${parsed.path}${author}${title} (${meta?.totalPages} pages) \n`
},
...pdfContent
]
],
structuredContent: {
fileName: path.basename(parsed.path),
filePath: parsed.path,
fileType: 'unsupported',
content: ''
},
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
};
}

Expand All @@ -113,26 +123,42 @@ export async function handleReadFile(args: unknown): Promise<ServerResult> {
const imageData = typeof fileResult.content === 'string'
? fileResult.content
: fileResult.content.toString('base64');
const imageSummary = `Image file: ${parsed.path} (${fileResult.mimeType})\n`;
return {
content: [
{
type: "text",
text: `Image file: ${parsed.path} (${fileResult.mimeType})\n`
text: imageSummary
},
{
type: "image",
data: imageData,
mimeType: fileResult.mimeType
}
],
structuredContent: {
fileName: path.basename(parsed.path),
filePath: parsed.path,
fileType: 'unsupported',
content: imageSummary
},
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
};
} else {
// For all other files, return as text
const textContent = typeof fileResult.content === 'string'
? fileResult.content
: fileResult.content.toString('utf8');
const previewFileType = resolvePreviewFileType(parsed.path);
return {
content: [{ type: "text", text: textContent }],
structuredContent: {
fileName: path.basename(parsed.path),
filePath: parsed.path,
fileType: previewFileType,
content: textContent
},
_meta: buildUiToolMeta(FILE_PREVIEW_RESOURCE_URI, true)
};
}
};
Expand Down
39 changes: 38 additions & 1 deletion src/handlers/history-handlers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import { toolHistory } from '../utils/toolHistory.js';
import { GetRecentToolCallsArgsSchema } from '../tools/schemas.js';
import { GetRecentToolCallsArgsSchema, TrackUiEventArgsSchema } from '../tools/schemas.js';
import { ServerResult } from '../types.js';
import { capture_ui_event } from '../utils/capture.js';

type TrackUiEventParams = Record<string, string | number | boolean | null>;

export function buildTrackUiEventCapturePayload(event: string, component: string, params: TrackUiEventParams): Record<string, string | number | boolean | null> {
return {
...params,
component,
event
};
}

/**
* Handle get_recent_tool_calls command
Expand Down Expand Up @@ -38,3 +49,29 @@ export async function handleGetRecentToolCalls(args: unknown): Promise<ServerRes
};
}
}

/**
* Handle track_ui_event command
*/
export async function handleTrackUiEvent(args: unknown): Promise<ServerResult> {
try {
const parsed = TrackUiEventArgsSchema.parse(args);

await capture_ui_event('mcp_ui_event', buildTrackUiEventCapturePayload(parsed.event, parsed.component, parsed.params));

return {
content: [{
type: "text",
text: `Tracked UI event: ${parsed.event}`
}]
};
} catch (error) {
return {
content: [{
type: "text",
text: `Error tracking UI event: ${error instanceof Error ? error.message : String(error)}`
}],
isError: true
};
}
}
Loading