Skip to content

Commit e359184

Browse files
feat: adds exported-data resource
1 parent 4b9fa16 commit e359184

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

src/resources/common/exported-data.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import {
2+
CompleteResourceTemplateCallback,
3+
ListResourcesCallback,
4+
ReadResourceTemplateCallback,
5+
ResourceTemplate,
6+
} from "@modelcontextprotocol/sdk/server/mcp.js";
7+
import { Server } from "../../server.js";
8+
9+
export class ExportedData {
10+
private readonly name = "exported-data";
11+
private readonly description = "Data files exported in the current session.";
12+
private readonly uri = "exported-data://{exportName}";
13+
14+
constructor(private readonly server: Server) {
15+
this.server.session.on("export-available", (uri) => {
16+
this.server.mcpServer.sendResourceListChanged();
17+
void this.server.mcpServer.server.sendResourceUpdated({
18+
uri,
19+
});
20+
this.server.mcpServer.sendResourceListChanged();
21+
});
22+
this.server.session.on("export-expired", () => {
23+
this.server.mcpServer.sendResourceListChanged();
24+
});
25+
}
26+
27+
public register(): void {
28+
this.server.mcpServer.registerResource(
29+
this.name,
30+
new ResourceTemplate(this.uri, {
31+
/**
32+
* A few clients have the capability of listing templated
33+
* resources as well and this callback provides support for that
34+
* */
35+
list: this.listResourcesCallback,
36+
/**
37+
* This is to provide auto completion when user starts typing in
38+
* value for template variable, in our case, exportName */
39+
complete: {
40+
exportName: this.autoCompleteExportName,
41+
},
42+
}),
43+
{ description: this.description },
44+
this.readResourceCallback
45+
);
46+
}
47+
48+
private listResourcesCallback: ListResourcesCallback = () => {
49+
const sessionId = this.server.session.sessionId;
50+
if (!sessionId) {
51+
// Note that we don't throw error here because this is a
52+
// non-critical path and safe to return the most harmless value.
53+
54+
// TODO: log warn here
55+
return { resources: [] };
56+
}
57+
58+
const sessionExports = this.server.exportsManager.listAvailableExports();
59+
return {
60+
resources: sessionExports.map(({ name, uri }) => ({
61+
name: name,
62+
description: this.exportNameToDescription(name),
63+
uri: uri,
64+
mimeType: "application/json",
65+
})),
66+
};
67+
};
68+
69+
private autoCompleteExportName: CompleteResourceTemplateCallback = (value) => {
70+
const sessionId = this.server.session.sessionId;
71+
if (!sessionId) {
72+
// Note that we don't throw error here because this is a
73+
// non-critical path and safe to return the most harmless value.
74+
75+
// TODO: log warn here
76+
return [];
77+
}
78+
79+
const sessionExports = this.server.exportsManager.listAvailableExports();
80+
return sessionExports.filter(({ name }) => name.startsWith(value)).map(({ name }) => name);
81+
};
82+
83+
private readResourceCallback: ReadResourceTemplateCallback = async (uri, { exportName }) => {
84+
const sessionId = this.server.session.sessionId;
85+
if (!sessionId) {
86+
throw new Error("Cannot retrieve exported data, session is not valid.");
87+
}
88+
89+
if (typeof exportName !== "string") {
90+
throw new Error("Cannot retrieve exported data, exportName not provided.");
91+
}
92+
93+
return {
94+
contents: [
95+
{
96+
uri: this.server.exportsManager.exportNameToResourceURI(exportName),
97+
text: await this.server.exportsManager.readExport(exportName),
98+
mimeType: "application/json",
99+
},
100+
],
101+
};
102+
};
103+
104+
private exportNameToDescription(exportName: string) {
105+
const match = exportName.match(/^(.+)\.(\d+)\.json$/);
106+
if (!match) return "Exported data for an unknown namespace.";
107+
108+
const [, namespace, timestamp] = match;
109+
if (!namespace || !timestamp) {
110+
return "Exported data for an unknown namespace.";
111+
}
112+
return `Export from ${namespace} done on ${new Date(parseInt(timestamp)).toISOString()}`;
113+
}
114+
}

src/resources/resources.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ConfigResource } from "./common/config.js";
22
import { DebugResource } from "./common/debug.js";
3+
import { ExportedData } from "./common/exported-data.js";
34

4-
export const Resources = [ConfigResource, DebugResource] as const;
5+
export const Resources = [ConfigResource, DebugResource, ExportedData] as const;

0 commit comments

Comments
 (0)