mcp-proxy 5.6.1 → 5.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/bin/mcp-proxy.js +7 -1
- package/dist/bin/mcp-proxy.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1 -1
- package/dist/{stdio-D0Lv8ytu.js → stdio-so1-I7Pn.js} +43 -16
- package/dist/stdio-so1-I7Pn.js.map +1 -0
- package/jsr.json +1 -1
- package/package.json +1 -1
- package/src/bin/mcp-proxy.ts +6 -0
- package/src/fixtures/slow-stdio-server.ts +91 -0
- package/src/proxyServer.test.ts +148 -0
- package/src/proxyServer.ts +43 -10
- package/src/startHTTPServer.test.ts +480 -0
- package/src/startHTTPServer.ts +42 -2
- package/dist/stdio-D0Lv8ytu.js.map +0 -1
|
@@ -701,3 +701,483 @@ it("does not require auth for OPTIONS requests", async () => {
|
|
|
701
701
|
|
|
702
702
|
await httpServer.close();
|
|
703
703
|
});
|
|
704
|
+
|
|
705
|
+
// Stateless OAuth 2.0 JWT Bearer Token Authentication Tests (PR #37)
|
|
706
|
+
|
|
707
|
+
it("accepts requests with valid Bearer token in stateless mode", async () => {
|
|
708
|
+
const stdioTransport = new StdioClientTransport({
|
|
709
|
+
args: ["src/fixtures/simple-stdio-server.ts"],
|
|
710
|
+
command: "tsx",
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
const stdioClient = new Client(
|
|
714
|
+
{
|
|
715
|
+
name: "mcp-proxy",
|
|
716
|
+
version: "1.0.0",
|
|
717
|
+
},
|
|
718
|
+
{
|
|
719
|
+
capabilities: {},
|
|
720
|
+
},
|
|
721
|
+
);
|
|
722
|
+
|
|
723
|
+
await stdioClient.connect(stdioTransport);
|
|
724
|
+
|
|
725
|
+
const serverVersion = stdioClient.getServerVersion() as {
|
|
726
|
+
name: string;
|
|
727
|
+
version: string;
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
731
|
+
capabilities: Record<string, unknown>;
|
|
732
|
+
};
|
|
733
|
+
|
|
734
|
+
const port = await getRandomPort();
|
|
735
|
+
|
|
736
|
+
// Mock authenticate callback that validates JWT Bearer token
|
|
737
|
+
const mockAuthResult = { email: "test@example.com", userId: "user123" };
|
|
738
|
+
const authenticate = vi.fn().mockResolvedValue(mockAuthResult);
|
|
739
|
+
|
|
740
|
+
const httpServer = await startHTTPServer({
|
|
741
|
+
authenticate,
|
|
742
|
+
createServer: async () => {
|
|
743
|
+
const mcpServer = new Server(serverVersion, {
|
|
744
|
+
capabilities: serverCapabilities,
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
await proxyServer({
|
|
748
|
+
client: stdioClient,
|
|
749
|
+
server: mcpServer,
|
|
750
|
+
serverCapabilities,
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
return mcpServer;
|
|
754
|
+
},
|
|
755
|
+
port,
|
|
756
|
+
stateless: true, // Enable stateless mode
|
|
757
|
+
});
|
|
758
|
+
|
|
759
|
+
// Create a stateless streamable HTTP client with Bearer token
|
|
760
|
+
const streamTransport = new StreamableHTTPClientTransport(
|
|
761
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
762
|
+
{
|
|
763
|
+
requestInit: {
|
|
764
|
+
headers: {
|
|
765
|
+
Authorization: "Bearer valid-jwt-token",
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
const streamClient = new Client(
|
|
772
|
+
{
|
|
773
|
+
name: "stream-client-oauth",
|
|
774
|
+
version: "1.0.0",
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
capabilities: {},
|
|
778
|
+
},
|
|
779
|
+
);
|
|
780
|
+
|
|
781
|
+
await streamClient.connect(streamTransport);
|
|
782
|
+
|
|
783
|
+
// Test that we can make requests with valid authentication
|
|
784
|
+
const result = await streamClient.listResources();
|
|
785
|
+
expect(result).toEqual({
|
|
786
|
+
resources: [
|
|
787
|
+
{
|
|
788
|
+
name: "Example Resource",
|
|
789
|
+
uri: "file:///example.txt",
|
|
790
|
+
},
|
|
791
|
+
],
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
// Verify authenticate callback was called
|
|
795
|
+
expect(authenticate).toHaveBeenCalled();
|
|
796
|
+
|
|
797
|
+
await streamClient.close();
|
|
798
|
+
await httpServer.close();
|
|
799
|
+
await stdioClient.close();
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
it("returns 401 when authenticate callback returns null in stateless mode", async () => {
|
|
803
|
+
const stdioTransport = new StdioClientTransport({
|
|
804
|
+
args: ["src/fixtures/simple-stdio-server.ts"],
|
|
805
|
+
command: "tsx",
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
const stdioClient = new Client(
|
|
809
|
+
{
|
|
810
|
+
name: "mcp-proxy",
|
|
811
|
+
version: "1.0.0",
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
capabilities: {},
|
|
815
|
+
},
|
|
816
|
+
);
|
|
817
|
+
|
|
818
|
+
await stdioClient.connect(stdioTransport);
|
|
819
|
+
|
|
820
|
+
const serverVersion = stdioClient.getServerVersion() as {
|
|
821
|
+
name: string;
|
|
822
|
+
version: string;
|
|
823
|
+
};
|
|
824
|
+
|
|
825
|
+
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
826
|
+
capabilities: Record<string, unknown>;
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
const port = await getRandomPort();
|
|
830
|
+
|
|
831
|
+
// Mock authenticate callback that rejects invalid token
|
|
832
|
+
const authenticate = vi.fn().mockResolvedValue(null);
|
|
833
|
+
|
|
834
|
+
const httpServer = await startHTTPServer({
|
|
835
|
+
authenticate,
|
|
836
|
+
createServer: async () => {
|
|
837
|
+
const mcpServer = new Server(serverVersion, {
|
|
838
|
+
capabilities: serverCapabilities,
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
await proxyServer({
|
|
842
|
+
client: stdioClient,
|
|
843
|
+
server: mcpServer,
|
|
844
|
+
serverCapabilities,
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
return mcpServer;
|
|
848
|
+
},
|
|
849
|
+
port,
|
|
850
|
+
stateless: true,
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// Create client with invalid Bearer token
|
|
854
|
+
const streamTransport = new StreamableHTTPClientTransport(
|
|
855
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
856
|
+
{
|
|
857
|
+
requestInit: {
|
|
858
|
+
headers: {
|
|
859
|
+
Authorization: "Bearer invalid-jwt-token",
|
|
860
|
+
},
|
|
861
|
+
},
|
|
862
|
+
},
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
const streamClient = new Client(
|
|
866
|
+
{
|
|
867
|
+
name: "stream-client-invalid-token",
|
|
868
|
+
version: "1.0.0",
|
|
869
|
+
},
|
|
870
|
+
{
|
|
871
|
+
capabilities: {},
|
|
872
|
+
},
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
// Connection should fail due to invalid authentication
|
|
876
|
+
await expect(streamClient.connect(streamTransport)).rejects.toThrow();
|
|
877
|
+
|
|
878
|
+
// Verify authenticate callback was called
|
|
879
|
+
expect(authenticate).toHaveBeenCalled();
|
|
880
|
+
|
|
881
|
+
await httpServer.close();
|
|
882
|
+
await stdioClient.close();
|
|
883
|
+
});
|
|
884
|
+
|
|
885
|
+
it("returns 401 when authenticate callback throws error in stateless mode", async () => {
|
|
886
|
+
const stdioTransport = new StdioClientTransport({
|
|
887
|
+
args: ["src/fixtures/simple-stdio-server.ts"],
|
|
888
|
+
command: "tsx",
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
const stdioClient = new Client(
|
|
892
|
+
{
|
|
893
|
+
name: "mcp-proxy",
|
|
894
|
+
version: "1.0.0",
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
capabilities: {},
|
|
898
|
+
},
|
|
899
|
+
);
|
|
900
|
+
|
|
901
|
+
await stdioClient.connect(stdioTransport);
|
|
902
|
+
|
|
903
|
+
const serverVersion = stdioClient.getServerVersion() as {
|
|
904
|
+
name: string;
|
|
905
|
+
version: string;
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
909
|
+
capabilities: Record<string, unknown>;
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
const port = await getRandomPort();
|
|
913
|
+
|
|
914
|
+
// Mock authenticate callback that throws (e.g., JWKS endpoint failure)
|
|
915
|
+
const authenticate = vi
|
|
916
|
+
.fn()
|
|
917
|
+
.mockRejectedValue(new Error("JWKS fetch failed"));
|
|
918
|
+
|
|
919
|
+
const httpServer = await startHTTPServer({
|
|
920
|
+
authenticate,
|
|
921
|
+
createServer: async () => {
|
|
922
|
+
const mcpServer = new Server(serverVersion, {
|
|
923
|
+
capabilities: serverCapabilities,
|
|
924
|
+
});
|
|
925
|
+
|
|
926
|
+
await proxyServer({
|
|
927
|
+
client: stdioClient,
|
|
928
|
+
server: mcpServer,
|
|
929
|
+
serverCapabilities,
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
return mcpServer;
|
|
933
|
+
},
|
|
934
|
+
port,
|
|
935
|
+
stateless: true,
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
// Create client with Bearer token
|
|
939
|
+
const streamTransport = new StreamableHTTPClientTransport(
|
|
940
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
941
|
+
{
|
|
942
|
+
requestInit: {
|
|
943
|
+
headers: {
|
|
944
|
+
Authorization: "Bearer some-token",
|
|
945
|
+
},
|
|
946
|
+
},
|
|
947
|
+
},
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
const streamClient = new Client(
|
|
951
|
+
{
|
|
952
|
+
name: "stream-client-auth-error",
|
|
953
|
+
version: "1.0.0",
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
capabilities: {},
|
|
957
|
+
},
|
|
958
|
+
);
|
|
959
|
+
|
|
960
|
+
// Connection should fail due to authentication error
|
|
961
|
+
await expect(streamClient.connect(streamTransport)).rejects.toThrow();
|
|
962
|
+
|
|
963
|
+
// Verify authenticate callback was called
|
|
964
|
+
expect(authenticate).toHaveBeenCalled();
|
|
965
|
+
|
|
966
|
+
await httpServer.close();
|
|
967
|
+
await stdioClient.close();
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
it("does not call authenticate on subsequent requests in stateful mode", async () => {
|
|
971
|
+
const stdioTransport = new StdioClientTransport({
|
|
972
|
+
args: ["src/fixtures/simple-stdio-server.ts"],
|
|
973
|
+
command: "tsx",
|
|
974
|
+
});
|
|
975
|
+
|
|
976
|
+
const stdioClient = new Client(
|
|
977
|
+
{
|
|
978
|
+
name: "mcp-proxy",
|
|
979
|
+
version: "1.0.0",
|
|
980
|
+
},
|
|
981
|
+
{
|
|
982
|
+
capabilities: {},
|
|
983
|
+
},
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
await stdioClient.connect(stdioTransport);
|
|
987
|
+
|
|
988
|
+
const serverVersion = stdioClient.getServerVersion() as {
|
|
989
|
+
name: string;
|
|
990
|
+
version: string;
|
|
991
|
+
};
|
|
992
|
+
|
|
993
|
+
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
994
|
+
capabilities: Record<string, unknown>;
|
|
995
|
+
};
|
|
996
|
+
|
|
997
|
+
const port = await getRandomPort();
|
|
998
|
+
|
|
999
|
+
// Mock authenticate callback
|
|
1000
|
+
const authenticate = vi.fn().mockResolvedValue({ userId: "user123" });
|
|
1001
|
+
|
|
1002
|
+
const onConnect = vi.fn().mockResolvedValue(undefined);
|
|
1003
|
+
const onClose = vi.fn().mockResolvedValue(undefined);
|
|
1004
|
+
|
|
1005
|
+
const httpServer = await startHTTPServer({
|
|
1006
|
+
authenticate,
|
|
1007
|
+
createServer: async () => {
|
|
1008
|
+
const mcpServer = new Server(serverVersion, {
|
|
1009
|
+
capabilities: serverCapabilities,
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
await proxyServer({
|
|
1013
|
+
client: stdioClient,
|
|
1014
|
+
server: mcpServer,
|
|
1015
|
+
serverCapabilities,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
return mcpServer;
|
|
1019
|
+
},
|
|
1020
|
+
onClose,
|
|
1021
|
+
onConnect,
|
|
1022
|
+
port,
|
|
1023
|
+
stateless: false, // Explicitly use stateful mode
|
|
1024
|
+
});
|
|
1025
|
+
|
|
1026
|
+
// Create client
|
|
1027
|
+
const streamTransport = new StreamableHTTPClientTransport(
|
|
1028
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
1029
|
+
);
|
|
1030
|
+
|
|
1031
|
+
const streamClient = new Client(
|
|
1032
|
+
{
|
|
1033
|
+
name: "stream-client-stateful",
|
|
1034
|
+
version: "1.0.0",
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
capabilities: {},
|
|
1038
|
+
},
|
|
1039
|
+
);
|
|
1040
|
+
|
|
1041
|
+
await streamClient.connect(streamTransport);
|
|
1042
|
+
|
|
1043
|
+
// Make first request
|
|
1044
|
+
await streamClient.listResources();
|
|
1045
|
+
|
|
1046
|
+
// Make second request
|
|
1047
|
+
await streamClient.listResources();
|
|
1048
|
+
|
|
1049
|
+
// In stateful mode, authenticate should NOT be called per-request
|
|
1050
|
+
// It may be called during initialization, but not on every tool call
|
|
1051
|
+
// The key is that it's not called multiple times for each request
|
|
1052
|
+
expect(authenticate).not.toHaveBeenCalled();
|
|
1053
|
+
|
|
1054
|
+
await streamClient.close();
|
|
1055
|
+
await httpServer.close();
|
|
1056
|
+
await stdioClient.close();
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
it("calls authenticate on every request in stateless mode", async () => {
|
|
1060
|
+
const stdioTransport = new StdioClientTransport({
|
|
1061
|
+
args: ["src/fixtures/simple-stdio-server.ts"],
|
|
1062
|
+
command: "tsx",
|
|
1063
|
+
});
|
|
1064
|
+
|
|
1065
|
+
const stdioClient = new Client(
|
|
1066
|
+
{
|
|
1067
|
+
name: "mcp-proxy",
|
|
1068
|
+
version: "1.0.0",
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
capabilities: {},
|
|
1072
|
+
},
|
|
1073
|
+
);
|
|
1074
|
+
|
|
1075
|
+
await stdioClient.connect(stdioTransport);
|
|
1076
|
+
|
|
1077
|
+
const serverVersion = stdioClient.getServerVersion() as {
|
|
1078
|
+
name: string;
|
|
1079
|
+
version: string;
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
const serverCapabilities = stdioClient.getServerCapabilities() as {
|
|
1083
|
+
capabilities: Record<string, unknown>;
|
|
1084
|
+
};
|
|
1085
|
+
|
|
1086
|
+
const port = await getRandomPort();
|
|
1087
|
+
|
|
1088
|
+
// Mock authenticate callback
|
|
1089
|
+
const authenticate = vi.fn().mockResolvedValue({ userId: "user123" });
|
|
1090
|
+
|
|
1091
|
+
const httpServer = await startHTTPServer({
|
|
1092
|
+
authenticate,
|
|
1093
|
+
createServer: async () => {
|
|
1094
|
+
const mcpServer = new Server(serverVersion, {
|
|
1095
|
+
capabilities: serverCapabilities,
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
await proxyServer({
|
|
1099
|
+
client: stdioClient,
|
|
1100
|
+
server: mcpServer,
|
|
1101
|
+
serverCapabilities,
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
return mcpServer;
|
|
1105
|
+
},
|
|
1106
|
+
port,
|
|
1107
|
+
stateless: true, // Enable stateless mode
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
// Create client with Bearer token
|
|
1111
|
+
const streamTransport = new StreamableHTTPClientTransport(
|
|
1112
|
+
new URL(`http://localhost:${port}/mcp`),
|
|
1113
|
+
{
|
|
1114
|
+
requestInit: {
|
|
1115
|
+
headers: {
|
|
1116
|
+
Authorization: "Bearer test-token",
|
|
1117
|
+
},
|
|
1118
|
+
},
|
|
1119
|
+
},
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
const streamClient = new Client(
|
|
1123
|
+
{
|
|
1124
|
+
name: "stream-client-per-request",
|
|
1125
|
+
version: "1.0.0",
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
capabilities: {},
|
|
1129
|
+
},
|
|
1130
|
+
);
|
|
1131
|
+
|
|
1132
|
+
await streamClient.connect(streamTransport);
|
|
1133
|
+
|
|
1134
|
+
const initialCallCount = authenticate.mock.calls.length;
|
|
1135
|
+
|
|
1136
|
+
// Make first request
|
|
1137
|
+
await streamClient.listResources();
|
|
1138
|
+
const firstRequestCallCount = authenticate.mock.calls.length;
|
|
1139
|
+
|
|
1140
|
+
// Make second request
|
|
1141
|
+
await streamClient.listResources();
|
|
1142
|
+
const secondRequestCallCount = authenticate.mock.calls.length;
|
|
1143
|
+
|
|
1144
|
+
// In stateless mode, authenticate should be called on EVERY request
|
|
1145
|
+
expect(firstRequestCallCount).toBeGreaterThan(initialCallCount);
|
|
1146
|
+
expect(secondRequestCallCount).toBeGreaterThan(firstRequestCallCount);
|
|
1147
|
+
|
|
1148
|
+
await streamClient.close();
|
|
1149
|
+
await httpServer.close();
|
|
1150
|
+
await stdioClient.close();
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
it("includes Authorization in CORS allowed headers", async () => {
|
|
1154
|
+
const port = await getRandomPort();
|
|
1155
|
+
|
|
1156
|
+
const httpServer = await startHTTPServer({
|
|
1157
|
+
createServer: async () => {
|
|
1158
|
+
const mcpServer = new Server(
|
|
1159
|
+
{ name: "test", version: "1.0.0" },
|
|
1160
|
+
{ capabilities: {} },
|
|
1161
|
+
);
|
|
1162
|
+
return mcpServer;
|
|
1163
|
+
},
|
|
1164
|
+
port,
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
// Test OPTIONS request to verify CORS headers
|
|
1168
|
+
const response = await fetch(`http://localhost:${port}/mcp`, {
|
|
1169
|
+
headers: {
|
|
1170
|
+
Origin: "https://example.com",
|
|
1171
|
+
},
|
|
1172
|
+
method: "OPTIONS",
|
|
1173
|
+
});
|
|
1174
|
+
|
|
1175
|
+
expect(response.status).toBe(204);
|
|
1176
|
+
|
|
1177
|
+
// Verify Authorization is in the allowed headers
|
|
1178
|
+
const allowedHeaders = response.headers.get("Access-Control-Allow-Headers");
|
|
1179
|
+
expect(allowedHeaders).toBeTruthy();
|
|
1180
|
+
expect(allowedHeaders).toContain("Authorization");
|
|
1181
|
+
|
|
1182
|
+
await httpServer.close();
|
|
1183
|
+
});
|
package/src/startHTTPServer.ts
CHANGED
|
@@ -93,6 +93,7 @@ const cleanupServer = async <T extends ServerLike>(
|
|
|
93
93
|
|
|
94
94
|
const handleStreamRequest = async <T extends ServerLike>({
|
|
95
95
|
activeTransports,
|
|
96
|
+
authenticate,
|
|
96
97
|
createServer,
|
|
97
98
|
enableJsonResponse,
|
|
98
99
|
endpoint,
|
|
@@ -107,6 +108,7 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
107
108
|
string,
|
|
108
109
|
{ server: T; transport: StreamableHTTPServerTransport }
|
|
109
110
|
>;
|
|
111
|
+
authenticate?: (request: http.IncomingMessage) => Promise<unknown>;
|
|
110
112
|
createServer: (request: http.IncomingMessage) => Promise<T>;
|
|
111
113
|
enableJsonResponse?: boolean;
|
|
112
114
|
endpoint: string;
|
|
@@ -132,6 +134,41 @@ const handleStreamRequest = async <T extends ServerLike>({
|
|
|
132
134
|
|
|
133
135
|
const body = await getBody(req);
|
|
134
136
|
|
|
137
|
+
// Per-request authentication in stateless mode
|
|
138
|
+
if (stateless && authenticate) {
|
|
139
|
+
try {
|
|
140
|
+
const authResult = await authenticate(req);
|
|
141
|
+
if (!authResult) {
|
|
142
|
+
res.setHeader("Content-Type", "application/json");
|
|
143
|
+
res.writeHead(401).end(
|
|
144
|
+
JSON.stringify({
|
|
145
|
+
error: {
|
|
146
|
+
code: -32000,
|
|
147
|
+
message: "Unauthorized: Authentication failed"
|
|
148
|
+
},
|
|
149
|
+
id: (body as { id?: unknown })?.id ?? null,
|
|
150
|
+
jsonrpc: "2.0"
|
|
151
|
+
})
|
|
152
|
+
);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
} catch (error) {
|
|
156
|
+
console.error("Authentication error:", error);
|
|
157
|
+
res.setHeader("Content-Type", "application/json");
|
|
158
|
+
res.writeHead(401).end(
|
|
159
|
+
JSON.stringify({
|
|
160
|
+
error: {
|
|
161
|
+
code: -32000,
|
|
162
|
+
message: "Unauthorized: Authentication error"
|
|
163
|
+
},
|
|
164
|
+
id: (body as { id?: unknown })?.id ?? null,
|
|
165
|
+
jsonrpc: "2.0"
|
|
166
|
+
})
|
|
167
|
+
);
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
135
172
|
if (sessionId) {
|
|
136
173
|
const activeTransport = activeTransports[sessionId];
|
|
137
174
|
if (!activeTransport) {
|
|
@@ -457,6 +494,7 @@ const handleSSERequest = async <T extends ServerLike>({
|
|
|
457
494
|
|
|
458
495
|
export const startHTTPServer = async <T extends ServerLike>({
|
|
459
496
|
apiKey,
|
|
497
|
+
authenticate,
|
|
460
498
|
createServer,
|
|
461
499
|
enableJsonResponse,
|
|
462
500
|
eventStore,
|
|
@@ -470,6 +508,7 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
470
508
|
streamEndpoint = "/mcp",
|
|
471
509
|
}: {
|
|
472
510
|
apiKey?: string;
|
|
511
|
+
authenticate?: (request: http.IncomingMessage) => Promise<unknown>;
|
|
473
512
|
createServer: (request: http.IncomingMessage) => Promise<T>;
|
|
474
513
|
enableJsonResponse?: boolean;
|
|
475
514
|
eventStore?: EventStore;
|
|
@@ -508,8 +547,8 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
508
547
|
res.setHeader("Access-Control-Allow-Origin", origin.origin);
|
|
509
548
|
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
510
549
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
511
|
-
res.setHeader("Access-Control-Allow-Headers", "
|
|
512
|
-
res.setHeader("Access-Control-Expose-Headers", "
|
|
550
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id");
|
|
551
|
+
res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
|
|
513
552
|
} catch (error) {
|
|
514
553
|
console.error("[mcp-proxy] error parsing origin", error);
|
|
515
554
|
}
|
|
@@ -553,6 +592,7 @@ export const startHTTPServer = async <T extends ServerLike>({
|
|
|
553
592
|
streamEndpoint &&
|
|
554
593
|
(await handleStreamRequest({
|
|
555
594
|
activeTransports: activeStreamTransports,
|
|
595
|
+
authenticate,
|
|
556
596
|
createServer,
|
|
557
597
|
enableJsonResponse,
|
|
558
598
|
endpoint: streamEndpoint,
|