1
+ // Authors: Andrew Nolan, Brandon Walderman
2
+ // Date: 7-16-2025
3
+ // Sample code. NOT to be used in production settings.
4
+
5
+ // Predefined stamps for the initial collection
6
+ let stamps = [
7
+ { name : 'Blue Penny' , description : 'A famous stamp from Mauritius, issued in 1847.' , year : 1847 , imageUrl : 'images/blue-penny.jpg' } ,
8
+ { name : 'Inverted Jenny' , description : 'A US stamp featuring an upside-down airplane, issued in 1918.' , year : 1918 , imageUrl : 'images/inverted-jenny.png' } ,
9
+ { name : 'Penny Black' , description : 'The world’s first adhesive stamp, issued in 1840 in the UK.' , year : 1840 , imageUrl : 'images/penny-black.png' } ,
10
+ ] ;
11
+
12
+ // Function to render the stamp collection
13
+ function renderStamps ( ) {
14
+ const stampCollectionDiv = document . getElementById ( 'stampCollection' ) ;
15
+ stampCollectionDiv . innerHTML = stamps . map ( stamp => {
16
+ // Dynamically build the stamp's HTML
17
+ let stampHtml = `
18
+ <div>
19
+ <p><strong>${ stamp . name } </strong> (${ stamp . year } ): ${ stamp . description } </p>
20
+ ` ;
21
+
22
+ // Only add the image if a valid URL is present
23
+ if ( stamp . imageUrl ) {
24
+ stampHtml += `<img src="${ stamp . imageUrl } " alt="${ stamp . name } " />` ;
25
+ }
26
+
27
+ stampHtml += `</div>` ;
28
+ return stampHtml ;
29
+ } ) . join ( '' ) ;
30
+ }
31
+
32
+ // Function to handle adding a new stamp
33
+ function addStamp ( stampName , stampDescription , stampYear , stampImageUrl ) {
34
+ // Add the new stamp to the collection
35
+ stamps . push ( {
36
+ name : stampName ,
37
+ description : stampDescription ,
38
+ year : stampYear ,
39
+ imageUrl : stampImageUrl || null
40
+ } ) ;
41
+
42
+ // Confirm addition and update the collection
43
+ document . getElementById ( 'confirmationMessage' ) . textContent = `Stamp "${ stampName } " added successfully!` ;
44
+ renderStamps ( ) ;
45
+ }
46
+
47
+ document . getElementById ( 'addStampForm' ) . addEventListener ( 'submit' , ( event ) => {
48
+ event . preventDefault ( ) ;
49
+
50
+ const stampName = document . getElementById ( 'stampName' ) . value ;
51
+ const stampDescription = document . getElementById ( 'stampDescription' ) . value ;
52
+ const stampYear = document . getElementById ( 'stampYear' ) . value ;
53
+ const stampImageUrl = document . getElementById ( 'stampImageUrl' ) . value ;
54
+
55
+ addStamp ( stampName , stampDescription , stampYear , stampImageUrl ) ;
56
+ } ) ;
57
+
58
+ if ( 'agent' in window ) {
59
+ window . agent . provideContext ( {
60
+ tools : [
61
+ {
62
+ name : "add-stamp" ,
63
+ description : "Add a new stamp to the collection" ,
64
+ params : {
65
+ type : "object" ,
66
+ properties : {
67
+ name : { type : "string" , description : "The name of the stamp" } ,
68
+ description : { type : "string" , description : "A brief description of the stamp" } ,
69
+ year : { type : "number" , description : "The year the stamp was issued" } ,
70
+ imageUrl : { type : "string" , description : "An optional image URL for the stamp" }
71
+ } ,
72
+ required : [ "name" , "description" , "year" ]
73
+ } ,
74
+ } ,
75
+ {
76
+ name : "search-stamp" ,
77
+ description : "Search for stamps by name or year" ,
78
+ params : {
79
+ type : "object" ,
80
+ properties : {
81
+ name : { type : "string" , description : "The name of the stamp to search for" } ,
82
+ year : { type : "number" , description : "The year of the stamp to search for" } ,
83
+ } ,
84
+ } ,
85
+ }
86
+ ]
87
+ } ) ;
88
+
89
+ window . addEventListener ( 'toolcall' , async ( e ) => {
90
+ if ( e . name === 'add-stamp' ) {
91
+ const { name, description, year, imageUrl } = e . input ;
92
+ addStamp ( name , description , year , imageUrl ) ;
93
+
94
+ return e . respondWith ( {
95
+ content : [
96
+ {
97
+ type : "text" ,
98
+ text : `Stamp "${ name } " added successfully! The collection now contains ${ stamps . length } stamps.` ,
99
+ } ,
100
+ ]
101
+ } ) ;
102
+ } else if ( e . name === 'search-stamp' ) {
103
+ const { name, year } = e . input ;
104
+
105
+ // Filter stamps by name or year
106
+ const results = stamps . filter ( ( stamp ) =>
107
+ ( name && stamp . name . toLowerCase ( ) . includes ( name . toLowerCase ( ) ) ) ||
108
+ ( year && stamp . year === year )
109
+ ) ;
110
+
111
+ if ( results . length === 0 ) {
112
+ return {
113
+ content : [
114
+ {
115
+ type : "text" ,
116
+ text : "No stamps found matching the search criteria." ,
117
+ } ,
118
+ ] ,
119
+ } ;
120
+ }
121
+
122
+ // Format results for MCP
123
+ const formattedResults = results . map ( ( stamp ) => ( {
124
+ name : stamp . name ,
125
+ year : stamp . year ,
126
+ description : stamp . description ,
127
+ imageUrl : stamp . imageUrl || "No image available" ,
128
+ } ) ) ;
129
+
130
+ const responseText = formattedResults
131
+ . map (
132
+ ( stamp ) =>
133
+ `Name: ${ stamp . name } \nYear: ${ stamp . year } \nDescription: ${ stamp . description } \nImage: ${ stamp . imageUrl } \n---`
134
+ )
135
+ . join ( "\n" ) ;
136
+
137
+ // Prepare content array starting with text
138
+ const content = [
139
+ {
140
+ type : "text" ,
141
+ text : responseText ,
142
+ } ,
143
+ ] ;
144
+
145
+ // Add images for stamps that have imageUrl
146
+ for ( const stamp of results ) {
147
+ if ( stamp . imageUrl && stamp . imageUrl !== "No image available" ) {
148
+ const imageData = await imageUrlToBase64 ( stamp . imageUrl ) ;
149
+ if ( imageData ) {
150
+ content . push ( {
151
+ type : "image" ,
152
+ data : imageData . base64Data ,
153
+ mimeType : imageData . mimeType ,
154
+ } ) ;
155
+ }
156
+ }
157
+ }
158
+
159
+ return e . respondWith ( { content } ) ;
160
+ }
161
+ } )
162
+ }
163
+
164
+ // Helper function to convert image URL to base64
165
+ async function imageUrlToBase64 ( imageUrl ) {
166
+ try {
167
+ const response = await fetch ( imageUrl ) ;
168
+ if ( ! response . ok ) {
169
+ throw new Error ( `Failed to fetch image: ${ response . status } ` ) ;
170
+ }
171
+
172
+ const blob = await response . blob ( ) ;
173
+ const mimeType = blob . type || 'image/jpeg' ;
174
+
175
+ return new Promise ( ( resolve , reject ) => {
176
+ const reader = new FileReader ( ) ;
177
+ reader . onload = ( ) => {
178
+ const base64Data = reader . result . split ( ',' ) [ 1 ] ;
179
+ resolve ( { base64Data, mimeType } ) ;
180
+ } ;
181
+ reader . onerror = reject ;
182
+ reader . readAsDataURL ( blob ) ;
183
+ } ) ;
184
+ } catch ( error ) {
185
+ console . error ( 'Error converting image to base64:' , error ) ;
186
+ return null ;
187
+ }
188
+ }
189
+
190
+ // Initial render of stamps
191
+ renderStamps ( ) ;
0 commit comments