@drop2run/mcp 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/dist/index.js +519 -54
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# @drop2run/mcp
|
|
2
2
|
|
|
3
|
-
An MCP server that lets Claude — or any MCP client — publish
|
|
4
|
-
[Drop2Run](https://dropto.run)
|
|
3
|
+
An MCP server that lets Claude — or any MCP client — publish what it made to
|
|
4
|
+
[Drop2Run](https://dropto.run): an HTML page, a markdown note, a folder of documents or a built site. It
|
|
5
|
+
returns a live HTTPS URL.
|
|
5
6
|
|
|
6
7
|
Full documentation at [dropto.run/docs/mcp](https://dropto.run/docs/mcp).
|
|
7
8
|
|
|
@@ -89,8 +90,9 @@ it as `subdomain`; say nothing and the name is generated, which is the right def
|
|
|
89
90
|
nobody is going to link to.
|
|
90
91
|
|
|
91
92
|
**A site can be documents instead of a built site.** A publish needs an `index.html` at the top level,
|
|
92
|
-
or at least one `.md`, `.markdown` or `.
|
|
93
|
-
note is a whole site, and Claude does not have to wrap it in HTML
|
|
93
|
+
or at least one document — `.md`, `.markdown`, `.pdf`, `.docx`, `.xlsx` or `.epub` — and those are
|
|
94
|
+
served through the reader. So a single note is a whole site, and Claude does not have to wrap it in HTML
|
|
95
|
+
to publish it.
|
|
94
96
|
|
|
95
97
|
**`publish_files` is text.** A PDF or an image has to come off a disk with `publish_dir`, because these
|
|
96
98
|
files arrive as JSON strings.
|
package/dist/index.js
CHANGED
|
@@ -374,6 +374,485 @@ async function problemToError(response) {
|
|
|
374
374
|
const message = typeof problem.detail === "string" ? problem.detail : typeof problem.title === "string" ? problem.title : `The server responded with ${response.status}.`;
|
|
375
375
|
return new DeployError(code, message, problem);
|
|
376
376
|
}
|
|
377
|
+
const FILE_TYPES = [
|
|
378
|
+
// Documents. The three extensions that make a folder a documents site.
|
|
379
|
+
{
|
|
380
|
+
ext: ".md",
|
|
381
|
+
mime: "text/markdown; charset=utf-8",
|
|
382
|
+
kind: "markdown",
|
|
383
|
+
document: true,
|
|
384
|
+
reader: true,
|
|
385
|
+
editable: true
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
ext: ".markdown",
|
|
389
|
+
mime: "text/markdown; charset=utf-8",
|
|
390
|
+
kind: "markdown",
|
|
391
|
+
document: true,
|
|
392
|
+
reader: true,
|
|
393
|
+
editable: true
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
ext: ".pdf",
|
|
397
|
+
mime: "application/pdf",
|
|
398
|
+
kind: "pdf",
|
|
399
|
+
document: true,
|
|
400
|
+
reader: true,
|
|
401
|
+
editable: false
|
|
402
|
+
},
|
|
403
|
+
// Office documents and ebooks. Each has its own kind because each needs its own renderer, loaded
|
|
404
|
+
// only when one is opened — `.docx` through mammoth, `.xlsx` through read-excel-file, `.epub`
|
|
405
|
+
// through foliate-js.
|
|
406
|
+
{
|
|
407
|
+
ext: ".docx",
|
|
408
|
+
mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
409
|
+
kind: "docx",
|
|
410
|
+
document: true,
|
|
411
|
+
reader: true,
|
|
412
|
+
editable: false
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
ext: ".xlsx",
|
|
416
|
+
mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
417
|
+
kind: "xlsx",
|
|
418
|
+
document: true,
|
|
419
|
+
reader: true,
|
|
420
|
+
editable: false
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
ext: ".epub",
|
|
424
|
+
mime: "application/epub+zip",
|
|
425
|
+
kind: "epub",
|
|
426
|
+
document: true,
|
|
427
|
+
reader: true,
|
|
428
|
+
editable: false
|
|
429
|
+
},
|
|
430
|
+
// Prose the reader shows preformatted.
|
|
431
|
+
{
|
|
432
|
+
ext: ".txt",
|
|
433
|
+
mime: "text/plain; charset=utf-8",
|
|
434
|
+
kind: "text",
|
|
435
|
+
document: false,
|
|
436
|
+
reader: true,
|
|
437
|
+
editable: true
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
ext: ".log",
|
|
441
|
+
mime: "text/plain; charset=utf-8",
|
|
442
|
+
kind: "text",
|
|
443
|
+
document: false,
|
|
444
|
+
reader: true,
|
|
445
|
+
editable: false
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
ext: ".csv",
|
|
449
|
+
mime: "text/csv; charset=utf-8",
|
|
450
|
+
kind: "text",
|
|
451
|
+
document: false,
|
|
452
|
+
reader: true,
|
|
453
|
+
editable: true
|
|
454
|
+
},
|
|
455
|
+
// Structured text. Read through the reader inside a folder of notes, which is why these carry
|
|
456
|
+
// `reader: true` while the source formats below do not.
|
|
457
|
+
{
|
|
458
|
+
ext: ".json",
|
|
459
|
+
mime: "application/json",
|
|
460
|
+
kind: "code",
|
|
461
|
+
document: false,
|
|
462
|
+
reader: true,
|
|
463
|
+
editable: true
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
ext: ".yaml",
|
|
467
|
+
mime: "text/yaml; charset=utf-8",
|
|
468
|
+
kind: "code",
|
|
469
|
+
document: false,
|
|
470
|
+
reader: true,
|
|
471
|
+
editable: true
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
ext: ".yml",
|
|
475
|
+
mime: "text/yaml; charset=utf-8",
|
|
476
|
+
kind: "code",
|
|
477
|
+
document: false,
|
|
478
|
+
reader: true,
|
|
479
|
+
editable: true
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
// No registered media type, so it takes text/plain rather than an invented one. The point is
|
|
483
|
+
// only that a browser shows it instead of downloading it.
|
|
484
|
+
ext: ".toml",
|
|
485
|
+
mime: "text/plain; charset=utf-8",
|
|
486
|
+
kind: "code",
|
|
487
|
+
document: false,
|
|
488
|
+
reader: true,
|
|
489
|
+
editable: true
|
|
490
|
+
},
|
|
491
|
+
// Source the viewer highlights. A link straight to one of these hands over the file.
|
|
492
|
+
{
|
|
493
|
+
ext: ".css",
|
|
494
|
+
mime: "text/css; charset=utf-8",
|
|
495
|
+
kind: "code",
|
|
496
|
+
document: false,
|
|
497
|
+
reader: false,
|
|
498
|
+
editable: true
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
ext: ".js",
|
|
502
|
+
mime: "text/javascript; charset=utf-8",
|
|
503
|
+
kind: "code",
|
|
504
|
+
document: false,
|
|
505
|
+
reader: false,
|
|
506
|
+
editable: true
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
ext: ".ts",
|
|
510
|
+
mime: null,
|
|
511
|
+
kind: "code",
|
|
512
|
+
document: false,
|
|
513
|
+
reader: false,
|
|
514
|
+
editable: false
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
ext: ".tsx",
|
|
518
|
+
mime: null,
|
|
519
|
+
kind: "code",
|
|
520
|
+
document: false,
|
|
521
|
+
reader: false,
|
|
522
|
+
editable: false
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
ext: ".jsx",
|
|
526
|
+
mime: null,
|
|
527
|
+
kind: "code",
|
|
528
|
+
document: false,
|
|
529
|
+
reader: false,
|
|
530
|
+
editable: false
|
|
531
|
+
},
|
|
532
|
+
{
|
|
533
|
+
ext: ".sh",
|
|
534
|
+
mime: null,
|
|
535
|
+
kind: "code",
|
|
536
|
+
document: false,
|
|
537
|
+
reader: false,
|
|
538
|
+
editable: false
|
|
539
|
+
},
|
|
540
|
+
// The languages an assistant writes most, beyond the web's own. Same row as `.ts`: shown as source
|
|
541
|
+
// when somebody opens one in the reader or publishes it on its own, handed over as a file when a
|
|
542
|
+
// link names it directly, and no media type — adding one changes what a browser does with a
|
|
543
|
+
// published file, which I10 makes a decision of its own rather than a side effect of this list.
|
|
544
|
+
{
|
|
545
|
+
ext: ".py",
|
|
546
|
+
mime: null,
|
|
547
|
+
kind: "code",
|
|
548
|
+
document: false,
|
|
549
|
+
reader: false,
|
|
550
|
+
editable: false
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
ext: ".sql",
|
|
554
|
+
mime: null,
|
|
555
|
+
kind: "code",
|
|
556
|
+
document: false,
|
|
557
|
+
reader: false,
|
|
558
|
+
editable: false
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
ext: ".go",
|
|
562
|
+
mime: null,
|
|
563
|
+
kind: "code",
|
|
564
|
+
document: false,
|
|
565
|
+
reader: false,
|
|
566
|
+
editable: false
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
ext: ".rs",
|
|
570
|
+
mime: null,
|
|
571
|
+
kind: "code",
|
|
572
|
+
document: false,
|
|
573
|
+
reader: false,
|
|
574
|
+
editable: false
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
ext: ".java",
|
|
578
|
+
mime: null,
|
|
579
|
+
kind: "code",
|
|
580
|
+
document: false,
|
|
581
|
+
reader: false,
|
|
582
|
+
editable: false
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
ext: ".kt",
|
|
586
|
+
mime: null,
|
|
587
|
+
kind: "code",
|
|
588
|
+
document: false,
|
|
589
|
+
reader: false,
|
|
590
|
+
editable: false
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
ext: ".swift",
|
|
594
|
+
mime: null,
|
|
595
|
+
kind: "code",
|
|
596
|
+
document: false,
|
|
597
|
+
reader: false,
|
|
598
|
+
editable: false
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
ext: ".rb",
|
|
602
|
+
mime: null,
|
|
603
|
+
kind: "code",
|
|
604
|
+
document: false,
|
|
605
|
+
reader: false,
|
|
606
|
+
editable: false
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
ext: ".php",
|
|
610
|
+
mime: null,
|
|
611
|
+
kind: "code",
|
|
612
|
+
document: false,
|
|
613
|
+
reader: false,
|
|
614
|
+
editable: false
|
|
615
|
+
},
|
|
616
|
+
{
|
|
617
|
+
ext: ".c",
|
|
618
|
+
mime: null,
|
|
619
|
+
kind: "code",
|
|
620
|
+
document: false,
|
|
621
|
+
reader: false,
|
|
622
|
+
editable: false
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
ext: ".h",
|
|
626
|
+
mime: null,
|
|
627
|
+
kind: "code",
|
|
628
|
+
document: false,
|
|
629
|
+
reader: false,
|
|
630
|
+
editable: false
|
|
631
|
+
},
|
|
632
|
+
{
|
|
633
|
+
ext: ".cpp",
|
|
634
|
+
mime: null,
|
|
635
|
+
kind: "code",
|
|
636
|
+
document: false,
|
|
637
|
+
reader: false,
|
|
638
|
+
editable: false
|
|
639
|
+
},
|
|
640
|
+
{
|
|
641
|
+
ext: ".cs",
|
|
642
|
+
mime: null,
|
|
643
|
+
kind: "code",
|
|
644
|
+
document: false,
|
|
645
|
+
reader: false,
|
|
646
|
+
editable: false
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
ext: ".r",
|
|
650
|
+
mime: null,
|
|
651
|
+
kind: "code",
|
|
652
|
+
document: false,
|
|
653
|
+
reader: false,
|
|
654
|
+
editable: false
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
ext: ".xml",
|
|
658
|
+
mime: "application/xml",
|
|
659
|
+
kind: "code",
|
|
660
|
+
document: false,
|
|
661
|
+
reader: false,
|
|
662
|
+
editable: true
|
|
663
|
+
},
|
|
664
|
+
// Pictures, shown with an img element.
|
|
665
|
+
{
|
|
666
|
+
ext: ".png",
|
|
667
|
+
mime: "image/png",
|
|
668
|
+
kind: "image",
|
|
669
|
+
document: false,
|
|
670
|
+
reader: false,
|
|
671
|
+
editable: false
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
ext: ".jpg",
|
|
675
|
+
mime: "image/jpeg",
|
|
676
|
+
kind: "image",
|
|
677
|
+
document: false,
|
|
678
|
+
reader: false,
|
|
679
|
+
editable: false
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
ext: ".jpeg",
|
|
683
|
+
mime: "image/jpeg",
|
|
684
|
+
kind: "image",
|
|
685
|
+
document: false,
|
|
686
|
+
reader: false,
|
|
687
|
+
editable: false
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
ext: ".gif",
|
|
691
|
+
mime: "image/gif",
|
|
692
|
+
kind: "image",
|
|
693
|
+
document: false,
|
|
694
|
+
reader: false,
|
|
695
|
+
editable: false
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
ext: ".webp",
|
|
699
|
+
mime: "image/webp",
|
|
700
|
+
kind: "image",
|
|
701
|
+
document: false,
|
|
702
|
+
reader: false,
|
|
703
|
+
editable: false
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
ext: ".avif",
|
|
707
|
+
mime: "image/avif",
|
|
708
|
+
kind: "image",
|
|
709
|
+
document: false,
|
|
710
|
+
reader: false,
|
|
711
|
+
editable: false
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
// Text, hand-edited in practice, and script-carrying. Editable anyway: the Worker derives the
|
|
715
|
+
// type at serve time (I10) and already serves whatever was uploaded, so withholding the edit
|
|
716
|
+
// would withhold nothing else.
|
|
717
|
+
ext: ".svg",
|
|
718
|
+
mime: "image/svg+xml",
|
|
719
|
+
kind: "image",
|
|
720
|
+
document: false,
|
|
721
|
+
reader: false,
|
|
722
|
+
editable: true
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
ext: ".ico",
|
|
726
|
+
mime: "image/x-icon",
|
|
727
|
+
kind: "image",
|
|
728
|
+
document: false,
|
|
729
|
+
reader: false,
|
|
730
|
+
editable: false
|
|
731
|
+
},
|
|
732
|
+
// A customer's own pages. Never rendered inside the reader.
|
|
733
|
+
{
|
|
734
|
+
ext: ".html",
|
|
735
|
+
mime: "text/html; charset=utf-8",
|
|
736
|
+
kind: "html",
|
|
737
|
+
document: false,
|
|
738
|
+
reader: false,
|
|
739
|
+
editable: true
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
// ⚠️ No media type, and that is the state of the code rather than a decision: `TYPES` in
|
|
743
|
+
// `headers.ts` has never held `htm`, so a published `.htm` is served as octet-stream and
|
|
744
|
+
// downloads instead of rendering. Recorded here rather than fixed, because this table was
|
|
745
|
+
// introduced as a refactor and a refactor that changes what a URL answers is not one.
|
|
746
|
+
ext: ".htm",
|
|
747
|
+
mime: null,
|
|
748
|
+
kind: "html",
|
|
749
|
+
document: false,
|
|
750
|
+
reader: false,
|
|
751
|
+
editable: true
|
|
752
|
+
},
|
|
753
|
+
// Everything below renders as nothing — the viewer offers a download — but still needs a media
|
|
754
|
+
// type, because these are the files a static site is built out of.
|
|
755
|
+
{
|
|
756
|
+
ext: ".mjs",
|
|
757
|
+
mime: "text/javascript; charset=utf-8",
|
|
758
|
+
kind: "binary",
|
|
759
|
+
document: false,
|
|
760
|
+
reader: false,
|
|
761
|
+
editable: true
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
ext: ".cjs",
|
|
765
|
+
mime: null,
|
|
766
|
+
kind: "binary",
|
|
767
|
+
document: false,
|
|
768
|
+
reader: false,
|
|
769
|
+
editable: true
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
ext: ".map",
|
|
773
|
+
mime: "application/json",
|
|
774
|
+
kind: "binary",
|
|
775
|
+
document: false,
|
|
776
|
+
reader: false,
|
|
777
|
+
editable: false
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
ext: ".wasm",
|
|
781
|
+
mime: "application/wasm",
|
|
782
|
+
kind: "binary",
|
|
783
|
+
document: false,
|
|
784
|
+
reader: false,
|
|
785
|
+
editable: false
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
ext: ".woff2",
|
|
789
|
+
mime: "font/woff2",
|
|
790
|
+
kind: "binary",
|
|
791
|
+
document: false,
|
|
792
|
+
reader: false,
|
|
793
|
+
editable: false
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
ext: ".woff",
|
|
797
|
+
mime: "font/woff",
|
|
798
|
+
kind: "binary",
|
|
799
|
+
document: false,
|
|
800
|
+
reader: false,
|
|
801
|
+
editable: false
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
ext: ".ttf",
|
|
805
|
+
mime: "font/ttf",
|
|
806
|
+
kind: "binary",
|
|
807
|
+
document: false,
|
|
808
|
+
reader: false,
|
|
809
|
+
editable: false
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
ext: ".mp4",
|
|
813
|
+
mime: "video/mp4",
|
|
814
|
+
kind: "binary",
|
|
815
|
+
document: false,
|
|
816
|
+
reader: false,
|
|
817
|
+
editable: false
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
ext: ".webm",
|
|
821
|
+
mime: "video/webm",
|
|
822
|
+
kind: "binary",
|
|
823
|
+
document: false,
|
|
824
|
+
reader: false,
|
|
825
|
+
editable: false
|
|
826
|
+
}
|
|
827
|
+
];
|
|
828
|
+
function extensionsWhere(include) {
|
|
829
|
+
return FILE_TYPES.filter(include).map((type) => type.ext);
|
|
830
|
+
}
|
|
831
|
+
const DOCUMENT_EXTENSIONS$1 = extensionsWhere((type) => type.document);
|
|
832
|
+
const VIEWABLE_EXTENSIONS$1 = extensionsWhere(
|
|
833
|
+
(type) => type.kind !== "binary"
|
|
834
|
+
);
|
|
835
|
+
extensionsWhere((type) => type.reader);
|
|
836
|
+
extensionsWhere((type) => type.editable);
|
|
837
|
+
const WEB_ASSET_EXTENSIONS = [
|
|
838
|
+
".html",
|
|
839
|
+
".htm",
|
|
840
|
+
".js",
|
|
841
|
+
".mjs",
|
|
842
|
+
".cjs",
|
|
843
|
+
".css"
|
|
844
|
+
];
|
|
845
|
+
const GALLERY_EXTENSIONS = extensionsWhere(
|
|
846
|
+
(type) => type.kind === "image"
|
|
847
|
+
);
|
|
848
|
+
Object.fromEntries(
|
|
849
|
+
FILE_TYPES.filter((type) => type.mime !== null).map((type) => [type.ext, type.mime])
|
|
850
|
+
);
|
|
851
|
+
function extensionOf(path) {
|
|
852
|
+
const name = path.slice(path.lastIndexOf("/") + 1);
|
|
853
|
+
const dot = name.lastIndexOf(".");
|
|
854
|
+
return dot <= 0 ? "" : name.slice(dot).toLowerCase();
|
|
855
|
+
}
|
|
377
856
|
async function sha256Hex(bytes) {
|
|
378
857
|
const digest = await crypto.subtle.digest("SHA-256", bytes.slice().buffer);
|
|
379
858
|
return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
@@ -405,52 +884,29 @@ function shouldIgnore(path) {
|
|
|
405
884
|
return segments.some((segment) => IGNORED_DIRECTORIES.includes(segment));
|
|
406
885
|
}
|
|
407
886
|
const REQUIRED_INDEX = "index.html";
|
|
408
|
-
const DOCUMENT_EXTENSIONS =
|
|
887
|
+
const DOCUMENT_EXTENSIONS = DOCUMENT_EXTENSIONS$1;
|
|
409
888
|
function isDocumentPath(path) {
|
|
410
889
|
const lower = path.toLowerCase();
|
|
411
890
|
return DOCUMENT_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
412
891
|
}
|
|
413
|
-
const VIEWABLE_EXTENSIONS =
|
|
414
|
-
// Documents — these also count at any depth, via DOCUMENT_EXTENSIONS.
|
|
415
|
-
".md",
|
|
416
|
-
".markdown",
|
|
417
|
-
".pdf",
|
|
418
|
-
// Prose the reader shows preformatted.
|
|
419
|
-
".txt",
|
|
420
|
-
".log",
|
|
421
|
-
".csv",
|
|
422
|
-
// Source the reader shows with highlighting.
|
|
423
|
-
".json",
|
|
424
|
-
".yaml",
|
|
425
|
-
".yml",
|
|
426
|
-
".toml",
|
|
427
|
-
".css",
|
|
428
|
-
".js",
|
|
429
|
-
".ts",
|
|
430
|
-
".tsx",
|
|
431
|
-
".jsx",
|
|
432
|
-
".sh",
|
|
433
|
-
".xml",
|
|
434
|
-
// Pictures the reader shows with an img element.
|
|
435
|
-
".png",
|
|
436
|
-
".jpg",
|
|
437
|
-
".jpeg",
|
|
438
|
-
".gif",
|
|
439
|
-
".webp",
|
|
440
|
-
".avif",
|
|
441
|
-
".svg",
|
|
442
|
-
".ico",
|
|
443
|
-
// See the note above: a floor for a caller that did not rename it.
|
|
444
|
-
".html",
|
|
445
|
-
".htm"
|
|
446
|
-
];
|
|
892
|
+
const VIEWABLE_EXTENSIONS = VIEWABLE_EXTENSIONS$1;
|
|
447
893
|
function isViewablePath(path) {
|
|
448
894
|
const lower = path.toLowerCase();
|
|
449
895
|
return VIEWABLE_EXTENSIONS.some((extension) => lower.endsWith(extension));
|
|
450
896
|
}
|
|
897
|
+
function isImageFolder(paths) {
|
|
898
|
+
let pictures = false;
|
|
899
|
+
for (const path of paths) {
|
|
900
|
+
const ext = extensionOf(path);
|
|
901
|
+
if (WEB_ASSET_EXTENSIONS.includes(ext)) return false;
|
|
902
|
+
if (GALLERY_EXTENSIONS.includes(ext)) pictures = true;
|
|
903
|
+
}
|
|
904
|
+
return pictures;
|
|
905
|
+
}
|
|
451
906
|
function canPublish(paths) {
|
|
452
907
|
if (paths.some((path) => path === REQUIRED_INDEX)) return true;
|
|
453
908
|
if (paths.some(isDocumentPath)) return true;
|
|
909
|
+
if (isImageFolder(paths)) return true;
|
|
454
910
|
return paths.length === 1 && paths[0] !== void 0 && isViewablePath(paths[0]);
|
|
455
911
|
}
|
|
456
912
|
function renameOfLoneHtmlPage(paths) {
|
|
@@ -479,37 +935,42 @@ function checkLimits(files, limits) {
|
|
|
479
935
|
`A site needs an ${REQUIRED_INDEX} at its top level, or at least one .md or .pdf file to publish as a documents site — or be a single file that can be displayed on its own, such as a note, a PDF, a page or an image. Drop the folder that contains it, not the folder above.`
|
|
480
936
|
);
|
|
481
937
|
}
|
|
482
|
-
|
|
938
|
+
const refusal = sizeRefusal(
|
|
939
|
+
files.map((file) => ({ path: file.path, size: file.bytes.length })),
|
|
940
|
+
limits
|
|
941
|
+
);
|
|
942
|
+
if (refusal) throw refusal;
|
|
943
|
+
}
|
|
944
|
+
function sizeRefusal(files, limits) {
|
|
945
|
+
if (!limits) return null;
|
|
483
946
|
if (files.length > limits.maxFiles) {
|
|
484
|
-
|
|
947
|
+
return new DeployError(
|
|
485
948
|
ClientErrorCode.TooManyFiles,
|
|
486
949
|
`That is ${files.length} files, and this plan allows ${limits.maxFiles}.`,
|
|
487
950
|
{ limit: limits.maxFiles, actual: files.length }
|
|
488
951
|
);
|
|
489
952
|
}
|
|
490
|
-
const oversized = files.find((file) => file.
|
|
953
|
+
const oversized = files.find((file) => file.size > limits.maxFileBytes);
|
|
491
954
|
if (oversized) {
|
|
492
|
-
|
|
955
|
+
return new DeployError(
|
|
493
956
|
ClientErrorCode.FileTooLarge,
|
|
494
|
-
`${oversized.path} is ${formatBytes(oversized.
|
|
957
|
+
`${oversized.path} is ${formatBytes(oversized.size)}, and this plan allows ${formatBytes(limits.maxFileBytes)} per file.`,
|
|
495
958
|
{
|
|
496
959
|
path: oversized.path,
|
|
497
960
|
limit: limits.maxFileBytes,
|
|
498
|
-
actual: oversized.
|
|
961
|
+
actual: oversized.size
|
|
499
962
|
}
|
|
500
963
|
);
|
|
501
964
|
}
|
|
502
|
-
const total =
|
|
965
|
+
const total = files.reduce((sum, file) => sum + file.size, 0);
|
|
503
966
|
if (limits.maxSiteBytes !== null && total > limits.maxSiteBytes) {
|
|
504
|
-
|
|
967
|
+
return new DeployError(
|
|
505
968
|
ClientErrorCode.TooLarge,
|
|
506
969
|
`That drop is ${formatBytes(total)}, and this plan allows ${formatBytes(limits.maxSiteBytes)}.`,
|
|
507
970
|
{ limit: limits.maxSiteBytes, actual: total }
|
|
508
971
|
);
|
|
509
972
|
}
|
|
510
|
-
|
|
511
|
-
function totalBytes(files) {
|
|
512
|
-
return files.reduce((sum, file) => sum + file.bytes.length, 0);
|
|
973
|
+
return null;
|
|
513
974
|
}
|
|
514
975
|
const MAX_NAME_LENGTH = 60;
|
|
515
976
|
const HEAD_BYTES = 64 * 1024;
|
|
@@ -860,8 +1321,11 @@ async function publish(credentials, source, site, onProgress, subdomain) {
|
|
|
860
1321
|
});
|
|
861
1322
|
return { url, siteId: target.siteId, subdomain: target.subdomain, files, unchanged };
|
|
862
1323
|
}
|
|
863
|
-
function publishDirectory(credentials, directory, site, onProgress, subdomain) {
|
|
864
|
-
|
|
1324
|
+
async function publishDirectory(credentials, directory, site, onProgress, subdomain) {
|
|
1325
|
+
const found = await stat(directory).catch(() => null);
|
|
1326
|
+
if (found === null) throw new Error(`There is no folder at ${directory}.`);
|
|
1327
|
+
if (!found.isDirectory()) throw new Error(`${directory} is a file, not a folder to publish.`);
|
|
1328
|
+
return await publish(credentials, directorySource(directory), site, onProgress, subdomain);
|
|
865
1329
|
}
|
|
866
1330
|
function cleanPath(path) {
|
|
867
1331
|
const cleaned = path.trim().replaceAll("\\", "/").replace(/^\.?\//, "");
|
|
@@ -1064,7 +1528,7 @@ async function signInWithCode(waitSeconds, replace = false) {
|
|
|
1064
1528
|
"Call this tool again to keep waiting. Nothing has been changed yet."
|
|
1065
1529
|
].join("\n");
|
|
1066
1530
|
}
|
|
1067
|
-
const SERVER_VERSION = "0.
|
|
1531
|
+
const SERVER_VERSION = "0.6.0";
|
|
1068
1532
|
const CREDENTIAL_WRITE = {
|
|
1069
1533
|
readOnlyHint: false,
|
|
1070
1534
|
destructiveHint: false,
|
|
@@ -1136,7 +1600,7 @@ function createServer() {
|
|
|
1136
1600
|
{ name: "drop2run", version: SERVER_VERSION },
|
|
1137
1601
|
{
|
|
1138
1602
|
instructions: [
|
|
1139
|
-
"Publishes static sites to Drop2Run.",
|
|
1603
|
+
"Publishes pages, documents and static sites to Drop2Run, and returns a URL.",
|
|
1140
1604
|
"",
|
|
1141
1605
|
"Use publish_files for anything you wrote here — a page, a markdown note, several files",
|
|
1142
1606
|
"together — and publish_dir for a folder that already exists on disk. Leave `site` unset to",
|
|
@@ -1149,9 +1613,10 @@ function createServer() {
|
|
|
1149
1613
|
"be deleted by hand. `site` and `subdomain` are opposites: one publishes over a site that",
|
|
1150
1614
|
"exists, the other creates one that does not. Never pass both.",
|
|
1151
1615
|
"",
|
|
1152
|
-
"A site needs an index.html at its top level, or at least one .md, .markdown
|
|
1153
|
-
"which publishes as a documents site and is read through a
|
|
1154
|
-
"index.html, and a single note is a whole publish that needs
|
|
1616
|
+
"A site needs an index.html at its top level, or at least one document (.md, .markdown,",
|
|
1617
|
+
".pdf, .docx, .xlsx or .epub), which publishes as a documents site and is read through a",
|
|
1618
|
+
"viewer. So one page goes at index.html, and a single note is a whole publish that needs",
|
|
1619
|
+
"no wrapping in HTML.",
|
|
1155
1620
|
"",
|
|
1156
1621
|
"Publishing needs an access token. If there is none, call login — it opens a browser and",
|
|
1157
1622
|
"stores one, and no command line or manual token is involved. Where no browser can be",
|
|
@@ -1223,7 +1688,7 @@ function createServer() {
|
|
|
1223
1688
|
"publish_dir",
|
|
1224
1689
|
{
|
|
1225
1690
|
title: "Publish a folder",
|
|
1226
|
-
description: "Publishes a folder
|
|
1691
|
+
description: "Publishes a folder from disk and returns its URL. The folder needs an index.html at its root, or documents (.md, .pdf, .docx, .xlsx, .epub), which are served through the reader.",
|
|
1227
1692
|
inputSchema: {
|
|
1228
1693
|
path: z.string().min(1).describe("Absolute path of the folder to publish."),
|
|
1229
1694
|
site: z.string().optional().describe("Subdomain or site id to publish over. Omit to create a new site."),
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drop2run/mcp",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "MCP server that publishes a
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "MCP server that publishes what an AI made — a page, a document or a whole site — to Drop2Run and returns a live URL.",
|
|
5
5
|
"mcpName": "run.dropto/drop2run",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|