refactor: use MCP resource types for ui/download-file params

Replace custom {filename, content, mimeType, encoding} params with
standard MCP resource types: EmbeddedResource for inline content and
ResourceLink for host-resolved references.

New params shape:
  params: { contents: (EmbeddedResource | ResourceLink)[] }

This reuses existing MCP primitives instead of inventing new ones,
and enables hosts to show resource 'bubbles' or resolve ResourceLink
URIs via resources/read on the MCP server.
This commit is contained in:
Anton Pidkuiko MacBook 2026-02-20 12:00:35 +00:00
commit 3757271641
No known key found for this signature in database
2 changed files with 366 additions and 125 deletions

View file

@ -16,6 +16,7 @@ const DIST_DIR = import.meta.filename.endsWith(".ts")
? path.join(import.meta.dirname, "dist")
: import.meta.dirname;
const RESOURCE_URI = "ui://get-time/mcp-app.html";
const SAMPLE_DOWNLOAD_URI = "resource:///sample-report.txt";
/**
* Creates a new MCP server instance with tools and resources registered.
@ -70,5 +71,28 @@ export function createServer(): McpServer {
},
);
// Sample downloadable resource — used to demo ResourceLink in ui/download-file
server.resource(
SAMPLE_DOWNLOAD_URI,
SAMPLE_DOWNLOAD_URI,
{
mimeType: "text/plain",
},
async (): Promise<ReadResourceResult> => {
const content = [
"Integration Test Server — Sample Report",
`Generated: ${new Date().toISOString()}`,
"",
"This file was downloaded via MCP ResourceLink.",
"The host resolved it by calling resources/read on the server.",
].join("\n");
return {
contents: [
{ uri: SAMPLE_DOWNLOAD_URI, mimeType: "text/plain", text: content },
],
};
},
);
return server;
}

View file

@ -137,8 +137,7 @@ function GetTimeAppInner({
log.info("Open link request", isError ? "rejected" : "accepted");
}, [app, linkUrl]);
const canDownload =
app.getHostCapabilities()?.downloadFile !== undefined;
const canDownload = app.getHostCapabilities()?.downloadFile !== undefined;
const handleDownloadFile = useCallback(async () => {
const sampleContent = JSON.stringify(
@ -148,13 +147,35 @@ function GetTimeAppInner({
);
log.info("Requesting file download...");
const { isError } = await app.downloadFile({
filename: "export.json",
content: sampleContent,
mimeType: "application/json",
contents: [
{
type: "resource",
resource: {
uri: "file:///export.json",
mimeType: "application/json",
text: sampleContent,
},
},
],
});
log.info("Download", isError ? "rejected" : "accepted");
}, [app, serverTime]);
const handleDownloadLink = useCallback(async () => {
log.info("Requesting resource link download...");
const { isError } = await app.downloadFile({
contents: [
{
type: "resource_link",
uri: "resource:///sample-report.txt",
name: "sample-report.txt",
mimeType: "text/plain",
},
],
});
log.info("Resource link download", isError ? "rejected" : "accepted");
}, [app]);
return (
<main
className={styles.main}
@ -203,8 +224,11 @@ function GetTimeAppInner({
{canDownload && (
<div className={styles.action}>
<p>Export current server time as JSON file</p>
<button onClick={handleDownloadFile}>Download File</button>
<p>Download file via EmbeddedResource or ResourceLink</p>
<div style={{ display: "flex", gap: "8px" }}>
<button onClick={handleDownloadFile}>Embedded</button>
<button onClick={handleDownloadLink}>Link</button>
</div>
</div>
)}
</main>