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.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import process7 from "process";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "dynmcp",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.3.1",
|
|
11
11
|
description: "Dynamic MCP context management tool for AI MCP-enabled agents and clients.",
|
|
12
12
|
author: "Brandon Burrus <brandon@burrus.io>",
|
|
13
13
|
license: "MIT",
|
|
@@ -87,6 +87,7 @@ var package_default = {
|
|
|
87
87
|
"@commitlint/cli": "^21.0.1",
|
|
88
88
|
"@commitlint/config-conventional": "^21.0.1",
|
|
89
89
|
"@types/node": "^25.9.0",
|
|
90
|
+
"@vitest/coverage-v8": "^4.1.6",
|
|
90
91
|
husky: "^9.1.7",
|
|
91
92
|
tsup: "^8.5.1",
|
|
92
93
|
tsx: "^4.22.2",
|
|
@@ -100,7 +101,7 @@ import figlet from "figlet";
|
|
|
100
101
|
import chalk from "chalk";
|
|
101
102
|
|
|
102
103
|
// src/proxy/index.ts
|
|
103
|
-
import
|
|
104
|
+
import process6 from "process";
|
|
104
105
|
import { StdioClientTransport as StdioClientTransport2 } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
105
106
|
|
|
106
107
|
// src/config/schema.ts
|
|
@@ -363,33 +364,39 @@ function isYamlFile(filePath) {
|
|
|
363
364
|
// src/config/json-schema.ts
|
|
364
365
|
import { z as z2 } from "zod";
|
|
365
366
|
|
|
366
|
-
// src/proxy/
|
|
367
|
-
import
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
function
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
367
|
+
// src/proxy/orchestrator.ts
|
|
368
|
+
import process4 from "process";
|
|
369
|
+
|
|
370
|
+
// src/proxy/capability-aggregator.ts
|
|
371
|
+
function aggregateCapabilities(upstreams) {
|
|
372
|
+
const aggregated = {
|
|
373
|
+
tools: { listChanged: true }
|
|
374
|
+
};
|
|
375
|
+
for (const caps of upstreams) {
|
|
376
|
+
if (caps === void 0) continue;
|
|
377
|
+
if (caps.resources !== void 0) {
|
|
378
|
+
aggregated.resources ??= {};
|
|
379
|
+
if (caps.resources.subscribe === true) {
|
|
380
|
+
aggregated.resources.subscribe = true;
|
|
381
|
+
}
|
|
382
|
+
if (caps.resources.listChanged === true) {
|
|
383
|
+
aggregated.resources.listChanged = true;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
if (caps.prompts !== void 0) {
|
|
387
|
+
aggregated.prompts ??= {};
|
|
388
|
+
if (caps.prompts.listChanged === true) {
|
|
389
|
+
aggregated.prompts.listChanged = true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (caps.logging !== void 0) {
|
|
393
|
+
aggregated.logging ??= {};
|
|
394
|
+
}
|
|
395
|
+
if (caps.completions !== void 0) {
|
|
396
|
+
aggregated.completions ??= {};
|
|
391
397
|
}
|
|
392
398
|
}
|
|
399
|
+
return aggregated;
|
|
393
400
|
}
|
|
394
401
|
|
|
395
402
|
// src/proxy/tool-catalog.ts
|
|
@@ -495,15 +502,260 @@ function buildAnnotationLines(tool) {
|
|
|
495
502
|
return lines;
|
|
496
503
|
}
|
|
497
504
|
|
|
505
|
+
// src/proxy/notification-forwarder.ts
|
|
506
|
+
var NotificationForwarder = class {
|
|
507
|
+
constructor(registry, resourceRouter, promptRouter, toolsByMcp, setToolCatalog, namespaced) {
|
|
508
|
+
this.registry = registry;
|
|
509
|
+
this.resourceRouter = resourceRouter;
|
|
510
|
+
this.promptRouter = promptRouter;
|
|
511
|
+
this.toolsByMcp = toolsByMcp;
|
|
512
|
+
this.setToolCatalog = setToolCatalog;
|
|
513
|
+
this.namespaced = namespaced;
|
|
514
|
+
}
|
|
515
|
+
registry;
|
|
516
|
+
resourceRouter;
|
|
517
|
+
promptRouter;
|
|
518
|
+
toolsByMcp;
|
|
519
|
+
setToolCatalog;
|
|
520
|
+
namespaced;
|
|
521
|
+
hostHandlers = {};
|
|
522
|
+
setHostHandlers(handlers) {
|
|
523
|
+
this.hostHandlers = handlers;
|
|
524
|
+
}
|
|
525
|
+
async handleToolsListChanged(mcpName2) {
|
|
526
|
+
const client = this.registry.get(mcpName2);
|
|
527
|
+
if (client === void 0) return;
|
|
528
|
+
const tools = await client.listTools().catch(() => []);
|
|
529
|
+
this.toolsByMcp.set(mcpName2, tools);
|
|
530
|
+
const rebuilt = this.namespaced ? ToolCatalog.fromGrouped(this.toolsByMcp) : ToolCatalog.fromFlat([...this.toolsByMcp.values()][0] ?? []);
|
|
531
|
+
this.setToolCatalog(rebuilt);
|
|
532
|
+
await this.hostHandlers.onToolsListChanged?.();
|
|
533
|
+
}
|
|
534
|
+
async handleResourcesListChanged(mcpName2) {
|
|
535
|
+
const router = this.resourceRouter();
|
|
536
|
+
const client = this.registry.get(mcpName2);
|
|
537
|
+
if (router === null || client === void 0) return;
|
|
538
|
+
const [resources, templates] = await Promise.all([
|
|
539
|
+
client.listResources().catch(() => []),
|
|
540
|
+
client.listResourceTemplates().catch(() => [])
|
|
541
|
+
]);
|
|
542
|
+
router.setResources(mcpName2, resources);
|
|
543
|
+
router.setTemplates(mcpName2, templates);
|
|
544
|
+
await this.hostHandlers.onResourcesListChanged?.();
|
|
545
|
+
}
|
|
546
|
+
async handleResourceUpdated(params) {
|
|
547
|
+
await this.hostHandlers.onResourceUpdated?.(params);
|
|
548
|
+
}
|
|
549
|
+
async handlePromptsListChanged(mcpName2) {
|
|
550
|
+
const router = this.promptRouter();
|
|
551
|
+
const client = this.registry.get(mcpName2);
|
|
552
|
+
if (router === null || client === void 0) return;
|
|
553
|
+
const prompts = await client.listPrompts().catch(() => []);
|
|
554
|
+
router.setPrompts(mcpName2, prompts);
|
|
555
|
+
await this.hostHandlers.onPromptsListChanged?.();
|
|
556
|
+
}
|
|
557
|
+
/**
|
|
558
|
+
* Rewrites the upstream's `logger` field with the originating MCP's name as a
|
|
559
|
+
* prefix so the host can attribute log lines, then forwards the message to the
|
|
560
|
+
* host's log message handler.
|
|
561
|
+
*/
|
|
562
|
+
async handleLogMessage(mcpName2, params) {
|
|
563
|
+
const handler = this.hostHandlers.onLogMessage;
|
|
564
|
+
if (handler === void 0) return;
|
|
565
|
+
if (!this.namespaced) {
|
|
566
|
+
await handler(params);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const prefixed = {
|
|
570
|
+
...params,
|
|
571
|
+
logger: params.logger === void 0 ? mcpName2 : `${mcpName2}/${params.logger}`
|
|
572
|
+
};
|
|
573
|
+
await handler(prefixed);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
// src/proxy/prompt-router.ts
|
|
578
|
+
var PromptRouter = class {
|
|
579
|
+
mcpOrder;
|
|
580
|
+
perMcp;
|
|
581
|
+
nameOwners = /* @__PURE__ */ new Map();
|
|
582
|
+
detectedCollisions = [];
|
|
583
|
+
constructor(mcpOrder) {
|
|
584
|
+
this.mcpOrder = [...mcpOrder];
|
|
585
|
+
this.perMcp = new Map(this.mcpOrder.map((name) => [name, []]));
|
|
586
|
+
}
|
|
587
|
+
setPrompts(mcpName2, prompts) {
|
|
588
|
+
const entry = this.perMcp.get(mcpName2);
|
|
589
|
+
if (entry === void 0) {
|
|
590
|
+
throw new Error(`PromptRouter: unknown mcp "${mcpName2}"`);
|
|
591
|
+
}
|
|
592
|
+
this.perMcp.set(mcpName2, [...prompts]);
|
|
593
|
+
this.rebuild();
|
|
594
|
+
}
|
|
595
|
+
aggregatedPrompts() {
|
|
596
|
+
const result = [];
|
|
597
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
598
|
+
const entry = this.perMcp.get(mcpName2);
|
|
599
|
+
if (entry !== void 0) {
|
|
600
|
+
result.push(...entry);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return result;
|
|
604
|
+
}
|
|
605
|
+
ownerOf(promptName) {
|
|
606
|
+
return this.nameOwners.get(promptName);
|
|
607
|
+
}
|
|
608
|
+
collisions() {
|
|
609
|
+
return this.detectedCollisions;
|
|
610
|
+
}
|
|
611
|
+
rebuild() {
|
|
612
|
+
this.nameOwners = /* @__PURE__ */ new Map();
|
|
613
|
+
const collisions = [];
|
|
614
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
615
|
+
const prompts = this.perMcp.get(mcpName2);
|
|
616
|
+
if (prompts === void 0) continue;
|
|
617
|
+
for (const prompt of prompts) {
|
|
618
|
+
const existing = this.nameOwners.get(prompt.name);
|
|
619
|
+
if (existing === void 0) {
|
|
620
|
+
this.nameOwners.set(prompt.name, mcpName2);
|
|
621
|
+
} else {
|
|
622
|
+
collisions.push({ name: prompt.name, chosen: existing, shadowed: mcpName2 });
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
this.detectedCollisions = collisions;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
|
|
630
|
+
// src/proxy/resource-router.ts
|
|
631
|
+
var ResourceRouter = class {
|
|
632
|
+
mcpOrder;
|
|
633
|
+
perMcp;
|
|
634
|
+
uriOwners = /* @__PURE__ */ new Map();
|
|
635
|
+
templateOwners = [];
|
|
636
|
+
detectedCollisions = [];
|
|
637
|
+
constructor(mcpOrder) {
|
|
638
|
+
this.mcpOrder = [...mcpOrder];
|
|
639
|
+
this.perMcp = new Map(
|
|
640
|
+
this.mcpOrder.map((name) => [name, { resources: [], templates: [] }])
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
setResources(mcpName2, resources) {
|
|
644
|
+
const entry = this.perMcp.get(mcpName2);
|
|
645
|
+
if (entry === void 0) {
|
|
646
|
+
throw new Error(`ResourceRouter: unknown mcp "${mcpName2}"`);
|
|
647
|
+
}
|
|
648
|
+
entry.resources = [...resources];
|
|
649
|
+
this.rebuild();
|
|
650
|
+
}
|
|
651
|
+
setTemplates(mcpName2, templates) {
|
|
652
|
+
const entry = this.perMcp.get(mcpName2);
|
|
653
|
+
if (entry === void 0) {
|
|
654
|
+
throw new Error(`ResourceRouter: unknown mcp "${mcpName2}"`);
|
|
655
|
+
}
|
|
656
|
+
entry.templates = [...templates];
|
|
657
|
+
this.rebuild();
|
|
658
|
+
}
|
|
659
|
+
aggregatedResources() {
|
|
660
|
+
const result = [];
|
|
661
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
662
|
+
const entry = this.perMcp.get(mcpName2);
|
|
663
|
+
if (entry !== void 0) {
|
|
664
|
+
result.push(...entry.resources);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
return result;
|
|
668
|
+
}
|
|
669
|
+
aggregatedTemplates() {
|
|
670
|
+
const result = [];
|
|
671
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
672
|
+
const entry = this.perMcp.get(mcpName2);
|
|
673
|
+
if (entry !== void 0) {
|
|
674
|
+
result.push(...entry.templates);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return result;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Returns the mcpName that owns the given URI, or undefined if no upstream advertises it.
|
|
681
|
+
* Concrete URI matches take precedence over template prefix matches; templates are tried
|
|
682
|
+
* in config-file order (first-wins).
|
|
683
|
+
*/
|
|
684
|
+
ownerOf(uri) {
|
|
685
|
+
const concrete = this.uriOwners.get(uri);
|
|
686
|
+
if (concrete !== void 0) {
|
|
687
|
+
return concrete;
|
|
688
|
+
}
|
|
689
|
+
for (const { prefix, mcpName: mcpName2 } of this.templateOwners) {
|
|
690
|
+
if (prefix.length > 0 && uri.startsWith(prefix)) {
|
|
691
|
+
return mcpName2;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return void 0;
|
|
695
|
+
}
|
|
696
|
+
collisions() {
|
|
697
|
+
return this.detectedCollisions;
|
|
698
|
+
}
|
|
699
|
+
rebuild() {
|
|
700
|
+
this.uriOwners = /* @__PURE__ */ new Map();
|
|
701
|
+
this.templateOwners = [];
|
|
702
|
+
const collisions = [];
|
|
703
|
+
for (const mcpName2 of this.mcpOrder) {
|
|
704
|
+
const entry = this.perMcp.get(mcpName2);
|
|
705
|
+
if (entry === void 0) continue;
|
|
706
|
+
for (const resource of entry.resources) {
|
|
707
|
+
const existing = this.uriOwners.get(resource.uri);
|
|
708
|
+
if (existing === void 0) {
|
|
709
|
+
this.uriOwners.set(resource.uri, mcpName2);
|
|
710
|
+
} else {
|
|
711
|
+
collisions.push({ uri: resource.uri, chosen: existing, shadowed: mcpName2 });
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
for (const template of entry.templates) {
|
|
715
|
+
this.templateOwners.push({
|
|
716
|
+
prefix: literalPrefixOf(template.uriTemplate),
|
|
717
|
+
template,
|
|
718
|
+
mcpName: mcpName2
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
this.detectedCollisions = collisions;
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
function literalPrefixOf(uriTemplate) {
|
|
726
|
+
const idx = uriTemplate.indexOf("{");
|
|
727
|
+
return idx === -1 ? uriTemplate : uriTemplate.slice(0, idx);
|
|
728
|
+
}
|
|
729
|
+
|
|
498
730
|
// src/proxy/upstream-client.ts
|
|
499
731
|
import process3 from "process";
|
|
500
732
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
733
|
+
import {
|
|
734
|
+
CreateMessageRequestSchema,
|
|
735
|
+
ElicitRequestSchema,
|
|
736
|
+
ListRootsRequestSchema,
|
|
737
|
+
LoggingMessageNotificationSchema,
|
|
738
|
+
PromptListChangedNotificationSchema,
|
|
739
|
+
ResourceListChangedNotificationSchema,
|
|
740
|
+
ResourceUpdatedNotificationSchema,
|
|
741
|
+
ToolListChangedNotificationSchema
|
|
742
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
501
743
|
var UpstreamClient = class {
|
|
502
744
|
transport;
|
|
503
745
|
onTransportError;
|
|
746
|
+
notificationHandlers;
|
|
747
|
+
serverRequestHandlers;
|
|
504
748
|
client = null;
|
|
505
|
-
constructor({
|
|
749
|
+
constructor({
|
|
750
|
+
name,
|
|
751
|
+
transport,
|
|
752
|
+
onTransportError,
|
|
753
|
+
notifications,
|
|
754
|
+
serverRequests
|
|
755
|
+
}) {
|
|
506
756
|
this.transport = transport;
|
|
757
|
+
this.notificationHandlers = notifications ?? {};
|
|
758
|
+
this.serverRequestHandlers = serverRequests ?? {};
|
|
507
759
|
this.onTransportError = onTransportError ?? ((error) => {
|
|
508
760
|
process3.stderr.write(`[${name}] Upstream MCP transport error: ${error.message}
|
|
509
761
|
`);
|
|
@@ -511,14 +763,80 @@ var UpstreamClient = class {
|
|
|
511
763
|
}
|
|
512
764
|
async connect() {
|
|
513
765
|
this.transport.onerror = this.onTransportError;
|
|
514
|
-
this.client = new Client(
|
|
766
|
+
this.client = new Client(
|
|
767
|
+
{ name: "dynamic-discovery-mcp", version: "1.0.0" },
|
|
768
|
+
{
|
|
769
|
+
capabilities: {
|
|
770
|
+
// Declare every client-side capability the proxy may relay on behalf of the host.
|
|
771
|
+
// Actual reachability of each feature depends on what the host supports — if the
|
|
772
|
+
// host does not support sampling, for instance, the host call returns an error
|
|
773
|
+
// which we forward back to the upstream verbatim.
|
|
774
|
+
sampling: {},
|
|
775
|
+
elicitation: {},
|
|
776
|
+
roots: { listChanged: true }
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
);
|
|
780
|
+
this.registerServerRequestHandlers(this.client);
|
|
781
|
+
if (this.notificationHandlers.onToolsListChanged !== void 0) {
|
|
782
|
+
this.client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
|
|
783
|
+
await this.notificationHandlers.onToolsListChanged?.();
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
if (this.notificationHandlers.onResourcesListChanged !== void 0) {
|
|
787
|
+
this.client.setNotificationHandler(ResourceListChangedNotificationSchema, async () => {
|
|
788
|
+
await this.notificationHandlers.onResourcesListChanged?.();
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
if (this.notificationHandlers.onResourceUpdated !== void 0) {
|
|
792
|
+
this.client.setNotificationHandler(ResourceUpdatedNotificationSchema, async (notification) => {
|
|
793
|
+
await this.notificationHandlers.onResourceUpdated?.({ uri: notification.params.uri });
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
if (this.notificationHandlers.onPromptsListChanged !== void 0) {
|
|
797
|
+
this.client.setNotificationHandler(PromptListChangedNotificationSchema, async () => {
|
|
798
|
+
await this.notificationHandlers.onPromptsListChanged?.();
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
if (this.notificationHandlers.onLogMessage !== void 0) {
|
|
802
|
+
this.client.setNotificationHandler(LoggingMessageNotificationSchema, async (notification) => {
|
|
803
|
+
await this.notificationHandlers.onLogMessage?.(notification.params);
|
|
804
|
+
});
|
|
805
|
+
}
|
|
515
806
|
await this.client.connect(this.transport);
|
|
516
807
|
}
|
|
517
|
-
async
|
|
518
|
-
|
|
519
|
-
|
|
808
|
+
async setLoggingLevel(level, options) {
|
|
809
|
+
const client = this.requireClient();
|
|
810
|
+
await client.setLoggingLevel(level, options);
|
|
811
|
+
}
|
|
812
|
+
async listPrompts(options) {
|
|
813
|
+
const client = this.requireClient();
|
|
814
|
+
const result = await client.listPrompts(void 0, options);
|
|
815
|
+
return result.prompts;
|
|
816
|
+
}
|
|
817
|
+
async getPrompt(name, args, options) {
|
|
818
|
+
const client = this.requireClient();
|
|
819
|
+
const params = { name };
|
|
820
|
+
if (args !== void 0) {
|
|
821
|
+
params.arguments = args;
|
|
520
822
|
}
|
|
521
|
-
|
|
823
|
+
return client.getPrompt(params, options);
|
|
824
|
+
}
|
|
825
|
+
async complete(params, options) {
|
|
826
|
+
const client = this.requireClient();
|
|
827
|
+
return client.complete(params, options);
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Returns the capabilities advertised by the upstream server during initialize.
|
|
831
|
+
* Returns `undefined` if the client is not connected, or if the SDK has not yet
|
|
832
|
+
* recorded the server's capabilities (e.g. during a partially-completed handshake).
|
|
833
|
+
*/
|
|
834
|
+
getCapabilities() {
|
|
835
|
+
return this.client?.getServerCapabilities();
|
|
836
|
+
}
|
|
837
|
+
async listTools(options) {
|
|
838
|
+
const client = this.requireClient();
|
|
839
|
+
const result = await client.listTools(void 0, options);
|
|
522
840
|
return result.tools.map((tool) => {
|
|
523
841
|
const upstreamTool = {
|
|
524
842
|
name: tool.name,
|
|
@@ -540,13 +858,33 @@ var UpstreamClient = class {
|
|
|
540
858
|
return upstreamTool;
|
|
541
859
|
});
|
|
542
860
|
}
|
|
543
|
-
async callTool(name, input) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
}
|
|
547
|
-
const result = await this.client.callTool({ name, arguments: input });
|
|
861
|
+
async callTool(name, input, options) {
|
|
862
|
+
const client = this.requireClient();
|
|
863
|
+
const result = await client.callTool({ name, arguments: input }, void 0, options);
|
|
548
864
|
return result;
|
|
549
865
|
}
|
|
866
|
+
async listResources(options) {
|
|
867
|
+
const client = this.requireClient();
|
|
868
|
+
const result = await client.listResources(void 0, options);
|
|
869
|
+
return result.resources;
|
|
870
|
+
}
|
|
871
|
+
async listResourceTemplates(options) {
|
|
872
|
+
const client = this.requireClient();
|
|
873
|
+
const result = await client.listResourceTemplates(void 0, options);
|
|
874
|
+
return result.resourceTemplates;
|
|
875
|
+
}
|
|
876
|
+
async readResource(uri, options) {
|
|
877
|
+
const client = this.requireClient();
|
|
878
|
+
return client.readResource({ uri }, options);
|
|
879
|
+
}
|
|
880
|
+
async subscribeResource(uri, options) {
|
|
881
|
+
const client = this.requireClient();
|
|
882
|
+
await client.subscribeResource({ uri }, options);
|
|
883
|
+
}
|
|
884
|
+
async unsubscribeResource(uri, options) {
|
|
885
|
+
const client = this.requireClient();
|
|
886
|
+
await client.unsubscribeResource({ uri }, options);
|
|
887
|
+
}
|
|
550
888
|
async disconnect() {
|
|
551
889
|
if (this.client === null) {
|
|
552
890
|
return;
|
|
@@ -554,37 +892,198 @@ var UpstreamClient = class {
|
|
|
554
892
|
await this.client.close();
|
|
555
893
|
this.client = null;
|
|
556
894
|
}
|
|
895
|
+
/**
|
|
896
|
+
* Sends `notifications/roots/list_changed` to the upstream, letting it know that
|
|
897
|
+
* the host's set of filesystem roots has changed.
|
|
898
|
+
*/
|
|
899
|
+
async sendRootsListChanged() {
|
|
900
|
+
const client = this.requireClient();
|
|
901
|
+
await client.sendRootsListChanged();
|
|
902
|
+
}
|
|
903
|
+
registerServerRequestHandlers(client) {
|
|
904
|
+
if (this.serverRequestHandlers.onCreateMessage !== void 0) {
|
|
905
|
+
client.setRequestHandler(
|
|
906
|
+
CreateMessageRequestSchema,
|
|
907
|
+
async (request, extra) => {
|
|
908
|
+
return this.serverRequestHandlers.onCreateMessage(request.params, {
|
|
909
|
+
signal: extra.signal
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
if (this.serverRequestHandlers.onElicitInput !== void 0) {
|
|
915
|
+
client.setRequestHandler(
|
|
916
|
+
ElicitRequestSchema,
|
|
917
|
+
async (request, extra) => {
|
|
918
|
+
return this.serverRequestHandlers.onElicitInput(request.params, {
|
|
919
|
+
signal: extra.signal
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
);
|
|
923
|
+
}
|
|
924
|
+
if (this.serverRequestHandlers.onListRoots !== void 0) {
|
|
925
|
+
client.setRequestHandler(
|
|
926
|
+
ListRootsRequestSchema,
|
|
927
|
+
async (request, extra) => {
|
|
928
|
+
return this.serverRequestHandlers.onListRoots(request.params, {
|
|
929
|
+
signal: extra.signal
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
requireClient() {
|
|
936
|
+
if (this.client === null) {
|
|
937
|
+
throw new Error("Client is not connected. Call connect() first.");
|
|
938
|
+
}
|
|
939
|
+
return this.client;
|
|
940
|
+
}
|
|
557
941
|
};
|
|
558
942
|
|
|
559
|
-
// src/proxy/
|
|
560
|
-
var
|
|
561
|
-
config;
|
|
943
|
+
// src/proxy/upstream-registry.ts
|
|
944
|
+
var UpstreamRegistry = class {
|
|
562
945
|
clients = /* @__PURE__ */ new Map();
|
|
563
|
-
|
|
564
|
-
constructor(config) {
|
|
565
|
-
this.config = config;
|
|
566
|
-
}
|
|
567
|
-
async connect() {
|
|
568
|
-
const groups = /* @__PURE__ */ new Map();
|
|
946
|
+
async connectAll(entries) {
|
|
569
947
|
try {
|
|
570
|
-
for (const [mcpName2,
|
|
948
|
+
for (const [mcpName2, config] of entries) {
|
|
571
949
|
const client = new UpstreamClient({
|
|
572
950
|
name: mcpName2,
|
|
573
|
-
transport,
|
|
574
|
-
onTransportError:
|
|
575
|
-
|
|
576
|
-
|
|
951
|
+
transport: config.transport,
|
|
952
|
+
onTransportError: config.onTransportError,
|
|
953
|
+
notifications: config.notifications,
|
|
954
|
+
serverRequests: config.serverRequests
|
|
577
955
|
});
|
|
578
956
|
await client.connect();
|
|
579
|
-
const tools = await client.listTools();
|
|
580
957
|
this.clients.set(mcpName2, client);
|
|
581
|
-
groups.set(mcpName2, tools);
|
|
582
958
|
}
|
|
583
959
|
} catch (error) {
|
|
584
960
|
await this.disconnectAll();
|
|
585
961
|
throw error;
|
|
586
962
|
}
|
|
587
|
-
|
|
963
|
+
}
|
|
964
|
+
get(mcpName2) {
|
|
965
|
+
return this.clients.get(mcpName2);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Returns the sole connected client. Used by single-MCP (`--`) mode where the
|
|
969
|
+
* Orchestrator guarantees there is exactly one upstream. Returns undefined when
|
|
970
|
+
* zero or more than one client is connected.
|
|
971
|
+
*/
|
|
972
|
+
sole() {
|
|
973
|
+
if (this.clients.size !== 1) return void 0;
|
|
974
|
+
return this.clients.values().next().value;
|
|
975
|
+
}
|
|
976
|
+
names() {
|
|
977
|
+
return [...this.clients.keys()];
|
|
978
|
+
}
|
|
979
|
+
entries() {
|
|
980
|
+
return this.clients.entries();
|
|
981
|
+
}
|
|
982
|
+
size() {
|
|
983
|
+
return this.clients.size;
|
|
984
|
+
}
|
|
985
|
+
async disconnectAll() {
|
|
986
|
+
const disconnections = [...this.clients.values()].map((client) => client.disconnect());
|
|
987
|
+
await Promise.all(disconnections);
|
|
988
|
+
this.clients.clear();
|
|
989
|
+
}
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
// src/proxy/orchestrator.ts
|
|
993
|
+
var Orchestrator = class {
|
|
994
|
+
config;
|
|
995
|
+
registry = new UpstreamRegistry();
|
|
996
|
+
toolsByMcp = /* @__PURE__ */ new Map();
|
|
997
|
+
resourceRouter = null;
|
|
998
|
+
promptRouter = null;
|
|
999
|
+
toolCatalog = null;
|
|
1000
|
+
aggregatedCapabilities = null;
|
|
1001
|
+
serverRequestForwarders = {};
|
|
1002
|
+
forwarder;
|
|
1003
|
+
constructor(config) {
|
|
1004
|
+
if (!config.namespaced && config.mcps.size !== 1) {
|
|
1005
|
+
throw new Error(
|
|
1006
|
+
`Single-MCP (non-namespaced) mode requires exactly one upstream; got ${config.mcps.size}.`
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
this.config = config;
|
|
1010
|
+
this.forwarder = new NotificationForwarder(
|
|
1011
|
+
this.registry,
|
|
1012
|
+
() => this.resourceRouter,
|
|
1013
|
+
() => this.promptRouter,
|
|
1014
|
+
this.toolsByMcp,
|
|
1015
|
+
(catalog) => {
|
|
1016
|
+
this.toolCatalog = catalog;
|
|
1017
|
+
},
|
|
1018
|
+
this.config.namespaced
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
setNotificationHandlers(handlers) {
|
|
1022
|
+
this.forwarder.setHostHandlers(handlers);
|
|
1023
|
+
}
|
|
1024
|
+
setServerRequestForwarders(forwarders) {
|
|
1025
|
+
this.serverRequestForwarders = forwarders;
|
|
1026
|
+
}
|
|
1027
|
+
async connect() {
|
|
1028
|
+
const resourceRouter = new ResourceRouter([...this.config.mcps.keys()]);
|
|
1029
|
+
const promptRouter = new PromptRouter([...this.config.mcps.keys()]);
|
|
1030
|
+
const upstreamEntries = [
|
|
1031
|
+
...this.config.mcps
|
|
1032
|
+
].map(([mcpName2, { transport }]) => [
|
|
1033
|
+
mcpName2,
|
|
1034
|
+
{
|
|
1035
|
+
transport,
|
|
1036
|
+
onTransportError: (error) => {
|
|
1037
|
+
this.config.onTransportError?.(mcpName2, error);
|
|
1038
|
+
},
|
|
1039
|
+
notifications: {
|
|
1040
|
+
onToolsListChanged: () => this.forwarder.handleToolsListChanged(mcpName2),
|
|
1041
|
+
onResourcesListChanged: () => this.forwarder.handleResourcesListChanged(mcpName2),
|
|
1042
|
+
onResourceUpdated: (params) => this.forwarder.handleResourceUpdated(params),
|
|
1043
|
+
onPromptsListChanged: () => this.forwarder.handlePromptsListChanged(mcpName2),
|
|
1044
|
+
onLogMessage: (params) => this.forwarder.handleLogMessage(mcpName2, params)
|
|
1045
|
+
},
|
|
1046
|
+
serverRequests: {
|
|
1047
|
+
onCreateMessage: (params, opts) => this.forwardCreateMessage(params, opts),
|
|
1048
|
+
onElicitInput: (params, opts) => this.forwardElicitInput(params, opts),
|
|
1049
|
+
onListRoots: (params, opts) => this.forwardListRoots(params, opts)
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
]);
|
|
1053
|
+
await this.registry.connectAll(upstreamEntries);
|
|
1054
|
+
const capabilityList = [];
|
|
1055
|
+
this.toolsByMcp.clear();
|
|
1056
|
+
for (const [mcpName2, client] of this.registry.entries()) {
|
|
1057
|
+
const caps = client.getCapabilities();
|
|
1058
|
+
capabilityList.push(caps);
|
|
1059
|
+
const tools = await client.listTools();
|
|
1060
|
+
this.toolsByMcp.set(mcpName2, tools);
|
|
1061
|
+
if (caps?.resources !== void 0) {
|
|
1062
|
+
const [resources, templates] = await Promise.all([
|
|
1063
|
+
client.listResources().catch(() => []),
|
|
1064
|
+
client.listResourceTemplates().catch(() => [])
|
|
1065
|
+
]);
|
|
1066
|
+
resourceRouter.setResources(mcpName2, resources);
|
|
1067
|
+
resourceRouter.setTemplates(mcpName2, templates);
|
|
1068
|
+
}
|
|
1069
|
+
if (caps?.prompts !== void 0) {
|
|
1070
|
+
const prompts = await client.listPrompts().catch(() => []);
|
|
1071
|
+
promptRouter.setPrompts(mcpName2, prompts);
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
this.toolCatalog = this.config.namespaced ? ToolCatalog.fromGrouped(this.toolsByMcp) : ToolCatalog.fromFlat([...this.toolsByMcp.values()][0] ?? []);
|
|
1075
|
+
this.aggregatedCapabilities = aggregateCapabilities(capabilityList);
|
|
1076
|
+
this.resourceRouter = resourceRouter;
|
|
1077
|
+
this.promptRouter = promptRouter;
|
|
1078
|
+
logCollisions(resourceRouter, promptRouter);
|
|
1079
|
+
}
|
|
1080
|
+
async disconnectAll() {
|
|
1081
|
+
await this.registry.disconnectAll();
|
|
1082
|
+
this.toolsByMcp.clear();
|
|
1083
|
+
this.toolCatalog = null;
|
|
1084
|
+
this.aggregatedCapabilities = null;
|
|
1085
|
+
this.resourceRouter = null;
|
|
1086
|
+
this.promptRouter = null;
|
|
588
1087
|
}
|
|
589
1088
|
get catalog() {
|
|
590
1089
|
if (this.toolCatalog === null) {
|
|
@@ -592,168 +1091,624 @@ var Orchestrator = class {
|
|
|
592
1091
|
}
|
|
593
1092
|
return this.toolCatalog;
|
|
594
1093
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
1094
|
+
get capabilities() {
|
|
1095
|
+
if (this.aggregatedCapabilities === null) {
|
|
1096
|
+
throw new Error("Orchestrator is not connected. Call connect() first.");
|
|
1097
|
+
}
|
|
1098
|
+
return this.aggregatedCapabilities;
|
|
1099
|
+
}
|
|
1100
|
+
// === Forward-direction request routing ===
|
|
1101
|
+
async callTool(displayName, input, options) {
|
|
1102
|
+
if (this.config.namespaced) {
|
|
1103
|
+
const { mcpName: mcpName2, toolName } = splitNamespacedName(displayName, this.registry.names());
|
|
1104
|
+
return this.requireClient(mcpName2, "tool").callTool(toolName, input, options);
|
|
1105
|
+
}
|
|
1106
|
+
const sole = this.registry.sole();
|
|
1107
|
+
if (sole === void 0) {
|
|
1108
|
+
throw new Error("Orchestrator is not connected. Call connect() first.");
|
|
601
1109
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1110
|
+
return sole.callTool(displayName, input, options);
|
|
1111
|
+
}
|
|
1112
|
+
listResources() {
|
|
1113
|
+
return this.requireResourceRouter().aggregatedResources();
|
|
1114
|
+
}
|
|
1115
|
+
listResourceTemplates() {
|
|
1116
|
+
return this.requireResourceRouter().aggregatedTemplates();
|
|
1117
|
+
}
|
|
1118
|
+
async readResource(uri, options) {
|
|
1119
|
+
return this.resolveResourceOwner(uri).readResource(uri, options);
|
|
1120
|
+
}
|
|
1121
|
+
async subscribeResource(uri, options) {
|
|
1122
|
+
await this.resolveResourceOwner(uri).subscribeResource(uri, options);
|
|
1123
|
+
}
|
|
1124
|
+
async unsubscribeResource(uri, options) {
|
|
1125
|
+
await this.resolveResourceOwner(uri).unsubscribeResource(uri, options);
|
|
1126
|
+
}
|
|
1127
|
+
listPrompts() {
|
|
1128
|
+
return this.requirePromptRouter().aggregatedPrompts();
|
|
1129
|
+
}
|
|
1130
|
+
async getPrompt(name, args, options) {
|
|
1131
|
+
return this.resolvePromptOwner(name).getPrompt(name, args, options);
|
|
1132
|
+
}
|
|
1133
|
+
async complete(params, options) {
|
|
1134
|
+
const client = this.resolveCompletionTarget(params.ref);
|
|
1135
|
+
return client.complete(params, options);
|
|
1136
|
+
}
|
|
1137
|
+
// === Broadcasts ===
|
|
1138
|
+
/**
|
|
1139
|
+
* Broadcasts a `logging/setLevel` request to every upstream advertising the logging
|
|
1140
|
+
* capability. Errors from individual upstreams are swallowed so a single misbehaving
|
|
1141
|
+
* upstream cannot break the broadcast for others; failures are written to stderr.
|
|
1142
|
+
*/
|
|
1143
|
+
async setLoggingLevel(level, options) {
|
|
1144
|
+
await this.broadcastAsync(
|
|
1145
|
+
(client) => client.getCapabilities()?.logging !== void 0 ? client.setLoggingLevel(level, options) : Promise.resolve(),
|
|
1146
|
+
"setLoggingLevel"
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Broadcasts `notifications/roots/list_changed` to every connected upstream — the
|
|
1151
|
+
* proxy declares roots capability uniformly to all upstreams so every one of them
|
|
1152
|
+
* may have requested roots and needs to know the list changed.
|
|
1153
|
+
*/
|
|
1154
|
+
async broadcastRootsListChanged() {
|
|
1155
|
+
await this.broadcastAsync((client) => client.sendRootsListChanged(), "sendRootsListChanged");
|
|
1156
|
+
}
|
|
1157
|
+
// === Server-initiated request forwarders (upstream → host) ===
|
|
1158
|
+
async forwardCreateMessage(params, options) {
|
|
1159
|
+
const handler = this.serverRequestForwarders.onCreateMessage;
|
|
1160
|
+
if (handler === void 0) {
|
|
1161
|
+
throw new Error("Proxy does not support sampling: host has not registered a handler.");
|
|
1162
|
+
}
|
|
1163
|
+
return handler(params, options);
|
|
1164
|
+
}
|
|
1165
|
+
async forwardElicitInput(params, options) {
|
|
1166
|
+
const handler = this.serverRequestForwarders.onElicitInput;
|
|
1167
|
+
if (handler === void 0) {
|
|
1168
|
+
throw new Error("Proxy does not support elicitation: host has not registered a handler.");
|
|
1169
|
+
}
|
|
1170
|
+
return handler(params, options);
|
|
1171
|
+
}
|
|
1172
|
+
async forwardListRoots(params, options) {
|
|
1173
|
+
const handler = this.serverRequestForwarders.onListRoots;
|
|
1174
|
+
if (handler === void 0) {
|
|
1175
|
+
throw new Error("Proxy does not support roots: host has not registered a handler.");
|
|
1176
|
+
}
|
|
1177
|
+
return handler(params, options);
|
|
1178
|
+
}
|
|
1179
|
+
// === Internal helpers ===
|
|
1180
|
+
requireResourceRouter() {
|
|
1181
|
+
if (this.resourceRouter === null) {
|
|
1182
|
+
throw new Error("Orchestrator is not connected. Call connect() first.");
|
|
1183
|
+
}
|
|
1184
|
+
return this.resourceRouter;
|
|
1185
|
+
}
|
|
1186
|
+
requirePromptRouter() {
|
|
1187
|
+
if (this.promptRouter === null) {
|
|
1188
|
+
throw new Error("Orchestrator is not connected. Call connect() first.");
|
|
1189
|
+
}
|
|
1190
|
+
return this.promptRouter;
|
|
1191
|
+
}
|
|
1192
|
+
resolveResourceOwner(uri) {
|
|
1193
|
+
const owner = this.requireResourceRouter().ownerOf(uri);
|
|
1194
|
+
if (owner === void 0) {
|
|
1195
|
+
throw new Error(`Unknown resource URI: "${uri}". No upstream MCP advertises it.`);
|
|
1196
|
+
}
|
|
1197
|
+
return this.requireClient(owner, "resource");
|
|
1198
|
+
}
|
|
1199
|
+
resolvePromptOwner(name) {
|
|
1200
|
+
const owner = this.requirePromptRouter().ownerOf(name);
|
|
1201
|
+
if (owner === void 0) {
|
|
1202
|
+
throw new Error(`Unknown prompt: "${name}". No upstream MCP advertises it.`);
|
|
1203
|
+
}
|
|
1204
|
+
return this.requireClient(owner, "prompt");
|
|
1205
|
+
}
|
|
1206
|
+
resolveCompletionTarget(ref) {
|
|
1207
|
+
if (ref.type === "ref/prompt") {
|
|
1208
|
+
return this.resolvePromptOwner(ref.name);
|
|
1209
|
+
}
|
|
1210
|
+
if (ref.type === "ref/resource") {
|
|
1211
|
+
return this.resolveResourceOwner(ref.uri);
|
|
1212
|
+
}
|
|
1213
|
+
const unknownRef = ref;
|
|
1214
|
+
throw new Error(`Unsupported completion ref type: "${unknownRef.type}"`);
|
|
1215
|
+
}
|
|
1216
|
+
requireClient(mcpName2, role) {
|
|
1217
|
+
const client = this.registry.get(mcpName2);
|
|
605
1218
|
if (client === void 0) {
|
|
606
|
-
|
|
607
|
-
throw new Error(`Unknown MCP: "${mcpName2}". Available MCPs: ${available}`);
|
|
1219
|
+
throw new Error(`Internal error: ${role} owner "${mcpName2}" has no connected client.`);
|
|
608
1220
|
}
|
|
609
|
-
return client
|
|
1221
|
+
return client;
|
|
610
1222
|
}
|
|
611
|
-
async
|
|
612
|
-
const
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1223
|
+
async broadcastAsync(action, label) {
|
|
1224
|
+
const targets = [];
|
|
1225
|
+
for (const [mcpName2, client] of this.registry.entries()) {
|
|
1226
|
+
targets.push(
|
|
1227
|
+
action(client).catch((error) => {
|
|
1228
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1229
|
+
process4.stderr.write(`dynmcp: ${label} failed for "${mcpName2}": ${message}
|
|
1230
|
+
`);
|
|
1231
|
+
})
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
await Promise.all(targets);
|
|
616
1235
|
}
|
|
617
1236
|
};
|
|
1237
|
+
function splitNamespacedName(namespacedName, knownMcpNames) {
|
|
1238
|
+
const separatorIndex = namespacedName.indexOf("/");
|
|
1239
|
+
if (separatorIndex === -1) {
|
|
1240
|
+
throw new Error(
|
|
1241
|
+
`Invalid namespaced tool name: "${namespacedName}". Expected format: "mcpName/toolName".`
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
const mcpName2 = namespacedName.slice(0, separatorIndex);
|
|
1245
|
+
const toolName = namespacedName.slice(separatorIndex + 1);
|
|
1246
|
+
if (!knownMcpNames.includes(mcpName2)) {
|
|
1247
|
+
const available = [...knownMcpNames].sort().join(", ");
|
|
1248
|
+
throw new Error(`Unknown MCP: "${mcpName2}". Available MCPs: ${available}`);
|
|
1249
|
+
}
|
|
1250
|
+
return { mcpName: mcpName2, toolName };
|
|
1251
|
+
}
|
|
1252
|
+
function logCollisions(resourceRouter, promptRouter) {
|
|
1253
|
+
for (const collision of resourceRouter.collisions()) {
|
|
1254
|
+
process4.stderr.write(
|
|
1255
|
+
`dynmcp: resource URI collision: "${collision.uri}" is provided by "${collision.chosen}" and "${collision.shadowed}"; routing to "${collision.chosen}".
|
|
1256
|
+
`
|
|
1257
|
+
);
|
|
1258
|
+
}
|
|
1259
|
+
for (const collision of promptRouter.collisions()) {
|
|
1260
|
+
process4.stderr.write(
|
|
1261
|
+
`dynmcp: prompt name collision: "${collision.name}" is provided by "${collision.chosen}" and "${collision.shadowed}"; routing to "${collision.chosen}".
|
|
1262
|
+
`
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
618
1266
|
|
|
619
1267
|
// src/proxy/server.ts
|
|
620
|
-
import
|
|
621
|
-
import {
|
|
1268
|
+
import process5 from "process";
|
|
1269
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
1270
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1271
|
+
import {
|
|
1272
|
+
CallToolRequestSchema,
|
|
1273
|
+
CompleteRequestSchema,
|
|
1274
|
+
GetPromptRequestSchema,
|
|
1275
|
+
ListPromptsRequestSchema,
|
|
1276
|
+
ListResourcesRequestSchema,
|
|
1277
|
+
ListResourceTemplatesRequestSchema,
|
|
1278
|
+
ListToolsRequestSchema,
|
|
1279
|
+
ReadResourceRequestSchema,
|
|
1280
|
+
RootsListChangedNotificationSchema,
|
|
1281
|
+
SetLevelRequestSchema,
|
|
1282
|
+
SubscribeRequestSchema,
|
|
1283
|
+
UnsubscribeRequestSchema
|
|
1284
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
622
1285
|
import { z as z3 } from "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 = z3.object({ tool_name: z3.string() });
|
|
1305
|
+
var UseToolArgsSchema = z3.object({
|
|
1306
|
+
tool_name: z3.string(),
|
|
1307
|
+
tool_input: z3.record(z3.string(), z3.unknown()).default({})
|
|
1308
|
+
});
|
|
623
1309
|
var ProxyServer = class {
|
|
624
1310
|
catalog;
|
|
625
1311
|
callTool;
|
|
626
|
-
|
|
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
|
+
}) {
|
|
627
1329
|
this.catalog = catalog;
|
|
628
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 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(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;
|
|
629
1398
|
}
|
|
630
1399
|
async start() {
|
|
631
|
-
const server =
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
1400
|
+
const server = this.buildServer();
|
|
1401
|
+
const transport = new StdioServerTransport();
|
|
1402
|
+
process5.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(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
|
+
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
|
+
};
|
|
1511
|
+
}
|
|
1512
|
+
);
|
|
1513
|
+
}
|
|
1514
|
+
registerResourceHandlers(server, callbacks) {
|
|
1515
|
+
server.setRequestHandler(
|
|
1516
|
+
ListResourcesRequestSchema,
|
|
1517
|
+
async () => ({
|
|
1518
|
+
resources: callbacks.listResources()
|
|
1519
|
+
})
|
|
1520
|
+
);
|
|
1521
|
+
server.setRequestHandler(
|
|
1522
|
+
ListResourceTemplatesRequestSchema,
|
|
1523
|
+
async () => ({
|
|
1524
|
+
resourceTemplates: callbacks.listResourceTemplates()
|
|
1525
|
+
})
|
|
1526
|
+
);
|
|
1527
|
+
server.setRequestHandler(
|
|
1528
|
+
ReadResourceRequestSchema,
|
|
1529
|
+
async (request, extra) => {
|
|
1530
|
+
return callbacks.readResource(request.params.uri, this.buildCallOptions(request, extra));
|
|
641
1531
|
}
|
|
1532
|
+
);
|
|
1533
|
+
server.setRequestHandler(SubscribeRequestSchema, async (request, extra) => {
|
|
1534
|
+
await callbacks.subscribeResource(request.params.uri, this.buildCallOptions(request, extra));
|
|
1535
|
+
return {};
|
|
642
1536
|
});
|
|
643
|
-
server.
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1537
|
+
server.setRequestHandler(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
|
+
ListPromptsRequestSchema,
|
|
1548
|
+
async () => ({
|
|
1549
|
+
prompts: callbacks.listPrompts()
|
|
1550
|
+
})
|
|
1551
|
+
);
|
|
1552
|
+
server.setRequestHandler(
|
|
1553
|
+
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
|
+
CompleteRequestSchema,
|
|
1566
|
+
async (request, extra) => {
|
|
1567
|
+
return callback(request.params, this.buildCallOptions(request, extra));
|
|
656
1568
|
}
|
|
1569
|
+
);
|
|
1570
|
+
}
|
|
1571
|
+
registerLoggingHandler(server, callback) {
|
|
1572
|
+
server.setRequestHandler(SetLevelRequestSchema, async (request, extra) => {
|
|
1573
|
+
await callback(request.params.level, this.buildCallOptions(request, extra));
|
|
1574
|
+
return {};
|
|
657
1575
|
});
|
|
658
|
-
|
|
659
|
-
|
|
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
|
+
}
|
|
660
1585
|
}
|
|
661
1586
|
};
|
|
662
1587
|
|
|
1588
|
+
// src/proxy/transport-factory.ts
|
|
1589
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
1590
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
1591
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
1592
|
+
function createTransport(config) {
|
|
1593
|
+
switch (config.transport) {
|
|
1594
|
+
case "stdio":
|
|
1595
|
+
return new StdioClientTransport({
|
|
1596
|
+
command: config.command,
|
|
1597
|
+
args: config.args,
|
|
1598
|
+
env: config.env
|
|
1599
|
+
});
|
|
1600
|
+
case "streamable-http":
|
|
1601
|
+
return new StreamableHTTPClientTransport(
|
|
1602
|
+
new URL(config.url),
|
|
1603
|
+
config.headers ? { requestInit: { headers: config.headers } } : void 0
|
|
1604
|
+
);
|
|
1605
|
+
case "sse":
|
|
1606
|
+
return new 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
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
663
1617
|
// src/proxy/index.ts
|
|
1618
|
+
var SINGLE_MCP_NAME = "__default__";
|
|
664
1619
|
async function startProxy(command, args) {
|
|
665
|
-
let isShuttingDown = false;
|
|
666
1620
|
const transport = new StdioClientTransport2({ command, args });
|
|
667
|
-
const
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
`);
|
|
673
|
-
shutdown(1);
|
|
674
|
-
}
|
|
675
|
-
});
|
|
676
|
-
const shutdown = (exitCode) => {
|
|
677
|
-
if (isShuttingDown) return;
|
|
678
|
-
isShuttingDown = true;
|
|
679
|
-
upstreamClient.disconnect().catch((error) => {
|
|
680
|
-
process5.stderr.write(
|
|
681
|
-
`dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
|
|
682
|
-
`
|
|
683
|
-
);
|
|
684
|
-
}).finally(() => process5.exit(exitCode));
|
|
685
|
-
};
|
|
686
|
-
try {
|
|
687
|
-
await upstreamClient.connect();
|
|
688
|
-
} catch (error) {
|
|
689
|
-
process5.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
690
|
-
`);
|
|
691
|
-
process5.exit(1);
|
|
692
|
-
}
|
|
693
|
-
let tools;
|
|
694
|
-
try {
|
|
695
|
-
tools = await upstreamClient.listTools();
|
|
696
|
-
} catch (error) {
|
|
697
|
-
process5.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
698
|
-
`);
|
|
699
|
-
process5.exit(1);
|
|
700
|
-
}
|
|
701
|
-
const catalog = ToolCatalog.fromFlat(tools);
|
|
702
|
-
const proxyServer = new ProxyServer({
|
|
703
|
-
catalog,
|
|
704
|
-
callTool: (name, input) => upstreamClient.callTool(name, input)
|
|
1621
|
+
const mcps = /* @__PURE__ */ new Map([[SINGLE_MCP_NAME, { transport }]]);
|
|
1622
|
+
const orchestrator = buildOrchestrator({
|
|
1623
|
+
mcps,
|
|
1624
|
+
namespaced: false,
|
|
1625
|
+
transportErrorPrefix: () => "Upstream MCP"
|
|
705
1626
|
});
|
|
706
|
-
|
|
707
|
-
process5.on("SIGTERM", () => shutdown(0));
|
|
708
|
-
process5.stdin.on("end", () => shutdown(0));
|
|
709
|
-
process5.stdin.on("close", () => shutdown(0));
|
|
710
|
-
try {
|
|
711
|
-
await proxyServer.start();
|
|
712
|
-
} catch (error) {
|
|
713
|
-
shutdown(1);
|
|
714
|
-
throw error;
|
|
715
|
-
}
|
|
1627
|
+
await runProxy(orchestrator);
|
|
716
1628
|
}
|
|
717
1629
|
async function startProxyFromConfig(options = {}) {
|
|
718
|
-
let isShuttingDown = false;
|
|
719
1630
|
const config = loadConfig(options);
|
|
720
1631
|
const mcps = /* @__PURE__ */ new Map();
|
|
721
1632
|
for (const [name, entry] of Object.entries(config.mcp)) {
|
|
722
1633
|
mcps.set(name, { transport: createTransport(entry) });
|
|
723
1634
|
}
|
|
724
|
-
const orchestrator =
|
|
1635
|
+
const orchestrator = buildOrchestrator({
|
|
725
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,
|
|
726
1647
|
onTransportError: (mcpName2, error) => {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1648
|
+
process6.stderr.write(
|
|
1649
|
+
`${params.transportErrorPrefix(mcpName2)} transport error: ${error.message}
|
|
1650
|
+
`
|
|
1651
|
+
);
|
|
1652
|
+
activeShutdown.shutdown?.(1);
|
|
730
1653
|
}
|
|
731
1654
|
});
|
|
1655
|
+
}
|
|
1656
|
+
async function runProxy(orchestrator) {
|
|
1657
|
+
let isShuttingDown = false;
|
|
732
1658
|
const shutdown = (exitCode) => {
|
|
733
1659
|
if (isShuttingDown) return;
|
|
734
1660
|
isShuttingDown = true;
|
|
735
1661
|
orchestrator.disconnectAll().catch((error) => {
|
|
736
|
-
|
|
1662
|
+
process6.stderr.write(
|
|
737
1663
|
`dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
|
|
738
1664
|
`
|
|
739
1665
|
);
|
|
740
|
-
}).finally(() =>
|
|
1666
|
+
}).finally(() => process6.exit(exitCode));
|
|
741
1667
|
};
|
|
1668
|
+
activeShutdown.shutdown = shutdown;
|
|
742
1669
|
try {
|
|
743
1670
|
await orchestrator.connect();
|
|
744
1671
|
} catch (error) {
|
|
745
|
-
|
|
1672
|
+
process6.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
746
1673
|
`);
|
|
747
|
-
|
|
1674
|
+
process6.exit(1);
|
|
1675
|
+
return;
|
|
748
1676
|
}
|
|
749
1677
|
const proxyServer = new ProxyServer({
|
|
750
|
-
catalog: orchestrator.catalog,
|
|
751
|
-
|
|
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)
|
|
752
1702
|
});
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
process6.on("SIGINT", () => shutdown(0));
|
|
1709
|
+
process6.on("SIGTERM", () => shutdown(0));
|
|
1710
|
+
process6.stdin.on("end", () => shutdown(0));
|
|
1711
|
+
process6.stdin.on("close", () => shutdown(0));
|
|
757
1712
|
try {
|
|
758
1713
|
await proxyServer.start();
|
|
759
1714
|
} catch (error) {
|
|
@@ -773,43 +1728,40 @@ var cliBanner = chalk.bold.magentaBright(
|
|
|
773
1728
|
var cli = new Command(package_default.name).description(package_default.description).version(package_default.version).addHelpText("beforeAll", cliBanner).addHelpText(
|
|
774
1729
|
"after",
|
|
775
1730
|
"\nExamples:\n dynmcp -- npx -y chrome-devtools-mcp@latest\n dynmcp --config ./mcp.json\n"
|
|
776
|
-
).option("-c, --config <path>", "Path to config file (JSON or YAML)").option(
|
|
777
|
-
|
|
778
|
-
"Path to a .env file for environment variable interpolation"
|
|
779
|
-
).allowExcessArguments(true).passThroughOptions(true).action(async (_options, cmd) => {
|
|
780
|
-
const separatorIndex = process6.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 = process7.argv.indexOf("--");
|
|
781
1733
|
const configPath = cmd.opts().config;
|
|
782
1734
|
const envFilePath = cmd.opts().env;
|
|
783
1735
|
if (separatorIndex !== -1) {
|
|
784
|
-
const [command, ...args] =
|
|
1736
|
+
const [command, ...args] = process7.argv.slice(separatorIndex + 1);
|
|
785
1737
|
if (command === void 0) {
|
|
786
|
-
|
|
1738
|
+
process7.stderr.write(
|
|
787
1739
|
"dynmcp: no upstream command provided after --.\nUsage: dynmcp -- <command> [args...]\n"
|
|
788
1740
|
);
|
|
789
|
-
|
|
1741
|
+
process7.exit(1);
|
|
790
1742
|
}
|
|
791
1743
|
try {
|
|
792
1744
|
await startProxy(command, args);
|
|
793
1745
|
} catch (error) {
|
|
794
|
-
|
|
1746
|
+
process7.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
795
1747
|
`);
|
|
796
|
-
|
|
1748
|
+
process7.exit(1);
|
|
797
1749
|
}
|
|
798
1750
|
return;
|
|
799
1751
|
}
|
|
800
1752
|
try {
|
|
801
1753
|
await startProxyFromConfig({ configPath, envFilePath });
|
|
802
1754
|
} catch (error) {
|
|
803
|
-
|
|
1755
|
+
process7.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
804
1756
|
`);
|
|
805
|
-
|
|
1757
|
+
process7.exit(1);
|
|
806
1758
|
}
|
|
807
1759
|
});
|
|
808
1760
|
|
|
809
1761
|
// src/index.ts
|
|
810
|
-
import
|
|
1762
|
+
import process8 from "process";
|
|
811
1763
|
async function main() {
|
|
812
|
-
cli.parse(
|
|
1764
|
+
cli.parse(process8.argv);
|
|
813
1765
|
}
|
|
814
1766
|
main();
|
|
815
1767
|
//# sourceMappingURL=index.js.map
|