You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
add the NO_PROXY env variable feature to avoid proxy calling MCP servers listening on localhost #3340
I am using gemini-cli behind my company proxy that works only for internet websites and doesn't work for the local network or localhost. I put the proxy url in the http_proxy and https_proxy enviroment variables in my .bat, It is ok only for the tools that go to the internet, unfortunately for the MCP servers that are listening on localhost or 127.0.0.1 those MCP servers don't work because there is no way to exclude localhost and 127.0.0.1 from the proxy. I use many nodejs apps and they all use the env variable NO_PROXY to exclude some address, that doesnt work for gemini-cli. So how can I use my MCP servers if I am behind a company proxy?
What did you expect to happen?
setting NO_PROXY=localhost I expected gemini-cli working with MCP server or services on the localhost even if I am behind a proxy to navigate internet.
Client information
Details
$ gemini /about About Gemini CLI ││ ││ CLI Version 0.1.9 ││ Git Commit 34935d6 ││ Model gemini-2.5-pro ││ Sandbox no sandbox ││ OS win32 ││ Auth Method OAuth
Login information
No response
Anything else we need to know?
No response
Reacted by Laurent Magnien, arekp, Patsakula Nikita, Seth Yuan and Huang, Yu-Ting
in the meanwhile the matter gets the proper solution, I've resolved patching the file node_modules@google\gemini-cli\node_modules\undici\lib\dispatcher\proxy-agent.js
putting in it the conditions no to use a proxy if url belongs to localhost or a private network ip address, so now my MCP servers listening on the localhost work even if the internet is accessed only through the company proxy.
in the meanwhile the matter gets the proper solution, I've resolved patching the file node_modules@google\gemini-cli\node_modules\undici\lib\dispatcher\proxy-agent.js putting in it the conditions no to use a proxy if url belongs to localhost or a private network ip address, so now my MCP servers listening on the localhost work even if the internet is accessed only through the company proxy.
hu-bo, you asked for the code, I am copyng it here BUT what I did it is absolutely not a good practice, I made that patch only because I was desperate to make my local MCP servers listening on localhost work behind the company proxy. Keep in mind that every time you update the gemini-cli project it will overwrite your patch, so you must patch again the file "node_modules@google\gemini-cli\node_modules\undici\lib\dispatcher\proxy-agent.js" until they officially fix this matter. Forgive the italian comments in my code (I am Italian), use this code at your own risk, just use it to understand the logic because if they change something on it before to fix the matter, you have to implement again this patch logic.
the code below
.................................................................
@PARAM {string} hostname L'hostname di destinazione.
@returns {boolean} true se il proxy deve essere bypassato.
*/
function shouldBypassProxy (hostname) {
if (!hostname) return false
// Bypass per localhost e indirizzi IP non pubblici
if (hostname === 'localhost' || hostname === '0.0.0.0' || /^(?:10.|127.|169.254|192.168.|172.(?:1[6-9]|2\d|3[0-1]))/.test(hostname)) {
return true
}
// Bypass in base alla variabile d'ambiente NO_PROXY
const noProxyEnv = process.env.NO_PROXY || process.env.no_proxy
if (!noProxyEnv) return false
for (const rule of noProxyRules) {
if (rule === '*') return true
if (rule.startsWith('.') && hostname.endsWith(rule)) return true
if (hostname === rule) return true
}
return false
}
// ---------- FINE MODIFICA ----------
function defaultFactory (origin, opts) {
return new Pool(origin, opts)
}
const noop = () => {}
class ProxyClient extends DispatcherBase {
#client = null
constructor (origin, opts) {
if (typeof origin === 'string') {
origin = new URL(origin)
}
if (origin.protocol !== 'http:' && origin.protocol !== 'https:') {
throw new InvalidArgumentError('ProxyClient only supports http and https protocols')
}
super()
this.#client = new Client(origin, opts)
}
}
class ProxyAgent extends DispatcherBase {
constructor (opts) {
if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) {
throw new InvalidArgumentError('Proxy uri is mandatory')
}
const { clientFactory = defaultFactory } = opts
if (typeof clientFactory !== 'function') {
throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.')
}
const { proxyTunnel = true } = opts
super()
const url = this.#getUrl(opts)
const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url
this[kProxy] = { uri: href, protocol }
this[kRequestTls] = opts.requestTls
this[kProxyTls] = opts.proxyTls
this[kProxyHeaders] = opts.headers || {}
if (opts.auth && opts.token) {
throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token')
} else if (opts.auth) {
/* @deprecated in favour of opts.token */
this[kProxyHeaders]['proxy-authorization'] = `Basic ${opts.auth}`
} else if (opts.token) {
this[kProxyHeaders]['proxy-authorization'] = opts.token
} else if (username && password) {
this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}`
}
const factory = (!proxyTunnel && protocol === 'http:')
? (origin, options) => {
if (origin.protocol === 'http:') {
return new ProxyClient(origin, options)
}
return new Client(origin, options)
}
: undefined
const connect = buildConnector({ ...opts.proxyTls })
this[kConnectEndpoint] = buildConnector({ ...opts.requestTls })
this[kClient] = clientFactory(url, { connect, factory })
this[kTunnelProxy] = proxyTunnel
this[kAgent] = new Agent({
...opts,
connect: async (opts, callback) => {
let requestedPath = opts.host
if (!opts.port) {
requestedPath += `:${defaultProtocolPort(opts.protocol)}`
}
try {
const { socket, statusCode } = await this[kClient].connect({
origin,
port,
path: requestedPath,
signal: opts.signal,
headers: {
...this[kProxyHeaders],
host: opts.host,
...(opts.connections == null || opts.connections > 0 ? { 'proxy-connection': 'keep-alive' } : {})
},
servername: this[kProxyTls]?.servername || proxyHostname
})
if (statusCode !== 200) {
socket.on('error', noop).destroy()
callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`))
}
if (opts.protocol !== 'https:') {
callback(null, socket)
return
}
let servername
if (this[kRequestTls]) {
servername = this[kRequestTls].servername
} else {
servername = opts.servername
}
this[kConnectEndpoint]({ ...opts, servername, httpSocket: socket }, callback)
} catch (err) {
if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') {
// Throw a custom error to avoid loop in client.js#connect
callback(new SecureProxyConnectionError(err))
} else {
callback(err)
}
}
}
})
// ---------- INIZIO MODIFICA: AGGIUNTA AGENT DIRETTO ----------
this[kDirectClient] = new Agent({ ...opts, factory: clientFactory })
// ---------- FINE MODIFICA ----------
}
dispatch (opts, handler) {
const targetUrl = new URL(opts.origin)
// ---------- INIZIO MODIFICA: CONTROLLO BYPASS PRIMA DI TUTTO ----------
if (shouldBypassProxy(targetUrl.hostname)) {
// Usa l'agent diretto invece di quello proxy
return this[kDirectClient].dispatch(opts, handler)
}
// ---------- FINE MODIFICA ----------
const headers = buildHeaders(opts.headers)
throwIfProxyAuthIsSent(headers)
if (headers && !('host' in headers) && !('Host' in headers)) {
headers.host = targetUrl.host
}
if (!this.#shouldConnect(targetUrl)) {
opts.path = opts.origin + opts.path
}
return this[kAgent].dispatch(
{
...opts,
headers
},
handler
)
@returns {Record<string, string>} /
function buildHeaders (headers) {
// When using undici.fetch, the headers list is stored
// as an array.
if (Array.isArray(headers)) {
/* @type {Record<string, string>} */
const headersPair = {}
for (let i = 0; i < headers.length; i += 2) {
headersPair[headers[i]] = headers[i + 1]
}
Previous versions of ProxyAgent suggests the Proxy-Authorization in request headers
Nevertheless, it was changed and to avoid a security vulnerability by end users
this check was created.
It should be removed in the next major version for performance reasons
*/
function throwIfProxyAuthIsSent (headers) {
const existProxyAuth = headers && Object.keys(headers)
.find((key) => key.toLowerCase() === 'proxy-authorization')
if (existProxyAuth) {
throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor')
}
}
Same request, i use MCP server on a LAN server and proxy web for internet.
env variable work for http_proxy and https_proxy but didn't work for no_proxy
Thanks a lot
My NO_PROXY environment variable is set as follows:
echo %NO_PROXY%
localhost,127.0.0.1
But I still get this error when launching gemini:
failed to start or connect to MCP server 'local-mcp-server'
{"url":"http://localhost:8081/sse"};
Error: SSE error: TypeError: fetch failed: Error: Request was cancelled.: Proxy response
(403) !== 200 when HTTP Tunneling
And indeed, if I run /mcp, the local server is disconnected :
> /mcp
ℹ Configured MCP servers:
🔴 local-mcp-server - Disconnected (0 tools cached)
No tools available
Closing because the comments indicate this issue might be fixed, has a workaround, is no longer an issue, or is resolved. Feel free to reopen if the problem persists.
Activity
biagiomaf commentedon Jul 6, 2025
in the meanwhile the matter gets the proper solution, I've resolved patching the file node_modules@google\gemini-cli\node_modules\undici\lib\dispatcher\proxy-agent.js
putting in it the conditions no to use a proxy if url belongs to localhost or a private network ip address, so now my MCP servers listening on the localhost work even if the internet is accessed only through the company proxy.
hu-bo commentedon Jul 11, 2025
Could you please refer to the code
biagiomaf commentedon Jul 11, 2025
hu-bo, you asked for the code, I am copyng it here BUT what I did it is absolutely not a good practice, I made that patch only because I was desperate to make my local MCP servers listening on localhost work behind the company proxy. Keep in mind that every time you update the gemini-cli project it will overwrite your patch, so you must patch again the file "node_modules@google\gemini-cli\node_modules\undici\lib\dispatcher\proxy-agent.js" until they officially fix this matter. Forgive the italian comments in my code (I am Italian), use this code at your own risk, just use it to understand the logic because if they change something on it before to fix the matter, you have to implement again this patch logic.
the code below
.................................................................
'use strict'
const { kProxy, kClose, kDestroy, kDispatch, kConnector } = require('../core/symbols')
const { URL } = require('node:url')
const Agent = require('./agent')
const Pool = require('./pool')
const DispatcherBase = require('./dispatcher-base')
const { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = require('../core/errors')
const buildConnector = require('../core/connect')
const Client = require('./client')
const kAgent = Symbol('proxy agent')
const kClient = Symbol('proxy client')
const kProxyHeaders = Symbol('proxy headers')
const kRequestTls = Symbol('request tls settings')
const kProxyTls = Symbol('proxy tls settings')
const kConnectEndpoint = Symbol('connect endpoint function')
const kTunnelProxy = Symbol('tunnel proxy')
// ---------- INIZIO MODIFICA: AGGIUNTA LOGICA BYPASS ----------
const kDirectClient = Symbol('direct client')
/**
truese il proxy deve essere bypassato.*/
function shouldBypassProxy (hostname) {
if (!hostname) return false
// Bypass per localhost e indirizzi IP non pubblici
if (hostname === 'localhost' || hostname === '0.0.0.0' || /^(?:10.|127.|169.254|192.168.|172.(?:1[6-9]|2\d|3[0-1]))/.test(hostname)) {
return true
}
// Bypass in base alla variabile d'ambiente NO_PROXY
const noProxyEnv = process.env.NO_PROXY || process.env.no_proxy
if (!noProxyEnv) return false
const noProxyRules = noProxyEnv.split(',').map(r => r.trim()).filter(Boolean)
for (const rule of noProxyRules) {
if (rule === '*') return true
if (rule.startsWith('.') && hostname.endsWith(rule)) return true
if (hostname === rule) return true
}
return false
}
// ---------- FINE MODIFICA ----------
function defaultProtocolPort (protocol) {
return protocol === 'https:' ? 443 : 80
}
function defaultFactory (origin, opts) {
return new Pool(origin, opts)
}
const noop = () => {}
class ProxyClient extends DispatcherBase {
#client = null
constructor (origin, opts) {
if (typeof origin === 'string') {
origin = new URL(origin)
}
}
async [kClose] () {
await this.#client.close()
}
async [kDestroy] () {
await this.#client.destroy()
}
async [kDispatch] (opts, handler) {
const { method, origin } = opts
if (method === 'CONNECT') {
this.#client[kConnector]({
origin,
port: opts.port || defaultProtocolPort(opts.protocol),
path: opts.host,
signal: opts.signal,
headers: {
...this[kProxyHeaders],
host: opts.host
},
servername: this[kProxyTls]?.servername || opts.servername
},
(err, socket) => {
if (err) {
handler.callback(err)
} else {
handler.callback(null, { socket, statusCode: 200 })
}
}
)
return
}
if (typeof origin === 'string') {
opts.origin = new URL(origin)
}
}
}
class ProxyAgent extends DispatcherBase {
constructor (opts) {
if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) {
throw new InvalidArgumentError('Proxy uri is mandatory')
}
}
dispatch (opts, handler) {
const targetUrl = new URL(opts.origin)
}
/**
*/
#getUrl (opts) {
if (typeof opts === 'string') {
return new URL(opts)
} else if (opts instanceof URL) {
return opts
} else {
return new URL(opts.uri)
}
}
async [kClose] () {
await this[kAgent].close()
await this[kClient].close()
// ---------- INIZIO MODIFICA: CHIUSURA AGENT DIRETTO ----------
await this[kDirectClient].close()
// ---------- FINE MODIFICA ----------
}
async [kDestroy] () {
await this[kAgent].destroy()
await this[kClient].destroy()
// ---------- INIZIO MODIFICA: DISTRUZIONE AGENT DIRETTO ----------
await this[kDirectClient].destroy()
// ---------- FINE MODIFICA ----------
}
#shouldConnect (uri) {
if (typeof uri === 'string') {
uri = new URL(uri)
}
if (this[kTunnelProxy]) {
return true
}
if (uri.protocol !== 'http:' || this[kProxy].protocol !== 'http:') {
return true
}
return false
}
}
/**
@PARAM {string[] | Record<string, string>} headers
@returns {Record<string, string>}
/
function buildHeaders (headers) {
// When using undici.fetch, the headers list is stored
// as an array.
if (Array.isArray(headers)) {
/* @type {Record<string, string>} */
const headersPair = {}
for (let i = 0; i < headers.length; i += 2) {
headersPair[headers[i]] = headers[i + 1]
}
return headersPair
}
return headers
}
/**
*/
function throwIfProxyAuthIsSent (headers) {
const existProxyAuth = headers && Object.keys(headers)
.find((key) => key.toLowerCase() === 'proxy-authorization')
if (existProxyAuth) {
throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor')
}
}
module.exports = ProxyAgent
zobi commentedon Jul 30, 2025
Same request, i use MCP server on a LAN server and proxy web for internet.
env variable work for http_proxy and https_proxy but didn't work for no_proxy
Thanks a lot
Blackoutta commentedon Aug 15, 2025
same problem here
lmagnien commentedon Aug 19, 2025
I seem to be experiencing the same issue.
My NO_PROXY environment variable is set as follows:
But I still get this error when launching gemini:
And indeed, if I run /mcp, the local server is disconnected :
Environment details:
fredericdasilva commentedon Aug 31, 2025
+1
jmrichardson commentedon Sep 2, 2025
+1
kuanyong-wong-partior commentedon Sep 18, 2025
+1
3 remaining items
fireinice commentedon Mar 14, 2026
+1
cocosheng-g commentedon Apr 9, 2026
Closing because the comments indicate this issue might be fixed, has a workaround, is no longer an issue, or is resolved. Feel free to reopen if the problem persists.
fireinice commentedon Apr 9, 2026
all pr related are not merged yet and i dont see any work around