Skip to content

Commit 5c49388

Browse files
committed
Merge remote-tracking branch 'origin/main' into rli/blocking-lsp-threads
2 parents 0d00a4c + 3f106a6 commit 5c49388

File tree

11 files changed

+130
-32
lines changed

11 files changed

+130
-32
lines changed

.changes/3.76.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"date" : "2025-06-12",
3+
"version" : "3.76",
4+
"entries" : [ {
5+
"type" : "feature",
6+
"description" : "Add MCP support for Amazon Q chat"
7+
} ]
8+
}

.changes/next-release/feature-91688ea0-032c-48db-a160-7dfbe71a736d.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# _3.76_ (2025-06-12)
2+
- **(Feature)** Add MCP support for Amazon Q chat
3+
14
# _3.75_ (2025-06-11)
25
- **(Feature)** Support for Amazon Q Builder ID paid tier
36

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
# Toolkit Version
5-
toolkitVersion=3.76-SNAPSHOT
5+
toolkitVersion=3.77-SNAPSHOT
66

77
# Publish Settings
88
publishToken=

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/Browser.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,6 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)
139139
},
140140
141141
"${activeProfile?.profileName.orEmpty()}")
142-
const commands = [hybridChatConnector.initialQuickActions[0], hybridChatConnector.initialQuickActions[1]]
143142
amazonQChat.createChat(
144143
{
145144
postMessage: message => {
@@ -148,7 +147,7 @@ class Browser(parent: Disposable, private val webUri: URI, val project: Project)
148147
},
149148
{
150149
agenticMode: true,
151-
quickActionCommands: commands,
150+
quickActionCommands: [],
152151
disclaimerAcknowledged: ${MeetQSettings.getInstance().disclaimerAcknowledged},
153152
pairProgrammingAcknowledged: ${MeetQSettings.getInstance().pairProgrammingAcknowledged}
154153
},

plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/webview/BrowserConnector.kt

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ import software.aws.toolkits.jetbrains.services.amazonq.util.command
9494
import software.aws.toolkits.jetbrains.services.amazonq.util.tabType
9595
import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.AmazonQTheme
9696
import software.aws.toolkits.jetbrains.services.amazonq.webview.theme.ThemeBrowserAdapter
97+
import software.aws.toolkits.jetbrains.services.amazonqCodeScan.auth.isCodeScanAvailable
98+
import software.aws.toolkits.jetbrains.services.amazonqCodeTest.auth.isCodeTestAvailable
99+
import software.aws.toolkits.jetbrains.services.amazonqDoc.auth.isDocAvailable
100+
import software.aws.toolkits.jetbrains.services.amazonqFeatureDev.auth.isFeatureDevAvailable
101+
import software.aws.toolkits.jetbrains.services.codemodernizer.utils.isCodeTransformAvailable
97102
import software.aws.toolkits.jetbrains.services.codewhisperer.settings.CodeWhispererConfigurable
98103
import software.aws.toolkits.jetbrains.settings.MeetQSettings
99104
import software.aws.toolkits.telemetry.MetricResult
@@ -173,12 +178,7 @@ class BrowserConnector(
173178
uiReady.await()
174179

175180
// Chat options including history and quick actions need to be sent in once UI is ready
176-
val showChatOptions = """{
177-
"command": "chatOptions",
178-
"params": ${Gson().toJson(AwsServerCapabilitiesProvider.getInstance(project).getChatOptions())}
179-
}
180-
""".trimIndent()
181-
browser.postChat(showChatOptions)
181+
updateQuickActionsInBrowser(browser)
182182

183183
// Send inbound messages to the browser
184184
val inboundMessages = connections.map { it.messagesFromAppToUi.flow }.merge()
@@ -535,6 +535,45 @@ class BrowserConnector(
535535
browser.postChat(cancelMessage)
536536
}
537537

538+
private fun updateQuickActionsInBrowser(browser: Browser) {
539+
val isFeatureDevAvailable = isFeatureDevAvailable(project)
540+
val isCodeTransformAvailable = isCodeTransformAvailable(project)
541+
val isDocAvailable = isDocAvailable(project)
542+
val isCodeScanAvailable = isCodeScanAvailable(project)
543+
val isCodeTestAvailable = isCodeTestAvailable(project)
544+
545+
val script = """
546+
try {
547+
const tempConnector = connectorAdapter.initiateAdapter(
548+
false,
549+
true, // the two values are not used here, needed for constructor
550+
$isFeatureDevAvailable,
551+
$isCodeTransformAvailable,
552+
$isDocAvailable,
553+
$isCodeScanAvailable,
554+
$isCodeTestAvailable,
555+
{ postMessage: () => {} }
556+
);
557+
558+
const commands = tempConnector.initialQuickActions?.slice(0, 2) || [];
559+
const options = ${Gson().toJson(AwsServerCapabilitiesProvider.getInstance(project).getChatOptions())};
560+
options.quickActions.quickActionsCommandGroups = [
561+
...commands,
562+
...options.quickActions.quickActionsCommandGroups
563+
];
564+
565+
window.postMessage({
566+
command: "chatOptions",
567+
params: options
568+
});
569+
} catch (e) {
570+
console.error("Error updating quick actions:", e);
571+
}
572+
""".trimIndent()
573+
574+
browser.jcefBrowser.cefBrowser.executeJavaScript(script, browser.jcefBrowser.cefBrowser.url, 0)
575+
}
576+
538577
private fun cancelInflightRequests(tabId: String) {
539578
chatCommunicationManager.getInflightRequestForTab(tabId)?.let { request ->
540579
request.cancel(true)

plugins/amazonq/codetransform/jetbrains-community/src/software/aws/toolkits/jetbrains/services/codemodernizer/plan/CodeModernizerPlanEditor.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ class CodeModernizerPlanEditor(val project: Project, private val virtualFile: Vi
490490
JPanel().apply {
491491
layout = BoxLayout(this, BoxLayout.Y_AXIS)
492492
jobStatistics.forEach { stat ->
493-
if (!stat.name.isNullOrEmpty() && !stat.value.isNullOrEmpty()) {
493+
if (!stat.name.isNullOrEmpty() && !stat.value.isNullOrEmpty() && stat.name == "linesOfCode") {
494494
val formattedStatName = getFormattedString(stat.name)
495495
add(
496496
JLabel(

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLanguageClientImpl.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ class AmazonQLanguageClientImpl(private val project: Project) : AmazonQLanguageC
295295
AmazonQLspConstants.LSP_Q_CONFIGURATION_KEY -> {
296296
add(
297297
AmazonQLspConfiguration(
298-
optOutTelemetry = AwsSettings.getInstance().isTelemetryEnabled,
298+
optOutTelemetry = !AwsSettings.getInstance().isTelemetryEnabled,
299299
customization = CodeWhispererModelConfigurator.getInstance().activeCustomization(project)?.arn,
300300
// local context
301301
projectContext = ProjectContextConfiguration(

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/AmazonQLspService.kt

Lines changed: 62 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@ import com.intellij.openapi.project.Project
2222
import com.intellij.openapi.util.Disposer
2323
import com.intellij.openapi.util.Key
2424
import com.intellij.openapi.util.SystemInfo
25+
import com.intellij.util.EnvironmentUtil
26+
import com.intellij.util.io.DigestUtil
2527
import com.intellij.util.io.await
2628
import com.intellij.util.net.HttpConfigurable
2729
import com.intellij.util.net.JdkProxyProvider
30+
import com.intellij.util.net.ssl.CertificateManager
2831
import kotlinx.coroutines.CoroutineScope
2932
import kotlinx.coroutines.Deferred
3033
import kotlinx.coroutines.Job
@@ -77,6 +80,7 @@ import software.aws.toolkits.jetbrains.services.amazonq.lsp.textdocument.TextDoc
7780
import software.aws.toolkits.jetbrains.services.amazonq.lsp.util.WorkspaceFolderUtil.createWorkspaceFolders
7881
import software.aws.toolkits.jetbrains.services.amazonq.lsp.workspace.WorkspaceServiceHandler
7982
import software.aws.toolkits.jetbrains.services.amazonq.profile.QDefaultServiceConfig
83+
import software.aws.toolkits.jetbrains.services.amazonq.profile.QEndpoints
8084
import software.aws.toolkits.jetbrains.services.cwc.controller.chat.telemetry.getStartUrl
8185
import software.aws.toolkits.jetbrains.services.telemetry.ClientMetadata
8286
import software.aws.toolkits.jetbrains.settings.LspSettings
@@ -388,21 +392,60 @@ private class AmazonQServerInstance(private val project: Project, private val cs
388392
// will cause slow service init, but maybe fine for now. will not block UI since fetch/extract will be under background progress
389393
val artifact = runBlocking { service<ArtifactManager>().fetchArtifact(project) }.toAbsolutePath()
390394

391-
// more network calls
392-
// make assumption that all requests will resolve to the same CA
393-
// also terrible assumption that default endpoint is reachable
394-
val qUri = URI(QDefaultServiceConfig.ENDPOINT)
395-
val extraCaCerts = try {
396-
val rtsTrustChain = TrustChainUtil.getTrustChain(qUri)
397-
398-
Files.createTempFile("q-extra-ca", ".pem").apply {
399-
writeText(
400-
TrustChainUtil.certsToPem(rtsTrustChain)
401-
)
395+
// make some network calls for troubleshooting
396+
listOf(*QEndpoints.listRegionEndpoints().map { it.endpoint }.toTypedArray(), QDefaultServiceConfig.ENDPOINT).forEach { endpoint ->
397+
try {
398+
val qUri = URI(endpoint)
399+
val rtsTrustChain = TrustChainUtil.getTrustChain(qUri)
400+
val trustRoot = rtsTrustChain.last()
401+
// ATS is cross-signed against starfield certs: https://www.amazontrust.com/repository/
402+
if (listOf("Amazon Root CA", "Starfield Technologies").any { trustRoot.subjectX500Principal.name.contains(it) }) {
403+
LOG.info { "Trust chain for $endpoint ends with public-like CA with sha256 fingerprint: ${DigestUtil.sha256Hex(trustRoot.encoded)}" }
404+
} else {
405+
LOG.info {
406+
"""
407+
|Trust chain for $endpoint transits private CA:
408+
|${buildString {
409+
rtsTrustChain.forEach { cert ->
410+
append("Issuer: ${cert.issuerX500Principal}, ")
411+
append("Subject: ${cert.subjectX500Principal}, ")
412+
append("Fingerprint: ${DigestUtil.sha256Hex(cert.encoded)}\n\t")
413+
}
414+
}}
415+
""".trimMargin("|")
416+
}
417+
LOG.debug { "Full trust chain info for $endpoint: $rtsTrustChain" }
418+
}
419+
} catch (e: Exception) {
420+
LOG.info { "${e.message}: Could not resolve trust chain for $endpoint" }
402421
}
403-
} catch (e: Exception) {
404-
LOG.info(e) { "Could not resolve trust chain for $qUri, skipping NODE_EXTRA_CA_CERTS" }
422+
}
423+
424+
val userEnvNodeCaCerts = EnvironmentUtil.getValue("NODE_EXTRA_CA_CERTS")
425+
// if user has NODE_EXTRA_CA_CERTS in their environment, assume they know what they're doing
426+
val extraCaCerts = if (!userEnvNodeCaCerts.isNullOrEmpty()) {
427+
LOG.info { "Skipping injection of IDE trust store, user already defines NODE_EXTRA_CA_CERTS: $userEnvNodeCaCerts" }
428+
405429
null
430+
} else {
431+
try {
432+
// otherwise include everything the IDE knows about
433+
val allAcceptedIssuers = CertificateManager.getInstance().trustManager.acceptedIssuers
434+
val customIssuers = CertificateManager.getInstance().customTrustManager.acceptedIssuers
435+
LOG.info {
436+
"Injecting ${allAcceptedIssuers.size} IDE trusted certificates (${customIssuers.size} from IDE custom manager) into NODE_EXTRA_CA_CERTS"
437+
}
438+
439+
Files.createTempFile("q-extra-ca", ".pem").apply {
440+
writeText(
441+
TrustChainUtil.certsToPem(allAcceptedIssuers.toList())
442+
)
443+
}.toAbsolutePath().toString()
444+
} catch (e: Exception) {
445+
LOG.warn(e) { "Could not inject IDE trust store into NODE_EXTRA_CA_CERTS" }
446+
447+
null
448+
}
406449
}
407450

408451
val node = if (SystemInfo.isWindows) "node.exe" else "node"
@@ -415,8 +458,13 @@ private class AmazonQServerInstance(private val project: Project, private val cs
415458
"--set-credentials-encryption-key",
416459
).withEnvironment(
417460
buildMap {
418-
extraCaCerts?.let { put("NODE_EXTRA_CA_CERTS", it.toAbsolutePath().toString()) }
461+
extraCaCerts?.let {
462+
LOG.info { "Starting Flare with NODE_EXTRA_CA_CERTS: $it" }
463+
put("NODE_EXTRA_CA_CERTS", it)
464+
}
419465

466+
// assume default endpoint will pick correct proxy if needed
467+
val qUri = URI(QDefaultServiceConfig.ENDPOINT)
420468
val proxy = JdkProxyProvider.getInstance().proxySelector.select(qUri)
421469
// log if only socks proxy available
422470
.firstOrNull { it.type() == Proxy.Type.HTTP }

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,14 @@ class DefaultAuthCredentialsService(
111111
return CompletableFuture.failedFuture(e)
112112
}
113113

114-
return AmazonQLspService.executeIfRunning(project) { server ->
114+
val future = AmazonQLspService.executeIfRunning(project) { server ->
115115
server.updateTokenCredentials(payload)
116-
} ?: (CompletableFuture.failedFuture(IllegalStateException("LSP Server not running")))
116+
} ?: CompletableFuture.failedFuture(IllegalStateException("LSP Server not running"))
117+
118+
return future.thenApply { response ->
119+
updateConfiguration()
120+
response
121+
}
117122
}
118123

119124
override fun deleteTokenCredentials(): CompletableFuture<Unit> =

0 commit comments

Comments
 (0)