dynmcp 0.2.0 → 0.3.1
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/dist/index.cjs +1111 -181
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1131 -179
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -23,13 +23,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
23
|
));
|
|
24
24
|
|
|
25
25
|
// src/cli.ts
|
|
26
|
-
var
|
|
26
|
+
var import_node_process7 = __toESM(require("process"), 1);
|
|
27
27
|
var import_commander = require("commander");
|
|
28
28
|
|
|
29
29
|
// package.json
|
|
30
30
|
var package_default = {
|
|
31
31
|
name: "dynmcp",
|
|
32
|
-
version: "0.
|
|
32
|
+
version: "0.3.1",
|
|
33
33
|
description: "Dynamic MCP context management tool for AI MCP-enabled agents and clients.",
|
|
34
34
|
author: "Brandon Burrus <brandon@burrus.io>",
|
|
35
35
|
license: "MIT",
|
|
@@ -109,6 +109,7 @@ var package_default = {
|
|
|
109
109
|
"@commitlint/cli": "^21.0.1",
|
|
110
110
|
"@commitlint/config-conventional": "^21.0.1",
|
|
111
111
|
"@types/node": "^25.9.0",
|
|
112
|
+
"@vitest/coverage-v8": "^4.1.6",
|
|
112
113
|
husky: "^9.1.7",
|
|
113
114
|
tsup: "^8.5.1",
|
|
114
115
|
tsx: "^4.22.2",
|
|
@@ -122,8 +123,8 @@ var import_figlet = __toESM(require("figlet"), 1);
|
|
|
122
123
|
var import_chalk = __toESM(require("chalk"), 1);
|
|
123
124
|
|
|
124
125
|
// src/proxy/index.ts
|
|
125
|
-
var
|
|
126
|
-
var
|
|
126
|
+
var import_node_process6 = __toESM(require("process"), 1);
|
|
127
|
+
var import_stdio3 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
127
128
|
|
|
128
129
|
// src/config/schema.ts
|
|
129
130
|
var import_zod = require("zod");
|
|
@@ -385,33 +386,39 @@ function isYamlFile(filePath) {
|
|
|
385
386
|
// src/config/json-schema.ts
|
|
386
387
|
var import_zod2 = require("zod");
|
|
387
388
|
|
|
388
|
-
// src/proxy/
|
|
389
|
-
var
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
function
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
389
|
+
// src/proxy/orchestrator.ts
|
|
390
|
+
var import_node_process4 = __toESM(require("process"), 1);
|
|
391
|
+
|
|
392
|
+
// src/proxy/capability-aggregator.ts
|
|
393
|
+
function aggregateCapabilities(upstreams) {
|
|
394
|
+
const aggregated = {
|
|
395
|
+
tools: { listChanged: true }
|
|
396
|
+
};
|
|
397
|
+
for (const caps of upstreams) {
|
|
398
|
+
if (caps === void 0) continue;
|
|
399
|
+
if (caps.resources !== void 0) {
|
|
400
|
+
aggregated.resources ??= {};
|
|
401
|
+
if (caps.resources.subscribe === true) {
|
|
402
|
+
aggregated.resources.subscribe = true;
|
|
403
|
+
}
|
|
404
|
+
if (caps.resources.listChanged === true) {
|
|
405
|
+
aggregated.resources.listChanged = true;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
if (caps.prompts !== void 0) {
|
|
409
|
+
aggregated.prompts ??= {};
|
|
410
|
+
if (caps.prompts.listChanged === true) {
|
|
411
|
+
aggregated.prompts.listChanged = true;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (caps.logging !== void 0) {
|
|
415
|
+
aggregated.logging ??= {};
|
|
416
|
+
}
|
|
417
|
+
if (caps.completions !== void 0) {
|
|
418
|
+
aggregated.completions ??= {};
|
|
413
419
|
}
|
|
414
420
|
}
|
|
421
|
+
return aggregated;
|
|
415
422
|
}
|
|
416
423
|
|
|
417
424
|
// src/proxy/tool-catalog.ts
|
|
@@ -517,15 +524,251 @@ function buildAnnotationLines(tool) {
|
|
|
517
524
|
return lines;
|
|
518
525
|
}
|
|
519
526
|
|
|
527
|
+
// src/proxy/notification-forwarder.ts
|
|
528
|
+
var NotificationForwarder = class {
|
|
529
|
+
constructor(registry, resourceRouter, promptRouter, toolsByMcp, setToolCatalog, namespaced) {
|
|
530
|
+
this.registry = registry;
|
|
531
|
+
this.resourceRouter = resourceRouter;
|
|
532
|
+
this.promptRouter = promptRouter;
|
|
533
|
+
this.toolsByMcp = toolsByMcp;
|
|
534
|
+
this.setToolCatalog = setToolCatalog;
|
|
535
|
+
this.namespaced = namespaced;
|
|
536
|
+
}
|
|
537
|
+
registry;
|
|
538
|
+
resourceRouter;
|
|
539
|
+
promptRouter;
|
|
540
|
+
toolsByMcp;
|
|
541
|
+
setToolCatalog;
|
|
542
|
+
namespaced;
|
|
543
|
+
hostHandlers = {};
|
|
544
|
+
setHostHandlers(handlers) {
|
|
545
|
+
this.hostHandlers = handlers;
|
|
546
|
+
}
|
|
547
|
+
async handleToolsListChanged(mcpName2) {
|
|
548
|
+
const client = this.registry.get(mcpName2);
|
|
549
|
+
if (client === void 0) return;
|
|
550
|
+
const tools = await client.listTools().catch(() => []);
|
|
551
|
+
this.toolsByMcp.set(mcpName2, tools);
|
|
552
|
+
const rebuilt = this.namespaced ? ToolCatalog.fromGrouped(this.toolsByMcp) : ToolCatalog.fromFlat([...this.toolsByMcp.values()][0] ?? []);
|
|
553
|
+
this.setToolCatalog(rebuilt);
|
|
554
|
+
await this.hostHandlers.onToolsListChanged?.();
|
|
555
|
+
}
|
|
556
|
+
async handleResourcesListChanged(mcpName2) {
|
|
557
|
+
const router = this.resourceRouter();
|
|
558
|
+
const client = this.registry.get(mcpName2);
|
|
559
|
+
if (router === null || client === void 0) return;
|
|
560
|
+
const [resources, templates] = await Promise.all([
|
|
561
|
+
client.listResources().catch(() => []),
|
|
562
|
+
client.listResourceTemplates().catch(() => [])
|
|
563
|
+
]);
|
|
564
|
+
router.setResources(mcpName2, resources);
|
|
565
|
+
router.setTemplates(mcpName2, templates);
|
|
566
|
+
await this.hostHandlers.onResourcesListChanged?.();
|
|
567
|
+
}
|
|
568
|
+
async handleResourceUpdated(params) {
|
|
569
|
+
await this.hostHandlers.onResourceUpdated?.(params);
|
|
570
|
+
}
|
|
571
|
+
async handlePromptsListChanged(mcpName2) {
|
|
572
|
+
const router = this.promptRouter();
|
|
573
|
+
const client = this.registry.get(mcpName2);
|
|
574
|
+
if (router === null || client === void 0) return;
|
|
575
|
+
const prompts = await client.listPrompts().catch(() => []);
|
|
576
|
+
router.setPrompts(mcpName2, prompts);
|
|
577
|
+
await this.hostHandlers.onPromptsListChanged?.();
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Rewrites the upstream's `logger` field with the originating MCP's name as a
|
|
581
|
+
* prefix so the host can attribute log lines, then forwards the message to the
|
|
582
|
+
* host's log message handler.
|
|
583
|
+
*/
|
|
584
|
+
async handleLogMessage(mcpName2, params) {
|
|
585
|
+
const handler = this.hostHandlers.onLogMessage;
|
|
586
|
+
if (handler === void 0) return;
|
|
587
|
+
if (!this.namespaced) {
|
|
588
|
+
await handler(params);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
const prefixed = {
|
|
592
|
+
...params,
|
|
593
|
+
logger: params.logger === void 0 ? mcpName2 : `${mcpName2}/${params.logger}`
|
|
594
|
+
};
|
|
595
|
+
await handler(prefixed);
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
// src/proxy/prompt-router.ts
|
|
600
|
+
var PromptRouter = class {
|
|
601
|
+
mcpOrder;
|
|
602
|
+
perMcp;
|
|
603
|
+
nameOwners = /* @__PURE__ */ new Map();
|
|
604
|
+
detectedCollisions = [];
|
|
605
|
+
constructor(mcpOrder) {
|
|
606
|
+
this.mcpOrder = [...mcpOrder];
|
|
607
|
+
this.perMcp = new Map(this.mcpOrder.map((name) => [name, []]));
|
|
608
|
+
}
|
|
609
|
+
setPrompts(mcpName2, prompts) {
|
|
610
|
+
const entry = this.perMcp.get(mcpName2);
|
|
611
|
+
if (entry === void 0) {
|
|
612
|
+
throw new Error(`PromptRouter: unknown mcp "${mcpName2}"`);
|
|
613
|
+
}
|
|
614
|
+
this.perMcp.set(mcpName2, [...prompts]);
|
|
615
|
+
this.rebuild();
|
|
616
|
+
}
|
|
617
|
+
aggregatedPrompts() {
|
|
618
|
+
const result = [];
|
|
619
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
620
|
+
const entry = this.perMcp.get(mcpName2);
|
|
621
|
+
if (entry !== void 0) {
|
|
622
|
+
result.push(...entry);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
ownerOf(promptName) {
|
|
628
|
+
return this.nameOwners.get(promptName);
|
|
629
|
+
}
|
|
630
|
+
collisions() {
|
|
631
|
+
return this.detectedCollisions;
|
|
632
|
+
}
|
|
633
|
+
rebuild() {
|
|
634
|
+
this.nameOwners = /* @__PURE__ */ new Map();
|
|
635
|
+
const collisions = [];
|
|
636
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
637
|
+
const prompts = this.perMcp.get(mcpName2);
|
|
638
|
+
if (prompts === void 0) continue;
|
|
639
|
+
for (const prompt of prompts) {
|
|
640
|
+
const existing = this.nameOwners.get(prompt.name);
|
|
641
|
+
if (existing === void 0) {
|
|
642
|
+
this.nameOwners.set(prompt.name, mcpName2);
|
|
643
|
+
} else {
|
|
644
|
+
collisions.push({ name: prompt.name, chosen: existing, shadowed: mcpName2 });
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
this.detectedCollisions = collisions;
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
// src/proxy/resource-router.ts
|
|
653
|
+
var ResourceRouter = class {
|
|
654
|
+
mcpOrder;
|
|
655
|
+
perMcp;
|
|
656
|
+
uriOwners = /* @__PURE__ */ new Map();
|
|
657
|
+
templateOwners = [];
|
|
658
|
+
detectedCollisions = [];
|
|
659
|
+
constructor(mcpOrder) {
|
|
660
|
+
this.mcpOrder = [...mcpOrder];
|
|
661
|
+
this.perMcp = new Map(
|
|
662
|
+
this.mcpOrder.map((name) => [name, { resources: [], templates: [] }])
|
|
663
|
+
);
|
|
664
|
+
}
|
|
665
|
+
setResources(mcpName2, resources) {
|
|
666
|
+
const entry = this.perMcp.get(mcpName2);
|
|
667
|
+
if (entry === void 0) {
|
|
668
|
+
throw new Error(`ResourceRouter: unknown mcp "${mcpName2}"`);
|
|
669
|
+
}
|
|
670
|
+
entry.resources = [...resources];
|
|
671
|
+
this.rebuild();
|
|
672
|
+
}
|
|
673
|
+
setTemplates(mcpName2, templates) {
|
|
674
|
+
const entry = this.perMcp.get(mcpName2);
|
|
675
|
+
if (entry === void 0) {
|
|
676
|
+
throw new Error(`ResourceRouter: unknown mcp "${mcpName2}"`);
|
|
677
|
+
}
|
|
678
|
+
entry.templates = [...templates];
|
|
679
|
+
this.rebuild();
|
|
680
|
+
}
|
|
681
|
+
aggregatedResources() {
|
|
682
|
+
const result = [];
|
|
683
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
684
|
+
const entry = this.perMcp.get(mcpName2);
|
|
685
|
+
if (entry !== void 0) {
|
|
686
|
+
result.push(...entry.resources);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return result;
|
|
690
|
+
}
|
|
691
|
+
aggregatedTemplates() {
|
|
692
|
+
const result = [];
|
|
693
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
694
|
+
const entry = this.perMcp.get(mcpName2);
|
|
695
|
+
if (entry !== void 0) {
|
|
696
|
+
result.push(...entry.templates);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return result;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Returns the mcpName that owns the given URI, or undefined if no upstream advertises it.
|
|
703
|
+
* Concrete URI matches take precedence over template prefix matches; templates are tried
|
|
704
|
+
* in config-file order (first-wins).
|
|
705
|
+
*/
|
|
706
|
+
ownerOf(uri) {
|
|
707
|
+
const concrete = this.uriOwners.get(uri);
|
|
708
|
+
if (concrete !== void 0) {
|
|
709
|
+
return concrete;
|
|
710
|
+
}
|
|
711
|
+
for (const { prefix, mcpName: mcpName2 } of this.templateOwners) {
|
|
712
|
+
if (prefix.length > 0 && uri.startsWith(prefix)) {
|
|
713
|
+
return mcpName2;
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return void 0;
|
|
717
|
+
}
|
|
718
|
+
collisions() {
|
|
719
|
+
return this.detectedCollisions;
|
|
720
|
+
}
|
|
721
|
+
rebuild() {
|
|
722
|
+
this.uriOwners = /* @__PURE__ */ new Map();
|
|
723
|
+
this.templateOwners = [];
|
|
724
|
+
const collisions = [];
|
|
725
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
726
|
+
const entry = this.perMcp.get(mcpName2);
|
|
727
|
+
if (entry === void 0) continue;
|
|
728
|
+
for (const resource of entry.resources) {
|
|
729
|
+
const existing = this.uriOwners.get(resource.uri);
|
|
730
|
+
if (existing === void 0) {
|
|
731
|
+
this.uriOwners.set(resource.uri, mcpName2);
|
|
732
|
+
} else {
|
|
733
|
+
collisions.push({ uri: resource.uri, chosen: existing, shadowed: mcpName2 });
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
for (const template of entry.templates) {
|
|
737
|
+
this.templateOwners.push({
|
|
738
|
+
prefix: literalPrefixOf(template.uriTemplate),
|
|
739
|
+
template,
|
|
740
|
+
mcpName: mcpName2
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
this.detectedCollisions = collisions;
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
function literalPrefixOf(uriTemplate) {
|
|
748
|
+
const idx = uriTemplate.indexOf("{");
|
|
749
|
+
return idx === -1 ? uriTemplate : uriTemplate.slice(0, idx);
|
|
750
|
+
}
|
|
751
|
+
|
|
520
752
|
// src/proxy/upstream-client.ts
|
|
521
753
|
var import_node_process3 = __toESM(require("process"), 1);
|
|
522
754
|
var import_client = require("@modelcontextprotocol/sdk/client/index.js");
|
|
755
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
523
756
|
var UpstreamClient = class {
|
|
524
757
|
transport;
|
|
525
758
|
onTransportError;
|
|
759
|
+
notificationHandlers;
|
|
760
|
+
serverRequestHandlers;
|
|
526
761
|
client = null;
|
|
527
|
-
constructor({
|
|
762
|
+
constructor({
|
|
763
|
+
name,
|
|
764
|
+
transport,
|
|
765
|
+
onTransportError,
|
|
766
|
+
notifications,
|
|
767
|
+
serverRequests
|
|
768
|
+
}) {
|
|
528
769
|
this.transport = transport;
|
|
770
|
+
this.notificationHandlers = notifications ?? {};
|
|
771
|
+
this.serverRequestHandlers = serverRequests ?? {};
|
|
529
772
|
this.onTransportError = onTransportError ?? ((error) => {
|
|
530
773
|
import_node_process3.default.stderr.write(`[${name}] Upstream MCP transport error: ${error.message}
|
|
531
774
|
`);
|
|
@@ -533,14 +776,80 @@ var UpstreamClient = class {
|
|
|
533
776
|
}
|
|
534
777
|
async connect() {
|
|
535
778
|
this.transport.onerror = this.onTransportError;
|
|
536
|
-
this.client = new import_client.Client(
|
|
779
|
+
this.client = new import_client.Client(
|
|
780
|
+
{ name: "dynamic-discovery-mcp", version: "1.0.0" },
|
|
781
|
+
{
|
|
782
|
+
capabilities: {
|
|
783
|
+
// Declare every client-side capability the proxy may relay on behalf of the host.
|
|
784
|
+
// Actual reachability of each feature depends on what the host supports — if the
|
|
785
|
+
// host does not support sampling, for instance, the host call returns an error
|
|
786
|
+
// which we forward back to the upstream verbatim.
|
|
787
|
+
sampling: {},
|
|
788
|
+
elicitation: {},
|
|
789
|
+
roots: { listChanged: true }
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
);
|
|
793
|
+
this.registerServerRequestHandlers(this.client);
|
|
794
|
+
if (this.notificationHandlers.onToolsListChanged !== void 0) {
|
|
795
|
+
this.client.setNotificationHandler(import_types.ToolListChangedNotificationSchema, async () => {
|
|
796
|
+
await this.notificationHandlers.onToolsListChanged?.();
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
if (this.notificationHandlers.onResourcesListChanged !== void 0) {
|
|
800
|
+
this.client.setNotificationHandler(import_types.ResourceListChangedNotificationSchema, async () => {
|
|
801
|
+
await this.notificationHandlers.onResourcesListChanged?.();
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
if (this.notificationHandlers.onResourceUpdated !== void 0) {
|
|
805
|
+
this.client.setNotificationHandler(import_types.ResourceUpdatedNotificationSchema, async (notification) => {
|
|
806
|
+
await this.notificationHandlers.onResourceUpdated?.({ uri: notification.params.uri });
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
if (this.notificationHandlers.onPromptsListChanged !== void 0) {
|
|
810
|
+
this.client.setNotificationHandler(import_types.PromptListChangedNotificationSchema, async () => {
|
|
811
|
+
await this.notificationHandlers.onPromptsListChanged?.();
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
if (this.notificationHandlers.onLogMessage !== void 0) {
|
|
815
|
+
this.client.setNotificationHandler(import_types.LoggingMessageNotificationSchema, async (notification) => {
|
|
816
|
+
await this.notificationHandlers.onLogMessage?.(notification.params);
|
|
817
|
+
});
|
|
818
|
+
}
|
|
537
819
|
await this.client.connect(this.transport);
|
|
538
820
|
}
|
|
539
|
-
async
|
|
540
|
-
|
|
541
|
-
|
|
821
|
+
async setLoggingLevel(level, options) {
|
|
822
|
+
const client = this.requireClient();
|
|
823
|
+
await client.setLoggingLevel(level, options);
|
|
824
|
+
}
|
|
825
|
+
async listPrompts(options) {
|
|
826
|
+
const client = this.requireClient();
|
|
827
|
+
const result = await client.listPrompts(void 0, options);
|
|
828
|
+
return result.prompts;
|
|
829
|
+
}
|
|
830
|
+
async getPrompt(name, args, options) {
|
|
831
|
+
const client = this.requireClient();
|
|
832
|
+
const params = { name };
|
|
833
|
+
if (args !== void 0) {
|
|
834
|
+
params.arguments = args;
|
|
542
835
|
}
|
|
543
|
-
|
|
836
|
+
return client.getPrompt(params, options);
|
|
837
|
+
}
|
|
838
|
+
async complete(params, options) {
|
|
839
|
+
const client = this.requireClient();
|
|
840
|
+
return client.complete(params, options);
|
|
841
|
+
}
|
|
842
|
+
/**
|
|
843
|
+
* Returns the capabilities advertised by the upstream server during initialize.
|
|
844
|
+
* Returns `undefined` if the client is not connected, or if the SDK has not yet
|
|
845
|
+
* recorded the server's capabilities (e.g. during a partially-completed handshake).
|
|
846
|
+
*/
|
|
847
|
+
getCapabilities() {
|
|
848
|
+
return this.client?.getServerCapabilities();
|
|
849
|
+
}
|
|
850
|
+
async listTools(options) {
|
|
851
|
+
const client = this.requireClient();
|
|
852
|
+
const result = await client.listTools(void 0, options);
|
|
544
853
|
return result.tools.map((tool) => {
|
|
545
854
|
const upstreamTool = {
|
|
546
855
|
name: tool.name,
|
|
@@ -562,13 +871,33 @@ var UpstreamClient = class {
|
|
|
562
871
|
return upstreamTool;
|
|
563
872
|
});
|
|
564
873
|
}
|
|
565
|
-
async callTool(name, input) {
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
const result = await this.client.callTool({ name, arguments: input });
|
|
874
|
+
async callTool(name, input, options) {
|
|
875
|
+
const client = this.requireClient();
|
|
876
|
+
const result = await client.callTool({ name, arguments: input }, void 0, options);
|
|
570
877
|
return result;
|
|
571
878
|
}
|
|
879
|
+
async listResources(options) {
|
|
880
|
+
const client = this.requireClient();
|
|
881
|
+
const result = await client.listResources(void 0, options);
|
|
882
|
+
return result.resources;
|
|
883
|
+
}
|
|
884
|
+
async listResourceTemplates(options) {
|
|
885
|
+
const client = this.requireClient();
|
|
886
|
+
const result = await client.listResourceTemplates(void 0, options);
|
|
887
|
+
return result.resourceTemplates;
|
|
888
|
+
}
|
|
889
|
+
async readResource(uri, options) {
|
|
890
|
+
const client = this.requireClient();
|
|
891
|
+
return client.readResource({ uri }, options);
|
|
892
|
+
}
|
|
893
|
+
async subscribeResource(uri, options) {
|
|
894
|
+
const client = this.requireClient();
|
|
895
|
+
await client.subscribeResource({ uri }, options);
|
|
896
|
+
}
|
|
897
|
+
async unsubscribeResource(uri, options) {
|
|
898
|
+
const client = this.requireClient();
|
|
899
|
+
await client.unsubscribeResource({ uri }, options);
|
|
900
|
+
}
|
|
572
901
|
async disconnect() {
|
|
573
902
|
if (this.client === null) {
|
|
574
903
|
return;
|
|
@@ -576,37 +905,198 @@ var UpstreamClient = class {
|
|
|
576
905
|
await this.client.close();
|
|
577
906
|
this.client = null;
|
|
578
907
|
}
|
|
908
|
+
/**
|
|
909
|
+
* Sends `notifications/roots/list_changed` to the upstream, letting it know that
|
|
910
|
+
* the host's set of filesystem roots has changed.
|
|
911
|
+
*/
|
|
912
|
+
async sendRootsListChanged() {
|
|
913
|
+
const client = this.requireClient();
|
|
914
|
+
await client.sendRootsListChanged();
|
|
915
|
+
}
|
|
916
|
+
registerServerRequestHandlers(client) {
|
|
917
|
+
if (this.serverRequestHandlers.onCreateMessage !== void 0) {
|
|
918
|
+
client.setRequestHandler(
|
|
919
|
+
import_types.CreateMessageRequestSchema,
|
|
920
|
+
async (request, extra) => {
|
|
921
|
+
return this.serverRequestHandlers.onCreateMessage(request.params, {
|
|
922
|
+
signal: extra.signal
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
if (this.serverRequestHandlers.onElicitInput !== void 0) {
|
|
928
|
+
client.setRequestHandler(
|
|
929
|
+
import_types.ElicitRequestSchema,
|
|
930
|
+
async (request, extra) => {
|
|
931
|
+
return this.serverRequestHandlers.onElicitInput(request.params, {
|
|
932
|
+
signal: extra.signal
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
if (this.serverRequestHandlers.onListRoots !== void 0) {
|
|
938
|
+
client.setRequestHandler(
|
|
939
|
+
import_types.ListRootsRequestSchema,
|
|
940
|
+
async (request, extra) => {
|
|
941
|
+
return this.serverRequestHandlers.onListRoots(request.params, {
|
|
942
|
+
signal: extra.signal
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
requireClient() {
|
|
949
|
+
if (this.client === null) {
|
|
950
|
+
throw new Error("Client is not connected. Call connect() first.");
|
|
951
|
+
}
|
|
952
|
+
return this.client;
|
|
953
|
+
}
|
|
579
954
|
};
|
|
580
955
|
|
|
581
|
-
// src/proxy/
|
|
582
|
-
var
|
|
583
|
-
config;
|
|
956
|
+
// src/proxy/upstream-registry.ts
|
|
957
|
+
var UpstreamRegistry = class {
|
|
584
958
|
clients = /* @__PURE__ */ new Map();
|
|
585
|
-
|
|
586
|
-
constructor(config) {
|
|
587
|
-
this.config = config;
|
|
588
|
-
}
|
|
589
|
-
async connect() {
|
|
590
|
-
const groups = /* @__PURE__ */ new Map();
|
|
959
|
+
async connectAll(entries) {
|
|
591
960
|
try {
|
|
592
|
-
for (const [mcpName2,
|
|
961
|
+
for (const [mcpName2, config] of entries) {
|
|
593
962
|
const client = new UpstreamClient({
|
|
594
963
|
name: mcpName2,
|
|
595
|
-
transport,
|
|
596
|
-
onTransportError:
|
|
597
|
-
|
|
598
|
-
|
|
964
|
+
transport: config.transport,
|
|
965
|
+
onTransportError: config.onTransportError,
|
|
966
|
+
notifications: config.notifications,
|
|
967
|
+
serverRequests: config.serverRequests
|
|
599
968
|
});
|
|
600
969
|
await client.connect();
|
|
601
|
-
const tools = await client.listTools();
|
|
602
970
|
this.clients.set(mcpName2, client);
|
|
603
|
-
groups.set(mcpName2, tools);
|
|
604
971
|
}
|
|
605
972
|
} catch (error) {
|
|
606
973
|
await this.disconnectAll();
|
|
607
974
|
throw error;
|
|
608
975
|
}
|
|
609
|
-
|
|
976
|
+
}
|
|
977
|
+
get(mcpName2) {
|
|
978
|
+
return this.clients.get(mcpName2);
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Returns the sole connected client. Used by single-MCP (`--`) mode where the
|
|
982
|
+
* Orchestrator guarantees there is exactly one upstream. Returns undefined when
|
|
983
|
+
* zero or more than one client is connected.
|
|
984
|
+
*/
|
|
985
|
+
sole() {
|
|
986
|
+
if (this.clients.size !== 1) return void 0;
|
|
987
|
+
return this.clients.values().next().value;
|
|
988
|
+
}
|
|
989
|
+
names() {
|
|
990
|
+
return [...this.clients.keys()];
|
|
991
|
+
}
|
|
992
|
+
entries() {
|
|
993
|
+
return this.clients.entries();
|
|
994
|
+
}
|
|
995
|
+
size() {
|
|
996
|
+
return this.clients.size;
|
|
997
|
+
}
|
|
998
|
+
async disconnectAll() {
|
|
999
|
+
const disconnections = [...this.clients.values()].map((client) => client.disconnect());
|
|
1000
|
+
await Promise.all(disconnections);
|
|
1001
|
+
this.clients.clear();
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
|
|
1005
|
+
// src/proxy/orchestrator.ts
|
|
1006
|
+
var Orchestrator = class {
|
|
1007
|
+
config;
|
|
1008
|
+
registry = new UpstreamRegistry();
|
|
1009
|
+
toolsByMcp = /* @__PURE__ */ new Map();
|
|
1010
|
+
resourceRouter = null;
|
|
1011
|
+
promptRouter = null;
|
|
1012
|
+
toolCatalog = null;
|
|
1013
|
+
aggregatedCapabilities = null;
|
|
1014
|
+
serverRequestForwarders = {};
|
|
1015
|
+
forwarder;
|
|
1016
|
+
constructor(config) {
|
|
1017
|
+
if (!config.namespaced && config.mcps.size !== 1) {
|
|
1018
|
+
throw new Error(
|
|
1019
|
+
`Single-MCP (non-namespaced) mode requires exactly one upstream; got ${config.mcps.size}.`
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
this.config = config;
|
|
1023
|
+
this.forwarder = new NotificationForwarder(
|
|
1024
|
+
this.registry,
|
|
1025
|
+
() => this.resourceRouter,
|
|
1026
|
+
() => this.promptRouter,
|
|
1027
|
+
this.toolsByMcp,
|
|
1028
|
+
(catalog) => {
|
|
1029
|
+
this.toolCatalog = catalog;
|
|
1030
|
+
},
|
|
1031
|
+
this.config.namespaced
|
|
1032
|
+
);
|
|
1033
|
+
}
|
|
1034
|
+
setNotificationHandlers(handlers) {
|
|
1035
|
+
this.forwarder.setHostHandlers(handlers);
|
|
1036
|
+
}
|
|
1037
|
+
setServerRequestForwarders(forwarders) {
|
|
1038
|
+
this.serverRequestForwarders = forwarders;
|
|
1039
|
+
}
|
|
1040
|
+
async connect() {
|
|
1041
|
+
const resourceRouter = new ResourceRouter([...this.config.mcps.keys()]);
|
|
1042
|
+
const promptRouter = new PromptRouter([...this.config.mcps.keys()]);
|
|
1043
|
+
const upstreamEntries = [
|
|
1044
|
+
...this.config.mcps
|
|
1045
|
+
].map(([mcpName2, { transport }]) => [
|
|
1046
|
+
mcpName2,
|
|
1047
|
+
{
|
|
1048
|
+
transport,
|
|
1049
|
+
onTransportError: (error) => {
|
|
1050
|
+
this.config.onTransportError?.(mcpName2, error);
|
|
1051
|
+
},
|
|
1052
|
+
notifications: {
|
|
1053
|
+
onToolsListChanged: () => this.forwarder.handleToolsListChanged(mcpName2),
|
|
1054
|
+
onResourcesListChanged: () => this.forwarder.handleResourcesListChanged(mcpName2),
|
|
1055
|
+
onResourceUpdated: (params) => this.forwarder.handleResourceUpdated(params),
|
|
1056
|
+
onPromptsListChanged: () => this.forwarder.handlePromptsListChanged(mcpName2),
|
|
1057
|
+
onLogMessage: (params) => this.forwarder.handleLogMessage(mcpName2, params)
|
|
1058
|
+
},
|
|
1059
|
+
serverRequests: {
|
|
1060
|
+
onCreateMessage: (params, opts) => this.forwardCreateMessage(params, opts),
|
|
1061
|
+
onElicitInput: (params, opts) => this.forwardElicitInput(params, opts),
|
|
1062
|
+
onListRoots: (params, opts) => this.forwardListRoots(params, opts)
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
]);
|
|
1066
|
+
await this.registry.connectAll(upstreamEntries);
|
|
1067
|
+
const capabilityList = [];
|
|
1068
|
+
this.toolsByMcp.clear();
|
|
1069
|
+
for (const [mcpName2, client] of this.registry.entries()) {
|
|
1070
|
+
const caps = client.getCapabilities();
|
|
1071
|
+
capabilityList.push(caps);
|
|
1072
|
+
const tools = await client.listTools();
|
|
1073
|
+
this.toolsByMcp.set(mcpName2, tools);
|
|
1074
|
+
if (caps?.resources !== void 0) {
|
|
1075
|
+
const [resources, templates] = await Promise.all([
|
|
1076
|
+
client.listResources().catch(() => []),
|
|
1077
|
+
client.listResourceTemplates().catch(() => [])
|
|
1078
|
+
]);
|
|
1079
|
+
resourceRouter.setResources(mcpName2, resources);
|
|
1080
|
+
resourceRouter.setTemplates(mcpName2, templates);
|
|
1081
|
+
}
|
|
1082
|
+
if (caps?.prompts !== void 0) {
|
|
1083
|
+
const prompts = await client.listPrompts().catch(() => []);
|
|
1084
|
+
promptRouter.setPrompts(mcpName2, prompts);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
this.toolCatalog = this.config.namespaced ? ToolCatalog.fromGrouped(this.toolsByMcp) : ToolCatalog.fromFlat([...this.toolsByMcp.values()][0] ?? []);
|
|
1088
|
+
this.aggregatedCapabilities = aggregateCapabilities(capabilityList);
|
|
1089
|
+
this.resourceRouter = resourceRouter;
|
|
1090
|
+
this.promptRouter = promptRouter;
|
|
1091
|
+
logCollisions(resourceRouter, promptRouter);
|
|
1092
|
+
}
|
|
1093
|
+
async disconnectAll() {
|
|
1094
|
+
await this.registry.disconnectAll();
|
|
1095
|
+
this.toolsByMcp.clear();
|
|
1096
|
+
this.toolCatalog = null;
|
|
1097
|
+
this.aggregatedCapabilities = null;
|
|
1098
|
+
this.resourceRouter = null;
|
|
1099
|
+
this.promptRouter = null;
|
|
610
1100
|
}
|
|
611
1101
|
get catalog() {
|
|
612
1102
|
if (this.toolCatalog === null) {
|
|
@@ -614,168 +1104,611 @@ var Orchestrator = class {
|
|
|
614
1104
|
}
|
|
615
1105
|
return this.toolCatalog;
|
|
616
1106
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
1107
|
+
get capabilities() {
|
|
1108
|
+
if (this.aggregatedCapabilities === null) {
|
|
1109
|
+
throw new Error("Orchestrator is not connected. Call connect() first.");
|
|
1110
|
+
}
|
|
1111
|
+
return this.aggregatedCapabilities;
|
|
1112
|
+
}
|
|
1113
|
+
// === Forward-direction request routing ===
|
|
1114
|
+
async callTool(displayName, input, options) {
|
|
1115
|
+
if (this.config.namespaced) {
|
|
1116
|
+
const { mcpName: mcpName2, toolName } = splitNamespacedName(displayName, this.registry.names());
|
|
1117
|
+
return this.requireClient(mcpName2, "tool").callTool(toolName, input, options);
|
|
1118
|
+
}
|
|
1119
|
+
const sole = this.registry.sole();
|
|
1120
|
+
if (sole === void 0) {
|
|
1121
|
+
throw new Error("Orchestrator is not connected. Call connect() first.");
|
|
623
1122
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
1123
|
+
return sole.callTool(displayName, input, options);
|
|
1124
|
+
}
|
|
1125
|
+
listResources() {
|
|
1126
|
+
return this.requireResourceRouter().aggregatedResources();
|
|
1127
|
+
}
|
|
1128
|
+
listResourceTemplates() {
|
|
1129
|
+
return this.requireResourceRouter().aggregatedTemplates();
|
|
1130
|
+
}
|
|
1131
|
+
async readResource(uri, options) {
|
|
1132
|
+
return this.resolveResourceOwner(uri).readResource(uri, options);
|
|
1133
|
+
}
|
|
1134
|
+
async subscribeResource(uri, options) {
|
|
1135
|
+
await this.resolveResourceOwner(uri).subscribeResource(uri, options);
|
|
1136
|
+
}
|
|
1137
|
+
async unsubscribeResource(uri, options) {
|
|
1138
|
+
await this.resolveResourceOwner(uri).unsubscribeResource(uri, options);
|
|
1139
|
+
}
|
|
1140
|
+
listPrompts() {
|
|
1141
|
+
return this.requirePromptRouter().aggregatedPrompts();
|
|
1142
|
+
}
|
|
1143
|
+
async getPrompt(name, args, options) {
|
|
1144
|
+
return this.resolvePromptOwner(name).getPrompt(name, args, options);
|
|
1145
|
+
}
|
|
1146
|
+
async complete(params, options) {
|
|
1147
|
+
const client = this.resolveCompletionTarget(params.ref);
|
|
1148
|
+
return client.complete(params, options);
|
|
1149
|
+
}
|
|
1150
|
+
// === Broadcasts ===
|
|
1151
|
+
/**
|
|
1152
|
+
* Broadcasts a `logging/setLevel` request to every upstream advertising the logging
|
|
1153
|
+
* capability. Errors from individual upstreams are swallowed so a single misbehaving
|
|
1154
|
+
* upstream cannot break the broadcast for others; failures are written to stderr.
|
|
1155
|
+
*/
|
|
1156
|
+
async setLoggingLevel(level, options) {
|
|
1157
|
+
await this.broadcastAsync(
|
|
1158
|
+
(client) => client.getCapabilities()?.logging !== void 0 ? client.setLoggingLevel(level, options) : Promise.resolve(),
|
|
1159
|
+
"setLoggingLevel"
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Broadcasts `notifications/roots/list_changed` to every connected upstream — the
|
|
1164
|
+
* proxy declares roots capability uniformly to all upstreams so every one of them
|
|
1165
|
+
* may have requested roots and needs to know the list changed.
|
|
1166
|
+
*/
|
|
1167
|
+
async broadcastRootsListChanged() {
|
|
1168
|
+
await this.broadcastAsync((client) => client.sendRootsListChanged(), "sendRootsListChanged");
|
|
1169
|
+
}
|
|
1170
|
+
// === Server-initiated request forwarders (upstream → host) ===
|
|
1171
|
+
async forwardCreateMessage(params, options) {
|
|
1172
|
+
const handler = this.serverRequestForwarders.onCreateMessage;
|
|
1173
|
+
if (handler === void 0) {
|
|
1174
|
+
throw new Error("Proxy does not support sampling: host has not registered a handler.");
|
|
1175
|
+
}
|
|
1176
|
+
return handler(params, options);
|
|
1177
|
+
}
|
|
1178
|
+
async forwardElicitInput(params, options) {
|
|
1179
|
+
const handler = this.serverRequestForwarders.onElicitInput;
|
|
1180
|
+
if (handler === void 0) {
|
|
1181
|
+
throw new Error("Proxy does not support elicitation: host has not registered a handler.");
|
|
1182
|
+
}
|
|
1183
|
+
return handler(params, options);
|
|
1184
|
+
}
|
|
1185
|
+
async forwardListRoots(params, options) {
|
|
1186
|
+
const handler = this.serverRequestForwarders.onListRoots;
|
|
1187
|
+
if (handler === void 0) {
|
|
1188
|
+
throw new Error("Proxy does not support roots: host has not registered a handler.");
|
|
1189
|
+
}
|
|
1190
|
+
return handler(params, options);
|
|
1191
|
+
}
|
|
1192
|
+
// === Internal helpers ===
|
|
1193
|
+
requireResourceRouter() {
|
|
1194
|
+
if (this.resourceRouter === null) {
|
|
1195
|
+
throw new Error("Orchestrator is not connected. Call connect() first.");
|
|
1196
|
+
}
|
|
1197
|
+
return this.resourceRouter;
|
|
1198
|
+
}
|
|
1199
|
+
requirePromptRouter() {
|
|
1200
|
+
if (this.promptRouter === null) {
|
|
1201
|
+
throw new Error("Orchestrator is not connected. Call connect() first.");
|
|
1202
|
+
}
|
|
1203
|
+
return this.promptRouter;
|
|
1204
|
+
}
|
|
1205
|
+
resolveResourceOwner(uri) {
|
|
1206
|
+
const owner = this.requireResourceRouter().ownerOf(uri);
|
|
1207
|
+
if (owner === void 0) {
|
|
1208
|
+
throw new Error(`Unknown resource URI: "${uri}". No upstream MCP advertises it.`);
|
|
1209
|
+
}
|
|
1210
|
+
return this.requireClient(owner, "resource");
|
|
1211
|
+
}
|
|
1212
|
+
resolvePromptOwner(name) {
|
|
1213
|
+
const owner = this.requirePromptRouter().ownerOf(name);
|
|
1214
|
+
if (owner === void 0) {
|
|
1215
|
+
throw new Error(`Unknown prompt: "${name}". No upstream MCP advertises it.`);
|
|
1216
|
+
}
|
|
1217
|
+
return this.requireClient(owner, "prompt");
|
|
1218
|
+
}
|
|
1219
|
+
resolveCompletionTarget(ref) {
|
|
1220
|
+
if (ref.type === "ref/prompt") {
|
|
1221
|
+
return this.resolvePromptOwner(ref.name);
|
|
1222
|
+
}
|
|
1223
|
+
if (ref.type === "ref/resource") {
|
|
1224
|
+
return this.resolveResourceOwner(ref.uri);
|
|
1225
|
+
}
|
|
1226
|
+
const unknownRef = ref;
|
|
1227
|
+
throw new Error(`Unsupported completion ref type: "${unknownRef.type}"`);
|
|
1228
|
+
}
|
|
1229
|
+
requireClient(mcpName2, role) {
|
|
1230
|
+
const client = this.registry.get(mcpName2);
|
|
627
1231
|
if (client === void 0) {
|
|
628
|
-
|
|
629
|
-
throw new Error(`Unknown MCP: "${mcpName2}". Available MCPs: ${available}`);
|
|
1232
|
+
throw new Error(`Internal error: ${role} owner "${mcpName2}" has no connected client.`);
|
|
630
1233
|
}
|
|
631
|
-
return client
|
|
1234
|
+
return client;
|
|
632
1235
|
}
|
|
633
|
-
async
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
1236
|
+
async broadcastAsync(action, label) {
|
|
1237
|
+
const targets = [];
|
|
1238
|
+
for (const [mcpName2, client] of this.registry.entries()) {
|
|
1239
|
+
targets.push(
|
|
1240
|
+
action(client).catch((error) => {
|
|
1241
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1242
|
+
import_node_process4.default.stderr.write(`dynmcp: ${label} failed for "${mcpName2}": ${message}
|
|
1243
|
+
`);
|
|
1244
|
+
})
|
|
1245
|
+
);
|
|
1246
|
+
}
|
|
1247
|
+
await Promise.all(targets);
|
|
638
1248
|
}
|
|
639
1249
|
};
|
|
1250
|
+
function splitNamespacedName(namespacedName, knownMcpNames) {
|
|
1251
|
+
const separatorIndex = namespacedName.indexOf("/");
|
|
1252
|
+
if (separatorIndex === -1) {
|
|
1253
|
+
throw new Error(
|
|
1254
|
+
`Invalid namespaced tool name: "${namespacedName}". Expected format: "mcpName/toolName".`
|
|
1255
|
+
);
|
|
1256
|
+
}
|
|
1257
|
+
const mcpName2 = namespacedName.slice(0, separatorIndex);
|
|
1258
|
+
const toolName = namespacedName.slice(separatorIndex + 1);
|
|
1259
|
+
if (!knownMcpNames.includes(mcpName2)) {
|
|
1260
|
+
const available = [...knownMcpNames].sort().join(", ");
|
|
1261
|
+
throw new Error(`Unknown MCP: "${mcpName2}". Available MCPs: ${available}`);
|
|
1262
|
+
}
|
|
1263
|
+
return { mcpName: mcpName2, toolName };
|
|
1264
|
+
}
|
|
1265
|
+
function logCollisions(resourceRouter, promptRouter) {
|
|
1266
|
+
for (const collision of resourceRouter.collisions()) {
|
|
1267
|
+
import_node_process4.default.stderr.write(
|
|
1268
|
+
`dynmcp: resource URI collision: "${collision.uri}" is provided by "${collision.chosen}" and "${collision.shadowed}"; routing to "${collision.chosen}".
|
|
1269
|
+
`
|
|
1270
|
+
);
|
|
1271
|
+
}
|
|
1272
|
+
for (const collision of promptRouter.collisions()) {
|
|
1273
|
+
import_node_process4.default.stderr.write(
|
|
1274
|
+
`dynmcp: prompt name collision: "${collision.name}" is provided by "${collision.chosen}" and "${collision.shadowed}"; routing to "${collision.chosen}".
|
|
1275
|
+
`
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
640
1279
|
|
|
641
1280
|
// src/proxy/server.ts
|
|
642
|
-
var
|
|
643
|
-
var
|
|
1281
|
+
var import_node_process5 = __toESM(require("process"), 1);
|
|
1282
|
+
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
1283
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
1284
|
+
var import_types2 = require("@modelcontextprotocol/sdk/types.js");
|
|
644
1285
|
var import_zod3 = require("zod");
|
|
1286
|
+
var DISCOVER_TOOL_NAME = "discover_tool";
|
|
1287
|
+
var USE_TOOL_NAME = "use_tool";
|
|
1288
|
+
var USE_TOOL_DESCRIPTION = "Use a tool that was previously discovered with the discover_tool tool.";
|
|
1289
|
+
var DISCOVER_TOOL_INPUT_SCHEMA = {
|
|
1290
|
+
type: "object",
|
|
1291
|
+
properties: {
|
|
1292
|
+
tool_name: { type: "string" }
|
|
1293
|
+
},
|
|
1294
|
+
required: ["tool_name"]
|
|
1295
|
+
};
|
|
1296
|
+
var USE_TOOL_INPUT_SCHEMA = {
|
|
1297
|
+
type: "object",
|
|
1298
|
+
properties: {
|
|
1299
|
+
tool_name: { type: "string" },
|
|
1300
|
+
tool_input: { type: "object", additionalProperties: true, default: {} }
|
|
1301
|
+
},
|
|
1302
|
+
required: ["tool_name"]
|
|
1303
|
+
};
|
|
1304
|
+
var DiscoverToolArgsSchema = import_zod3.z.object({ tool_name: import_zod3.z.string() });
|
|
1305
|
+
var UseToolArgsSchema = import_zod3.z.object({
|
|
1306
|
+
tool_name: import_zod3.z.string(),
|
|
1307
|
+
tool_input: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.unknown()).default({})
|
|
1308
|
+
});
|
|
645
1309
|
var ProxyServer = class {
|
|
646
1310
|
catalog;
|
|
647
1311
|
callTool;
|
|
648
|
-
|
|
1312
|
+
capabilities;
|
|
1313
|
+
resources;
|
|
1314
|
+
prompts;
|
|
1315
|
+
complete;
|
|
1316
|
+
setLoggingLevelCallback;
|
|
1317
|
+
onRootsListChangedCallback;
|
|
1318
|
+
sdkServer = null;
|
|
1319
|
+
constructor({
|
|
1320
|
+
catalog,
|
|
1321
|
+
callTool,
|
|
1322
|
+
capabilities,
|
|
1323
|
+
resources,
|
|
1324
|
+
prompts,
|
|
1325
|
+
complete,
|
|
1326
|
+
setLoggingLevel,
|
|
1327
|
+
onRootsListChanged
|
|
1328
|
+
}) {
|
|
649
1329
|
this.catalog = catalog;
|
|
650
1330
|
this.callTool = callTool;
|
|
1331
|
+
this.capabilities = capabilities;
|
|
1332
|
+
this.resources = resources;
|
|
1333
|
+
this.prompts = prompts;
|
|
1334
|
+
this.complete = complete;
|
|
1335
|
+
this.setLoggingLevelCallback = setLoggingLevel;
|
|
1336
|
+
this.onRootsListChangedCallback = onRootsListChanged;
|
|
1337
|
+
}
|
|
1338
|
+
buildServer() {
|
|
1339
|
+
const server = new import_server.Server(
|
|
1340
|
+
{
|
|
1341
|
+
name: "dynamic-discovery-mcp",
|
|
1342
|
+
version: package_default.version
|
|
1343
|
+
},
|
|
1344
|
+
{
|
|
1345
|
+
capabilities: this.capabilities
|
|
1346
|
+
}
|
|
1347
|
+
);
|
|
1348
|
+
this.registerToolHandlers(server);
|
|
1349
|
+
if (this.capabilities.resources !== void 0 && this.resources !== void 0) {
|
|
1350
|
+
this.registerResourceHandlers(server, this.resources);
|
|
1351
|
+
}
|
|
1352
|
+
if (this.capabilities.prompts !== void 0 && this.prompts !== void 0) {
|
|
1353
|
+
this.registerPromptHandlers(server, this.prompts);
|
|
1354
|
+
}
|
|
1355
|
+
if (this.capabilities.completions !== void 0 && this.complete !== void 0) {
|
|
1356
|
+
this.registerCompletionHandler(server, this.complete);
|
|
1357
|
+
}
|
|
1358
|
+
if (this.capabilities.logging !== void 0 && this.setLoggingLevelCallback !== void 0) {
|
|
1359
|
+
this.registerLoggingHandler(server, this.setLoggingLevelCallback);
|
|
1360
|
+
}
|
|
1361
|
+
if (this.onRootsListChangedCallback !== void 0) {
|
|
1362
|
+
const callback = this.onRootsListChangedCallback;
|
|
1363
|
+
server.setNotificationHandler(import_types2.RootsListChangedNotificationSchema, async () => {
|
|
1364
|
+
await callback();
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
this.sdkServer = server;
|
|
1368
|
+
return server;
|
|
1369
|
+
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Forwards an upstream-initiated `sampling/createMessage` request to the host. The
|
|
1372
|
+
* upstream's abort signal is threaded through so cancellation by the upstream
|
|
1373
|
+
* propagates to the host.
|
|
1374
|
+
*/
|
|
1375
|
+
async forwardCreateMessage(params, options) {
|
|
1376
|
+
const server = this.requireSdkServer();
|
|
1377
|
+
return server.createMessage(params, options);
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Forwards an upstream-initiated `elicitation/create` request to the host.
|
|
1381
|
+
*/
|
|
1382
|
+
async forwardElicitInput(params, options) {
|
|
1383
|
+
const server = this.requireSdkServer();
|
|
1384
|
+
return server.elicitInput(params, options);
|
|
1385
|
+
}
|
|
1386
|
+
/**
|
|
1387
|
+
* Forwards an upstream-initiated `roots/list` request to the host.
|
|
1388
|
+
*/
|
|
1389
|
+
async forwardListRoots(params, options) {
|
|
1390
|
+
const server = this.requireSdkServer();
|
|
1391
|
+
return server.listRoots(params, options);
|
|
1392
|
+
}
|
|
1393
|
+
requireSdkServer() {
|
|
1394
|
+
if (this.sdkServer === null) {
|
|
1395
|
+
throw new Error("ProxyServer is not built. Call buildServer() before forwarding requests.");
|
|
1396
|
+
}
|
|
1397
|
+
return this.sdkServer;
|
|
651
1398
|
}
|
|
652
1399
|
async start() {
|
|
653
|
-
const server =
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1400
|
+
const server = this.buildServer();
|
|
1401
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
1402
|
+
import_node_process5.default.stderr.write("Starting dynamic-discovery-mcp server over stdio\n");
|
|
1403
|
+
await server.connect(transport);
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Notifies the host that the discover_tool description has changed because an upstream
|
|
1407
|
+
* emitted `notifications/tools/list_changed`. The host should re-fetch the tools list
|
|
1408
|
+
* to pick up the regenerated catalog. Silently no-ops if `buildServer()` has not been
|
|
1409
|
+
* called yet.
|
|
1410
|
+
*/
|
|
1411
|
+
async sendToolListChanged() {
|
|
1412
|
+
if (this.sdkServer !== null) {
|
|
1413
|
+
await this.sdkServer.sendToolListChanged();
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Notifies the host that the proxy's aggregated resource list has changed. Silently
|
|
1418
|
+
* no-ops if `buildServer()` has not been called yet. Errors propagate.
|
|
1419
|
+
*/
|
|
1420
|
+
async sendResourceListChanged() {
|
|
1421
|
+
if (this.sdkServer !== null) {
|
|
1422
|
+
await this.sdkServer.sendResourceListChanged();
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Notifies the host that a specific subscribed resource has changed. Silently no-ops
|
|
1427
|
+
* if `buildServer()` has not been called yet.
|
|
1428
|
+
*/
|
|
1429
|
+
async sendResourceUpdated(params) {
|
|
1430
|
+
if (this.sdkServer !== null) {
|
|
1431
|
+
await this.sdkServer.sendResourceUpdated(params);
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
/**
|
|
1435
|
+
* Notifies the host that the proxy's aggregated prompt list has changed. Silently
|
|
1436
|
+
* no-ops if `buildServer()` has not been called yet.
|
|
1437
|
+
*/
|
|
1438
|
+
async sendPromptListChanged() {
|
|
1439
|
+
if (this.sdkServer !== null) {
|
|
1440
|
+
await this.sdkServer.sendPromptListChanged();
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Builds per-call options for a request handler. Extracts the host's
|
|
1445
|
+
* `progressToken` from `_meta` (if any) and wires an `onprogress` callback that
|
|
1446
|
+
* re-emits progress notifications back to the host under that same token. This
|
|
1447
|
+
* is the single seam where progress translation lives — every forward-direction
|
|
1448
|
+
* handler routes through here.
|
|
1449
|
+
*/
|
|
1450
|
+
buildCallOptions(request, extra) {
|
|
1451
|
+
const options = { signal: extra.signal };
|
|
1452
|
+
const progressToken = request.params._meta?.progressToken;
|
|
1453
|
+
if (progressToken !== void 0) {
|
|
1454
|
+
options.onprogress = (progress) => {
|
|
1455
|
+
void extra.sendNotification({
|
|
1456
|
+
method: "notifications/progress",
|
|
1457
|
+
params: {
|
|
1458
|
+
progressToken,
|
|
1459
|
+
progress: progress.progress,
|
|
1460
|
+
total: progress.total,
|
|
1461
|
+
message: progress.message
|
|
1462
|
+
}
|
|
1463
|
+
});
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1466
|
+
return options;
|
|
1467
|
+
}
|
|
1468
|
+
registerToolHandlers(server) {
|
|
1469
|
+
server.setRequestHandler(import_types2.ListToolsRequestSchema, async () => ({
|
|
1470
|
+
tools: [
|
|
1471
|
+
{
|
|
1472
|
+
name: DISCOVER_TOOL_NAME,
|
|
1473
|
+
description: this.catalog().discoverToolDescription,
|
|
1474
|
+
inputSchema: DISCOVER_TOOL_INPUT_SCHEMA
|
|
1475
|
+
},
|
|
1476
|
+
{
|
|
1477
|
+
name: USE_TOOL_NAME,
|
|
1478
|
+
description: USE_TOOL_DESCRIPTION,
|
|
1479
|
+
inputSchema: USE_TOOL_INPUT_SCHEMA
|
|
1480
|
+
}
|
|
1481
|
+
]
|
|
1482
|
+
}));
|
|
1483
|
+
server.setRequestHandler(
|
|
1484
|
+
import_types2.CallToolRequestSchema,
|
|
1485
|
+
async (request, extra) => {
|
|
1486
|
+
const { name, arguments: rawArgs } = request.params;
|
|
1487
|
+
const catalog = this.catalog();
|
|
1488
|
+
if (name === DISCOVER_TOOL_NAME) {
|
|
1489
|
+
const args = DiscoverToolArgsSchema.parse(rawArgs ?? {});
|
|
1490
|
+
return {
|
|
1491
|
+
content: [{ type: "text", text: catalog.getToolDetails(args.tool_name) }]
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
if (name === USE_TOOL_NAME) {
|
|
1495
|
+
const args = UseToolArgsSchema.parse(rawArgs ?? {});
|
|
1496
|
+
if (!catalog.tools.has(args.tool_name)) {
|
|
1497
|
+
return {
|
|
1498
|
+
content: [{ type: "text", text: catalog.getToolDetails(args.tool_name) }]
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
return await this.callTool(
|
|
1502
|
+
args.tool_name,
|
|
1503
|
+
args.tool_input,
|
|
1504
|
+
this.buildCallOptions(request, extra)
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1507
|
+
return {
|
|
1508
|
+
isError: true,
|
|
1509
|
+
content: [{ type: "text", text: `Unknown tool: "${name}"` }]
|
|
1510
|
+
};
|
|
663
1511
|
}
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
registerResourceHandlers(server, callbacks) {
|
|
1515
|
+
server.setRequestHandler(
|
|
1516
|
+
import_types2.ListResourcesRequestSchema,
|
|
1517
|
+
async () => ({
|
|
1518
|
+
resources: callbacks.listResources()
|
|
1519
|
+
})
|
|
1520
|
+
);
|
|
1521
|
+
server.setRequestHandler(
|
|
1522
|
+
import_types2.ListResourceTemplatesRequestSchema,
|
|
1523
|
+
async () => ({
|
|
1524
|
+
resourceTemplates: callbacks.listResourceTemplates()
|
|
1525
|
+
})
|
|
1526
|
+
);
|
|
1527
|
+
server.setRequestHandler(
|
|
1528
|
+
import_types2.ReadResourceRequestSchema,
|
|
1529
|
+
async (request, extra) => {
|
|
1530
|
+
return callbacks.readResource(request.params.uri, this.buildCallOptions(request, extra));
|
|
1531
|
+
}
|
|
1532
|
+
);
|
|
1533
|
+
server.setRequestHandler(import_types2.SubscribeRequestSchema, async (request, extra) => {
|
|
1534
|
+
await callbacks.subscribeResource(request.params.uri, this.buildCallOptions(request, extra));
|
|
1535
|
+
return {};
|
|
664
1536
|
});
|
|
665
|
-
server.
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1537
|
+
server.setRequestHandler(import_types2.UnsubscribeRequestSchema, async (request, extra) => {
|
|
1538
|
+
await callbacks.unsubscribeResource(
|
|
1539
|
+
request.params.uri,
|
|
1540
|
+
this.buildCallOptions(request, extra)
|
|
1541
|
+
);
|
|
1542
|
+
return {};
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
registerPromptHandlers(server, callbacks) {
|
|
1546
|
+
server.setRequestHandler(
|
|
1547
|
+
import_types2.ListPromptsRequestSchema,
|
|
1548
|
+
async () => ({
|
|
1549
|
+
prompts: callbacks.listPrompts()
|
|
1550
|
+
})
|
|
1551
|
+
);
|
|
1552
|
+
server.setRequestHandler(
|
|
1553
|
+
import_types2.GetPromptRequestSchema,
|
|
1554
|
+
async (request, extra) => {
|
|
1555
|
+
return callbacks.getPrompt(
|
|
1556
|
+
request.params.name,
|
|
1557
|
+
request.params.arguments,
|
|
1558
|
+
this.buildCallOptions(request, extra)
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
);
|
|
1562
|
+
}
|
|
1563
|
+
registerCompletionHandler(server, callback) {
|
|
1564
|
+
server.setRequestHandler(
|
|
1565
|
+
import_types2.CompleteRequestSchema,
|
|
1566
|
+
async (request, extra) => {
|
|
1567
|
+
return callback(request.params, this.buildCallOptions(request, extra));
|
|
678
1568
|
}
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
registerLoggingHandler(server, callback) {
|
|
1572
|
+
server.setRequestHandler(import_types2.SetLevelRequestSchema, async (request, extra) => {
|
|
1573
|
+
await callback(request.params.level, this.buildCallOptions(request, extra));
|
|
1574
|
+
return {};
|
|
679
1575
|
});
|
|
680
|
-
|
|
681
|
-
|
|
1576
|
+
}
|
|
1577
|
+
/**
|
|
1578
|
+
* Forwards a log message from an upstream MCP to the host. Silently no-ops if
|
|
1579
|
+
* `buildServer()` has not been called yet.
|
|
1580
|
+
*/
|
|
1581
|
+
async sendLoggingMessage(params) {
|
|
1582
|
+
if (this.sdkServer !== null) {
|
|
1583
|
+
await this.sdkServer.sendLoggingMessage(params);
|
|
1584
|
+
}
|
|
682
1585
|
}
|
|
683
1586
|
};
|
|
684
1587
|
|
|
685
|
-
// src/proxy/
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
upstreamClient.disconnect().catch((error) => {
|
|
702
|
-
import_node_process5.default.stderr.write(
|
|
703
|
-
`dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
|
|
704
|
-
`
|
|
1588
|
+
// src/proxy/transport-factory.ts
|
|
1589
|
+
var import_stdio2 = require("@modelcontextprotocol/sdk/client/stdio.js");
|
|
1590
|
+
var import_streamableHttp = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
|
|
1591
|
+
var import_sse = require("@modelcontextprotocol/sdk/client/sse.js");
|
|
1592
|
+
function createTransport(config) {
|
|
1593
|
+
switch (config.transport) {
|
|
1594
|
+
case "stdio":
|
|
1595
|
+
return new import_stdio2.StdioClientTransport({
|
|
1596
|
+
command: config.command,
|
|
1597
|
+
args: config.args,
|
|
1598
|
+
env: config.env
|
|
1599
|
+
});
|
|
1600
|
+
case "streamable-http":
|
|
1601
|
+
return new import_streamableHttp.StreamableHTTPClientTransport(
|
|
1602
|
+
new URL(config.url),
|
|
1603
|
+
config.headers ? { requestInit: { headers: config.headers } } : void 0
|
|
705
1604
|
);
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
let tools;
|
|
716
|
-
try {
|
|
717
|
-
tools = await upstreamClient.listTools();
|
|
718
|
-
} catch (error) {
|
|
719
|
-
import_node_process5.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
720
|
-
`);
|
|
721
|
-
import_node_process5.default.exit(1);
|
|
1605
|
+
case "sse":
|
|
1606
|
+
return new import_sse.SSEClientTransport(
|
|
1607
|
+
new URL(config.url),
|
|
1608
|
+
config.headers ? { requestInit: { headers: config.headers } } : void 0
|
|
1609
|
+
);
|
|
1610
|
+
default: {
|
|
1611
|
+
const _exhaustive = config;
|
|
1612
|
+
return _exhaustive;
|
|
1613
|
+
}
|
|
722
1614
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// src/proxy/index.ts
|
|
1618
|
+
var SINGLE_MCP_NAME = "__default__";
|
|
1619
|
+
async function startProxy(command, args) {
|
|
1620
|
+
const transport = new import_stdio3.StdioClientTransport({ command, args });
|
|
1621
|
+
const mcps = /* @__PURE__ */ new Map([[SINGLE_MCP_NAME, { transport }]]);
|
|
1622
|
+
const orchestrator = buildOrchestrator({
|
|
1623
|
+
mcps,
|
|
1624
|
+
namespaced: false,
|
|
1625
|
+
transportErrorPrefix: () => "Upstream MCP"
|
|
727
1626
|
});
|
|
728
|
-
|
|
729
|
-
import_node_process5.default.on("SIGTERM", () => shutdown(0));
|
|
730
|
-
import_node_process5.default.stdin.on("end", () => shutdown(0));
|
|
731
|
-
import_node_process5.default.stdin.on("close", () => shutdown(0));
|
|
732
|
-
try {
|
|
733
|
-
await proxyServer.start();
|
|
734
|
-
} catch (error) {
|
|
735
|
-
shutdown(1);
|
|
736
|
-
throw error;
|
|
737
|
-
}
|
|
1627
|
+
await runProxy(orchestrator);
|
|
738
1628
|
}
|
|
739
1629
|
async function startProxyFromConfig(options = {}) {
|
|
740
|
-
let isShuttingDown = false;
|
|
741
1630
|
const config = loadConfig(options);
|
|
742
1631
|
const mcps = /* @__PURE__ */ new Map();
|
|
743
1632
|
for (const [name, entry] of Object.entries(config.mcp)) {
|
|
744
1633
|
mcps.set(name, { transport: createTransport(entry) });
|
|
745
1634
|
}
|
|
746
|
-
const orchestrator =
|
|
1635
|
+
const orchestrator = buildOrchestrator({
|
|
747
1636
|
mcps,
|
|
1637
|
+
namespaced: true,
|
|
1638
|
+
transportErrorPrefix: (mcpName2) => `Upstream MCP "${mcpName2}"`
|
|
1639
|
+
});
|
|
1640
|
+
await runProxy(orchestrator);
|
|
1641
|
+
}
|
|
1642
|
+
var activeShutdown = { shutdown: null };
|
|
1643
|
+
function buildOrchestrator(params) {
|
|
1644
|
+
return new Orchestrator({
|
|
1645
|
+
mcps: params.mcps,
|
|
1646
|
+
namespaced: params.namespaced,
|
|
748
1647
|
onTransportError: (mcpName2, error) => {
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
1648
|
+
import_node_process6.default.stderr.write(
|
|
1649
|
+
`${params.transportErrorPrefix(mcpName2)} transport error: ${error.message}
|
|
1650
|
+
`
|
|
1651
|
+
);
|
|
1652
|
+
activeShutdown.shutdown?.(1);
|
|
752
1653
|
}
|
|
753
1654
|
});
|
|
1655
|
+
}
|
|
1656
|
+
async function runProxy(orchestrator) {
|
|
1657
|
+
let isShuttingDown = false;
|
|
754
1658
|
const shutdown = (exitCode) => {
|
|
755
1659
|
if (isShuttingDown) return;
|
|
756
1660
|
isShuttingDown = true;
|
|
757
1661
|
orchestrator.disconnectAll().catch((error) => {
|
|
758
|
-
|
|
1662
|
+
import_node_process6.default.stderr.write(
|
|
759
1663
|
`dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
|
|
760
1664
|
`
|
|
761
1665
|
);
|
|
762
|
-
}).finally(() =>
|
|
1666
|
+
}).finally(() => import_node_process6.default.exit(exitCode));
|
|
763
1667
|
};
|
|
1668
|
+
activeShutdown.shutdown = shutdown;
|
|
764
1669
|
try {
|
|
765
1670
|
await orchestrator.connect();
|
|
766
1671
|
} catch (error) {
|
|
767
|
-
|
|
1672
|
+
import_node_process6.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
768
1673
|
`);
|
|
769
|
-
|
|
1674
|
+
import_node_process6.default.exit(1);
|
|
1675
|
+
return;
|
|
770
1676
|
}
|
|
771
1677
|
const proxyServer = new ProxyServer({
|
|
772
|
-
catalog: orchestrator.catalog,
|
|
773
|
-
|
|
1678
|
+
catalog: () => orchestrator.catalog,
|
|
1679
|
+
capabilities: orchestrator.capabilities,
|
|
1680
|
+
callTool: (name, input, options) => orchestrator.callTool(name, input, options),
|
|
1681
|
+
resources: orchestrator.capabilities.resources !== void 0 ? {
|
|
1682
|
+
listResources: () => orchestrator.listResources(),
|
|
1683
|
+
listResourceTemplates: () => orchestrator.listResourceTemplates(),
|
|
1684
|
+
readResource: (uri, options) => orchestrator.readResource(uri, options),
|
|
1685
|
+
subscribeResource: (uri, options) => orchestrator.subscribeResource(uri, options),
|
|
1686
|
+
unsubscribeResource: (uri, options) => orchestrator.unsubscribeResource(uri, options)
|
|
1687
|
+
} : void 0,
|
|
1688
|
+
prompts: orchestrator.capabilities.prompts !== void 0 ? {
|
|
1689
|
+
listPrompts: () => orchestrator.listPrompts(),
|
|
1690
|
+
getPrompt: (name, args, options) => orchestrator.getPrompt(name, args, options)
|
|
1691
|
+
} : void 0,
|
|
1692
|
+
complete: orchestrator.capabilities.completions !== void 0 ? (params, options) => orchestrator.complete(params, options) : void 0,
|
|
1693
|
+
setLoggingLevel: orchestrator.capabilities.logging !== void 0 ? (level, options) => orchestrator.setLoggingLevel(level, options) : void 0,
|
|
1694
|
+
onRootsListChanged: () => orchestrator.broadcastRootsListChanged()
|
|
1695
|
+
});
|
|
1696
|
+
orchestrator.setNotificationHandlers({
|
|
1697
|
+
onToolsListChanged: () => proxyServer.sendToolListChanged(),
|
|
1698
|
+
onResourcesListChanged: () => proxyServer.sendResourceListChanged(),
|
|
1699
|
+
onResourceUpdated: (params) => proxyServer.sendResourceUpdated(params),
|
|
1700
|
+
onPromptsListChanged: () => proxyServer.sendPromptListChanged(),
|
|
1701
|
+
onLogMessage: (params) => proxyServer.sendLoggingMessage(params)
|
|
774
1702
|
});
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1703
|
+
orchestrator.setServerRequestForwarders({
|
|
1704
|
+
onCreateMessage: (params, options) => proxyServer.forwardCreateMessage(params, options),
|
|
1705
|
+
onElicitInput: (params, options) => proxyServer.forwardElicitInput(params, options),
|
|
1706
|
+
onListRoots: (params, options) => proxyServer.forwardListRoots(params, options)
|
|
1707
|
+
});
|
|
1708
|
+
import_node_process6.default.on("SIGINT", () => shutdown(0));
|
|
1709
|
+
import_node_process6.default.on("SIGTERM", () => shutdown(0));
|
|
1710
|
+
import_node_process6.default.stdin.on("end", () => shutdown(0));
|
|
1711
|
+
import_node_process6.default.stdin.on("close", () => shutdown(0));
|
|
779
1712
|
try {
|
|
780
1713
|
await proxyServer.start();
|
|
781
1714
|
} catch (error) {
|
|
@@ -795,43 +1728,40 @@ var cliBanner = import_chalk.default.bold.magentaBright(
|
|
|
795
1728
|
var cli = new import_commander.Command(package_default.name).description(package_default.description).version(package_default.version).addHelpText("beforeAll", cliBanner).addHelpText(
|
|
796
1729
|
"after",
|
|
797
1730
|
"\nExamples:\n dynmcp -- npx -y chrome-devtools-mcp@latest\n dynmcp --config ./mcp.json\n"
|
|
798
|
-
).option("-c, --config <path>", "Path to config file (JSON or YAML)").option(
|
|
799
|
-
|
|
800
|
-
"Path to a .env file for environment variable interpolation"
|
|
801
|
-
).allowExcessArguments(true).passThroughOptions(true).action(async (_options, cmd) => {
|
|
802
|
-
const separatorIndex = import_node_process6.default.argv.indexOf("--");
|
|
1731
|
+
).option("-c, --config <path>", "Path to config file (JSON or YAML)").option("-e, --env <path>", "Path to a .env file for environment variable interpolation").allowExcessArguments(true).passThroughOptions(true).action(async (_options, cmd) => {
|
|
1732
|
+
const separatorIndex = import_node_process7.default.argv.indexOf("--");
|
|
803
1733
|
const configPath = cmd.opts().config;
|
|
804
1734
|
const envFilePath = cmd.opts().env;
|
|
805
1735
|
if (separatorIndex !== -1) {
|
|
806
|
-
const [command, ...args] =
|
|
1736
|
+
const [command, ...args] = import_node_process7.default.argv.slice(separatorIndex + 1);
|
|
807
1737
|
if (command === void 0) {
|
|
808
|
-
|
|
1738
|
+
import_node_process7.default.stderr.write(
|
|
809
1739
|
"dynmcp: no upstream command provided after --.\nUsage: dynmcp -- <command> [args...]\n"
|
|
810
1740
|
);
|
|
811
|
-
|
|
1741
|
+
import_node_process7.default.exit(1);
|
|
812
1742
|
}
|
|
813
1743
|
try {
|
|
814
1744
|
await startProxy(command, args);
|
|
815
1745
|
} catch (error) {
|
|
816
|
-
|
|
1746
|
+
import_node_process7.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
817
1747
|
`);
|
|
818
|
-
|
|
1748
|
+
import_node_process7.default.exit(1);
|
|
819
1749
|
}
|
|
820
1750
|
return;
|
|
821
1751
|
}
|
|
822
1752
|
try {
|
|
823
1753
|
await startProxyFromConfig({ configPath, envFilePath });
|
|
824
1754
|
} catch (error) {
|
|
825
|
-
|
|
1755
|
+
import_node_process7.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
826
1756
|
`);
|
|
827
|
-
|
|
1757
|
+
import_node_process7.default.exit(1);
|
|
828
1758
|
}
|
|
829
1759
|
});
|
|
830
1760
|
|
|
831
1761
|
// src/index.ts
|
|
832
|
-
var
|
|
1762
|
+
var import_node_process8 = __toESM(require("process"), 1);
|
|
833
1763
|
async function main() {
|
|
834
|
-
cli.parse(
|
|
1764
|
+
cli.parse(import_node_process8.default.argv);
|
|
835
1765
|
}
|
|
836
1766
|
main();
|
|
837
1767
|
//# sourceMappingURL=index.cjs.map
|