mcp-proxy 5.9.0 → 5.11.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.
@@ -1585,6 +1585,150 @@ it("returns 500 when createServer throws non-auth error", async () => {
1585
1585
  await httpServer.close();
1586
1586
  });
1587
1587
 
1588
+ it("includes WWW-Authenticate header in 401 response with OAuth config", async () => {
1589
+ const port = await getRandomPort();
1590
+
1591
+ const httpServer = await startHTTPServer({
1592
+ createServer: async () => {
1593
+ throw new Error("Invalid JWT token");
1594
+ },
1595
+ oauth: {
1596
+ protectedResource: {
1597
+ resource: "https://example.com",
1598
+ },
1599
+ realm: "mcp-server",
1600
+ },
1601
+ port,
1602
+ stateless: true,
1603
+ });
1604
+
1605
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1606
+ body: JSON.stringify({
1607
+ id: 1,
1608
+ jsonrpc: "2.0",
1609
+ method: "initialize",
1610
+ params: {
1611
+ capabilities: {},
1612
+ clientInfo: { name: "test", version: "1.0.0" },
1613
+ protocolVersion: "2024-11-05",
1614
+ },
1615
+ }),
1616
+ headers: {
1617
+ "Accept": "application/json, text/event-stream",
1618
+ "Content-Type": "application/json",
1619
+ },
1620
+ method: "POST",
1621
+ });
1622
+
1623
+ expect(response.status).toBe(401);
1624
+
1625
+ const wwwAuthHeader = response.headers.get("WWW-Authenticate");
1626
+ expect(wwwAuthHeader).toBeTruthy();
1627
+ expect(wwwAuthHeader).toContain('Bearer');
1628
+ expect(wwwAuthHeader).toContain('realm="mcp-server"');
1629
+ expect(wwwAuthHeader).toContain('resource_metadata="https://example.com/.well-known/oauth-protected-resource"');
1630
+ expect(wwwAuthHeader).toContain('error="invalid_token"');
1631
+ expect(wwwAuthHeader).toContain('error_description="Invalid JWT token"');
1632
+
1633
+ await httpServer.close();
1634
+ });
1635
+
1636
+ it("includes WWW-Authenticate header when authenticate callback fails with OAuth", async () => {
1637
+ const port = await getRandomPort();
1638
+
1639
+ const authenticate = vi.fn().mockRejectedValue(new Error("Token signature verification failed"));
1640
+
1641
+ const httpServer = await startHTTPServer({
1642
+ authenticate,
1643
+ createServer: async () => {
1644
+ const mcpServer = new Server(
1645
+ { name: "test", version: "1.0.0" },
1646
+ { capabilities: {} },
1647
+ );
1648
+ return mcpServer;
1649
+ },
1650
+ oauth: {
1651
+ error_uri: "https://example.com/docs/errors",
1652
+ protectedResource: {
1653
+ resource: "https://api.example.com",
1654
+ },
1655
+ realm: "example-api",
1656
+ },
1657
+ port,
1658
+ stateless: true,
1659
+ });
1660
+
1661
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1662
+ body: JSON.stringify({
1663
+ id: 1,
1664
+ jsonrpc: "2.0",
1665
+ method: "initialize",
1666
+ params: {
1667
+ capabilities: {},
1668
+ clientInfo: { name: "test", version: "1.0.0" },
1669
+ protocolVersion: "2024-11-05",
1670
+ },
1671
+ }),
1672
+ headers: {
1673
+ "Accept": "application/json, text/event-stream",
1674
+ "Authorization": "Bearer expired-token",
1675
+ "Content-Type": "application/json",
1676
+ },
1677
+ method: "POST",
1678
+ });
1679
+
1680
+ expect(response.status).toBe(401);
1681
+ expect(authenticate).toHaveBeenCalled();
1682
+
1683
+ const wwwAuthHeader = response.headers.get("WWW-Authenticate");
1684
+ expect(wwwAuthHeader).toBeTruthy();
1685
+ expect(wwwAuthHeader).toContain('Bearer');
1686
+ expect(wwwAuthHeader).toContain('realm="example-api"');
1687
+ expect(wwwAuthHeader).toContain('resource_metadata="https://api.example.com/.well-known/oauth-protected-resource"');
1688
+ expect(wwwAuthHeader).toContain('error="invalid_token"');
1689
+ expect(wwwAuthHeader).toContain('error_description="Token signature verification failed"');
1690
+ expect(wwwAuthHeader).toContain('error_uri="https://example.com/docs/errors"');
1691
+
1692
+ await httpServer.close();
1693
+ });
1694
+
1695
+ it("does not include WWW-Authenticate header in 401 response without OAuth config", async () => {
1696
+ const port = await getRandomPort();
1697
+
1698
+ const httpServer = await startHTTPServer({
1699
+ createServer: async () => {
1700
+ throw new Error("Authentication required");
1701
+ },
1702
+ port,
1703
+ stateless: true,
1704
+ });
1705
+
1706
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1707
+ body: JSON.stringify({
1708
+ id: 1,
1709
+ jsonrpc: "2.0",
1710
+ method: "initialize",
1711
+ params: {
1712
+ capabilities: {},
1713
+ clientInfo: { name: "test", version: "1.0.0" },
1714
+ protocolVersion: "2024-11-05",
1715
+ },
1716
+ }),
1717
+ headers: {
1718
+ "Accept": "application/json, text/event-stream",
1719
+ "Content-Type": "application/json",
1720
+ },
1721
+ method: "POST",
1722
+ });
1723
+
1724
+ expect(response.status).toBe(401);
1725
+
1726
+ const wwwAuthHeader = response.headers.get("WWW-Authenticate");
1727
+ expect(wwwAuthHeader).toBeNull();
1728
+
1729
+ await httpServer.close();
1730
+ });
1731
+
1588
1732
  it("succeeds when authenticate returns { authenticated: true } in stateless mode", async () => {
1589
1733
  const stdioTransport = new StdioClientTransport({
1590
1734
  args: ["src/fixtures/simple-stdio-server.ts"],
@@ -1675,3 +1819,251 @@ it("succeeds when authenticate returns { authenticated: true } in stateless mode
1675
1819
  await httpServer.close();
1676
1820
  await stdioClient.close();
1677
1821
  });
1822
+
1823
+ // CORS Configuration Tests
1824
+
1825
+ it("supports wildcard CORS headers", async () => {
1826
+ const port = await getRandomPort();
1827
+
1828
+ const httpServer = await startHTTPServer({
1829
+ cors: {
1830
+ allowedHeaders: "*",
1831
+ },
1832
+ createServer: async () => {
1833
+ const mcpServer = new Server(
1834
+ { name: "test", version: "1.0.0" },
1835
+ { capabilities: {} },
1836
+ );
1837
+ return mcpServer;
1838
+ },
1839
+ port,
1840
+ });
1841
+
1842
+ // Test OPTIONS request to verify CORS headers
1843
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1844
+ headers: {
1845
+ Origin: "https://example.com",
1846
+ },
1847
+ method: "OPTIONS",
1848
+ });
1849
+
1850
+ expect(response.status).toBe(204);
1851
+
1852
+ // Verify wildcard is used for allowed headers
1853
+ const allowedHeaders = response.headers.get("Access-Control-Allow-Headers");
1854
+ expect(allowedHeaders).toBe("*");
1855
+
1856
+ await httpServer.close();
1857
+ });
1858
+
1859
+ it("supports custom CORS headers array", async () => {
1860
+ const port = await getRandomPort();
1861
+
1862
+ const httpServer = await startHTTPServer({
1863
+ cors: {
1864
+ allowedHeaders: ["Content-Type", "X-Custom-Header", "X-API-Key"],
1865
+ },
1866
+ createServer: async () => {
1867
+ const mcpServer = new Server(
1868
+ { name: "test", version: "1.0.0" },
1869
+ { capabilities: {} },
1870
+ );
1871
+ return mcpServer;
1872
+ },
1873
+ port,
1874
+ });
1875
+
1876
+ // Test OPTIONS request to verify CORS headers
1877
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1878
+ headers: {
1879
+ Origin: "https://example.com",
1880
+ },
1881
+ method: "OPTIONS",
1882
+ });
1883
+
1884
+ expect(response.status).toBe(204);
1885
+
1886
+ // Verify custom headers are used
1887
+ const allowedHeaders = response.headers.get("Access-Control-Allow-Headers");
1888
+ expect(allowedHeaders).toBe("Content-Type, X-Custom-Header, X-API-Key");
1889
+
1890
+ await httpServer.close();
1891
+ });
1892
+
1893
+ it("supports origin validation with array", async () => {
1894
+ const port = await getRandomPort();
1895
+
1896
+ const httpServer = await startHTTPServer({
1897
+ cors: {
1898
+ origin: ["https://app.example.com", "https://admin.example.com"],
1899
+ },
1900
+ createServer: async () => {
1901
+ const mcpServer = new Server(
1902
+ { name: "test", version: "1.0.0" },
1903
+ { capabilities: {} },
1904
+ );
1905
+ return mcpServer;
1906
+ },
1907
+ port,
1908
+ });
1909
+
1910
+ // Test with allowed origin
1911
+ const response1 = await fetch(`http://localhost:${port}/mcp`, {
1912
+ headers: {
1913
+ Origin: "https://app.example.com",
1914
+ },
1915
+ method: "OPTIONS",
1916
+ });
1917
+
1918
+ expect(response1.status).toBe(204);
1919
+ expect(response1.headers.get("Access-Control-Allow-Origin")).toBe("https://app.example.com");
1920
+
1921
+ // Test with disallowed origin
1922
+ const response2 = await fetch(`http://localhost:${port}/mcp`, {
1923
+ headers: {
1924
+ Origin: "https://malicious.com",
1925
+ },
1926
+ method: "OPTIONS",
1927
+ });
1928
+
1929
+ expect(response2.status).toBe(204);
1930
+ expect(response2.headers.get("Access-Control-Allow-Origin")).toBeNull();
1931
+
1932
+ await httpServer.close();
1933
+ });
1934
+
1935
+ it("supports origin validation with function", async () => {
1936
+ const port = await getRandomPort();
1937
+
1938
+ const httpServer = await startHTTPServer({
1939
+ cors: {
1940
+ origin: (origin: string) => origin.endsWith(".example.com"),
1941
+ },
1942
+ createServer: async () => {
1943
+ const mcpServer = new Server(
1944
+ { name: "test", version: "1.0.0" },
1945
+ { capabilities: {} },
1946
+ );
1947
+ return mcpServer;
1948
+ },
1949
+ port,
1950
+ });
1951
+
1952
+ // Test with allowed origin
1953
+ const response1 = await fetch(`http://localhost:${port}/mcp`, {
1954
+ headers: {
1955
+ Origin: "https://subdomain.example.com",
1956
+ },
1957
+ method: "OPTIONS",
1958
+ });
1959
+
1960
+ expect(response1.status).toBe(204);
1961
+ expect(response1.headers.get("Access-Control-Allow-Origin")).toBe("https://subdomain.example.com");
1962
+
1963
+ // Test with disallowed origin
1964
+ const response2 = await fetch(`http://localhost:${port}/mcp`, {
1965
+ headers: {
1966
+ Origin: "https://malicious.com",
1967
+ },
1968
+ method: "OPTIONS",
1969
+ });
1970
+
1971
+ expect(response2.status).toBe(204);
1972
+ expect(response2.headers.get("Access-Control-Allow-Origin")).toBeNull();
1973
+
1974
+ await httpServer.close();
1975
+ });
1976
+
1977
+ it("disables CORS when cors: false", async () => {
1978
+ const port = await getRandomPort();
1979
+
1980
+ const httpServer = await startHTTPServer({
1981
+ cors: false,
1982
+ createServer: async () => {
1983
+ const mcpServer = new Server(
1984
+ { name: "test", version: "1.0.0" },
1985
+ { capabilities: {} },
1986
+ );
1987
+ return mcpServer;
1988
+ },
1989
+ port,
1990
+ });
1991
+
1992
+ // Test OPTIONS request - should not have CORS headers
1993
+ const response = await fetch(`http://localhost:${port}/mcp`, {
1994
+ headers: {
1995
+ Origin: "https://example.com",
1996
+ },
1997
+ method: "OPTIONS",
1998
+ });
1999
+
2000
+ expect(response.status).toBe(204);
2001
+ expect(response.headers.get("Access-Control-Allow-Origin")).toBeNull();
2002
+ expect(response.headers.get("Access-Control-Allow-Headers")).toBeNull();
2003
+
2004
+ await httpServer.close();
2005
+ });
2006
+
2007
+ it("uses default CORS settings when cors: true", async () => {
2008
+ const port = await getRandomPort();
2009
+
2010
+ const httpServer = await startHTTPServer({
2011
+ cors: true,
2012
+ createServer: async () => {
2013
+ const mcpServer = new Server(
2014
+ { name: "test", version: "1.0.0" },
2015
+ { capabilities: {} },
2016
+ );
2017
+ return mcpServer;
2018
+ },
2019
+ port,
2020
+ });
2021
+
2022
+ // Test OPTIONS request to verify default CORS headers
2023
+ const response = await fetch(`http://localhost:${port}/mcp`, {
2024
+ headers: {
2025
+ Origin: "https://example.com",
2026
+ },
2027
+ method: "OPTIONS",
2028
+ });
2029
+
2030
+ expect(response.status).toBe(204);
2031
+ expect(response.headers.get("Access-Control-Allow-Origin")).toBe("*");
2032
+ expect(response.headers.get("Access-Control-Allow-Headers")).toBe("Content-Type, Authorization, Accept, Mcp-Session-Id, Last-Event-Id");
2033
+ expect(response.headers.get("Access-Control-Allow-Credentials")).toBe("true");
2034
+
2035
+ await httpServer.close();
2036
+ });
2037
+
2038
+ it("supports custom methods and maxAge", async () => {
2039
+ const port = await getRandomPort();
2040
+
2041
+ const httpServer = await startHTTPServer({
2042
+ cors: {
2043
+ maxAge: 86400,
2044
+ methods: ["GET", "POST", "PUT", "DELETE"],
2045
+ },
2046
+ createServer: async () => {
2047
+ const mcpServer = new Server(
2048
+ { name: "test", version: "1.0.0" },
2049
+ { capabilities: {} },
2050
+ );
2051
+ return mcpServer;
2052
+ },
2053
+ port,
2054
+ });
2055
+
2056
+ // Test OPTIONS request to verify custom settings
2057
+ const response = await fetch(`http://localhost:${port}/mcp`, {
2058
+ headers: {
2059
+ Origin: "https://example.com",
2060
+ },
2061
+ method: "OPTIONS",
2062
+ });
2063
+
2064
+ expect(response.status).toBe(204);
2065
+ expect(response.headers.get("Access-Control-Allow-Methods")).toBe("GET, POST, PUT, DELETE");
2066
+ expect(response.headers.get("Access-Control-Max-Age")).toBe("86400");
2067
+
2068
+ await httpServer.close();
2069
+ });