Skip to content

Commit a91b598

Browse files
committed
Allow localhost MCP client URLs
1 parent a1eab1d commit a91b598

File tree

4 files changed

+32
-17
lines changed

4 files changed

+32
-17
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"agents": patch
3+
---
4+
5+
Allow MCP clients to connect to localhost and loopback URLs again for local development while continuing to block private, link-local, and metadata endpoints.

docs/mcp-client.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,14 @@ These options are persisted and used when reconnecting after hibernation or afte
116116
MCP server URLs are validated before connection to prevent Server-Side Request Forgery (SSRF). The following URL targets are blocked:
117117
118118
- Private/internal IP ranges (RFC 1918: `10.x`, `172.16-31.x`, `192.168.x`)
119-
- Loopback addresses (`127.x`, `::1`)
119+
- Unspecified addresses (`0.0.0.0`, `::`)
120120
- Link-local addresses (`169.254.x`, `fe80::`)
121121
- Cloud metadata endpoints (`169.254.169.254`)
122+
- IPv6 unique-local addresses (`fc00::/7`)
122123
123-
If you need to connect to an internal MCP server, use the [RPC transport](./mcp-transports.md) with a Durable Object binding instead of HTTP.
124+
Loopback development URLs such as `localhost`, `127.0.0.1`, and `::1` are allowed.
125+
126+
If you need to connect to another internal MCP server, use the [RPC transport](./mcp-transports.md) with a Durable Object binding instead of HTTP.
124127
125128
### Return Value
126129

packages/agents/src/mcp/client.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,19 @@ const defaultClientOptions: ConstructorParameters<typeof Client>[1] = {
3636

3737
/**
3838
* Blocked hostname patterns for SSRF protection.
39-
* Prevents MCP client from connecting to internal/private network addresses.
39+
* Prevents MCP client from connecting to internal/private network addresses
40+
* while allowing loopback hosts for local development.
4041
*/
4142
const BLOCKED_HOSTNAMES = new Set([
42-
"localhost",
4343
"0.0.0.0",
44-
"[::1]",
4544
"[::]",
4645
"metadata.google.internal"
4746
]);
4847

4948
/**
5049
* Check whether a hostname looks like a private/internal IP address.
51-
* Blocks RFC 1918, link-local, loopback, and cloud metadata endpoints.
50+
* Blocks RFC 1918, link-local, unique-local, unspecified,
51+
* and cloud metadata endpoints.
5252
*/
5353
function isBlockedUrl(url: string): boolean {
5454
let parsed: URL;
@@ -72,8 +72,6 @@ function isBlockedUrl(url: string): boolean {
7272
if (a === 172 && b >= 16 && b <= 31) return true;
7373
// 192.168.0.0/16
7474
if (a === 192 && b === 168) return true;
75-
// 127.0.0.0/8 (loopback)
76-
if (a === 127) return true;
7775
// 169.254.0.0/16 (link-local / cloud metadata)
7876
if (a === 169 && b === 254) return true;
7977
// 0.0.0.0/8

packages/agents/src/tests/mcp/client-manager.test.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2930,22 +2930,31 @@ describe("MCPClientManager OAuth Integration", () => {
29302930
});
29312931

29322932
describe("SSRF URL validation", () => {
2933-
it("should reject localhost URLs", async () => {
2933+
it("should allow localhost URLs with ports for local development", async () => {
29342934
await expect(
29352935
manager.registerServer("s1", {
29362936
url: "http://localhost:8080/mcp",
2937-
name: "bad"
2937+
name: "local"
29382938
})
2939-
).rejects.toThrow("Blocked URL");
2939+
).resolves.toBe("s1");
29402940
});
29412941

2942-
it("should reject 127.x.x.x loopback URLs", async () => {
2942+
it("should allow 127.0.0.1 loopback URLs", async () => {
29432943
await expect(
29442944
manager.registerServer("s2", {
29452945
url: "http://127.0.0.1/mcp",
2946-
name: "bad"
2946+
name: "local-loopback"
29472947
})
2948-
).rejects.toThrow("Blocked URL");
2948+
).resolves.toBe("s2");
2949+
});
2950+
2951+
it("should allow other 127.x.x.x loopback URLs with custom ports", async () => {
2952+
await expect(
2953+
manager.registerServer("s2b", {
2954+
url: "http://127.12.34.56:9999/mcp",
2955+
name: "local-loopback-port"
2956+
})
2957+
).resolves.toBe("s2b");
29492958
});
29502959

29512960
it("should reject RFC 1918 10.x.x.x URLs", async () => {
@@ -3002,13 +3011,13 @@ describe("MCPClientManager OAuth Integration", () => {
30023011
).rejects.toThrow("Blocked URL");
30033012
});
30043013

3005-
it("should reject IPv6 loopback", async () => {
3014+
it("should allow IPv6 loopback", async () => {
30063015
await expect(
30073016
manager.registerServer("s9", {
30083017
url: "http://[::1]:8080/mcp",
3009-
name: "bad"
3018+
name: "local-ipv6"
30103019
})
3011-
).rejects.toThrow("Blocked URL");
3020+
).resolves.toBe("s9");
30123021
});
30133022

30143023
it("should reject IPv6 unique local (fc00::/7) URLs", async () => {

0 commit comments

Comments
 (0)