Skip to content

Used multiple chunks of WHO(not properly fomatted) to provide context to AI system #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .idea/artifacts/kotlin_sdk_jvm_0_4_0.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions .idea/codeStyles/codeStyleConfig.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions samples/kotlin-mcp-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {
implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
implementation("org.slf4j:slf4j-nop:$slf4jVersion")
implementation("com.anthropic:anthropic-java:$anthropicVersion")

}

tasks.test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,18 @@ import kotlinx.io.buffered
import kotlinx.serialization.json.JsonObject
import kotlin.jvm.optionals.getOrNull

import java.nio.file.Files
import java.nio.file.Paths

import java.io.File
import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.TextResourceContents

class MCPClient : AutoCloseable {
// Configures using the `ANTHROPIC_API_KEY` and `ANTHROPIC_AUTH_TOKEN` environment variables
private val anthropic = AnthropicOkHttpClient.fromEnv()

private val anthropic = AnthropicOkHttpClient.builder()
.apiKey(System.getenv("ANTHROPIC_API_KEY") )
.build()
Comment on lines +27 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these changes necessary? AnthropicOkHttpClient.fromEnv() looks for ANTHROPIC_API_KEY in the environment variables by default


// Initialize MCP client
private val mcp: Client = Client(clientInfo = Implementation(name = "mcp-client-cli", version = "1.0.0"))
Expand All @@ -36,10 +45,13 @@ class MCPClient : AutoCloseable {
return JsonValue.fromJsonNode(node)
}

// Connect to the server using the path to the server


private val resourceContents: MutableMap<String, String> = mutableMapOf()

suspend fun connectToServer(serverScriptPath: String) {
try {
// Build the command based on the file extension of the server script
// Build command to start server
val command = buildList {
when (serverScriptPath.substringAfterLast(".")) {
"js" -> add("node")
Expand All @@ -50,19 +62,19 @@ class MCPClient : AutoCloseable {
add(serverScriptPath)
}

// Start the server process
// Start server process
val process = ProcessBuilder(command).start()

// Setup I/O transport using the process streams
// Setup transport
val transport = StdioClientTransport(
input = process.inputStream.asSource().buffered(),
output = process.outputStream.asSink().buffered()
)

// Connect the MCP client to the server using the transport
// Connect MCP client
mcp.connect(transport)

// Request the list of available tools from the server
// List tools
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my opinion, it’s better to keep detailed comments in the example. For new users, a comment like List tools doesn’t provide any additional information

val toolsResult = mcp.listTools()
tools = toolsResult?.tools?.map { tool ->
ToolUnion.ofTool(
Expand All @@ -79,17 +91,56 @@ class MCPClient : AutoCloseable {
.build()
)
} ?: emptyList()

println("Connected to server with tools: ${tools.joinToString(", ") { it.tool().get().name() }}")

// // List all resources
val resourcesResult = mcp.listResources()
val resources = resourcesResult?.resources ?: emptyList()

println("Found ${resources.size} resources from server.")

for (resource in resources) {
println("Loading resource: ${resource.name} (${resource.uri})")
val readRequest = ReadResourceRequest(uri = resource.uri)
val readResult = mcp.readResource(readRequest)

val content = readResult?.contents
?.filterIsInstance<TextResourceContents>()
?.joinToString("\n") { it.text }
if (content != null) {
resourceContents[resource.uri] = content
println("Successfully loaded resource (${content.length} characters) for URI: ${resource.uri}")
} else {
println("Warning: No content found for resource: ${resource.uri}")
}
}

} catch (e: Exception) {
println("Failed to connect to MCP server: $e")
throw e
}
}

private val messages = mutableListOf<MessageParam>()

// Process a user query and return a string response
suspend fun processQuery(query: String): String {
// Create an initial message with a user's query
val messages = mutableListOf(

// / Prepend resources as SYSTEM message if they are loaded
if (resourceContents.isNotEmpty()) {
Copy link
Preview

Copilot AI May 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] For clarity of role semantics, consider using MessageParam.Role.SYSTEM instead of ASSISTANT when prepending reference resources as context to the conversation.

Copilot uses AI. Check for mistakes.

val combinedResourceText = resourceContents.values.joinToString("\n\n")
messages.add(
MessageParam.builder()
.role(MessageParam.Role.ASSISTANT) // system role for context
.content("Reference Resources:\n$combinedResourceText")
.build()
)
}


messages.add(
MessageParam.builder()
.role(MessageParam.Role.USER)
.content(query)
Expand All @@ -104,11 +155,76 @@ class MCPClient : AutoCloseable {
.build()
)

// val assistantReply = response.content().firstOrNull()?.text()?.getOrNull()?.text()


// val finalText = mutableListOf<String>()
// response.content().forEach { content ->
// when {
// // Append text outputs from the response
// content.isText() -> finalText.add(content.text().getOrNull()?.text() ?: "")
//
// // If the response indicates a tool use, process it further
// content.isToolUse() -> {
// val toolName = content.toolUse().get().name()
// val toolArgs =
// content.toolUse().get()._input().convert(object : TypeReference<Map<String, JsonValue>>() {})
//
// // Call the tool with provided arguments
// val result = mcp.callTool(
// name = toolName,
// arguments = toolArgs ?: emptyMap()
// )
// finalText.add("[Calling tool $toolName with args $toolArgs]")
//
// // Add the tool result message to the conversation
// messages.add(
// MessageParam.builder()
// .role(MessageParam.Role.USER)
// .content(
// """
// "type": "tool_result",
// "tool_name": $toolName,
// "result": ${result?.content?.joinToString("\n") { (it as TextContent).text ?: "" }}
// """.trimIndent()
// )
// .build()
// )
//
// // Retrieve an updated response after tool execution
// val aiResponse = anthropic.messages().create(
// messageParamsBuilder
// .messages(messages)
// .build()
// )
//
// // Append the updated response to final text
// finalText.add(aiResponse.content().first().text().getOrNull()?.text() ?: "")
// }
// }
// }
//
// return finalText.joinToString("\n", prefix = "", postfix = "")
// }
Comment on lines +158 to +208
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please leave a comment in the code explaining what this part does and why it is commented out

val finalText = mutableListOf<String>()

response.content().forEach { content ->
when {
// Append text outputs from the response
content.isText() -> finalText.add(content.text().getOrNull()?.text() ?: "")
content.isText() -> {
val text = content.text().getOrNull()?.text()
if (!text.isNullOrBlank()) {
finalText.add(text)

// Save assistant response to memory
messages.add(
MessageParam.builder()
.role(MessageParam.Role.ASSISTANT)
.content(text)
.build()
)
}
}

// If the response indicates a tool use, process it further
content.isToolUse() -> {
Expand All @@ -121,19 +237,22 @@ class MCPClient : AutoCloseable {
name = toolName,
arguments = toolArgs ?: emptyMap()
)

finalText.add("[Calling tool $toolName with args $toolArgs]")

// Add the tool result message to the conversation
// Add the tool_result to messages
val toolResultContent = """
{
"type": "tool_result",
"tool_name": "$toolName",
"result": "${result?.content?.joinToString("\n") { (it as TextContent).text ?: "" }}"
}
""".trimIndent()

messages.add(
MessageParam.builder()
.role(MessageParam.Role.USER)
.content(
"""
"type": "tool_result",
"tool_name": $toolName,
"result": ${result?.content?.joinToString("\n") { (it as TextContent).text ?: "" }}
""".trimIndent()
)
.content(toolResultContent)
.build()
)

Expand All @@ -144,15 +263,27 @@ class MCPClient : AutoCloseable {
.build()
)

// Append the updated response to final text
finalText.add(aiResponse.content().first().text().getOrNull()?.text() ?: "")
val aiReply = aiResponse.content().firstOrNull()?.text()?.getOrNull()?.text()
if (!aiReply.isNullOrBlank()) {
finalText.add(aiReply)

// Save assistant's new response after tool use
messages.add(
MessageParam.builder()
.role(MessageParam.Role.ASSISTANT)
.content(aiReply)
.build()
)
}
}
}
}

return finalText.joinToString("\n", prefix = "", postfix = "")
}



// Main chat loop for interacting with the user
suspend fun chatLoop() {
println("\nMCP Client Started!")
Expand All @@ -173,4 +304,4 @@ class MCPClient : AutoCloseable {
anthropic.close()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,34 @@ fun main(args: Array<String>) = runBlocking {
val client = MCPClient()
client.use {
client.connectToServer(serverPath)
println("starting mcp client")
client.chatLoop()
}
}
}


//fun main(args: Array<String>) = runBlocking {
// if (args.isEmpty()) {
// println("Please provide the path to the MCP server script as a command-line argument.")
// return@runBlocking
// }
//
// val serverScriptPath = args[0]
// MCPClient().use { client ->
// try {
// client.connectToServer(serverScriptPath)
// client.chatLoop()
// } catch (e: Exception) {
// println("Error: ${e.message}")
// e.printStackTrace()
// }
// }
//}


//fun main() = runBlocking {
// val client = MCPClient()
// client.connectToServer(System.getenv("SERVER_PATH")!!)
// client.chatLoop()
//}

Comment on lines +15 to +41
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please leave only one main function and remove all the commented-out code

Loading