There's several issues that will control what needs to be done. Is the reply going to be a /notice or a /query directly to the person requesting the 15 lines, or are the 15 lines going to be flooded into channel each time someone does it? Should the 15 lines be parsed to exclude events like join/part/quit/etc? The 15 lines would be from the perspective of whoever is running the script, so if someone sends a /notice to that nick containing objectionable or private content, you'd need to filter that out of the display. Do you want to filter out all/part of the timestamp when displaying the 15 lines?
This snippet sends the last 15 lines of the 1st channel into window @test:
//window @test | var %a $line($chan(1),0) | if (%a <= 15) var %range 1-15 | else var %range $+($calc(%a -14),-,%a) | filter -ww $chan(1) @test *
If you want to exclude the event non-message lines, you'd need to capture more than 15 so you'd have some extras to compensate for the discarded lines.
Another way to do this is just make a script to monitor the :TEXT: event for that channel, and possibly the :ACTION: event to capture /me message. This method by default would ignore join/part/quit, so if you wanted to add those events, they would also need to be scripted to add displays to @test. The script could echo each nick+message to @test in the format you like, and delete any lines off the top when there's more than 15 lines. Then the script can respond to the !last15 to display the window's contents, without counting that !command as a channel message.
Your script can easily get more complicated if you want to include a bad-word filter that prevents tricksters from getting you banned for using bad words, where 1 nick uses the badword and gets banned, then another nick uses !last15 to make you repeat it and gets you banned too.