diff --git a/cpg-mcp/src/integrationTest/kotlin/mcp/prompts/CpgSuggestConceptsPromptTest.kt b/cpg-mcp/src/integrationTest/kotlin/mcp/prompts/CpgSuggestConceptsPromptTest.kt new file mode 100644 index 00000000000..319c20d0ac9 --- /dev/null +++ b/cpg-mcp/src/integrationTest/kotlin/mcp/prompts/CpgSuggestConceptsPromptTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2026, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.mcp.prompts + +import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addSuggestConceptsPrompt +import de.fraunhofer.aisec.cpg.mcp.utils.withClient +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequest +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptRequestParams +import io.modelcontextprotocol.kotlin.sdk.types.TextContent +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class CpgSuggestConceptsPromptTest { + @Test + fun suggestConceptsNoDescription() = + withClient(registerPrompts = { addSuggestConceptsPrompt() }) { client -> + val result = + client.getPrompt( + GetPromptRequest(GetPromptRequestParams(name = "suggest_concepts")) + ) + + assertNotNull(result) + val text = (result.messages.firstOrNull()?.content as? TextContent)?.text + assertNotNull(text, "Prompt message should have text content") + assertFalse( + "## Additional Context" in text, + "Result should not contain 'Additional Context' when no description is given", + ) + } + + @Test + fun suggestConceptsWithDescription() = + withClient(registerPrompts = { addSuggestConceptsPrompt() }) { client -> + val result = + client.getPrompt( + GetPromptRequest( + GetPromptRequestParams( + name = "suggest_concepts", + arguments = + mapOf("description" to "We have some additional context here."), + ) + ) + ) + + assertNotNull(result) + val text = (result.messages.firstOrNull()?.content as? TextContent)?.text + assertNotNull(text, "Prompt message should have text content") + assertTrue( + "## Additional Context" in text, + "Result should contain 'Additional Context' when description is provided", + ) + } +} diff --git a/cpg-mcp/src/integrationTest/kotlin/mcp/tools/CpgLlmAnalyzeToolTest.kt b/cpg-mcp/src/integrationTest/kotlin/mcp/tools/CpgLlmAnalyzeToolTest.kt deleted file mode 100644 index 527dcec9b11..00000000000 --- a/cpg-mcp/src/integrationTest/kotlin/mcp/tools/CpgLlmAnalyzeToolTest.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2026, Fraunhofer AISEC. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ -package de.fraunhofer.aisec.cpg.mcp.tools - -import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addCpgLlmAnalyzeTool -import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.runCpgAnalyze -import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.utils.CpgAnalyzePayload -import de.fraunhofer.aisec.cpg.mcp.utils.withClient -import io.modelcontextprotocol.kotlin.sdk.types.TextContent -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertIs -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import org.junit.jupiter.api.BeforeEach - -class CpgLlmAnalyzeToolTest { - @BeforeEach - fun setAnalysisResult() { - val payload = - CpgAnalyzePayload(content = "def hello():\n print('Hello World')", extension = "py") - runCpgAnalyze(payload, runPasses = true, cleanup = true) - } - - @Test - fun cpgLlmAnalyzeNoPayload() = - withClient(registerTools = { addCpgLlmAnalyzeTool() }) { client -> - val result = client.callTool(name = "cpg_llm_analyze", arguments = emptyMap()) - - val resultContent = result.content.firstOrNull() - assertIs(resultContent) - assertNotNull(resultContent.text, "Result content should not be null") - assertFalse( - "## Additional Context" in resultContent.text, - "Result content should not contain the section 'Additional Context'", - ) - } - - @Test - fun cpgLlmAnalyzeWithPayload() = - withClient(registerTools = { addCpgLlmAnalyzeTool() }) { client -> - val result = - client.callTool( - name = "cpg_llm_analyze", - arguments = mapOf("description" to "We have some additional context here."), - ) - - val resultContent = result.content.firstOrNull() - assertIs(resultContent) - assertNotNull(resultContent.text, "Result content should not be null") - assertTrue( - "## Additional Context" in resultContent.text, - "Result content should contain the section 'Additional Context'", - ) - } -} diff --git a/cpg-mcp/src/integrationTest/kotlin/mcp/utils/McpTestSetup.kt b/cpg-mcp/src/integrationTest/kotlin/mcp/utils/McpTestSetup.kt index 2fd8e21b971..a7e6c1a9457 100644 --- a/cpg-mcp/src/integrationTest/kotlin/mcp/utils/McpTestSetup.kt +++ b/cpg-mcp/src/integrationTest/kotlin/mcp/utils/McpTestSetup.kt @@ -46,7 +46,11 @@ import kotlinx.coroutines.withTimeout * server and client. */ @OptIn(ExperimentalMcpApi::class) -fun withClient(registerTools: Server.() -> Unit = {}, test: suspend (Client) -> Unit) = +fun withClient( + registerTools: Server.() -> Unit = {}, + registerPrompts: Server.() -> Unit = {}, + test: suspend (Client) -> Unit, +): Unit = runBlocking(Dispatchers.Default) { val serverReady = CompletableDeferred() @@ -65,6 +69,7 @@ fun withClient(registerTools: Server.() -> Unit = {}, test: suspend (Client) -> server.onConnect { serverReady.complete(Unit) } server.registerTools() + server.registerPrompts() val (clientTransport, serverTransport) = ChannelTransport.createLinkedPair() val client = Client(Implementation(name = "test-client", version = "1.0.0")) diff --git a/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/McpServer.kt b/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/McpServer.kt index a964b313f9d..bcc38471db6 100644 --- a/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/McpServer.kt +++ b/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/McpServer.kt @@ -28,10 +28,10 @@ package de.fraunhofer.aisec.cpg.mcp.mcpserver import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addCpgAnalyzeTool import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addCpgApplyConceptsTool import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addCpgDataflowTool -import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addCpgLlmAnalyzeTool import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addCpgTranslate import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addListPasses import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addRunPass +import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.addSuggestConceptsPrompt import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.getAllArgs import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.getArgByIndexOrName import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.listAvailableConcepts @@ -48,11 +48,11 @@ import io.modelcontextprotocol.kotlin.sdk.types.ServerCapabilities fun configureServer( configure: Server.() -> Server = { + // TOOLS this.addCpgTranslate() this.addListPasses() this.addRunPass() this.addCpgAnalyzeTool() - this.addCpgLlmAnalyzeTool() this.addCpgApplyConceptsTool() this.addCpgDataflowTool() this.listFunctions() @@ -64,6 +64,8 @@ fun configureServer( this.getAllArgs() this.getArgByIndexOrName() this.listConceptsAndOperations() + // PROMPTS + this.addSuggestConceptsPrompt() this } ): Server { diff --git a/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/CpgLlmAnalyzeTool.kt b/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/CpgLlmAnalyzeTool.kt deleted file mode 100644 index bf0deb320e0..00000000000 --- a/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/CpgLlmAnalyzeTool.kt +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright (c) 2025, Fraunhofer AISEC. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * $$$$$$\ $$$$$$$\ $$$$$$\ - * $$ __$$\ $$ __$$\ $$ __$$\ - * $$ / \__|$$ | $$ |$$ / \__| - * $$ | $$$$$$$ |$$ |$$$$\ - * $$ | $$ ____/ $$ |\_$$ | - * $$ | $$\ $$ | $$ | $$ | - * \$$$$$ |$$ | \$$$$$ | - * \______/ \__| \______/ - * - */ -package de.fraunhofer.aisec.cpg.mcp.mcpserver.tools - -import de.fraunhofer.aisec.cpg.mcp.mcpserver.cpgDescription -import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.utils.CpgLlmAnalyzePayload -import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.utils.getAvailableConcepts -import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.utils.getAvailableOperations -import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.utils.toObject -import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.utils.toSchema -import io.modelcontextprotocol.kotlin.sdk.server.Server -import io.modelcontextprotocol.kotlin.sdk.types.CallToolResult -import io.modelcontextprotocol.kotlin.sdk.types.TextContent -import io.modelcontextprotocol.kotlin.sdk.types.ToolSchema -import kotlinx.serialization.json.JsonPrimitive -import kotlinx.serialization.json.buildJsonObject -import kotlinx.serialization.json.put -import kotlinx.serialization.json.putJsonArray -import kotlinx.serialization.json.putJsonObject - -fun Server.addCpgLlmAnalyzeTool() { - val toolDescription = - """ - Generate a prompt asking the LLM to suggest concepts/operations. - - This tool creates a detailed prompt that asks the LLM to act as a software engineer with expertise in software security - and suggest appropriate concepts and operations for the analyzed code. - After using this tool, review the suggestions before applying them with cpg_apply_concepts. - """ - .trimIndent() - - this.addTool( - name = "cpg_llm_analyze", - description = toolDescription, - inputSchema = CpgLlmAnalyzePayload::class.toSchema(), - // outputSchema = outputSchema - not supported by all LLMs yet - ) { request -> - try { - val payload = - if (request.arguments.isNullOrEmpty()) { - CpgLlmAnalyzePayload() - } else { - request.arguments?.toObject() ?: CpgLlmAnalyzePayload() - } - - val hasAnalysisResult = globalAnalysisResult != null - val availableConcepts = getAvailableConcepts() - val availableOperations = getAvailableOperations() - - val prompt = buildString { - appendLine("# Code Analysis") - appendLine() - appendLine( - "You are a Software engineer with expertise in software security for more than 10 years.\n" - ) - appendLine() - - appendLine("## About CPG") - appendLine(cpgDescription) - appendLine() - - appendLine("## Goal") - appendLine( - "Mark interesting data and operations so we can analyze how data flows through code to discover patterns." - ) - appendLine() - - appendLine("## Understanding Concepts and Operations") - appendLine() - appendLine("**Concepts** mark 'what something IS':") - appendLine("- Applied to data-holding nodes (variables, fields, return values)") - appendLine("- Examples: user_email → Data, api_token → Secret") - appendLine("- Purpose: Track where important data is stored") - appendLine() - appendLine("**Operations** mark 'what something DOES':") - appendLine("- Applied to nodes that perform actions (function calls, method calls)") - appendLine( - "- Examples: requests.post() → HttpRequest, file.write() → FileWrite, encrypt() → Encryption" - ) - appendLine("- Purpose: Track what happens to important data") - appendLine() - - appendLine("## Rules") - appendLine( - "1. **Domain consistency**: When using Operations, they must match their Concept's domain (e.g., GetSecret needs Secret, HttpRequest needs HttpClient)" - ) - appendLine( - "2. **Operations always need Concepts**: Every Operation MUST have a conceptNodeId pointing to an existing Concept node. However, Concepts can stand alone without Operations." - ) - appendLine() - - appendLine("## Your Task") - appendLine("1. Analyze each node for relevance") - appendLine("2. Suggest appropriate overlays using fully qualified class names") - appendLine("3. Ensure concept-operation pairs belong to the same domain") - appendLine() - appendLine( - "**IMPORTANT:** Use only existing CPG concepts/operations from the list below." - ) - appendLine( - "For additional context, you can check docstrings in the Fraunhofer CPG repository on GitHub, especially the cpg-concepts module." - ) - appendLine() - - appendLine("Available concepts:") - availableConcepts.forEach { appendLine("- ${it.name}") } - appendLine() - appendLine("Available operations:") - availableOperations.forEach { appendLine("- ${it.name}") } - appendLine() - - if (payload.description != null) { - appendLine("## Additional Context") - appendLine(payload.description) - appendLine() - } - - if (hasAnalysisResult) { - appendLine("## Nodes to Analyze") - appendLine( - "Use the nodes from the previous cpg_analyze tool response to make your suggestions." - ) - } else { - appendLine("## No Analysis Available") - appendLine("No CPG analysis available. Analyze a file first using cpg_analyze.") - } - appendLine() - appendLine("## Expected Response Format") - appendLine("Respond with a JSON object in this exact format:") - appendLine( - """ - { - "overlaySuggestions": [ - { - "nodeId": "1234", - "overlay": "fully.qualified.class.Name", - "overlayType": "Concept" | "Operation", - "conceptNodeId": "string (REQUIRED for operations)", - "reasoning": "Security reasoning for this classification", - "securityImpact": "Potential security implications" - } - ] - } - """ - .trimIndent() - ) - appendLine() - appendLine( - "**IMPORTANT**: After providing your analysis, WAIT for user approval before applying any concepts. Do not automatically execute cpg_apply_concepts." - ) - } - - CallToolResult(content = listOf(TextContent(prompt))) - } catch (e: Exception) { - CallToolResult( - content = - listOf( - TextContent("Error generating prompt: ${e.message ?: e::class.simpleName}") - ) - ) - } - } -} - -// Note: The output schema is not supported by all LLMs yet. -@Suppress("unused") -val outputSchema = - ToolSchema( - properties = - buildJsonObject { - putJsonObject("prompt") { - put("type", "string") - put("description", "Generated prompt for LLM analysis") - } - putJsonObject("expectedResponseFormat") { - put("type", "object") - put("description", "Expected JSON structure for LLM response") - putJsonObject("properties") { - putJsonObject("overlaySuggestions") { - put("type", "array") - put("description", "List of concept/operation suggestions") - putJsonObject("items") { - put("type", "object") - putJsonObject("properties") { - putJsonObject("nodeId") { - put("type", "string") - put("description", "NodeId of the CPG node") - } - putJsonObject("overlay") { - put("type", "string") - put( - "description", - "Fully qualified name of concept or operation class", - ) - } - putJsonObject("overlayType") { - put("type", "string") - put( - "description", - "Type of overlay: 'Concept' or 'Operation'", - ) - } - putJsonObject("conceptNodeId") { - put("type", "string") - put( - "description", - "NodeId of concept this operation references (REQUIRED for ALL operations)", - ) - } - putJsonObject("arguments") { - put("type", "object") - put("description", "Additional constructor arguments") - } - putJsonObject("reasoning") { - put("type", "string") - put( - "description", - "Security reasoning for this classification", - ) - } - putJsonObject("securityImpact") { - put("type", "string") - put("description", "Potential security implications") - } - } - putJsonArray("required") { - add(JsonPrimitive("nodeId")) - add(JsonPrimitive("overlay")) - add(JsonPrimitive("overlayType")) - } - } - } - } - } - } - ) diff --git a/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/CpgSuggestConceptsPrompt.kt b/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/CpgSuggestConceptsPrompt.kt new file mode 100644 index 00000000000..e46bdb22c3f --- /dev/null +++ b/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/CpgSuggestConceptsPrompt.kt @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2025, Fraunhofer AISEC. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * $$$$$$\ $$$$$$$\ $$$$$$\ + * $$ __$$\ $$ __$$\ $$ __$$\ + * $$ / \__|$$ | $$ |$$ / \__| + * $$ | $$$$$$$ |$$ |$$$$\ + * $$ | $$ ____/ $$ |\_$$ | + * $$ | $$\ $$ | $$ | $$ | + * \$$$$$ |$$ | \$$$$$ | + * \______/ \__| \______/ + * + */ +package de.fraunhofer.aisec.cpg.mcp.mcpserver.tools + +import de.fraunhofer.aisec.cpg.mcp.mcpserver.cpgDescription +import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.utils.getAvailableConcepts +import de.fraunhofer.aisec.cpg.mcp.mcpserver.tools.utils.getAvailableOperations +import io.modelcontextprotocol.kotlin.sdk.server.Server +import io.modelcontextprotocol.kotlin.sdk.types.GetPromptResult +import io.modelcontextprotocol.kotlin.sdk.types.PromptArgument +import io.modelcontextprotocol.kotlin.sdk.types.PromptMessage +import io.modelcontextprotocol.kotlin.sdk.types.Role +import io.modelcontextprotocol.kotlin.sdk.types.TextContent + +fun Server.addSuggestConceptsPrompt() { + this.addPrompt( + name = "suggest_concepts", + description = + "Generates a prompt that guides the LLM to suggest CPG concepts and operations for the analyzed code. Use this before applying concepts with cpg_apply_concepts.", + arguments = + listOf( + PromptArgument( + name = "description", + description = + "Optional additional context or focus area for the analysis, e.g. 'focus on authentication handling' or 'look for encryption handling'.", + required = false, + ) + ), + ) { request -> + val description = request.arguments?.get("description") + val availableConcepts = getAvailableConcepts() + val availableOperations = getAvailableOperations() + + GetPromptResult( + description = + "Guides the LLM to explore the CPG and suggest concept/operation overlays for security-relevant nodes.", + messages = + listOf( + PromptMessage( + role = Role.User, + content = + TextContent( + text = + buildString { + appendLine("## About CPG") + appendLine(cpgDescription) + appendLine() + + appendLine("## Goal") + appendLine( + "Identify nodes in the CPG that represent security-relevant data or operations, and suggest appropriate concept/operation overlays for them." + ) + appendLine( + "This allows us to analyze how sensitive data flows through the code and discover security-relevant patterns." + ) + appendLine() + + appendLine("## Understanding Concepts and Operations") + appendLine() + appendLine("**Concepts** mark 'what something IS':") + appendLine( + "- Applied to data-holding nodes (variables, fields)" + ) + appendLine( + "- Examples: user_email → Data, api_token → Secret" + ) + appendLine( + "- Purpose: Track where important data is stored" + ) + appendLine() + appendLine("**Operations** mark 'what something DOES':") + appendLine( + "- Applied to nodes that perform actions (function calls, method calls)" + ) + appendLine( + "- Examples: requests.post() → HttpRequest, file.write() → FileWrite, encrypt() → Encryption" + ) + appendLine( + "- Purpose: Track what happens to important data" + ) + appendLine() + + appendLine("## Rules") + appendLine( + "1. **Domain consistency**: Operations must match their Concept's domain (e.g., GetSecret needs Secret, HttpRequest needs HttpClient)" + ) + appendLine( + "2. **Operations always need Concepts**: Every Operation MUST have a conceptNodeId pointing to an existing Concept node. Concepts can stand alone." + ) + appendLine( + "3. **Use only existing overlays**: Only use the concepts and operations listed below." + ) + appendLine() + + appendLine("## Available Concepts") + availableConcepts.forEach { appendLine("- ${it.name}") } + appendLine() + appendLine("## Available Operations") + availableOperations.forEach { appendLine("- ${it.name}") } + appendLine() + + if (description != null) { + appendLine("## Additional Context") + appendLine(description) + appendLine() + } + + appendLine("## How to Explore the Code") + appendLine( + "You can use the tools to explore the code before making suggestions." + ) + appendLine( + "The listing tools e.g., `cpg_list_functions` or `cpg_list_records`, provide a summary of functions or classes with the name, parameters and code." + ) + appendLine( + "To inspect interesting nodes in more detail use `cpg_get_node`. This retrieves the complete details of a specific node filtered by its ID." + ) + + appendLine("## Expected Response Format") + appendLine( + "After exploring the code, respond with a JSON object in this exact format:" + ) + appendLine( + """ + { + "conceptAssignments": [ + { + "nodeId": "1234", + "overlay": "fully.qualified.class.Name", + "overlayType": "Concept", + "reasoning": "Reason for this classification", + "securityImpact": "Potential security implications" + }, + { + "nodeId": "5678", + "overlay": "fully.qualified.class.Name", + "overlayType": "Operation", + "conceptNodeId": "1234", + "reasoning": "Reason for this classification", + "securityImpact": "Potential security implications" + } + ] + } + """ + .trimIndent() + ) + appendLine() + appendLine( + "**IMPORTANT**: After providing your suggestions, WAIT for user approval before applying anything. Do not automatically call `cpg_apply_concepts`." + ) + } + ), + ) + ), + ) + } +} diff --git a/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/utils/Payloads.kt b/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/utils/Payloads.kt index 73b2e7b87ac..519217ab30b 100644 --- a/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/utils/Payloads.kt +++ b/cpg-mcp/src/main/kotlin/de/fraunhofer/aisec/cpg/mcp/mcpserver/tools/utils/Payloads.kt @@ -100,12 +100,6 @@ data class CpgDataflowPayload( @Description("Target concept type (e.g., 'HttpRequest', 'Call')") val to: String, ) -@Serializable -data class CpgLlmAnalyzePayload( - @Description("A special description of what to take care of while analyzing the target") - val description: String? = null -) - /** * This class represents information about a pass, including its fully qualified name (FQN), a * description, required node type, dependencies, and soft dependencies. diff --git a/cpg-mcp/src/test/kotlin/de/fraunhofer/aisec/cpg/mcp/CpgAnalyzeToolTest.kt b/cpg-mcp/src/test/kotlin/de/fraunhofer/aisec/cpg/mcp/CpgAnalyzeToolTest.kt index 5160f9588b0..51bc4f828a3 100644 --- a/cpg-mcp/src/test/kotlin/de/fraunhofer/aisec/cpg/mcp/CpgAnalyzeToolTest.kt +++ b/cpg-mcp/src/test/kotlin/de/fraunhofer/aisec/cpg/mcp/CpgAnalyzeToolTest.kt @@ -50,7 +50,6 @@ class CpgAnalyzeToolTest { "cpg_list_passes", "cpg_run_pass", "cpg_analyze", - "cpg_llm_analyze", "cpg_apply_concepts", "cpg_dataflow", "cpg_list_functions", @@ -65,6 +64,7 @@ class CpgAnalyzeToolTest { ), testServer.tools.keys, ) + assertEquals(setOf("suggest_concepts"), testServer.prompts.keys) } @Test