├── .github
└── workflows
│ ├── gradle-publish.yml
│ └── validate-pr.yml
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── api
└── kotlin-sdk.api
├── build.gradle.kts
├── docs
├── -m-c-p -kotlin -s-d-k
│ ├── io.modelcontextprotocol.kotlin.sdk.client
│ │ ├── -client-options
│ │ │ ├── -client-options.html
│ │ │ ├── capabilities.html
│ │ │ └── index.html
│ │ ├── -client
│ │ │ ├── -client.html
│ │ │ ├── assert-request-handler-capability.html
│ │ │ ├── call-tool.html
│ │ │ ├── complete.html
│ │ │ ├── connect.html
│ │ │ ├── get-prompt.html
│ │ │ ├── get-server-capabilities.html
│ │ │ ├── get-server-version.html
│ │ │ ├── index.html
│ │ │ ├── list-prompts.html
│ │ │ ├── list-resource-templates.html
│ │ │ ├── list-resources.html
│ │ │ ├── list-tools.html
│ │ │ ├── ping.html
│ │ │ ├── read-resource.html
│ │ │ ├── send-roots-list-changed.html
│ │ │ ├── set-logging-level.html
│ │ │ ├── subscribe-resource.html
│ │ │ └── unsubscribe-resource.html
│ │ ├── -s-s-e-client-transport
│ │ │ ├── -s-s-e-client-transport.html
│ │ │ ├── close.html
│ │ │ ├── index.html
│ │ │ ├── on-close.html
│ │ │ ├── on-error.html
│ │ │ ├── on-message.html
│ │ │ ├── send.html
│ │ │ └── start.html
│ │ ├── -stdio-client-transport
│ │ │ ├── -stdio-client-transport.html
│ │ │ ├── close.html
│ │ │ ├── index.html
│ │ │ ├── on-close.html
│ │ │ ├── on-error.html
│ │ │ ├── on-message.html
│ │ │ ├── send.html
│ │ │ └── start.html
│ │ ├── -web-socket-client-transport
│ │ │ ├── -web-socket-client-transport.html
│ │ │ ├── index.html
│ │ │ └── session.html
│ │ ├── index.html
│ │ ├── mcp-sse-transport.html
│ │ ├── mcp-sse.html
│ │ ├── mcp-web-socket-transport.html
│ │ └── mcp-web-socket.html
│ ├── io.modelcontextprotocol.kotlin.sdk.server
│ │ ├── -m-c-p.html
│ │ ├── -registered-prompt
│ │ │ ├── -registered-prompt.html
│ │ │ ├── index.html
│ │ │ ├── message-provider.html
│ │ │ └── prompt.html
│ │ ├── -registered-resource
│ │ │ ├── -registered-resource.html
│ │ │ ├── index.html
│ │ │ ├── read-handler.html
│ │ │ └── resource.html
│ │ ├── -registered-tool
│ │ │ ├── -registered-tool.html
│ │ │ ├── handler.html
│ │ │ ├── index.html
│ │ │ └── tool.html
│ │ ├── -s-s-e-server-transport
│ │ │ ├── -s-s-e-server-transport.html
│ │ │ ├── close.html
│ │ │ ├── handle-message.html
│ │ │ ├── handle-post-message.html
│ │ │ ├── index.html
│ │ │ ├── on-close.html
│ │ │ ├── on-error.html
│ │ │ ├── on-message.html
│ │ │ ├── send.html
│ │ │ ├── session-id.html
│ │ │ └── start.html
│ │ ├── -server-options
│ │ │ ├── -server-options.html
│ │ │ ├── capabilities.html
│ │ │ └── index.html
│ │ ├── -server
│ │ │ ├── -server.html
│ │ │ ├── add-prompt.html
│ │ │ ├── add-prompts.html
│ │ │ ├── add-resource.html
│ │ │ ├── add-resources.html
│ │ │ ├── add-tool.html
│ │ │ ├── add-tools.html
│ │ │ ├── assert-request-handler-capability.html
│ │ │ ├── client-capabilities.html
│ │ │ ├── client-version.html
│ │ │ ├── create-message.html
│ │ │ ├── index.html
│ │ │ ├── list-roots.html
│ │ │ ├── on-close-callback.html
│ │ │ ├── on-initialized.html
│ │ │ ├── onclose.html
│ │ │ ├── ping.html
│ │ │ ├── send-logging-message.html
│ │ │ ├── send-prompt-list-changed.html
│ │ │ ├── send-resource-list-changed.html
│ │ │ ├── send-resource-updated.html
│ │ │ └── send-tool-list-changed.html
│ │ ├── -stdio-server-transport
│ │ │ ├── -stdio-server-transport.html
│ │ │ ├── close.html
│ │ │ ├── index.html
│ │ │ ├── on-close.html
│ │ │ ├── on-error.html
│ │ │ ├── on-message.html
│ │ │ ├── send.html
│ │ │ └── start.html
│ │ ├── -web-socket-mcp-server-transport
│ │ │ ├── -web-socket-mcp-server-transport.html
│ │ │ └── index.html
│ │ ├── index.html
│ │ ├── mcp-web-socket-transport.html
│ │ └── mcp-web-socket.html
│ ├── io.modelcontextprotocol.kotlin.sdk.shared
│ │ ├── -d-e-f-a-u-l-t_-r-e-q-u-e-s-t_-t-i-m-e-o-u-t.html
│ │ ├── -progress-callback
│ │ │ └── index.html
│ │ ├── -protocol-options
│ │ │ ├── -protocol-options.html
│ │ │ ├── enforce-strict-capabilities.html
│ │ │ ├── index.html
│ │ │ └── timeout.html
│ │ ├── -protocol
│ │ │ ├── -protocol.html
│ │ │ ├── assert-request-handler-capability.html
│ │ │ ├── close.html
│ │ │ ├── connect.html
│ │ │ ├── fallback-notification-handler.html
│ │ │ ├── fallback-request-handler.html
│ │ │ ├── index.html
│ │ │ ├── notification-handlers.html
│ │ │ ├── notification.html
│ │ │ ├── onclose.html
│ │ │ ├── onerror.html
│ │ │ ├── remove-notification-handler.html
│ │ │ ├── remove-request-handler.html
│ │ │ ├── request.html
│ │ │ ├── set-notification-handler.html
│ │ │ ├── set-request-handler.html
│ │ │ └── transport.html
│ │ ├── -read-buffer
│ │ │ ├── -read-buffer.html
│ │ │ ├── append.html
│ │ │ ├── clear.html
│ │ │ ├── index.html
│ │ │ └── read-message.html
│ │ ├── -request-handler-extra
│ │ │ ├── -request-handler-extra.html
│ │ │ └── index.html
│ │ ├── -request-options
│ │ │ ├── -request-options.html
│ │ │ ├── index.html
│ │ │ ├── on-progress.html
│ │ │ └── timeout.html
│ │ ├── -transport
│ │ │ ├── close.html
│ │ │ ├── index.html
│ │ │ ├── on-close.html
│ │ │ ├── on-error.html
│ │ │ ├── on-message.html
│ │ │ ├── send.html
│ │ │ └── start.html
│ │ ├── -web-socket-mcp-transport
│ │ │ ├── -web-socket-mcp-transport.html
│ │ │ ├── close.html
│ │ │ ├── index.html
│ │ │ ├── on-close.html
│ │ │ ├── on-error.html
│ │ │ ├── on-message.html
│ │ │ ├── send.html
│ │ │ └── start.html
│ │ └── index.html
│ ├── io.modelcontextprotocol.kotlin.sdk
│ │ ├── -blob-resource-contents
│ │ │ ├── -blob-resource-contents.html
│ │ │ ├── blob.html
│ │ │ ├── index.html
│ │ │ ├── mime-type.html
│ │ │ └── uri.html
│ │ ├── -call-tool-request
│ │ │ ├── -call-tool-request.html
│ │ │ ├── _meta.html
│ │ │ ├── arguments.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ └── name.html
│ │ ├── -call-tool-result-base
│ │ │ ├── content.html
│ │ │ ├── index.html
│ │ │ └── is-error.html
│ │ ├── -call-tool-result
│ │ │ ├── -call-tool-result.html
│ │ │ ├── _meta.html
│ │ │ ├── content.html
│ │ │ ├── index.html
│ │ │ └── is-error.html
│ │ ├── -cancelled-notification
│ │ │ ├── -cancelled-notification.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ ├── reason.html
│ │ │ └── request-id.html
│ │ ├── -client-capabilities
│ │ │ ├── -client-capabilities.html
│ │ │ ├── -roots
│ │ │ │ ├── -roots.html
│ │ │ │ ├── index.html
│ │ │ │ └── list-changed.html
│ │ │ ├── experimental.html
│ │ │ ├── index.html
│ │ │ ├── roots.html
│ │ │ └── sampling.html
│ │ ├── -client-notification
│ │ │ └── index.html
│ │ ├── -client-request
│ │ │ └── index.html
│ │ ├── -client-result
│ │ │ └── index.html
│ │ ├── -compatibility-call-tool-result
│ │ │ ├── -compatibility-call-tool-result.html
│ │ │ ├── _meta.html
│ │ │ ├── content.html
│ │ │ ├── index.html
│ │ │ ├── is-error.html
│ │ │ └── tool-result.html
│ │ ├── -complete-request
│ │ │ ├── -argument
│ │ │ │ ├── -argument.html
│ │ │ │ ├── index.html
│ │ │ │ ├── name.html
│ │ │ │ └── value.html
│ │ │ ├── -complete-request.html
│ │ │ ├── _meta.html
│ │ │ ├── argument.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ └── ref.html
│ │ ├── -complete-result
│ │ │ ├── -complete-result.html
│ │ │ ├── -completion
│ │ │ │ ├── -completion.html
│ │ │ │ ├── has-more.html
│ │ │ │ ├── index.html
│ │ │ │ ├── total.html
│ │ │ │ └── values.html
│ │ │ ├── _meta.html
│ │ │ ├── completion.html
│ │ │ └── index.html
│ │ ├── -create-message-request
│ │ │ ├── -create-message-request.html
│ │ │ ├── -include-context
│ │ │ │ ├── all-servers
│ │ │ │ │ └── index.html
│ │ │ │ ├── entries.html
│ │ │ │ ├── index.html
│ │ │ │ ├── none
│ │ │ │ │ └── index.html
│ │ │ │ ├── this-server
│ │ │ │ │ └── index.html
│ │ │ │ ├── value-of.html
│ │ │ │ └── values.html
│ │ │ ├── _meta.html
│ │ │ ├── include-context.html
│ │ │ ├── index.html
│ │ │ ├── max-tokens.html
│ │ │ ├── messages.html
│ │ │ ├── metadata.html
│ │ │ ├── method.html
│ │ │ ├── model-preferences.html
│ │ │ ├── stop-sequences.html
│ │ │ ├── system-prompt.html
│ │ │ └── temperature.html
│ │ ├── -create-message-result
│ │ │ ├── -create-message-result.html
│ │ │ ├── _meta.html
│ │ │ ├── content.html
│ │ │ ├── index.html
│ │ │ ├── model.html
│ │ │ ├── role.html
│ │ │ └── stop-reason.html
│ │ ├── -cursor
│ │ │ └── index.html
│ │ ├── -custom-meta
│ │ │ ├── -custom-meta.html
│ │ │ ├── _meta.html
│ │ │ └── index.html
│ │ ├── -custom-request
│ │ │ ├── -custom-request.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -embedded-resource
│ │ │ ├── -companion
│ │ │ │ ├── -t-y-p-e.html
│ │ │ │ └── index.html
│ │ │ ├── -embedded-resource.html
│ │ │ ├── index.html
│ │ │ ├── resource.html
│ │ │ └── type.html
│ │ ├── -empty-request-result
│ │ │ ├── -empty-request-result.html
│ │ │ ├── _meta.html
│ │ │ └── index.html
│ │ ├── -error-code
│ │ │ ├── -defined
│ │ │ │ ├── -connection-closed
│ │ │ │ │ └── index.html
│ │ │ │ ├── -internal-error
│ │ │ │ │ └── index.html
│ │ │ │ ├── -invalid-params
│ │ │ │ │ └── index.html
│ │ │ │ ├── -invalid-request
│ │ │ │ │ └── index.html
│ │ │ │ ├── -method-not-found
│ │ │ │ │ └── index.html
│ │ │ │ ├── -parse-error
│ │ │ │ │ └── index.html
│ │ │ │ ├── -request-timeout
│ │ │ │ │ └── index.html
│ │ │ │ ├── code.html
│ │ │ │ ├── entries.html
│ │ │ │ ├── index.html
│ │ │ │ ├── value-of.html
│ │ │ │ └── values.html
│ │ │ ├── -unknown
│ │ │ │ ├── -unknown.html
│ │ │ │ ├── code.html
│ │ │ │ └── index.html
│ │ │ ├── code.html
│ │ │ └── index.html
│ │ ├── -get-prompt-request
│ │ │ ├── -get-prompt-request.html
│ │ │ ├── _meta.html
│ │ │ ├── arguments.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ └── name.html
│ │ ├── -get-prompt-result
│ │ │ ├── -get-prompt-result.html
│ │ │ ├── _meta.html
│ │ │ ├── description.html
│ │ │ ├── index.html
│ │ │ └── messages.html
│ │ ├── -image-content
│ │ │ ├── -companion
│ │ │ │ ├── -t-y-p-e.html
│ │ │ │ └── index.html
│ │ │ ├── -image-content.html
│ │ │ ├── data.html
│ │ │ ├── index.html
│ │ │ ├── mime-type.html
│ │ │ └── type.html
│ │ ├── -implementation
│ │ │ ├── -implementation.html
│ │ │ ├── index.html
│ │ │ ├── name.html
│ │ │ └── version.html
│ │ ├── -initialize-request
│ │ │ ├── -initialize-request.html
│ │ │ ├── _meta.html
│ │ │ ├── capabilities.html
│ │ │ ├── client-info.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ └── protocol-version.html
│ │ ├── -initialize-result
│ │ │ ├── -initialize-result.html
│ │ │ ├── _meta.html
│ │ │ ├── capabilities.html
│ │ │ ├── index.html
│ │ │ ├── protocol-version.html
│ │ │ └── server-info.html
│ │ ├── -initialized-notification
│ │ │ ├── -initialized-notification.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -j-s-o-n-r-p-c-error
│ │ │ ├── -j-s-o-n-r-p-c-error.html
│ │ │ ├── code.html
│ │ │ ├── data.html
│ │ │ ├── index.html
│ │ │ └── message.html
│ │ ├── -j-s-o-n-r-p-c-message
│ │ │ └── index.html
│ │ ├── -j-s-o-n-r-p-c-notification
│ │ │ ├── -j-s-o-n-r-p-c-notification.html
│ │ │ ├── index.html
│ │ │ ├── jsonrpc.html
│ │ │ ├── method.html
│ │ │ └── params.html
│ │ ├── -j-s-o-n-r-p-c-request
│ │ │ ├── -j-s-o-n-r-p-c-request.html
│ │ │ ├── id.html
│ │ │ ├── index.html
│ │ │ ├── jsonrpc.html
│ │ │ ├── method.html
│ │ │ └── params.html
│ │ ├── -j-s-o-n-r-p-c-response
│ │ │ ├── -j-s-o-n-r-p-c-response.html
│ │ │ ├── error.html
│ │ │ ├── id.html
│ │ │ ├── index.html
│ │ │ ├── jsonrpc.html
│ │ │ └── result.html
│ │ ├── -j-s-o-n-r-p-c_-v-e-r-s-i-o-n.html
│ │ ├── -l-a-t-e-s-t_-p-r-o-t-o-c-o-l_-v-e-r-s-i-o-n.html
│ │ ├── -l-i-b_-v-e-r-s-i-o-n.html
│ │ ├── -list-prompts-request
│ │ │ ├── -list-prompts-request.html
│ │ │ ├── _meta.html
│ │ │ ├── cursor.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -list-prompts-result
│ │ │ ├── -list-prompts-result.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── next-cursor.html
│ │ │ └── prompts.html
│ │ ├── -list-resource-templates-request
│ │ │ ├── -list-resource-templates-request.html
│ │ │ ├── _meta.html
│ │ │ ├── cursor.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -list-resource-templates-result
│ │ │ ├── -list-resource-templates-result.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── next-cursor.html
│ │ │ └── resource-templates.html
│ │ ├── -list-resources-request
│ │ │ ├── -list-resources-request.html
│ │ │ ├── _meta.html
│ │ │ ├── cursor.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -list-resources-result
│ │ │ ├── -list-resources-result.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── next-cursor.html
│ │ │ └── resources.html
│ │ ├── -list-roots-request
│ │ │ ├── -list-roots-request.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -list-roots-result
│ │ │ ├── -list-roots-result.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ └── roots.html
│ │ ├── -list-tools-request
│ │ │ ├── -list-tools-request.html
│ │ │ ├── _meta.html
│ │ │ ├── cursor.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -list-tools-result
│ │ │ ├── -list-tools-result.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── next-cursor.html
│ │ │ └── tools.html
│ │ ├── -logging-level
│ │ │ ├── alert
│ │ │ │ └── index.html
│ │ │ ├── critical
│ │ │ │ └── index.html
│ │ │ ├── debug
│ │ │ │ └── index.html
│ │ │ ├── emergency
│ │ │ │ └── index.html
│ │ │ ├── entries.html
│ │ │ ├── error
│ │ │ │ └── index.html
│ │ │ ├── index.html
│ │ │ ├── info
│ │ │ │ └── index.html
│ │ │ ├── notice
│ │ │ │ └── index.html
│ │ │ ├── value-of.html
│ │ │ ├── values.html
│ │ │ └── warning
│ │ │ │ └── index.html
│ │ ├── -logging-message-notification
│ │ │ ├── -logging-message-notification.html
│ │ │ ├── -set-level-request
│ │ │ │ ├── -set-level-request.html
│ │ │ │ ├── _meta.html
│ │ │ │ ├── index.html
│ │ │ │ ├── level.html
│ │ │ │ └── method.html
│ │ │ ├── _meta.html
│ │ │ ├── data.html
│ │ │ ├── index.html
│ │ │ ├── level.html
│ │ │ ├── logger.html
│ │ │ └── method.html
│ │ ├── -mcp-error
│ │ │ ├── -mcp-error.html
│ │ │ ├── code.html
│ │ │ ├── data.html
│ │ │ ├── index.html
│ │ │ └── message.html
│ │ ├── -method
│ │ │ ├── -custom
│ │ │ │ ├── -custom.html
│ │ │ │ ├── index.html
│ │ │ │ └── value.html
│ │ │ ├── -defined
│ │ │ │ ├── -completion-complete
│ │ │ │ │ └── index.html
│ │ │ │ ├── -initialize
│ │ │ │ │ └── index.html
│ │ │ │ ├── -logging-set-level
│ │ │ │ │ └── index.html
│ │ │ │ ├── -notifications-cancelled
│ │ │ │ │ └── index.html
│ │ │ │ ├── -notifications-initialized
│ │ │ │ │ └── index.html
│ │ │ │ ├── -notifications-message
│ │ │ │ │ └── index.html
│ │ │ │ ├── -notifications-progress
│ │ │ │ │ └── index.html
│ │ │ │ ├── -notifications-prompts-list-changed
│ │ │ │ │ └── index.html
│ │ │ │ ├── -notifications-resources-list-changed
│ │ │ │ │ └── index.html
│ │ │ │ ├── -notifications-resources-updated
│ │ │ │ │ └── index.html
│ │ │ │ ├── -notifications-roots-list-changed
│ │ │ │ │ └── index.html
│ │ │ │ ├── -notifications-tools-list-changed
│ │ │ │ │ └── index.html
│ │ │ │ ├── -ping
│ │ │ │ │ └── index.html
│ │ │ │ ├── -prompts-get
│ │ │ │ │ └── index.html
│ │ │ │ ├── -prompts-list
│ │ │ │ │ └── index.html
│ │ │ │ ├── -resources-list
│ │ │ │ │ └── index.html
│ │ │ │ ├── -resources-read
│ │ │ │ │ └── index.html
│ │ │ │ ├── -resources-subscribe
│ │ │ │ │ └── index.html
│ │ │ │ ├── -resources-templates-list
│ │ │ │ │ └── index.html
│ │ │ │ ├── -resources-unsubscribe
│ │ │ │ │ └── index.html
│ │ │ │ ├── -roots-list
│ │ │ │ │ └── index.html
│ │ │ │ ├── -sampling-create-message
│ │ │ │ │ └── index.html
│ │ │ │ ├── -tools-call
│ │ │ │ │ └── index.html
│ │ │ │ ├── -tools-list
│ │ │ │ │ └── index.html
│ │ │ │ ├── entries.html
│ │ │ │ ├── index.html
│ │ │ │ ├── value-of.html
│ │ │ │ ├── value.html
│ │ │ │ └── values.html
│ │ │ ├── index.html
│ │ │ └── value.html
│ │ ├── -model-hint
│ │ │ ├── -model-hint.html
│ │ │ ├── index.html
│ │ │ └── name.html
│ │ ├── -model-preferences
│ │ │ ├── -model-preferences.html
│ │ │ ├── cost-priority.html
│ │ │ ├── hints.html
│ │ │ ├── index.html
│ │ │ ├── intelligence-priority.html
│ │ │ └── speed-priority.html
│ │ ├── -notification
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -paginated-request
│ │ │ ├── _meta.html
│ │ │ ├── cursor.html
│ │ │ └── index.html
│ │ ├── -paginated-result
│ │ │ ├── index.html
│ │ │ └── next-cursor.html
│ │ ├── -ping-request
│ │ │ ├── -ping-request.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -progress-base
│ │ │ ├── index.html
│ │ │ ├── progress.html
│ │ │ └── total.html
│ │ ├── -progress-notification
│ │ │ ├── -progress-notification.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ ├── progress-token.html
│ │ │ ├── progress.html
│ │ │ └── total.html
│ │ ├── -progress-token
│ │ │ └── index.html
│ │ ├── -progress
│ │ │ ├── -progress.html
│ │ │ ├── index.html
│ │ │ ├── progress.html
│ │ │ └── total.html
│ │ ├── -prompt-argument
│ │ │ ├── -prompt-argument.html
│ │ │ ├── description.html
│ │ │ ├── index.html
│ │ │ ├── name.html
│ │ │ └── required.html
│ │ ├── -prompt-list-changed-notification
│ │ │ ├── -prompt-list-changed-notification.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -prompt-message-content-text-or-image
│ │ │ └── index.html
│ │ ├── -prompt-message-content
│ │ │ ├── index.html
│ │ │ └── type.html
│ │ ├── -prompt-message
│ │ │ ├── -prompt-message.html
│ │ │ ├── content.html
│ │ │ ├── index.html
│ │ │ └── role.html
│ │ ├── -prompt-reference
│ │ │ ├── -companion
│ │ │ │ ├── -t-y-p-e.html
│ │ │ │ └── index.html
│ │ │ ├── -prompt-reference.html
│ │ │ ├── index.html
│ │ │ ├── name.html
│ │ │ └── type.html
│ │ ├── -prompt
│ │ │ ├── -prompt.html
│ │ │ ├── arguments.html
│ │ │ ├── description.html
│ │ │ ├── index.html
│ │ │ └── name.html
│ │ ├── -read-resource-request
│ │ │ ├── -read-resource-request.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ └── uri.html
│ │ ├── -read-resource-result
│ │ │ ├── -read-resource-result.html
│ │ │ ├── _meta.html
│ │ │ ├── contents.html
│ │ │ └── index.html
│ │ ├── -reference
│ │ │ ├── index.html
│ │ │ └── type.html
│ │ ├── -request-id
│ │ │ └── index.html
│ │ ├── -request-result
│ │ │ └── index.html
│ │ ├── -request
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -resource-contents
│ │ │ ├── index.html
│ │ │ ├── mime-type.html
│ │ │ └── uri.html
│ │ ├── -resource-list-changed-notification
│ │ │ ├── -resource-list-changed-notification.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -resource-reference
│ │ │ ├── -companion
│ │ │ │ ├── -t-y-p-e.html
│ │ │ │ └── index.html
│ │ │ ├── -resource-reference.html
│ │ │ ├── index.html
│ │ │ ├── type.html
│ │ │ └── uri.html
│ │ ├── -resource-template
│ │ │ ├── -resource-template.html
│ │ │ ├── description.html
│ │ │ ├── index.html
│ │ │ ├── mime-type.html
│ │ │ ├── name.html
│ │ │ └── uri-template.html
│ │ ├── -resource-updated-notification
│ │ │ ├── -resource-updated-notification.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ └── uri.html
│ │ ├── -resource
│ │ │ ├── -resource.html
│ │ │ ├── description.html
│ │ │ ├── index.html
│ │ │ ├── mime-type.html
│ │ │ ├── name.html
│ │ │ └── uri.html
│ │ ├── -role
│ │ │ ├── assistant
│ │ │ │ └── index.html
│ │ │ ├── entries.html
│ │ │ ├── index.html
│ │ │ ├── user
│ │ │ │ └── index.html
│ │ │ ├── value-of.html
│ │ │ └── values.html
│ │ ├── -root
│ │ │ ├── -root.html
│ │ │ ├── index.html
│ │ │ ├── name.html
│ │ │ └── uri.html
│ │ ├── -roots-list-changed-notification
│ │ │ ├── -roots-list-changed-notification.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -s-u-p-p-o-r-t-e-d_-p-r-o-t-o-c-o-l_-v-e-r-s-i-o-n-s.html
│ │ ├── -sampling-message
│ │ │ ├── -sampling-message.html
│ │ │ ├── content.html
│ │ │ ├── index.html
│ │ │ └── role.html
│ │ ├── -server-capabilities
│ │ │ ├── -prompts
│ │ │ │ ├── -prompts.html
│ │ │ │ ├── index.html
│ │ │ │ └── list-changed.html
│ │ │ ├── -resources
│ │ │ │ ├── -resources.html
│ │ │ │ ├── index.html
│ │ │ │ ├── list-changed.html
│ │ │ │ └── subscribe.html
│ │ │ ├── -server-capabilities.html
│ │ │ ├── -tools
│ │ │ │ ├── -tools.html
│ │ │ │ ├── index.html
│ │ │ │ └── list-changed.html
│ │ │ ├── experimental.html
│ │ │ ├── index.html
│ │ │ ├── logging.html
│ │ │ ├── prompts.html
│ │ │ ├── resources.html
│ │ │ ├── sampling.html
│ │ │ └── tools.html
│ │ ├── -server-notification
│ │ │ └── index.html
│ │ ├── -server-request
│ │ │ └── index.html
│ │ ├── -server-result
│ │ │ └── index.html
│ │ ├── -stop-reason
│ │ │ ├── -end-turn
│ │ │ │ ├── index.html
│ │ │ │ └── value.html
│ │ │ ├── -max-tokens
│ │ │ │ ├── index.html
│ │ │ │ └── value.html
│ │ │ ├── -other
│ │ │ │ ├── -other.html
│ │ │ │ ├── index.html
│ │ │ │ └── value.html
│ │ │ ├── -stop-sequence
│ │ │ │ ├── index.html
│ │ │ │ └── value.html
│ │ │ ├── index.html
│ │ │ └── value.html
│ │ ├── -subscribe-request
│ │ │ ├── -subscribe-request.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ └── uri.html
│ │ ├── -text-content
│ │ │ ├── -companion
│ │ │ │ ├── -t-y-p-e.html
│ │ │ │ └── index.html
│ │ │ ├── -text-content.html
│ │ │ ├── index.html
│ │ │ ├── text.html
│ │ │ └── type.html
│ │ ├── -text-resource-contents
│ │ │ ├── -text-resource-contents.html
│ │ │ ├── index.html
│ │ │ ├── mime-type.html
│ │ │ ├── text.html
│ │ │ └── uri.html
│ │ ├── -tool-list-changed-notification
│ │ │ ├── -tool-list-changed-notification.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -tool
│ │ │ ├── -input
│ │ │ │ ├── -input.html
│ │ │ │ ├── index.html
│ │ │ │ ├── properties.html
│ │ │ │ └── type.html
│ │ │ ├── -tool.html
│ │ │ ├── description.html
│ │ │ ├── index.html
│ │ │ ├── input-schema.html
│ │ │ └── name.html
│ │ ├── -unknown-content
│ │ │ ├── -unknown-content.html
│ │ │ ├── index.html
│ │ │ └── type.html
│ │ ├── -unknown-method-request-or-notification
│ │ │ ├── -unknown-method-request-or-notification.html
│ │ │ ├── index.html
│ │ │ └── method.html
│ │ ├── -unknown-reference
│ │ │ ├── -unknown-reference.html
│ │ │ ├── index.html
│ │ │ └── type.html
│ │ ├── -unknown-resource-contents
│ │ │ ├── -unknown-resource-contents.html
│ │ │ ├── index.html
│ │ │ ├── mime-type.html
│ │ │ └── uri.html
│ │ ├── -unsubscribe-request
│ │ │ ├── -unsubscribe-request.html
│ │ │ ├── _meta.html
│ │ │ ├── index.html
│ │ │ ├── method.html
│ │ │ └── uri.html
│ │ ├── -with-meta
│ │ │ ├── -companion
│ │ │ │ ├── -empty.html
│ │ │ │ └── index.html
│ │ │ ├── _meta.html
│ │ │ └── index.html
│ │ └── index.html
│ └── package-list
├── images
│ ├── anchor-copy-button.svg
│ ├── arrow_down.svg
│ ├── burger.svg
│ ├── copy-icon.svg
│ ├── copy-successful-icon.svg
│ ├── footer-go-to-link.svg
│ ├── go-to-top-icon.svg
│ ├── homepage.svg
│ ├── logo-icon.svg
│ ├── nav-icons
│ │ ├── abstract-class-kotlin.svg
│ │ ├── abstract-class.svg
│ │ ├── annotation-kotlin.svg
│ │ ├── annotation.svg
│ │ ├── class-kotlin.svg
│ │ ├── class.svg
│ │ ├── enum-kotlin.svg
│ │ ├── enum.svg
│ │ ├── exception-class.svg
│ │ ├── field-value.svg
│ │ ├── field-variable.svg
│ │ ├── function.svg
│ │ ├── interface-kotlin.svg
│ │ ├── interface.svg
│ │ ├── object.svg
│ │ └── typealias-kotlin.svg
│ └── theme-toggle.svg
├── index.html
├── navigation.html
├── scripts
│ ├── clipboard.js
│ ├── main.js
│ ├── navigation-loader.js
│ ├── pages.json
│ ├── platform-content-handler.js
│ ├── prism.js
│ ├── sourceset_dependencies.js
│ └── symbol-parameters-wrapper_deferred.js
├── styles
│ ├── font-jb-sans-auto.css
│ ├── logo-styles.css
│ ├── main.css
│ ├── prism.css
│ └── style.css
└── ui-kit
│ ├── ui-kit.min.css
│ └── ui-kit.min.js
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── samples
├── kotlin-mcp-client
│ ├── .gitignore
│ ├── README.md
│ ├── build.gradle.kts
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── settings.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ └── io
│ │ └── modelcontextprotocol
│ │ └── sample
│ │ └── client
│ │ ├── MCPClient.kt
│ │ └── main.kt
├── kotlin-mcp-server
│ ├── .gitignore
│ ├── .idea
│ │ ├── .gitignore
│ │ ├── gradle.xml
│ │ ├── kotlinc.xml
│ │ ├── misc.xml
│ │ └── vcs.xml
│ ├── README.md
│ ├── build.gradle.kts
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ ├── gradle-wrapper.jar
│ │ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── settings.gradle.kts
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── server.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── main.jvm.kt
│ │ └── wasmJsMain
│ │ └── kotlin
│ │ └── main.wasmJs.kt
└── weather-stdio-server
│ ├── .gitignore
│ ├── README.md
│ ├── build.gradle.kts
│ ├── gradle.properties
│ ├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── gradlew
│ ├── gradlew.bat
│ ├── settings.gradle.kts
│ └── src
│ ├── main
│ └── kotlin
│ │ └── io
│ │ └── modelcontextprotocol
│ │ └── sample
│ │ └── server
│ │ ├── McpWeatherServer.kt
│ │ ├── WeatherApi.kt
│ │ └── main.kt
│ └── test
│ └── kotlin
│ └── io
│ └── modelcontextprotocol
│ └── sample
│ └── client
│ └── ClientStdio.kt
├── settings.gradle.kts
└── src
├── commonMain
└── kotlin
│ └── io
│ └── modelcontextprotocol
│ └── kotlin
│ └── sdk
│ ├── client
│ ├── Client.kt
│ ├── KtorClient.kt
│ ├── SSEClientTransport.kt
│ ├── StdioClientTransport.kt
│ ├── WebSocketClientTransport.kt
│ └── WebSocketMcpKtorClientExtensions.kt
│ ├── internal
│ └── utils.kt
│ ├── server
│ ├── KtorServer.kt
│ ├── SSEServerTransport.kt
│ ├── Server.kt
│ ├── StdioServerTransport.kt
│ ├── WebSocketMcpKtorServerExtensions.kt
│ └── WebSocketMcpServerTransport.kt
│ ├── shared
│ ├── Protocol.kt
│ ├── ReadBuffer.kt
│ ├── Transport.kt
│ └── WebSocketMcpTransport.kt
│ ├── types.kt
│ └── types.util.kt
├── commonTest
└── kotlin
│ ├── AudioContentSerializationTest.kt
│ ├── CallToolResultUtilsTest.kt
│ └── io
│ └── modelcontextprotocol
│ └── kotlin
│ └── sdk
│ ├── InMemoryTransport.kt
│ ├── ToolSerializationTest.kt
│ ├── client
│ ├── BaseTransportTest.kt
│ ├── InMemoryTransportTest.kt
│ ├── SseTransportTest.kt
│ ├── TypesTest.kt
│ └── WebSocketTransportTest.kt
│ ├── integration
│ └── SseIntegrationTest.kt
│ └── shared
│ └── ReadBufferTest.kt
├── iosMain
└── kotlin
│ └── io
│ └── modelcontextprotocol
│ └── kotlin
│ └── sdk
│ └── internal
│ └── utils.ios.kt
├── jsMain
└── kotlin
│ └── io
│ └── modelcontextprotocol
│ └── kotlin
│ └── sdk
│ └── internal
│ └── utils.js.kt
├── jvmMain
└── java
│ └── io
│ └── modelcontextprotocol
│ └── kotlin
│ └── sdk
│ └── internal
│ └── utils.jvm.kt
├── jvmTest
└── kotlin
│ ├── client
│ ├── ClientIntegrationTest.kt
│ ├── ClientTest.kt
│ └── StdioClientTransportTest.kt
│ └── server
│ ├── ServerTest.kt
│ └── StdioServerTransportTest.kt
└── wasmJsMain
└── kotlin
└── io
└── modelcontextprotocol
└── kotlin
└── sdk
└── internal
└── utils.wasmJs.kt
/.github/workflows/gradle-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 | # This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
6 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
7 |
8 | name: Release
9 |
10 | on:
11 | release:
12 | types: [created]
13 |
14 | jobs:
15 | build:
16 | runs-on: macos-latest
17 | environment: release
18 |
19 | permissions:
20 | contents: write
21 | packages: write
22 |
23 | steps:
24 | - uses: actions/checkout@v4
25 | - name: Set up JDK 21
26 | uses: actions/setup-java@v4
27 | with:
28 | java-version: '21'
29 | distribution: 'temurin'
30 | server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
31 | server-username: JRELEASER_MAVENCENTRAL_USERNAME
32 | server-password: JRELEASER_MAVENCENTRAL_PASSWORD
33 |
34 | - name: Setup Gradle
35 | uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
36 |
37 | - name: Verify publication configuration
38 | run: ./gradlew jreleaserConfig
39 | env:
40 | JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.OSSRH_USERNAME }}
41 | JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
42 | JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
43 | JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}
44 | JRELEASER_GPG_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }}
45 | JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
46 |
47 | - name: Clean Build with Gradle
48 | run: ./gradlew clean build
49 | env:
50 | JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.OSSRH_USERNAME }}
51 | JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
52 | JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
53 | JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}
54 | JRELEASER_GPG_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }}
55 |
56 | - name: Publish to OSSRH repository
57 | run: ./gradlew publish jreleaserFullRelease
58 | env:
59 | JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.OSSRH_USERNAME }}
60 | JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
61 | JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }}
62 | JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }}
63 | JRELEASER_GPG_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }}
64 | JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
65 |
--------------------------------------------------------------------------------
/.github/workflows/validate-pr.yml:
--------------------------------------------------------------------------------
1 | name: Validate PR
2 |
3 | on:
4 | workflow_dispatch:
5 | pull_request:
6 | types: [auto_merge_enabled]
7 |
8 | jobs:
9 | validate-pr:
10 | runs-on: macos-latest
11 | name: Validate PR
12 | steps:
13 | - uses: actions/checkout@v4
14 | - name: Set up JDK 21
15 | uses: actions/setup-java@v4
16 | with:
17 | java-version: '21'
18 | distribution: 'temurin'
19 | - name: Setup Gradle
20 | uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0
21 |
22 | - name: Clean Build with Gradle
23 | run: ./gradlew clean build
24 |
25 | - name: Disable Auto-Merge on Fail
26 | if: failure()
27 | run: gh pr merge --disable-auto "$PR_URL"
28 | env:
29 | PR_URL: ${{github.event.pull_request.html_url}}
30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | !gradle/wrapper/gradle-wrapper.jar
4 | !**/src/main/**/build/
5 | !**/src/test/**/build/
6 |
7 | ### IntelliJ IDEA ###
8 | .idea/modules.xml
9 | .idea/misc.xml
10 | .idea/jarRepositories.xml
11 | .idea/compiler.xml
12 | .idea/libraries/
13 | .idea
14 | *.iws
15 | *.iml
16 | *.ipr
17 | out/
18 | !**/src/main/**/out/
19 | !**/src/test/**/out/
20 |
21 | ### Kotlin ###
22 | .kotlin
23 | yarn.lock
24 |
25 | ### Eclipse ###
26 | .apt_generated
27 | .classpath
28 | .factorypath
29 | .project
30 | .settings
31 | .springBeans
32 | .sts4-cache
33 | bin/
34 | !**/src/main/**/bin/
35 | !**/src/test/**/bin/
36 |
37 | ### NetBeans ###
38 | /nbproject/private/
39 | /nbbuild/
40 | /dist/
41 | /nbdist/
42 | /.nb-gradle/
43 |
44 | ### VS Code ###
45 | .vscode/
46 |
47 | ### Mac OS ###
48 | .DS_Store
49 |
50 | ### Node.js ###
51 | node_modules
52 | dist
53 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | There are two main ways to contribute to the project — submitting issues and submitting
4 | fixes/changes/improvements via pull requests.
5 |
6 | ## Submitting issues
7 |
8 | Both bug reports and feature requests are welcome.
9 | Submit issues [here](https://github.com/modelcontextprotocol/kotlin-sdk/issues).
10 |
11 | * Search for existing issues to avoid reporting duplicates.
12 | * When submitting a bug report:
13 | * Test it against the most recently released version. It might have already been fixed.
14 | * Include the code that reproduces the problem. Provide a minimal, complete, and reproducible example.
15 | * However, don't put off reporting any weird or rarely appearing issues just because you cannot consistently
16 | reproduce them.
17 | * If the bug is related to behavior, explain what behavior you expected and what you got.
18 | * When submitting a feature request:
19 | * Explain why you need the feature — what's your use-case, what's your domain.
20 | * Explaining the problem you face is more important than suggesting a solution.
21 | Even if you don't have a proposed solution, please report your problem.
22 | * If there is an alternative way to do what you need, then show the code of the alternative.
23 |
24 | ## Submitting PRs
25 |
26 | We love PRs. Submit PRs [here](https://github.com/modelcontextprotocol/kotlin-sdk/pulls).
27 | However, please keep in mind that maintainers will have to support the resulting code of the project,
28 | so do familiarize yourself with the following guidelines.
29 |
30 | * All development (both new features and bug fixes) is performed in the `main` branch.
31 | * Please base your PRs on the `main` branch.
32 | * PR should be linked with the issue, excluding minor documentation changes, adding unit tests, and fixing typos.
33 | * If you make any code changes:
34 | * Follow the [Kotlin Coding Conventions](https://kotlinlang.org/docs/reference/coding-conventions.html).
35 | * [Build the project](#building) to ensure it all works and passes the tests.
36 | * If you fix a bug:
37 | * Write the test that reproduces the bug.
38 | * Fixes without tests are accepted only in exceptional circumstances if it can be shown that writing the
39 | corresponding test is too hard or otherwise impractical.
40 | * If you introduce any new public APIs:
41 | * All new APIs must come with documentation and tests.
42 | * If you plan API additions, please start by submitting an issue with the proposed API design to gather community
43 | feedback.
44 | * [Contact the maintainers](#contacting-maintainers) to coordinate any great work in advance via submitting an
45 | issue.
46 | * If you fix documentation:
47 | * If you plan extensive rewrites/additions to the docs, then
48 | please [contact the maintainers](#contacting-maintainers) to coordinate the work in advance.
49 |
50 | ## Style guides
51 |
52 | A few things to remember:
53 |
54 | * Your code should conform to
55 | the official [Kotlin code style guide](https://kotlinlang.org/docs/reference/coding-conventions.html).
56 | Code style is managed by [EditorConfig](https://www.jetbrains.com/help/idea/editorconfig.html),
57 | so make sure the EditorConfig plugin is enabled in the IDE.
58 | * Every public API (including functions, classes, objects and so on) should be documented,
59 | every parameter, property, return types and exceptions should be described properly.
60 |
61 | ## Commit messages
62 |
63 | * Commit messages should be written in English
64 | * They should be written in present tense using imperative mood
65 | ("Fix" instead of "Fixes", "Improve" instead of "Improved").
66 | Add the related bug reference to a commit message (bug number after a hash character between round braces).
67 |
68 | See [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/)
69 |
70 | ## Building
71 |
72 | ### Requirements
73 |
74 | * To build MCP Kotlin SDK, JDK version 21 or higher is required. Make sure this is your default JDK (`JAVA_HOME` is set
75 | accordingly)
76 | * The project can be opened in IntelliJ IDEA without additional prerequisites.
77 |
78 | ### Building MCP Kotlin SDK from source
79 |
80 | * Run `./gradlew assemble` to build the project and produce the corresponding artifacts.
81 | * Run `./gradlew test` to test the module and speed up development.
82 | * Run `./gradlew build` to build the project, which also runs all the tests.
83 |
84 | ## Contacting maintainers
85 |
86 | * If something cannot be done, not convenient, or does not work — submit an [issue](#submitting-issues).
87 | * "How to do something" questions — [StackOverflow](https://stackoverflow.com).
88 | * Discussions and general inquiries — use [KotlinLang Slack](https://kotl.in/slack).
89 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Anthropic, PBC
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-complete-request/ref.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ref
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
ref
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-create-message-result/role.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | role
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
role
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-j-s-o-n-r-p-c-error/code.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | code
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
code
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-j-s-o-n-r-p-c-request/id.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | id
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
id
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-j-s-o-n-r-p-c-response/id.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | id
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
id
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-l-i-b_-v-e-r-s-i-o-n.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | LIB_VERSION
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
LIB_VERSION
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-mcp-error/code.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | code
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
code
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-prompt-message/role.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | role
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
role
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-sampling-message/role.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | role
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
role
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/images/anchor-copy-button.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/docs/images/arrow_down.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/images/burger.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/docs/images/copy-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/images/copy-successful-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/images/footer-go-to-link.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/images/go-to-top-icon.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/docs/images/homepage.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/images/logo-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
15 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/abstract-class-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
27 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/abstract-class.svg:
--------------------------------------------------------------------------------
1 |
4 |
23 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/annotation-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/annotation.svg:
--------------------------------------------------------------------------------
1 |
4 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/class-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/class.svg:
--------------------------------------------------------------------------------
1 |
4 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/enum-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/enum.svg:
--------------------------------------------------------------------------------
1 |
4 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/exception-class.svg:
--------------------------------------------------------------------------------
1 |
4 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/field-value.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
11 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/field-variable.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
11 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/function.svg:
--------------------------------------------------------------------------------
1 |
4 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/interface-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/interface.svg:
--------------------------------------------------------------------------------
1 |
4 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/object.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/typealias-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
14 |
--------------------------------------------------------------------------------
/docs/images/theme-toggle.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/docs/scripts/clipboard.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 | */
4 |
5 | window.addEventListener('load', () => {
6 | document.querySelectorAll('span.copy-icon').forEach(element => {
7 | element.addEventListener('click', (el) => copyElementsContentToClipboard(element));
8 | })
9 |
10 | document.querySelectorAll('span.anchor-icon').forEach(element => {
11 | element.addEventListener('click', (el) => {
12 | if(element.hasAttribute('pointing-to')){
13 | const location = hrefWithoutCurrentlyUsedAnchor() + '#' + element.getAttribute('pointing-to')
14 | copyTextToClipboard(element, location)
15 | }
16 | });
17 | })
18 | })
19 |
20 | const copyElementsContentToClipboard = (element) => {
21 | const selection = window.getSelection();
22 | const range = document.createRange();
23 | range.selectNodeContents(element.parentNode.parentNode);
24 | selection.removeAllRanges();
25 | selection.addRange(range);
26 |
27 | copyAndShowPopup(element, () => selection.removeAllRanges())
28 | }
29 |
30 | const copyTextToClipboard = (element, text) => {
31 | var textarea = document.createElement("textarea");
32 | textarea.textContent = text;
33 | textarea.style.position = "fixed";
34 | document.body.appendChild(textarea);
35 | textarea.select();
36 |
37 | copyAndShowPopup(element, () => document.body.removeChild(textarea))
38 | }
39 |
40 | const copyAndShowPopup = (element, after) => {
41 | try {
42 | document.execCommand('copy');
43 | element.nextElementSibling.classList.add('active-popup');
44 | setTimeout(() => {
45 | element.nextElementSibling.classList.remove('active-popup');
46 | }, 1200);
47 | } catch (e) {
48 | console.error('Failed to write to clipboard:', e)
49 | }
50 | finally {
51 | if(after) after()
52 | }
53 | }
54 |
55 | const hrefWithoutCurrentlyUsedAnchor = () => window.location.href.split('#')[0]
56 |
57 |
--------------------------------------------------------------------------------
/docs/scripts/navigation-loader.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 | */
4 |
5 | navigationPageText = fetch(pathToRoot + "navigation.html").then(response => response.text())
6 |
7 | displayNavigationFromPage = () => {
8 | navigationPageText.then(data => {
9 | document.getElementById("sideMenu").innerHTML = data;
10 | }).then(() => {
11 | document.querySelectorAll(".overview > a").forEach(link => {
12 | link.setAttribute("href", pathToRoot + link.getAttribute("href"));
13 | })
14 | }).then(() => {
15 | document.querySelectorAll(".sideMenuPart").forEach(nav => {
16 | if (!nav.classList.contains("hidden"))
17 | nav.classList.add("hidden")
18 | })
19 | }).then(() => {
20 | revealNavigationForCurrentPage()
21 | }).then(() => {
22 | scrollNavigationToSelectedElement()
23 | })
24 | document.querySelectorAll('.footer a[href^="#"]').forEach(anchor => {
25 | anchor.addEventListener('click', function (e) {
26 | e.preventDefault();
27 | document.querySelector(this.getAttribute('href')).scrollIntoView({
28 | behavior: 'smooth'
29 | });
30 | });
31 | });
32 | }
33 |
34 | revealNavigationForCurrentPage = () => {
35 | let pageId = document.getElementById("content").attributes["pageIds"].value.toString();
36 | let parts = document.querySelectorAll(".sideMenuPart");
37 | let found = 0;
38 | do {
39 | parts.forEach(part => {
40 | if (part.attributes['pageId'].value.indexOf(pageId) !== -1 && found === 0) {
41 | found = 1;
42 | if (part.classList.contains("hidden")) {
43 | part.classList.remove("hidden");
44 | part.setAttribute('data-active', "");
45 | }
46 | revealParents(part)
47 | }
48 | });
49 | pageId = pageId.substring(0, pageId.lastIndexOf("/"))
50 | } while (pageId.indexOf("/") !== -1 && found === 0)
51 | };
52 | revealParents = (part) => {
53 | if (part.classList.contains("sideMenuPart")) {
54 | if (part.classList.contains("hidden"))
55 | part.classList.remove("hidden");
56 | revealParents(part.parentNode)
57 | }
58 | };
59 |
60 | scrollNavigationToSelectedElement = () => {
61 | let selectedElement = document.querySelector('div.sideMenuPart[data-active]')
62 | if (selectedElement == null) { // nothing selected, probably just the main page opened
63 | return
64 | }
65 |
66 | let hasIcon = selectedElement.querySelectorAll(":scope > div.overview span.nav-icon").length > 0
67 |
68 | // for instance enums also have children and are expandable, but are not package/module elements
69 | let isPackageElement = selectedElement.children.length > 1 && !hasIcon
70 | if (isPackageElement) {
71 | // if package is selected or linked, it makes sense to align it to top
72 | // so that you can see all the members it contains
73 | selectedElement.scrollIntoView(true)
74 | } else {
75 | // if a member within a package is linked, it makes sense to center it since it,
76 | // this should make it easier to look at surrounding members
77 | selectedElement.scrollIntoView({
78 | behavior: 'auto',
79 | block: 'center',
80 | inline: 'center'
81 | })
82 | }
83 | }
84 |
85 | /*
86 | This is a work-around for safari being IE of our times.
87 | It doesn't fire a DOMContentLoaded, presumabely because eventListener is added after it wants to do it
88 | */
89 | if (document.readyState == 'loading') {
90 | window.addEventListener('DOMContentLoaded', () => {
91 | displayNavigationFromPage()
92 | })
93 | } else {
94 | displayNavigationFromPage()
95 | }
96 |
--------------------------------------------------------------------------------
/docs/scripts/sourceset_dependencies.js:
--------------------------------------------------------------------------------
1 | sourceset_dependencies='{":/main":[]}'
--------------------------------------------------------------------------------
/docs/scripts/symbol-parameters-wrapper_deferred.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 | */
4 |
5 | // helps with some corner cases where starts working already,
6 | // but the signature is not yet long enough to be wrapped
7 | (function() {
8 | const leftPaddingPx = 60;
9 |
10 | function createNbspIndent() {
11 | let indent = document.createElement("span");
12 | indent.append(document.createTextNode("\u00A0\u00A0\u00A0\u00A0"));
13 | indent.classList.add("nbsp-indent");
14 | return indent;
15 | }
16 |
17 | function wrapSymbolParameters(entry) {
18 | const symbol = entry.target;
19 | const symbolBlockWidth = entry.borderBoxSize && entry.borderBoxSize[0] && entry.borderBoxSize[0].inlineSize;
20 |
21 | // Even though the script is marked as `defer` and we wait for `DOMContentLoaded` event,
22 | // or if this block is a part of hidden tab, it can happen that `symbolBlockWidth` is 0,
23 | // indicating that something hasn't been loaded.
24 | // In this case, observer will be triggered onсe again when it will be ready.
25 | if (symbolBlockWidth > 0) {
26 | const node = symbol.querySelector(".parameters");
27 |
28 | if (node) {
29 | // if window resize happened and observer was triggered, reset previously wrapped
30 | // parameters as they might not need wrapping anymore, and check again
31 | node.classList.remove("wrapped");
32 | node.querySelectorAll(".parameter .nbsp-indent")
33 | .forEach(indent => indent.remove());
34 |
35 | const innerTextWidth = Array.from(symbol.children)
36 | .filter(it => !it.classList.contains("block")) // blocks are usually on their own (like annotations), so ignore it
37 | .map(it => it.getBoundingClientRect().width)
38 | .reduce((a, b) => a + b, 0);
39 |
40 | // if signature text takes up more than a single line, wrap params for readability
41 | if (innerTextWidth > (symbolBlockWidth - leftPaddingPx)) {
42 | node.classList.add("wrapped");
43 | node.querySelectorAll(".parameter").forEach(param => {
44 | // has to be a physical indent so that it can be copied. styles like
45 | // paddings and `::before { content: " " }` do not work for that
46 | param.prepend(createNbspIndent());
47 | });
48 | }
49 | }
50 | }
51 | }
52 |
53 | const symbolsObserver = new ResizeObserver(entries => entries.forEach(wrapSymbolParameters));
54 |
55 | function initHandlers() {
56 | document.querySelectorAll("div.symbol").forEach(symbol => symbolsObserver.observe(symbol));
57 | }
58 |
59 | if (document.readyState === 'loading') window.addEventListener('DOMContentLoaded', initHandlers);
60 | else initHandlers();
61 |
62 | // ToDo: Add `unobserve` if dokka will be SPA-like:
63 | // https://github.com/w3c/csswg-drafts/issues/5155
64 | })();
65 |
--------------------------------------------------------------------------------
/docs/styles/font-jb-sans-auto.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 | */
4 |
5 | /* Light weight */
6 | @font-face {
7 | font-family: 'JetBrains Sans';
8 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Light.woff2') format('woff2'), url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Light.woff') format('woff');
9 | font-weight: 300;
10 | font-style: normal;
11 | }
12 | /* Regular weight */
13 | @font-face {
14 | font-family: 'JetBrains Sans';
15 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Regular.woff2') format('woff2'), url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-Regular.woff') format('woff');
16 | font-weight: 400;
17 | font-style: normal;
18 | }
19 | /* SemiBold weight */
20 | @font-face {
21 | font-family: 'JetBrains Sans';
22 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-SemiBold.woff2') format('woff2'), url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans-SemiBold.woff') format('woff');
23 | font-weight: 600;
24 | font-style: normal;
25 | }
26 |
27 | @supports (font-variation-settings: normal) {
28 | @font-face {
29 | font-family: 'JetBrains Sans';
30 | src: url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans.woff2') format('woff2 supports variations'),
31 | url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans.woff2') format('woff2-variations'),
32 | url('https://resources.jetbrains.com/storage/jetbrains-sans/JetBrainsSans.woff') format('woff-variations');
33 | font-weight: 100 900;
34 | font-style: normal;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/docs/styles/logo-styles.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2014-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3 | */
4 |
5 | :root {
6 | --dokka-logo-image-url: url('../images/logo-icon.svg');
7 | --dokka-logo-height: 50px;
8 | --dokka-logo-width: 50px;
9 | }
10 |
--------------------------------------------------------------------------------
/docs/styles/prism.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Custom Dokka styles
3 | */
4 | code .token {
5 | white-space: pre;
6 | }
7 |
8 | /**
9 | * Styles based on webhelp's prism.js styles
10 | * Changes:
11 | * - Since webhelp's styles are in .pcss, they use nesting which is not achievable in native CSS
12 | * so nested css blocks have been unrolled (like dark theme).
13 | * - Webhelp uses "Custom Class" prism.js plugin, so all of their prism classes are prefixed with "--prism".
14 | * Dokka doesn't seem to need this plugin at the moment, so all "--prism" prefixes have been removed.
15 | * - Removed all styles related to `pre` and `code` tags. Kotlinlang's resulting styles are so spread out and complicated
16 | * that it's difficult to gather in one place. Instead use code styles defined in the main Dokka styles,
17 | * which at the moment looks fairly similar.
18 | *
19 | * Based on prism.js default theme
20 | * Based on dabblet (http://dabblet.com)
21 | * @author Lea Verou
22 | */
23 |
24 | .token.comment,
25 | .token.prolog,
26 | .token.doctype,
27 | .token.cdata {
28 | color: #8c8c8c;
29 | }
30 |
31 | .token.punctuation {
32 | color: #999;
33 | }
34 |
35 | .token.namespace {
36 | opacity: 0.7;
37 | }
38 |
39 | .token.property,
40 | .token.tag,
41 | .token.boolean,
42 | .token.number,
43 | .token.constant,
44 | .token.symbol,
45 | .token.deleted {
46 | color: #871094;
47 | }
48 |
49 | .token.selector,
50 | .token.attr-name,
51 | .token.string,
52 | .token.char,
53 | .token.builtin,
54 | .token.inserted {
55 | color: #067d17;
56 | }
57 |
58 | .token.operator,
59 | .token.entity,
60 | .token.url,
61 | .language-css .token.string,
62 | .style .token.string {
63 | color: #9a6e3a;
64 | /* This background color was intended by the author of this theme. */
65 | background: hsla(0, 0%, 100%, 0.5);
66 | }
67 |
68 | .token.atrule,
69 | .token.attr-value,
70 | .token.keyword {
71 | font-size: inherit; /* to override .keyword */
72 | color: #0033b3;
73 | }
74 |
75 | .token.function {
76 | color: #00627a;
77 | }
78 |
79 | .token.class-name {
80 | color: #000000;
81 | }
82 |
83 | .token.regex,
84 | .token.important,
85 | .token.variable {
86 | color: #871094;
87 | }
88 |
89 | .token.important,
90 | .token.bold {
91 | font-weight: bold;
92 | }
93 | .token.italic {
94 | font-style: italic;
95 | }
96 |
97 | .token.entity {
98 | cursor: help;
99 | }
100 |
101 | .token.operator {
102 | background: none;
103 | }
104 |
105 | /*
106 | * DARK THEME
107 | */
108 | :root.theme-dark .token.comment,
109 | :root.theme-dark .token.prolog,
110 | :root.theme-dark .token.cdata {
111 | color: #808080;
112 | }
113 |
114 | :root.theme-dark .token.delimiter,
115 | :root.theme-dark .token.boolean,
116 | :root.theme-dark .token.keyword,
117 | :root.theme-dark .token.selector,
118 | :root.theme-dark .token.important,
119 | :root.theme-dark .token.atrule {
120 | color: #cc7832;
121 | }
122 |
123 | :root.theme-dark .token.operator,
124 | :root.theme-dark .token.punctuation,
125 | :root.theme-dark .token.attr-name {
126 | color: #a9b7c6;
127 | }
128 |
129 | :root.theme-dark .token.tag,
130 | :root.theme-dark .token.tag .punctuation,
131 | :root.theme-dark .token.doctype,
132 | :root.theme-dark .token.builtin {
133 | color: #e8bf6a;
134 | }
135 |
136 | :root.theme-dark .token.entity,
137 | :root.theme-dark .token.number,
138 | :root.theme-dark .token.symbol {
139 | color: #6897bb;
140 | }
141 |
142 | :root.theme-dark .token.property,
143 | :root.theme-dark .token.constant,
144 | :root.theme-dark .token.variable {
145 | color: #9876aa;
146 | }
147 |
148 | :root.theme-dark .token.string,
149 | :root.theme-dark .token.char {
150 | color: #6a8759;
151 | }
152 |
153 | :root.theme-dark .token.attr-value,
154 | :root.theme-dark .token.attr-value .punctuation {
155 | color: #a5c261;
156 | }
157 |
158 | :root.theme-dark .token.attr-value .punctuation:first-child {
159 | color: #a9b7c6;
160 | }
161 |
162 | :root.theme-dark .token.url {
163 | text-decoration: underline;
164 |
165 | color: #287bde;
166 | background: transparent;
167 | }
168 |
169 | :root.theme-dark .token.function {
170 | color: #ffc66d;
171 | }
172 |
173 | :root.theme-dark .token.regex {
174 | background: #364135;
175 | }
176 |
177 | :root.theme-dark .token.deleted {
178 | background: #484a4a;
179 | }
180 |
181 | :root.theme-dark .token.inserted {
182 | background: #294436;
183 | }
184 |
185 | :root.theme-dark .token.class-name {
186 | color: #a9b7c6;
187 | }
188 |
189 | :root.theme-dark .token.function {
190 | color: #ffc66d;
191 | }
192 |
193 | :root.theme-darkcode .language-css .token.property,
194 | :root.theme-darkcode .language-css,
195 | :root.theme-dark .token.property + .token.punctuation {
196 | color: #a9b7c6;
197 | }
198 |
199 | code.language-css .token.id {
200 | color: #ffc66d;
201 | }
202 |
203 | :root.theme-dark code.language-css .token.selector > .token.class,
204 | :root.theme-dark code.language-css .token.selector > .token.attribute,
205 | :root.theme-dark code.language-css .token.selector > .token.pseudo-class,
206 | :root.theme-dark code.language-css .token.selector > .token.pseudo-element {
207 | color: #ffc66d;
208 | }
209 |
210 | :root.theme-dark .language-plaintext .token {
211 | /* plaintext code should be colored as article text */
212 | color: inherit !important;
213 | }
214 |
--------------------------------------------------------------------------------
/docs/ui-kit/ui-kit.min.css:
--------------------------------------------------------------------------------
1 | :root{--breakpoint-desktop-min:900px;--breakpoint-tablet-max:899px;--breakpoint-tablet-min:440px;--breakpoint-mobile-max:439px;--breakpoint-mobile-min:360px;--color-key-blue:#307fff;--color-nav-bar:#27282c;--color-dark-theme-bg:#262628;--color-text-black:#000;--color-text-white:hsla(0,0%,100%,.96);--color-text-light-black:rgba(0,0,0,.7);--color-text-light-white:hsla(0,0%,100%,.7);--color-w05:hsla(0,0%,100%,.05);--color-w10:hsla(0,0%,100%,.1);--color-w20:hsla(0,0%,100%,.2);--color-w50:hsla(0,0%,100%,.5);--color-w70:hsla(0,0%,100%,.7);--color-w80:hsla(0,0%,100%,.8);--color-b05:rgba(0,0,0,.05);--color-b08:rgba(0,0,0,.08);--color-b20:rgba(0,0,0,.2);--color-b50:rgba(0,0,0,.5);--color-b70:rgba(0,0,0,.7);--color-cd-punctuation:#999;--color-cd-keyword:#0033b3;--color-cd-keyword-alternative:#cc7832;--color-cd-builtin:#067d17;--color-cd-builtin-alternative:#e7bf6a;--color-cd-function:#00627a;--color-cd-function-alternative:#ffc66d;--color-cd-operator:#9a6e3a;--color-cd-operator-alternative:#a9b7c6;--color-cd-body:#000;--color-cd-body-alternative:#a9b7c6;--color-generic:#539df3;--color-jvm:#4dbb5f;--color-js:#ffc700;--color-wasm:#fff;--size-s1:4px;--size-s2:8px;--size-s3:16px;--size-m1:24px;--size-m2:32px;--size-m3:48px;--size-l1:64px;--size-l2:72px;--size-ta1:40px;--size-ta2:52px;--font-family-default:JetBrains Sans,Inter,system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,Cantarell,Noto Sans,sans-serif,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Droid Sans,Helvetica Neue,Arial,sans-serif;--font-family-mono:JetBrains Mono,SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;--font-h1:600 44px/44px var(--font-family-default);--font-h2:600 32px/32px var(--font-family-default);--font-h3:600 24px/24px var(--font-family-default);--font-h4:600 16px/24px var(--font-family-default);--font-text-m:400 16px/24px var(--font-family-default);--font-text-s:400 13px/24px var(--font-family-default);--font-code:400 16px/24px var(--font-family-mono)}.platform-hinted>.platform-bookmarks-row,.tabs-section{margin-left:-8px;margin-right:-8px}.platform-hinted>.platform-bookmarks-row>.platform-bookmark,.section-tab{background-color:transparent;border:0;border-bottom:1px solid var(--inactive-tab-border-color);color:var(--inactive-section-color);cursor:pointer;font-size:var(--default-font-size);margin:0 8px;outline:none;padding:11px 3px}.platform-hinted>.platform-bookmarks-row{margin-bottom:16px;margin-bottom:var(--size-s3)}.no-js .platform-bookmarks-row+.sourceset-dependent-content{margin-top:8px;margin-top:var(--size-s2)}.no-js .platform-bookmarks-row+.sourceset-dependent-content:last-of-type{margin-top:0}.section-tab:hover{border-bottom:2px solid var(--default-font-color);color:var(--default-font-color)}.section-tab[data-active=""]{border-bottom:2px solid var(--active-tab-border-color);color:var(--active-section-color)}.tabs-section-body>div{margin-top:12px}.tabs-section-body .with-platform-tabs{padding-bottom:12px;padding-top:12px}.platform-hinted{display:block;flex:auto}.platform-hinted>.platform-bookmarks-row>.platform-bookmark{align-self:flex-start;background:inherit;flex:none;min-width:64px;order:5}.platform-hinted>.platform-bookmarks-row>.platform-bookmark:hover{border-bottom:2px solid var(--default-font-color);color:var(--default-font-color)}.platform-hinted>.platform-bookmarks-row>.platform-bookmark[data-active=""]{border-bottom:2px solid var(--active-tab-border-color);color:var(--active-section-color)}.js .platform-hinted>.content:not([data-active]),.js .tabs-section-body [data-togglable]:not([data-active]),.main-content[data-page-type=package] .tabs-section-body h2,.no-js .platform-bookmarks-row,.no-js .tabs-section{display:none}
2 |
--------------------------------------------------------------------------------
/docs/ui-kit/ui-kit.min.js:
--------------------------------------------------------------------------------
1 | (()=>{"use strict";var t=function(t){var e="function"==typeof Symbol&&Symbol.iterator,a=e&&t[e],r=0;if(a)return a.call(t);if(t&&"number"==typeof t.length)return{next:function(){return t&&r>=t.length&&(t=void 0),{value:t&&t[r++],done:!t}}};throw new TypeError(e?"Object is not iterable.":"Symbol.iterator is not defined.")},e=function(){var t=!1;try{var e="__testLocalStorageKey__";localStorage.setItem(e,e),localStorage.removeItem(e),t=!0}catch(t){console.error("Local storage is not available",t)}return{getItem:function(e){return t?localStorage.getItem(e):null},setItem:function(e,a){t&&localStorage.setItem(e,a)}}}();function a(e){var a,r=null===(a=e.getAttribute("data-togglable"))||void 0===a?void 0:a.split(",");!function(a){var r,o,n,l;try{for(var i=t(document.getElementsByClassName("tabs-section")),c=i.next();!c.done;c=i.next()){var u=c.value;try{for(var d=(n=void 0,t(u.children)),s=d.next();!s.done;s=d.next()){var g=s.value;g.getAttribute("data-togglable")===e.getAttribute("data-togglable")?g.setAttribute("data-active",""):g.removeAttribute("data-active")}}catch(t){n={error:t}}finally{try{s&&!s.done&&(l=d.return)&&l.call(d)}finally{if(n)throw n.error}}}}catch(t){r={error:t}}finally{try{c&&!c.done&&(o=i.return)&&o.call(i)}finally{if(r)throw r.error}}}(),document.querySelectorAll(".tabs-section-body *[data-togglable]").forEach((function(t){var e=t.getAttribute("data-togglable");r&&e&&r.includes(e)?t.setAttribute("data-active",""):t.classList.contains("sourceset-dependent-content")||t.removeAttribute("data-active")}))}window.initTabs=function(){var t=document.querySelector(".main-content"),r="active-tab-"+(t?t.getAttribute("data-page-type"):null);document.querySelectorAll("div[tabs-section]").forEach((function(t){!function(t){var e=t.querySelector("button[data-active]");e&&a(e)}(t),t.addEventListener("click",(function(t){var o=t.target,n=o?o.getAttribute("data-togglable"):null;n&&(e.setItem(r,JSON.stringify(n)),a(o))}))}));var o=e.getItem(r);if(o){var n=document.querySelector('div[tabs-section] > button[data-togglable="'+JSON.parse(o)+'"]');n&&a(n)}},window.toggleSections=a})();
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | org.gradle.jvmargs=-XX:+UseParallelGC
3 | org.gradle.parallel=true
4 | org.gradle.configuration-cache.parallel=true
5 |
6 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
7 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
8 |
9 | kotlin.daemon.jvmargs=-Xmx2G
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | # plugins version
3 | kotlin = "2.1.20"
4 | dokka = "2.0.0"
5 |
6 | # libraries version
7 | serialization = "1.7.3"
8 | coroutines = "1.10.2"
9 | ktor = "3.1.2"
10 | mockk = "1.13.13"
11 | logging = "7.0.0"
12 | jreleaser = "1.17.0"
13 | binaryCompatibilityValidatorPlugin = "0.17.0"
14 | slf4j = "2.0.16"
15 | kotest = "5.9.1"
16 |
17 | [libraries]
18 | # Kotlinx libraries
19 | kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
20 | kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version.ref = "logging" }
21 |
22 | # Ktor
23 | ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" }
24 | ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" }
25 | ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" }
26 | ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" }
27 |
28 | # Testing
29 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
30 | kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
31 | ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" }
32 | mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
33 | slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" }
34 | kotest-assertions-json = { group = "io.kotest", name = "kotest-assertions-json", version.ref = "kotest" }
35 |
36 | [plugins]
37 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
38 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
39 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
40 | jreleaser = { id = "org.jreleaser", version.ref = "jreleaser"}
41 | kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" }
42 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modelcontextprotocol/kotlin-sdk/0cff2ca71828857f998aa08c06fe73e206e7a8f5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-client/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | !gradle/wrapper/gradle-wrapper.jar
4 | !**/src/main/**/build/
5 | !**/src/test/**/build/
6 |
7 | ### IntelliJ IDEA ###
8 | .idea/
9 | *.iws
10 | *.iml
11 | *.ipr
12 | out/
13 | !**/src/main/**/out/
14 | !**/src/test/**/out/
15 |
16 | ### Kotlin ###
17 | .kotlin
18 |
19 | ### Eclipse ###
20 | .apt_generated
21 | .classpath
22 | .factorypath
23 | .project
24 | .settings
25 | .springBeans
26 | .sts4-cache
27 | bin/
28 | !**/src/main/**/bin/
29 | !**/src/test/**/bin/
30 |
31 | ### NetBeans ###
32 | /nbproject/private/
33 | /nbbuild/
34 | /dist/
35 | /nbdist/
36 | /.nb-gradle/
37 |
38 | ### VS Code ###
39 | .vscode/
40 |
41 | ### Mac OS ###
42 | .DS_Store
--------------------------------------------------------------------------------
/samples/kotlin-mcp-client/README.md:
--------------------------------------------------------------------------------
1 | # Kotlin MCP Client
2 |
3 | This project demonstrates how to build a Model Context Protocol (MCP) client in Kotlin that interacts with an MCP server
4 | via a STDIO transport layer while leveraging Anthropic's API for natural language processing. The client uses the MCP
5 | Kotlin SDK to communicate with an MCP server that exposes various tools, and it uses Anthropic's API to process user
6 | queries and integrate tool responses into the conversation.
7 |
8 | For more information about the MCP SDK and protocol, please refer to
9 | the [MCP documentation](https://modelcontextprotocol.io/introduction).
10 |
11 | ## Prerequisites
12 |
13 | - **Java 17 or later**
14 | - **Gradle** (or the Gradle wrapper provided with the project)
15 | - An Anthropic API key set in your environment variable `ANTHROPIC_API_KEY`
16 | - Basic understanding of MCP concepts and Kotlin programming
17 |
18 | ## Overview
19 |
20 | The client application performs the following tasks:
21 |
22 | - **Connecting to an MCP server** —
23 | launches an MCP server process (implemented in JavaScript, Python, or Java) using STDIO transport.
24 | It connects to the server, retrieves available tools, and converts them to Anthropic’s tool format.
25 | - **Processing queries** —
26 | accepts user queries, sends them to Anthropic’s API along with the registered tools, and handles responses.
27 | If the response indicates a tool should be called, it invokes the corresponding MCP tool and continues the
28 | conversation based on the tool’s result.
29 | - **Interactive chat loop** —
30 | runs an interactive command-line loop, allowing users to continuously submit queries and receive responses.
31 |
32 | ## Building and Running
33 |
34 | Use the Gradle wrapper to build the application. In a terminal, run:
35 |
36 | ```shell
37 | ./gradlew clean build -x test
38 | ```
39 |
40 | To run the client, execute the jar file and provide the path to your MCP server script.
41 |
42 | To run the client with any MCP server:
43 |
44 | ```shell
45 | java -jar build/libs/.jar path/to/server.jar # jvm server
46 | java -jar build/libs/.jar path/to/server.py # python server
47 | java -jar build/libs/.jar path/to/build/index.js # node server
48 | ```
49 |
50 | > [!NOTE]
51 | > The client uses STDIO transport, so it launches the MCP server as a separate process.
52 | > Ensure the server script is executable and is a valid `.js`, `.py`, or `.jar` file.
53 |
54 | ## Configuration for Anthropic
55 |
56 | Ensure your Anthropic API key is available in your environment:
57 |
58 | ```shell
59 | export ANTHROPIC_API_KEY=your_anthropic_api_key_here
60 | ```
61 |
62 | The client uses `AnthropicOkHttpClient.fromEnv()` to automatically load the API key from `ANTHROPIC_API_KEY` and
63 | `ANTHROPIC_AUTH_TOKEN` environment variables.
64 |
65 | ## Additional Resources
66 |
67 | - [MCP Specification](https://spec.modelcontextprotocol.io/)
68 | - [Kotlin MCP SDK](https://github.com/modelcontextprotocol/kotlin-sdk)
69 | - [Anthropic Java SDK](https://github.com/anthropics/anthropic-sdk-java/tree/main)
70 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-client/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm") version "2.1.10"
3 | application
4 | id("com.github.johnrengelman.shadow") version "8.1.1"
5 | }
6 |
7 | application {
8 | mainClass.set("io.modelcontextprotocol.sample.client.MainKt")
9 | }
10 |
11 |
12 | group = "org.example"
13 | version = "0.1.0"
14 |
15 | val mcpVersion = "0.5.0"
16 | val slf4jVersion = "2.0.9"
17 | val anthropicVersion = "0.8.0"
18 |
19 | dependencies {
20 | implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
21 | implementation("org.slf4j:slf4j-nop:$slf4jVersion")
22 | implementation("com.anthropic:anthropic-java:$anthropicVersion")
23 | }
24 |
25 | tasks.test {
26 | useJUnitPlatform()
27 | }
28 |
29 | kotlin {
30 | jvmToolchain(17)
31 | }
32 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-client/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modelcontextprotocol/kotlin-sdk/0cff2ca71828857f998aa08c06fe73e206e7a8f5/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-client/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-client/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "kotlin-mcp-client"
2 |
3 | plugins {
4 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
5 | }
6 |
7 | dependencyResolutionManagement {
8 | repositories {
9 | mavenCentral()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.sample.client
2 |
3 | import kotlinx.coroutines.runBlocking
4 |
5 | fun main(args: Array) = runBlocking {
6 | if (args.isEmpty()) throw IllegalArgumentException("Usage: java -jar /build/libs/kotlin-mcp-client-0.1.0-all.jar ")
7 | val serverPath = args.first()
8 | val client = MCPClient()
9 | client.use {
10 | client.connectToServer(serverPath)
11 | client.chatLoop()
12 | }
13 | }
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | !gradle/wrapper/gradle-wrapper.jar
4 | !**/src/main/**/build/
5 | !**/src/test/**/build/
6 |
7 | ### IntelliJ IDEA ###
8 | .idea/modules.xml
9 | .idea/jarRepositories.xml
10 | .idea/compiler.xml
11 | .idea/libraries/
12 | *.iws
13 | *.iml
14 | *.ipr
15 | out/
16 | !**/src/main/**/out/
17 | !**/src/test/**/out/
18 |
19 | ### Kotlin ###
20 | .kotlin
21 |
22 | ### Eclipse ###
23 | .apt_generated
24 | .classpath
25 | .factorypath
26 | .project
27 | .settings
28 | .springBeans
29 | .sts4-cache
30 | bin/
31 | !**/src/main/**/bin/
32 | !**/src/test/**/bin/
33 |
34 | ### NetBeans ###
35 | /nbproject/private/
36 | /nbbuild/
37 | /dist/
38 | /nbdist/
39 | /.nb-gradle/
40 |
41 | ### VS Code ###
42 | .vscode/
43 |
44 | ### Mac OS ###
45 | .DS_Store
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
16 |
17 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/README.md:
--------------------------------------------------------------------------------
1 | # MCP Kotlin Server Sample
2 |
3 | A sample implementation of an MCP (Model Communication Protocol) server in Kotlin that demonstrates different server
4 | configurations and transport methods for both JVM and WASM targets.
5 |
6 | ## Features
7 |
8 | - Multiple server operation modes:
9 | - Standard I/O server (JVM only)
10 | - SSE (Server-Sent Events) server with plain configuration (JVM, WASM)
11 | - SSE server using Ktor plugin (JVM, WASM)
12 | - Multiplatform support
13 | - Built-in capabilities for:
14 | - Prompts management
15 | - Resources handling
16 | - Tools integration
17 |
18 | ## Getting Started
19 |
20 | ### Running the Server
21 |
22 | You can run the server on the JVM or using Kotlin/WASM on Node.js.
23 |
24 |
25 | #### JVM:
26 |
27 | To run the server on the JVM (defaults to SSE mode with Ktor plugin on port 3001):
28 |
29 | ```bash
30 | ./gradlew runJvm
31 | ```
32 |
33 | #### WASM:
34 |
35 | To run the server using Kotlin/WASM on Node.js (defaults to SSE mode with Ktor plugin on port 3001):
36 |
37 | ```bash
38 | ./gradlew wasmJsNodeDevelopmentRun
39 | ```
40 |
41 | ### Connecting to the Server
42 |
43 | For servers on JVM or WASM:
44 | 1. Start the server
45 | 2. Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) to connect to `http://localhost:/sse`
46 |
47 | ## Server Capabilities
48 |
49 | - **Prompts**: Supports prompt management with list change notifications
50 | - **Resources**: Includes subscription support and list change notifications
51 | - **Tools**: Supports tool management with list change notifications
52 |
53 | ## Implementation Details
54 |
55 | The server is implemented using:
56 | - Ktor for HTTP server functionality
57 | - Kotlin coroutines for asynchronous operations
58 | - SSE for real-time communication
59 | - Standard I/O for command-line interface
60 | - Common Kotlin code shared between JVM and WASM targets
61 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/build.gradle.kts:
--------------------------------------------------------------------------------
1 | @file:OptIn(ExperimentalWasmDsl::class, ExperimentalKotlinGradlePluginApi::class)
2 |
3 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
4 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
5 |
6 | plugins {
7 | kotlin("multiplatform") version "2.1.20"
8 | kotlin("plugin.serialization") version "2.1.20"
9 | }
10 |
11 | group = "org.example"
12 | version = "0.1.0"
13 |
14 | repositories {
15 | mavenCentral()
16 | }
17 |
18 | val jvmMainClass = "Main_jvmKt"
19 |
20 | kotlin {
21 | jvmToolchain(17)
22 | jvm {
23 | binaries {
24 | executable {
25 | mainClass.set(jvmMainClass)
26 | }
27 | }
28 | val jvmJar by tasks.getting(org.gradle.jvm.tasks.Jar::class) {
29 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE
30 | doFirst {
31 | manifest {
32 | attributes["Main-Class"] = jvmMainClass
33 | }
34 |
35 | from(configurations["jvmRuntimeClasspath"].map { if (it.isDirectory) it else zipTree(it) })
36 | }
37 | }
38 | }
39 | wasmJs {
40 | nodejs()
41 | binaries.executable()
42 | }
43 |
44 | sourceSets {
45 | commonMain.dependencies {
46 | implementation("io.modelcontextprotocol:kotlin-sdk:0.5.0")
47 | }
48 | jvmMain.dependencies {
49 | implementation("org.slf4j:slf4j-nop:2.0.9")
50 | }
51 | wasmJsMain.dependencies {}
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
3 | kotlin.daemon.jvmargs=-Xmx2G
4 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modelcontextprotocol/kotlin-sdk/0cff2ca71828857f998aa08c06fe73e206e7a8f5/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 16 12:27:09 CET 2024
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
3 | }
4 | rootProject.name = "kotlin-mcp-server"
5 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/src/jvmMain/kotlin/main.jvm.kt:
--------------------------------------------------------------------------------
1 | import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport
2 | import kotlinx.coroutines.Job
3 | import kotlinx.coroutines.runBlocking
4 | import kotlinx.io.asSink
5 | import kotlinx.io.asSource
6 | import kotlinx.io.buffered
7 | import shared.configureServer
8 | import shared.runSseMcpServerUsingKtorPlugin
9 | import shared.runSseMcpServerWithPlainConfiguration
10 |
11 | /**
12 | * Start sse-server mcp on port 3001.
13 | *
14 | * @param args
15 | * - "--stdio": Runs an MCP server using standard input/output.
16 | * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided).
17 | * - "--sse-server ": Runs an SSE MCP server with a plain configuration.
18 | */
19 | fun main(args: Array): Unit = runBlocking {
20 | val command = args.firstOrNull() ?: "--sse-server-ktor"
21 | val port = args.getOrNull(1)?.toIntOrNull() ?: 3001
22 | when (command) {
23 | "--stdio" -> runMcpServerUsingStdio()
24 | "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port)
25 | "--sse-server" -> runSseMcpServerWithPlainConfiguration(port)
26 | else -> {
27 | System.err.println("Unknown command: $command")
28 | }
29 | }
30 | }
31 |
32 | fun runMcpServerUsingStdio() {
33 | // Note: The server will handle listing prompts, tools, and resources automatically.
34 | // The handleListResourceTemplates will return empty as defined in the Server code.
35 | val server = configureServer()
36 | val transport = StdioServerTransport(
37 | inputStream = System.`in`.asSource().buffered(),
38 | outputStream = System.out.asSink().buffered()
39 | )
40 |
41 | runBlocking {
42 | server.connect(transport)
43 | val done = Job()
44 | server.onClose {
45 | done.complete()
46 | }
47 | done.join()
48 | println("Server closed")
49 | }
50 | }
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/src/wasmJsMain/kotlin/main.wasmJs.kt:
--------------------------------------------------------------------------------
1 | import shared.runSseMcpServerUsingKtorPlugin
2 | import shared.runSseMcpServerWithPlainConfiguration
3 |
4 | /**
5 | * Start sse-server mcp on port 3001.
6 | *
7 | * @param args
8 | * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided).
9 | * - "--sse-server ": Runs an SSE MCP server with a plain configuration.
10 | */
11 | suspend fun main(args: Array) {
12 | val command = args.firstOrNull() ?: "--sse-server-ktor"
13 | val port = args.getOrNull(1)?.toIntOrNull() ?: 3001
14 | when (command) {
15 | "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port)
16 | "--sse-server" -> runSseMcpServerWithPlainConfiguration(port)
17 | else -> {
18 | error("Unknown command: $command")
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/samples/weather-stdio-server/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | build/
3 | !gradle/wrapper/gradle-wrapper.jar
4 | !**/src/main/**/build/
5 | !**/src/test/**/build/
6 |
7 | ### IntelliJ IDEA ###
8 | .idea/
9 | *.iws
10 | *.iml
11 | *.ipr
12 | out/
13 | !**/src/main/**/out/
14 | !**/src/test/**/out/
15 |
16 | ### Kotlin ###
17 | .kotlin
18 |
19 | ### Eclipse ###
20 | .apt_generated
21 | .classpath
22 | .factorypath
23 | .project
24 | .settings
25 | .springBeans
26 | .sts4-cache
27 | bin/
28 | !**/src/main/**/bin/
29 | !**/src/test/**/bin/
30 |
31 | ### NetBeans ###
32 | /nbproject/private/
33 | /nbbuild/
34 | /dist/
35 | /nbdist/
36 | /.nb-gradle/
37 |
38 | ### VS Code ###
39 | .vscode/
40 |
41 | ### Mac OS ###
42 | .DS_Store
--------------------------------------------------------------------------------
/samples/weather-stdio-server/README.md:
--------------------------------------------------------------------------------
1 | # Kotlin MCP Weather STDIO Server
2 |
3 | This project demonstrates how to build a Model Context Protocol (MCP) server in Kotlin that provides weather-related
4 | tools by consuming the National Weather Service (weather.gov) API. The server uses STDIO as the transport layer and
5 | leverages the Kotlin MCP SDK to expose weather forecast and alert tools.
6 |
7 | For more information about the MCP SDK and protocol, please refer to
8 | the [MCP documentation](https://modelcontextprotocol.io/introduction).
9 |
10 | ## Prerequisites
11 |
12 | - Java 17 or later
13 | - Gradle (or the Gradle wrapper provided with the project)
14 | - Basic understanding of MCP concepts
15 | - Basic understanding of Kotlin and Kotlin ecosystems (sush as kotlinx-serialization, coroutines, ktor)
16 |
17 | ## MCP Weather Server
18 |
19 | The project provides:
20 |
21 | - A lightweight MCP server built with Kotlin.
22 | - STDIO transport layer implementation for server-client communication.
23 | - Two weather tools:
24 | - **Weather Forecast Tool** — returns details such as temperature, wind information, and a detailed forecast for a
25 | given latitude/longitude.
26 | - **Weather Alerts Tool** — returns active weather alerts for a given US state.
27 |
28 | ## Building and running
29 |
30 | Use the Gradle wrapper to build the application. In a terminal run:
31 |
32 | ```shell
33 | ./gradlew clean build -x test
34 | ```
35 |
36 | To run the server:
37 |
38 | ```shell
39 | java -jar build/libs/.jar
40 | ```
41 |
42 | > [!NOTE]
43 | > The server uses STDIO transport, so it is typically launched in an environment where the client connects via standard
44 | > input/output.
45 |
46 | ## Tool Implementation
47 |
48 | The project registers two MCP tools using the Kotlin MCP SDK. Below is an overview of the core tool implementations:
49 |
50 | ### 1. Weather Forecast Tool
51 |
52 | This tool fetches the weather forecast for a specific latitude and longitude using the `weather.gov` API.
53 |
54 | Example tool registration in Kotlin:
55 |
56 | ```kotlin
57 | server.addTool(
58 | name = "get_forecast",
59 | description = """
60 | Get weather forecast for a specific latitude/longitude
61 | """.trimIndent(),
62 | inputSchema = Tool.Input(
63 | properties = JsonObject(
64 | mapOf(
65 | "latitude" to JsonObject(mapOf("type" to JsonPrimitive("number"))),
66 | "longitude" to JsonObject(mapOf("type" to JsonPrimitive("number"))),
67 | )
68 | ),
69 | required = listOf("latitude", "longitude")
70 | )
71 | ) { request ->
72 | // Implementation tool
73 | }
74 | ```
75 |
76 | ### 2. Weather Alerts Tool
77 |
78 | This tool retrieves active weather alerts for a US state.
79 |
80 | Example tool registration in Kotlin:
81 |
82 | ```kotlin
83 | server.addTool(
84 | name = "get_alerts",
85 | description = """
86 | Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)
87 | """.trimIndent(),
88 | inputSchema = Tool.Input(
89 | properties = JsonObject(
90 | mapOf(
91 | "state" to JsonObject(
92 | mapOf(
93 | "type" to JsonPrimitive("string"),
94 | "description" to JsonPrimitive("Two-letter US state code (e.g. CA, NY)")
95 | )
96 | ),
97 | )
98 | ),
99 | required = listOf("state")
100 | )
101 | ) { request ->
102 | // Implementation tool
103 | }
104 | ```
105 |
106 | ## Client Integration
107 |
108 | ### Kotlin Client Example
109 |
110 | Since the server uses STDIO for transport, the client typically connects via standard input/output streams. A sample
111 | client implementation can be found in the tests, demonstrating how to send tool requests and process responses.
112 |
113 | ### Claude for Desktop
114 |
115 | To integrate with Claude Desktop, add the following configuration to your Claude Desktop settings:
116 |
117 | ```json
118 | {
119 | "mcpServers": {
120 | "weather": {
121 | "command": "java",
122 | "args": [
123 | "-jar",
124 | "/absolute/path/to/.jar"
125 | ]
126 | }
127 | }
128 | }
129 | ```
130 |
131 | > [!NOTE]
132 | > Replace `/absolute/path/to/.jar` with the actual absolute path to your built jar file.
133 |
134 | ## Additional Resources
135 |
136 | - [MCP Specification](https://spec.modelcontextprotocol.io/)
137 | - [Kotlin MCP SDK](https://github.com/modelcontextprotocol/kotlin-sdk)
138 | - [Ktor Client Documentation](https://ktor.io/docs/welcome.html)
139 | - [Kotlinx Serialization](https://kotlinlang.org/docs/serialization.html)
140 |
141 |
--------------------------------------------------------------------------------
/samples/weather-stdio-server/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm") version "2.1.10"
3 | kotlin("plugin.serialization") version "2.1.10"
4 | id("com.github.johnrengelman.shadow") version "8.1.1"
5 | application
6 | }
7 |
8 | application {
9 | mainClass.set("io.modelcontextprotocol.sample.server.MainKt")
10 | }
11 |
12 |
13 | group = "org.example"
14 | version = "0.1.0"
15 |
16 | val mcpVersion = "0.5.0"
17 | val slf4jVersion = "2.0.9"
18 | val ktorVersion = "3.1.1"
19 |
20 | dependencies {
21 | implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion")
22 | implementation("org.slf4j:slf4j-nop:$slf4jVersion")
23 | implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion")
24 | implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion")
25 | testImplementation(kotlin("test"))
26 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.1")
27 | }
28 |
29 | tasks.test {
30 | useJUnitPlatform()
31 | }
32 |
33 | kotlin {
34 | jvmToolchain(17)
35 | }
36 |
--------------------------------------------------------------------------------
/samples/weather-stdio-server/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
--------------------------------------------------------------------------------
/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/modelcontextprotocol/kotlin-sdk/0cff2ca71828857f998aa08c06fe73e206e7a8f5/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
8 |
--------------------------------------------------------------------------------
/samples/weather-stdio-server/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 | @rem SPDX-License-Identifier: Apache-2.0
17 | @rem
18 |
19 | @if "%DEBUG%"=="" @echo off
20 | @rem ##########################################################################
21 | @rem
22 | @rem Gradle startup script for Windows
23 | @rem
24 | @rem ##########################################################################
25 |
26 | @rem Set local scope for the variables with windows NT shell
27 | if "%OS%"=="Windows_NT" setlocal
28 |
29 | set DIRNAME=%~dp0
30 | if "%DIRNAME%"=="" set DIRNAME=.
31 | @rem This is normally unused
32 | set APP_BASE_NAME=%~n0
33 | set APP_HOME=%DIRNAME%
34 |
35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
37 |
38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
40 |
41 | @rem Find java.exe
42 | if defined JAVA_HOME goto findJavaFromJavaHome
43 |
44 | set JAVA_EXE=java.exe
45 | %JAVA_EXE% -version >NUL 2>&1
46 | if %ERRORLEVEL% equ 0 goto execute
47 |
48 | echo. 1>&2
49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
50 | echo. 1>&2
51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
52 | echo location of your Java installation. 1>&2
53 |
54 | goto fail
55 |
56 | :findJavaFromJavaHome
57 | set JAVA_HOME=%JAVA_HOME:"=%
58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
59 |
60 | if exist "%JAVA_EXE%" goto execute
61 |
62 | echo. 1>&2
63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
64 | echo. 1>&2
65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2
66 | echo location of your Java installation. 1>&2
67 |
68 | goto fail
69 |
70 | :execute
71 | @rem Setup the command line
72 |
73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
74 |
75 |
76 | @rem Execute Gradle
77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
78 |
79 | :end
80 | @rem End local scope for the variables with windows NT shell
81 | if %ERRORLEVEL% equ 0 goto mainEnd
82 |
83 | :fail
84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
85 | rem the _cmd.exe /c_ return code!
86 | set EXIT_CODE=%ERRORLEVEL%
87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1
88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
89 | exit /b %EXIT_CODE%
90 |
91 | :mainEnd
92 | if "%OS%"=="Windows_NT" endlocal
93 |
94 | :omega
95 |
--------------------------------------------------------------------------------
/samples/weather-stdio-server/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | rootProject.name = "weather-stdio-server"
2 |
3 | plugins {
4 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
5 | }
6 |
7 | dependencyResolutionManagement {
8 | repositories {
9 | mavenCentral()
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.sample.server
2 |
3 | import io.ktor.client.*
4 | import io.ktor.client.plugins.*
5 | import io.ktor.client.plugins.contentnegotiation.*
6 | import io.ktor.http.*
7 | import io.ktor.serialization.kotlinx.json.*
8 | import io.ktor.utils.io.streams.*
9 | import io.modelcontextprotocol.kotlin.sdk.*
10 | import io.modelcontextprotocol.kotlin.sdk.server.Server
11 | import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
12 | import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport
13 | import kotlinx.coroutines.Job
14 | import kotlinx.coroutines.runBlocking
15 | import kotlinx.io.asSink
16 | import kotlinx.io.buffered
17 | import kotlinx.serialization.json.*
18 |
19 | // Main function to run the MCP server
20 | fun `run mcp server`() {
21 | // Base URL for the Weather API
22 | val baseUrl = "https://api.weather.gov"
23 |
24 | // Create an HTTP client with a default request configuration and JSON content negotiation
25 | val httpClient = HttpClient {
26 | defaultRequest {
27 | url(baseUrl)
28 | headers {
29 | append("Accept", "application/geo+json")
30 | append("User-Agent", "WeatherApiClient/1.0")
31 | }
32 | contentType(ContentType.Application.Json)
33 | }
34 | // Install content negotiation plugin for JSON serialization/deserialization
35 | install(ContentNegotiation) {
36 | json(Json {
37 | ignoreUnknownKeys = true
38 | prettyPrint = true
39 | })
40 | }
41 | }
42 |
43 | // Create the MCP Server instance with a basic implementation
44 | val server = Server(
45 | Implementation(
46 | name = "weather", // Tool name is "weather"
47 | version = "1.0.0" // Version of the implementation
48 | ),
49 | ServerOptions(
50 | capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true))
51 | )
52 | )
53 |
54 | // Register a tool to fetch weather alerts by state
55 | server.addTool(
56 | name = "get_alerts",
57 | description = """
58 | Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY)
59 | """.trimIndent(),
60 | inputSchema = Tool.Input(
61 | properties = buildJsonObject {
62 | putJsonObject("state") {
63 | put("type", "string")
64 | put("description", "Two-letter US state code (e.g. CA, NY)")
65 | }
66 | },
67 | required = listOf("state")
68 | )
69 | ) { request ->
70 | val state = request.arguments["state"]?.jsonPrimitive?.content ?: return@addTool CallToolResult(
71 | content = listOf(TextContent("The 'state' parameter is required."))
72 | )
73 |
74 | val alerts = httpClient.getAlerts(state)
75 |
76 | CallToolResult(content = alerts.map { TextContent(it) })
77 | }
78 |
79 | // Register a tool to fetch weather forecast by latitude and longitude
80 | server.addTool(
81 | name = "get_forecast",
82 | description = """
83 | Get weather forecast for a specific latitude/longitude
84 | """.trimIndent(),
85 | inputSchema = Tool.Input(
86 | properties = buildJsonObject {
87 | putJsonObject("latitude") {
88 | put("type", "number")
89 | }
90 | putJsonObject("longitude") {
91 | put("type", "number")
92 | }
93 | },
94 | required = listOf("latitude", "longitude")
95 | )
96 | ) { request ->
97 | val latitude = request.arguments["latitude"]?.jsonPrimitive?.doubleOrNull
98 | val longitude = request.arguments["longitude"]?.jsonPrimitive?.doubleOrNull
99 | if (latitude == null || longitude == null) {
100 | return@addTool CallToolResult(
101 | content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required."))
102 | )
103 | }
104 |
105 | val forecast = httpClient.getForecast(latitude, longitude)
106 |
107 | CallToolResult(content = forecast.map { TextContent(it) })
108 | }
109 |
110 | // Create a transport using standard IO for server communication
111 | val transport = StdioServerTransport(
112 | System.`in`.asInput(),
113 | System.out.asSink().buffered()
114 | )
115 |
116 | runBlocking {
117 | server.connect(transport)
118 | val done = Job()
119 | server.onClose {
120 | done.complete()
121 | }
122 | done.join()
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/WeatherApi.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.sample.server
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.call.body
5 | import io.ktor.client.request.get
6 | import kotlinx.serialization.Serializable
7 | import kotlinx.serialization.json.JsonObject
8 |
9 | // Extension function to fetch forecast information for given latitude and longitude
10 | suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List {
11 | // Build the URI using provided latitude and longitude
12 | val uri = "/points/$latitude,$longitude"
13 | // Request the points data from the API
14 | val points = this.get(uri).body()
15 |
16 | // Request the forecast using the URL provided in the points response
17 | val forecast = this.get(points.properties.forecast).body()
18 |
19 | // Map each forecast period to a formatted string
20 | return forecast.properties.periods.map { period ->
21 | """
22 | ${period.name}:
23 | Temperature: ${period.temperature} ${period.temperatureUnit}
24 | Wind: ${period.windSpeed} ${period.windDirection}
25 | Forecast: ${period.detailedForecast}
26 | """.trimIndent()
27 | }
28 | }
29 |
30 | // Extension function to fetch weather alerts for a given state
31 | suspend fun HttpClient.getAlerts(state: String): List {
32 | // Build the URI using the given state code
33 | val uri = "/alerts/active/area/$state"
34 | // Request the alerts data from the API
35 | val alerts = this.get(uri).body()
36 |
37 | // Map each alert feature to a formatted string
38 | return alerts.features.map { feature ->
39 | """
40 | Event: ${feature.properties.event}
41 | Area: ${feature.properties.areaDesc}
42 | Severity: ${feature.properties.severity}
43 | Description: ${feature.properties.description}
44 | Instruction: ${feature.properties.instruction}
45 | """.trimIndent()
46 | }
47 | }
48 |
49 | // Data class representing the points response from the API
50 | @Serializable
51 | data class Points(
52 | val properties: Properties
53 | ) {
54 | @Serializable
55 | data class Properties(val forecast: String)
56 | }
57 |
58 | // Data class representing the forecast response from the API
59 | @Serializable
60 | data class Forecast(
61 | val properties: Properties
62 | ) {
63 | @Serializable
64 | data class Properties(val periods: List)
65 |
66 | @Serializable
67 | data class Period(
68 | val number: Int, val name: String, val startTime: String, val endTime: String,
69 | val isDaytime: Boolean, val temperature: Int, val temperatureUnit: String,
70 | val temperatureTrend: String, val probabilityOfPrecipitation: JsonObject,
71 | val windSpeed: String, val windDirection: String,
72 | val shortForecast: String, val detailedForecast: String,
73 | )
74 | }
75 |
76 | // Data class representing the alerts response from the API
77 | @Serializable
78 | data class Alert(
79 | val features: List
80 | ) {
81 | @Serializable
82 | data class Feature(
83 | val properties: Properties
84 | )
85 |
86 | @Serializable
87 | data class Properties(
88 | val event: String, val areaDesc: String, val severity: String,
89 | val description: String, val instruction: String?,
90 | )
91 | }
92 |
--------------------------------------------------------------------------------
/samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.sample.server
2 |
3 | fun main() = `run mcp server`()
--------------------------------------------------------------------------------
/samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.sample.client
2 |
3 | import io.modelcontextprotocol.kotlin.sdk.CallToolRequest
4 | import io.modelcontextprotocol.kotlin.sdk.Implementation
5 | import io.modelcontextprotocol.kotlin.sdk.TextContent
6 | import io.modelcontextprotocol.kotlin.sdk.client.Client
7 | import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport
8 | import kotlinx.coroutines.runBlocking
9 | import kotlinx.io.asSink
10 | import kotlinx.io.asSource
11 | import kotlinx.io.buffered
12 | import kotlinx.serialization.json.JsonObject
13 | import kotlinx.serialization.json.JsonPrimitive
14 |
15 |
16 | fun main(): Unit = runBlocking {
17 | val process = ProcessBuilder("java", "-jar", "build/libs/weather-stdio-server-0.1.0-all.jar")
18 | .start()
19 |
20 | val transport = StdioClientTransport(
21 | input = process.inputStream.asSource().buffered(),
22 | output = process.outputStream.asSink().buffered()
23 | )
24 |
25 | // Initialize the MCP client with client information
26 | val client = Client(
27 | clientInfo = Implementation(name = "weather", version = "1.0.0"),
28 | )
29 |
30 | client.connect(transport)
31 |
32 |
33 | val toolsList = client.listTools()?.tools?.map { it.name }
34 | println("Available Tools = $toolsList")
35 |
36 | val weatherForecastResult = client.callTool(
37 | CallToolRequest(
38 | name = "get_forecast",
39 | arguments = JsonObject(mapOf("latitude" to JsonPrimitive(38.5816), "longitude" to JsonPrimitive(-121.4944)))
40 | )
41 | )?.content?.map { if (it is TextContent) it.text else it.toString() }
42 |
43 | println("Weather Forcast: ${weatherForecastResult?.joinToString(separator = "\n", prefix = "\n", postfix = "\n")}")
44 |
45 | val alertResult =
46 | client.callTool(
47 | CallToolRequest(
48 | name = "get_alerts",
49 | arguments = JsonObject(mapOf("state" to JsonPrimitive("TX")))
50 | )
51 | )?.content?.map { if (it is TextContent) it.text else it.toString() }
52 |
53 | println("Alert Response = $alertResult")
54 |
55 | client.close()
56 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | mavenCentral()
4 | gradlePluginPortal()
5 | }
6 |
7 | plugins {
8 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
9 | }
10 | }
11 |
12 | dependencyResolutionManagement {
13 | repositories {
14 | mavenCentral()
15 | }
16 | }
17 |
18 | rootProject.name = "kotlin-sdk"
19 |
20 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.client
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.request.HttpRequestBuilder
5 | import io.modelcontextprotocol.kotlin.sdk.Implementation
6 | import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION
7 | import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME
8 | import kotlin.time.Duration
9 |
10 | /**
11 | * Returns a new SSE transport for the Model Context Protocol using the provided HttpClient.
12 | *
13 | * @param urlString Optional URL of the MCP server.
14 | * @param reconnectionTime Optional duration to wait before attempting to reconnect.
15 | * @param requestBuilder Optional lambda to configure the HTTP request.
16 | * @return A [SSEClientTransport] configured for MCP communication.
17 | */
18 | public fun HttpClient.mcpSseTransport(
19 | urlString: String? = null,
20 | reconnectionTime: Duration? = null,
21 | requestBuilder: HttpRequestBuilder.() -> Unit = {},
22 | ): SseClientTransport = SseClientTransport(this, urlString, reconnectionTime, requestBuilder)
23 |
24 | /**
25 | * Creates and connects an MCP client over SSE using the provided HttpClient.
26 | *
27 | * @param urlString Optional URL of the MCP server.
28 | * @param reconnectionTime Optional duration to wait before attempting to reconnect.
29 | * @param requestBuilder Optional lambda to configure the HTTP request.
30 | * @return A connected [Client] ready for MCP communication.
31 | */
32 | public suspend fun HttpClient.mcpSse(
33 | urlString: String? = null,
34 | reconnectionTime: Duration? = null,
35 | requestBuilder: HttpRequestBuilder.() -> Unit = {},
36 | ): Client {
37 | val transport = mcpSseTransport(urlString, reconnectionTime, requestBuilder)
38 | val client = Client(
39 | Implementation(
40 | name = IMPLEMENTATION_NAME,
41 | version = LIB_VERSION
42 | )
43 | )
44 | client.connect(transport)
45 | return client
46 | }
47 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.client
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.plugins.websocket.webSocketSession
5 | import io.ktor.client.request.HttpRequestBuilder
6 | import io.ktor.client.request.header
7 | import io.ktor.http.HttpHeaders
8 | import io.ktor.websocket.WebSocketSession
9 | import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL
10 | import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport
11 | import kotlin.properties.Delegates
12 |
13 | /**
14 | * Client transport for WebSocket: this will connect to a server over the WebSocket protocol.
15 | */
16 | public class WebSocketClientTransport(
17 | private val client: HttpClient,
18 | private val urlString: String?,
19 | private val requestBuilder: HttpRequestBuilder.() -> Unit = {},
20 | ) : WebSocketMcpTransport() {
21 | override var session: WebSocketSession by Delegates.notNull()
22 |
23 | override suspend fun initializeSession() {
24 | session = urlString?.let {
25 | client.webSocketSession(it) {
26 | requestBuilder()
27 |
28 | header(HttpHeaders.SecWebSocketProtocol, MCP_SUBPROTOCOL)
29 | }
30 | } ?: client.webSocketSession {
31 | requestBuilder()
32 |
33 | header(HttpHeaders.SecWebSocketProtocol, MCP_SUBPROTOCOL)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.client
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.request.HttpRequestBuilder
5 | import io.modelcontextprotocol.kotlin.sdk.Implementation
6 | import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION
7 | import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME
8 |
9 | /**
10 | * Returns a new WebSocket transport for the Model Context Protocol using the provided HttpClient.
11 | *
12 | * @param urlString Optional URL of the MCP server.
13 | * @param requestBuilder Optional lambda to configure the HTTP request.
14 | * @return A [WebSocketClientTransport] configured for MCP communication.
15 | */
16 | public fun HttpClient.mcpWebSocketTransport(
17 | urlString: String? = null,
18 | requestBuilder: HttpRequestBuilder.() -> Unit = {},
19 | ): WebSocketClientTransport = WebSocketClientTransport(this, urlString, requestBuilder)
20 |
21 | /**
22 | * Creates and connects an MCP client over WebSocket using the provided HttpClient.
23 | *
24 | * @param urlString Optional URL of the MCP server.
25 | * @param requestBuilder Optional lambda to configure the HTTP request.
26 | * @return A connected [Client] ready for MCP communication.
27 | */
28 | public suspend fun HttpClient.mcpWebSocket(
29 | urlString: String? = null,
30 | requestBuilder: HttpRequestBuilder.() -> Unit = {},
31 | ): Client {
32 | val transport = mcpWebSocketTransport(urlString, requestBuilder)
33 | val client = Client(
34 | Implementation(
35 | name = IMPLEMENTATION_NAME,
36 | version = LIB_VERSION
37 | )
38 | )
39 | client.connect(transport)
40 | return client
41 | }
42 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.internal
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 |
5 | internal expect val IODispatcher: CoroutineDispatcher
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.server
2 |
3 | import io.github.oshai.kotlinlogging.KotlinLogging
4 | import io.ktor.http.HttpStatusCode
5 | import io.ktor.server.application.Application
6 | import io.ktor.server.application.install
7 | import io.ktor.server.response.respond
8 | import io.ktor.server.routing.Routing
9 | import io.ktor.server.routing.RoutingContext
10 | import io.ktor.server.routing.post
11 | import io.ktor.server.routing.route
12 | import io.ktor.server.routing.routing
13 | import io.ktor.server.sse.SSE
14 | import io.ktor.server.sse.ServerSSESession
15 | import io.ktor.server.sse.sse
16 | import io.ktor.util.collections.ConcurrentMap
17 | import io.ktor.utils.io.KtorDsl
18 |
19 | private val logger = KotlinLogging.logger {}
20 |
21 | @KtorDsl
22 | public fun Routing.mcp(path: String, block: () -> Server) {
23 | route(path) {
24 | mcp(block)
25 | }
26 | }
27 |
28 | /**
29 | * Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE).
30 | */
31 | @KtorDsl
32 | public fun Routing.mcp(block: () -> Server) {
33 | val transports = ConcurrentMap()
34 |
35 | sse {
36 | mcpSseEndpoint("", transports, block)
37 | }
38 |
39 | post {
40 | mcpPostEndpoint(transports)
41 | }
42 | }
43 |
44 | @Suppress("FunctionName")
45 | @Deprecated("Use mcp() instead", ReplaceWith("mcp(block)"), DeprecationLevel.WARNING)
46 | public fun Application.MCP(block: () -> Server) {
47 | mcp(block)
48 | }
49 |
50 | @KtorDsl
51 | public fun Application.mcp(block: () -> Server) {
52 | val transports = ConcurrentMap()
53 |
54 | install(SSE)
55 |
56 | routing {
57 | sse("/sse") {
58 | mcpSseEndpoint("/message", transports, block)
59 | }
60 |
61 | post("/message") {
62 | mcpPostEndpoint(transports)
63 | }
64 | }
65 | }
66 |
67 | private suspend fun ServerSSESession.mcpSseEndpoint(
68 | postEndpoint: String,
69 | transports: ConcurrentMap,
70 | block: () -> Server,
71 | ) {
72 | val transport = mcpSseTransport(postEndpoint, transports)
73 |
74 | val server = block()
75 |
76 | server.onClose {
77 | logger.info { "Server connection closed for sessionId: ${transport.sessionId}" }
78 | transports.remove(transport.sessionId)
79 | }
80 |
81 | server.connect(transport)
82 | logger.debug { "Server connected to transport for sessionId: ${transport.sessionId}" }
83 | }
84 |
85 | internal fun ServerSSESession.mcpSseTransport(
86 | postEndpoint: String,
87 | transports: ConcurrentMap,
88 | ): SseServerTransport {
89 | val transport = SseServerTransport(postEndpoint, this)
90 | transports[transport.sessionId] = transport
91 |
92 | logger.info { "New SSE connection established and stored with sessionId: ${transport.sessionId}" }
93 |
94 | return transport
95 | }
96 |
97 | internal suspend fun RoutingContext.mcpPostEndpoint(
98 | transports: ConcurrentMap,
99 | ) {
100 | val sessionId: String = call.request.queryParameters["sessionId"]
101 | ?: run {
102 | call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided")
103 | return
104 | }
105 |
106 | logger.debug { "Received message for sessionId: $sessionId" }
107 |
108 | val transport = transports[sessionId]
109 | if (transport == null) {
110 | logger.warn { "Session not found for sessionId: $sessionId" }
111 | call.respond(HttpStatusCode.NotFound, "Session not found")
112 | return
113 | }
114 |
115 | transport.handlePostMessage(call)
116 | logger.trace { "Message handled for sessionId: $sessionId" }
117 | }
118 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.server
2 |
3 | import io.ktor.http.ContentType
4 | import io.ktor.http.HttpStatusCode
5 | import io.ktor.http.encodeURLPath
6 | import io.ktor.server.application.ApplicationCall
7 | import io.ktor.server.request.contentType
8 | import io.ktor.server.request.receiveText
9 | import io.ktor.server.response.respondText
10 | import io.ktor.server.sse.ServerSSESession
11 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
12 | import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
13 | import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
14 | import kotlinx.coroutines.job
15 | import kotlinx.serialization.encodeToString
16 | import kotlin.concurrent.atomics.AtomicBoolean
17 | import kotlin.concurrent.atomics.ExperimentalAtomicApi
18 | import kotlin.uuid.ExperimentalUuidApi
19 | import kotlin.uuid.Uuid
20 |
21 | internal const val SESSION_ID_PARAM = "sessionId"
22 |
23 | @Deprecated("Use SseServerTransport instead", ReplaceWith("SseServerTransport"), DeprecationLevel.WARNING)
24 | public typealias SSEServerTransport = SseServerTransport
25 |
26 | /**
27 | * Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests.
28 | *
29 | * Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`.
30 | */
31 | @OptIn(ExperimentalAtomicApi::class)
32 | public class SseServerTransport(
33 | private val endpoint: String,
34 | private val session: ServerSSESession,
35 | ) : AbstractTransport() {
36 | private val initialized: AtomicBoolean = AtomicBoolean(false)
37 |
38 | @OptIn(ExperimentalUuidApi::class)
39 | public val sessionId: String = Uuid.random().toString()
40 |
41 | /**
42 | * Handles the initial SSE connection request.
43 | *
44 | * This should be called when a GET request is made to establish the SSE stream.
45 | */
46 | override suspend fun start() {
47 | if (!initialized.compareAndSet(expectedValue = false, newValue = true)) {
48 | error("SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.")
49 | }
50 |
51 | // Send the endpoint event
52 | session.send(
53 | event = "endpoint",
54 | data = "${endpoint.encodeURLPath()}?$SESSION_ID_PARAM=${sessionId}",
55 | )
56 |
57 | try {
58 | session.coroutineContext.job.join()
59 | } finally {
60 | _onClose.invoke()
61 | }
62 | }
63 |
64 | /**
65 | * Handles incoming POST messages.
66 | *
67 | * This should be called when a POST request is made to send a message to the server.
68 | */
69 | public suspend fun handlePostMessage(call: ApplicationCall) {
70 | if (!initialized.load()) {
71 | val message = "SSE connection not established"
72 | call.respondText(message, status = HttpStatusCode.InternalServerError)
73 | _onError.invoke(IllegalStateException(message))
74 | }
75 |
76 | val body = try {
77 | val ct = call.request.contentType()
78 | if (ct != ContentType.Application.Json) {
79 | error("Unsupported content-type: $ct")
80 | }
81 |
82 | call.receiveText()
83 | } catch (e: Exception) {
84 | call.respondText("Invalid message: ${e.message}", status = HttpStatusCode.BadRequest)
85 | _onError.invoke(e)
86 | return
87 | }
88 |
89 | try {
90 | handleMessage(body)
91 | } catch (e: Exception) {
92 | call.respondText("Error handling message $body: ${e.message}", status = HttpStatusCode.BadRequest)
93 | return
94 | }
95 |
96 | call.respondText("Accepted", status = HttpStatusCode.Accepted)
97 | }
98 |
99 | /**
100 | * Handle a client message, regardless of how it arrived.
101 | * This can be used to inform the server of messages that arrive via a means different from HTTP POST.
102 | */
103 | public suspend fun handleMessage(message: String) {
104 | try {
105 | val parsedMessage = McpJson.decodeFromString(message)
106 | _onMessage.invoke(parsedMessage)
107 | } catch (e: Exception) {
108 | _onError.invoke(e)
109 | throw e
110 | }
111 | }
112 |
113 | override suspend fun close() {
114 | session.close()
115 | _onClose.invoke()
116 | }
117 |
118 | override suspend fun send(message: JSONRPCMessage) {
119 | if (!initialized.load()) {
120 | error("Not connected")
121 | }
122 |
123 | session.send(
124 | event = "message",
125 | data = McpJson.encodeToString(message),
126 | )
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.server
2 |
3 | import io.ktor.server.routing.Route
4 | import io.ktor.server.websocket.WebSocketServerSession
5 | import io.ktor.server.websocket.webSocket
6 | import io.modelcontextprotocol.kotlin.sdk.Implementation
7 | import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION
8 | import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
9 | import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME
10 |
11 | /**
12 | * Registers a WebSocket route that establishes an MCP (Model Context Protocol) server session.
13 | *
14 | * @param options Optional server configuration settings for the MCP server.
15 | * @param handler A suspend function that defines the server's behavior.
16 | */
17 | public fun Route.mcpWebSocket(
18 | options: ServerOptions? = null,
19 | handler: suspend Server.() -> Unit = {},
20 | ) {
21 | webSocket {
22 | createMcpServer(this, options, handler)
23 | }
24 | }
25 |
26 | /**
27 | * Registers a WebSocket route at the specified [path] that establishes an MCP server session.
28 | *
29 | * @param path The URL path at which to register the WebSocket route.
30 | * @param options Optional server configuration settings for the MCP server.
31 | * @param handler A suspend function that defines the server's behavior.
32 | */
33 | public fun Route.mcpWebSocket(
34 | path: String,
35 | options: ServerOptions? = null,
36 | handler: suspend Server.() -> Unit = {},
37 | ) {
38 | webSocket(path) {
39 | createMcpServer(this, options, handler)
40 | }
41 | }
42 |
43 | /**
44 | * Registers a WebSocket route that creates an MCP server transport layer.
45 | *
46 | * @param handler A suspend function that defines the behavior of the transport layer.
47 | */
48 | public fun Route.mcpWebSocketTransport(
49 | handler: suspend WebSocketMcpServerTransport.() -> Unit = {},
50 | ) {
51 | webSocket {
52 | val transport = createMcpTransport(this)
53 | transport.start()
54 | handler(transport)
55 | transport.close()
56 | }
57 | }
58 |
59 | /**
60 | * Registers a WebSocket route at the specified [path] that creates an MCP server transport layer.
61 | *
62 | * @param path The URL path at which to register the WebSocket route.
63 | * @param handler A suspend function that defines the behavior of the transport layer.
64 | */
65 | public fun Route.mcpWebSocketTransport(
66 | path: String,
67 | handler: suspend WebSocketMcpServerTransport.() -> Unit = {},
68 | ) {
69 | webSocket(path) {
70 | val transport = createMcpTransport(this)
71 | transport.start()
72 | handler(transport)
73 | transport.close()
74 | }
75 | }
76 |
77 |
78 | private suspend fun Route.createMcpServer(
79 | session: WebSocketServerSession,
80 | options: ServerOptions?,
81 | handler: suspend Server.() -> Unit,
82 | ) {
83 | val transport = createMcpTransport(session)
84 |
85 | val server = Server(
86 | serverInfo = Implementation(
87 | name = IMPLEMENTATION_NAME,
88 | version = LIB_VERSION
89 | ),
90 | options = options ?: ServerOptions(
91 | capabilities = ServerCapabilities(
92 | prompts = ServerCapabilities.Prompts(listChanged = null),
93 | resources = ServerCapabilities.Resources(subscribe = null, listChanged = null),
94 | tools = ServerCapabilities.Tools(listChanged = null),
95 | )
96 | ),
97 | )
98 |
99 | server.connect(transport)
100 | handler(server)
101 | server.close()
102 | }
103 |
104 | private fun createMcpTransport(
105 | session: WebSocketServerSession,
106 | ): WebSocketMcpServerTransport {
107 | return WebSocketMcpServerTransport(session)
108 | }
109 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.server
2 |
3 | import io.ktor.http.HttpHeaders
4 | import io.ktor.server.websocket.WebSocketServerSession
5 | import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL
6 | import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport
7 |
8 | /**
9 | * Server-side implementation of the MCP (Model Context Protocol) transport over WebSocket.
10 | *
11 | * @property session The WebSocket server session used for communication.
12 | */
13 | public class WebSocketMcpServerTransport(
14 | override val session: WebSocketServerSession,
15 | ) : WebSocketMcpTransport() {
16 | override suspend fun initializeSession() {
17 | val subprotocol = session.call.request.headers[HttpHeaders.SecWebSocketProtocol]
18 | if (subprotocol != MCP_SUBPROTOCOL) {
19 | error("Invalid subprotocol: $subprotocol, expected $MCP_SUBPROTOCOL")
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.shared
2 |
3 | import io.ktor.utils.io.core.writeFully
4 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
5 | import kotlinx.io.Buffer
6 | import kotlinx.io.indexOf
7 | import kotlinx.io.readString
8 | import kotlinx.serialization.encodeToString
9 |
10 | /**
11 | * Buffers a continuous stdio stream into discrete JSON-RPC messages.
12 | */
13 | public class ReadBuffer {
14 | private val buffer: Buffer = Buffer()
15 |
16 | public fun append(chunk: ByteArray) {
17 | buffer.writeFully(chunk)
18 | }
19 |
20 | public fun readMessage(): JSONRPCMessage? {
21 | if (buffer.exhausted()) return null
22 | var lfIndex = buffer.indexOf('\n'.code.toByte())
23 | val line = when (lfIndex) {
24 | -1L -> return null
25 | 0L -> {
26 | buffer.skip(1)
27 | return null
28 | }
29 |
30 | else -> {
31 | var skipBytes = 1
32 | if (buffer[lfIndex - 1] == '\r'.code.toByte()) {
33 | lfIndex -= 1
34 | skipBytes += 1
35 | }
36 | val string = buffer.readString(lfIndex)
37 | buffer.skip(skipBytes.toLong())
38 | string
39 | }
40 | }
41 | return deserializeMessage(line)
42 | }
43 |
44 | public fun clear() {
45 | buffer.clear()
46 | }
47 | }
48 |
49 | internal fun deserializeMessage(line: String): JSONRPCMessage {
50 | return McpJson.decodeFromString(line)
51 | }
52 |
53 | internal fun serializeMessage(message: JSONRPCMessage): String {
54 | return McpJson.encodeToString(message) + "\n"
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.shared
2 |
3 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
4 | import kotlinx.coroutines.CompletableDeferred
5 |
6 | /**
7 | * Describes the minimal contract for MCP transport that a client or server can communicate over.
8 | */
9 | public interface Transport {
10 | /**
11 | * Starts processing messages on the transport, including any connection steps that might need to be taken.
12 | *
13 | * This method should only be called after callbacks are installed, or else messages may be lost.
14 | *
15 | * NOTE: This method should not be called explicitly when using Client, Server, or Protocol classes,
16 | * as they will implicitly call start().
17 | */
18 | public suspend fun start()
19 |
20 | /**
21 | * Sends a JSON-RPC message (request or response).
22 | */
23 | public suspend fun send(message: JSONRPCMessage)
24 |
25 | /**
26 | * Closes the connection.
27 | */
28 | public suspend fun close()
29 |
30 | /**
31 | * Callback for when the connection is closed for any reason.
32 | *
33 | * This should be invoked when close() is called as well.
34 | */
35 | public fun onClose(block: () -> Unit)
36 |
37 | /**
38 | * Callback for when an error occurs.
39 | *
40 | * Note that errors are not necessarily fatal; they are used for reporting any kind of
41 | * exceptional condition out of a band.
42 | */
43 | public fun onError(block: (Throwable) -> Unit)
44 |
45 | /**
46 | * Callback for when a message (request or response) is received over the connection.
47 | */
48 | public fun onMessage(block: suspend (JSONRPCMessage) -> Unit)
49 | }
50 |
51 | /**
52 | * Implements [onClose], [onError] and [onMessage] functions of [Transport] providing
53 | * corresponding [_onClose], [_onError] and [_onMessage] properties to use for an implementation.
54 | */
55 | @Suppress("PropertyName")
56 | public abstract class AbstractTransport : Transport {
57 | protected var _onClose: (() -> Unit) = {}
58 | private set
59 | protected var _onError: ((Throwable) -> Unit) = {}
60 | private set
61 |
62 | // to not skip messages
63 | private val _onMessageInitialized = CompletableDeferred()
64 | protected var _onMessage: (suspend ((JSONRPCMessage) -> Unit)) = {
65 | _onMessageInitialized.await()
66 | _onMessage.invoke(it)
67 | }
68 | private set
69 |
70 | override fun onClose(block: () -> Unit) {
71 | val old = _onClose
72 | _onClose = {
73 | old()
74 | block()
75 | }
76 | }
77 |
78 | override fun onError(block: (Throwable) -> Unit) {
79 | val old = _onError
80 | _onError = { e ->
81 | old(e)
82 | block(e)
83 | }
84 | }
85 |
86 | override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) {
87 | val old: suspend (JSONRPCMessage) -> Unit = when (_onMessageInitialized.isCompleted) {
88 | true -> _onMessage
89 | false -> { _ -> }
90 | }
91 |
92 | _onMessage = { message ->
93 | old(message)
94 | block(message)
95 | }
96 |
97 | _onMessageInitialized.complete(Unit)
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.shared
2 |
3 | import io.ktor.websocket.Frame
4 | import io.ktor.websocket.WebSocketSession
5 | import io.ktor.websocket.close
6 | import io.ktor.websocket.readText
7 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
8 | import kotlinx.coroutines.CoroutineName
9 | import kotlinx.coroutines.CoroutineScope
10 | import kotlinx.coroutines.InternalCoroutinesApi
11 | import kotlinx.coroutines.SupervisorJob
12 | import kotlinx.coroutines.channels.ClosedReceiveChannelException
13 | import kotlinx.coroutines.job
14 | import kotlinx.coroutines.launch
15 | import kotlinx.serialization.encodeToString
16 | import kotlin.concurrent.atomics.AtomicBoolean
17 | import kotlin.concurrent.atomics.ExperimentalAtomicApi
18 |
19 | internal const val MCP_SUBPROTOCOL = "mcp"
20 |
21 | /**
22 | * Abstract class representing a WebSocket transport for the Model Context Protocol (MCP).
23 | * Handles communication over a WebSocket session.
24 | */
25 | @OptIn(ExperimentalAtomicApi::class)
26 | public abstract class WebSocketMcpTransport : AbstractTransport() {
27 | private val scope by lazy {
28 | CoroutineScope(session.coroutineContext + SupervisorJob())
29 | }
30 |
31 | private val initialized: AtomicBoolean = AtomicBoolean(false)
32 |
33 | /**
34 | * The WebSocket session used for communication.
35 | */
36 | protected abstract val session: WebSocketSession
37 |
38 | /**
39 | * Initializes the WebSocket session
40 | */
41 | protected abstract suspend fun initializeSession()
42 |
43 | override suspend fun start() {
44 | if (!initialized.compareAndSet(expectedValue = false, newValue = true)) {
45 | error(
46 | "WebSocketClientTransport already started! " +
47 | "If using Client class, note that connect() calls start() automatically.",
48 | )
49 | }
50 |
51 | initializeSession()
52 |
53 | scope.launch(CoroutineName("WebSocketMcpTransport.collect#${hashCode()}")) {
54 | while (true) {
55 | val message = try {
56 | session.incoming.receive()
57 | } catch (_: ClosedReceiveChannelException) {
58 | return@launch
59 | }
60 |
61 | if (message !is Frame.Text) {
62 | val e = IllegalArgumentException("Expected text frame, got ${message::class.simpleName}: $message")
63 | _onError.invoke(e)
64 | throw e
65 | }
66 |
67 | try {
68 | val message = McpJson.decodeFromString(message.readText())
69 | _onMessage.invoke(message)
70 | } catch (e: Exception) {
71 | _onError.invoke(e)
72 | throw e
73 | }
74 | }
75 | }
76 |
77 | @OptIn(InternalCoroutinesApi::class)
78 | session.coroutineContext.job.invokeOnCompletion {
79 | if (it != null) {
80 | _onError.invoke(it)
81 | } else {
82 | _onClose.invoke()
83 | }
84 | }
85 | }
86 |
87 | override suspend fun send(message: JSONRPCMessage) {
88 | if (!initialized.load()) {
89 | error("Not connected")
90 | }
91 |
92 | session.outgoing.send(Frame.Text(McpJson.encodeToString(message)))
93 | }
94 |
95 | override suspend fun close() {
96 | if (!initialized.load()) {
97 | error("Not connected")
98 | }
99 |
100 | session.close()
101 | session.coroutineContext.job.join()
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/AudioContentSerializationTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk
2 |
3 | import io.kotest.assertions.json.shouldEqualJson
4 | import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
5 | import kotlinx.serialization.encodeToString
6 | import kotlin.test.Test
7 | import kotlin.test.assertEquals
8 |
9 | class AudioContentSerializationTest {
10 |
11 | private val audioContentJson = """
12 | {
13 | "data": "base64-encoded-audio-data",
14 | "mimeType": "audio/wav",
15 | "type": "audio"
16 | }
17 | """.trimIndent()
18 |
19 | private val audioContent = AudioContent(
20 | data = "base64-encoded-audio-data",
21 | mimeType = "audio/wav"
22 | )
23 |
24 | @Test
25 | fun `should serialize audio content`() {
26 | McpJson.encodeToString(audioContent) shouldEqualJson audioContentJson
27 | }
28 |
29 | @Test
30 | fun `should deserialize audio content`() {
31 | val content = McpJson.decodeFromString(audioContentJson)
32 | assertEquals(expected = audioContent, actual = content)
33 | }
34 | }
--------------------------------------------------------------------------------
/src/commonTest/kotlin/CallToolResultUtilsTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk
2 |
3 | import kotlinx.serialization.json.JsonPrimitive
4 | import kotlinx.serialization.json.buildJsonObject
5 | import kotlin.test.Test
6 | import kotlin.test.assertEquals
7 | import kotlin.test.assertFalse
8 | import kotlin.test.assertTrue
9 |
10 | class CallToolResultUtilsTest {
11 |
12 | @Test
13 | fun testOkWithOnlyText() {
14 | val content = "TextMessage"
15 | val result = CallToolResult.Companion.ok(content)
16 |
17 | assertEquals(1, result.content.size)
18 | assertEquals(content, (result.content[0] as TextContent).text)
19 | assertFalse(result.isError == true)
20 | assertEquals(EmptyJsonObject, result._meta)
21 | }
22 |
23 | @Test
24 | fun testOkWithMeta() {
25 | val content = "TextMessageWithMeta"
26 | val meta = buildJsonObject {
27 | put("key1", JsonPrimitive("value1"))
28 | put("key2", JsonPrimitive(42))
29 | }
30 | val result = CallToolResult.Companion.ok(content, meta)
31 |
32 | assertEquals(1, result.content.size)
33 | assertEquals(content, (result.content[0] as TextContent).text)
34 | assertFalse(result.isError == true)
35 | assertEquals(meta, result._meta)
36 | }
37 |
38 | @Test
39 | fun testErrorWithOnlyText() {
40 | val content = "ErrorMessage"
41 | val result = CallToolResult.Companion.error(content)
42 |
43 | assertEquals(1, result.content.size)
44 | assertEquals(content, (result.content[0] as TextContent).text)
45 | assertTrue(result.isError == true)
46 | assertEquals(EmptyJsonObject, result._meta)
47 | }
48 |
49 | @Test
50 | fun testErrorWithMeta() {
51 | val content = "ErrorMessageWithMeta"
52 | val meta = buildJsonObject {
53 | put("errorCode", JsonPrimitive(404))
54 | put("errorDetail", JsonPrimitive("资源未找到"))
55 | }
56 | val result = CallToolResult.Companion.error(content, meta)
57 |
58 | assertEquals(1, result.content.size)
59 | assertEquals(content, (result.content[0] as TextContent).text)
60 | assertTrue(result.isError == true)
61 | assertEquals(meta, result._meta)
62 | }
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/InMemoryTransport.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk
2 |
3 | import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport
4 |
5 | /**
6 | * In-memory transport for creating clients and servers that talk to each other within the same process.
7 | */
8 | class InMemoryTransport : AbstractTransport() {
9 | private var otherTransport: InMemoryTransport? = null
10 | private val messageQueue: MutableList = mutableListOf()
11 |
12 | /**
13 | * Creates a pair of linked in-memory transports that can communicate with each other.
14 | * One should be passed to a Client and one to a Server.
15 | */
16 | companion object {
17 | fun createLinkedPair(): Pair {
18 | val clientTransport = InMemoryTransport()
19 | val serverTransport = InMemoryTransport()
20 | clientTransport.otherTransport = serverTransport
21 | serverTransport.otherTransport = clientTransport
22 | return Pair(clientTransport, serverTransport)
23 | }
24 | }
25 |
26 | override suspend fun start() {
27 | // Process any messages that were queued before start was called
28 | while (messageQueue.isNotEmpty()) {
29 | messageQueue.removeFirstOrNull()?.let { message ->
30 | _onMessage.invoke(message) // todo?
31 | }
32 | }
33 | }
34 |
35 | override suspend fun close() {
36 | val other = otherTransport
37 | otherTransport = null
38 | other?.close()
39 | _onClose.invoke()
40 | }
41 |
42 | override suspend fun send(message: JSONRPCMessage) {
43 | val other = otherTransport ?: throw IllegalStateException("Not connected")
44 |
45 | other._onMessage.invoke(message)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk
2 |
3 | import io.kotest.assertions.json.shouldEqualJson
4 | import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
5 | import kotlinx.serialization.encodeToString
6 | import kotlinx.serialization.json.Json
7 | import kotlinx.serialization.json.JsonPrimitive
8 | import kotlinx.serialization.json.buildJsonObject
9 | import kotlin.test.Test
10 | import kotlin.test.assertEquals
11 |
12 | class ToolSerializationTest {
13 |
14 | // see https://docs.anthropic.com/en/docs/build-with-claude/tool-use
15 | /* language=json */
16 | private val getWeatherToolJson = """
17 | {
18 | "name": "get_weather",
19 | "description": "Get the current weather in a given location",
20 | "inputSchema": {
21 | "type": "object",
22 | "properties": {
23 | "location": {
24 | "type": "string",
25 | "description": "The city and state, e.g. San Francisco, CA"
26 | }
27 | },
28 | "required": ["location"]
29 | }
30 | }
31 | """.trimIndent()
32 |
33 | val getWeatherTool = Tool(
34 | name = "get_weather",
35 | description = "Get the current weather in a given location",
36 | inputSchema = Tool.Input(
37 | properties = buildJsonObject {
38 | put("location", buildJsonObject {
39 | put("type", JsonPrimitive("string"))
40 | put("description", JsonPrimitive("The city and state, e.g. San Francisco, CA"))
41 | })
42 | },
43 | required = listOf("location")
44 | )
45 | )
46 |
47 | @Test
48 | fun `should serialize get_weather tool`() {
49 | McpJson.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson
50 | }
51 |
52 | @Test
53 | fun `should deserialize get_weather tool`() {
54 | val tool = McpJson.decodeFromString(getWeatherToolJson)
55 | assertEquals(expected = getWeatherTool, actual = tool)
56 | }
57 |
58 | @Test
59 | fun `should always serialize default value`() {
60 | val json = Json(from = McpJson) {
61 | encodeDefaults = false
62 | }
63 | json.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/BaseTransportTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.client
2 |
3 | import io.modelcontextprotocol.kotlin.sdk.InitializedNotification
4 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
5 | import io.modelcontextprotocol.kotlin.sdk.PingRequest
6 | import io.modelcontextprotocol.kotlin.sdk.shared.Transport
7 | import io.modelcontextprotocol.kotlin.sdk.toJSON
8 | import kotlinx.coroutines.CompletableDeferred
9 | import kotlin.test.assertEquals
10 | import kotlin.test.assertFalse
11 | import kotlin.test.assertTrue
12 | import kotlin.test.fail
13 |
14 | abstract class BaseTransportTest {
15 | protected suspend fun testClientOpenClose(client: Transport) {
16 | client.onError { error ->
17 | fail("Unexpected error: $error")
18 | }
19 |
20 | var didClose = false
21 | client.onClose { didClose = true }
22 |
23 | client.start()
24 | assertFalse(didClose, "Transport should not be closed immediately after start")
25 |
26 | client.close()
27 | assertTrue(didClose, "Transport should be closed after close() call")
28 | }
29 |
30 | protected suspend fun testClientRead(client: Transport) {
31 | client.onError { error ->
32 | error.printStackTrace()
33 | fail("Unexpected error: $error")
34 | }
35 |
36 | val messages = listOf(
37 | PingRequest().toJSON(), InitializedNotification().toJSON()
38 | )
39 |
40 | val readMessages = mutableListOf()
41 | val finished = CompletableDeferred()
42 |
43 | client.onMessage { message ->
44 | readMessages.add(message)
45 | if (message == messages.last()) {
46 | finished.complete(Unit)
47 | }
48 | }
49 |
50 | client.start()
51 |
52 | for (message in messages) {
53 | client.send(message)
54 | }
55 |
56 | finished.await()
57 |
58 | assertEquals(messages, readMessages, "Assert messages received")
59 |
60 | client.close()
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/InMemoryTransportTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.client
2 |
3 | import io.modelcontextprotocol.kotlin.sdk.InMemoryTransport
4 | import io.modelcontextprotocol.kotlin.sdk.InitializedNotification
5 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
6 | import io.modelcontextprotocol.kotlin.sdk.toJSON
7 | import kotlinx.coroutines.test.runTest
8 | import kotlin.test.BeforeTest
9 | import kotlin.test.Test
10 | import kotlin.test.assertEquals
11 | import kotlin.test.assertFailsWith
12 | import kotlin.test.assertNotNull
13 | import kotlin.test.assertTrue
14 |
15 | class InMemoryTransportTest {
16 | private lateinit var clientTransport: InMemoryTransport
17 | private lateinit var serverTransport: InMemoryTransport
18 |
19 | @BeforeTest
20 | fun setUp() {
21 | val (client, server) = InMemoryTransport.createLinkedPair()
22 | clientTransport = client
23 | serverTransport = server
24 | }
25 |
26 | @Test
27 | fun `should create linked pair`() {
28 | assertNotNull(clientTransport)
29 | assertNotNull(serverTransport)
30 | }
31 |
32 | @Test
33 | fun `should start without error`() = runTest {
34 | clientTransport.start()
35 | serverTransport.start()
36 | // If no exception is thrown, the test passes
37 | }
38 |
39 | @Test
40 | fun `should send message from client to server`() = runTest {
41 | val message = InitializedNotification()
42 |
43 | var receivedMessage: JSONRPCMessage? = null
44 | serverTransport.onMessage { msg ->
45 | receivedMessage = msg
46 | }
47 |
48 | val rpcNotification = message.toJSON()
49 | clientTransport.send(rpcNotification)
50 | assertEquals(rpcNotification, receivedMessage)
51 | }
52 |
53 | @Test
54 | fun `should send message from server to client`() = runTest {
55 | val message = InitializedNotification()
56 | .toJSON()
57 |
58 | var receivedMessage: JSONRPCMessage? = null
59 | clientTransport.onMessage { msg ->
60 | receivedMessage = msg
61 | }
62 |
63 | serverTransport.send(message)
64 | assertEquals(message, receivedMessage)
65 | }
66 |
67 | @Test
68 | fun `should handle close`() = runTest {
69 | var clientClosed = false
70 | var serverClosed = false
71 |
72 | clientTransport.onClose {
73 | clientClosed = true
74 | }
75 |
76 | serverTransport.onClose {
77 | serverClosed = true
78 | }
79 |
80 | clientTransport.close()
81 | assertTrue(clientClosed)
82 | assertTrue(serverClosed)
83 | }
84 |
85 | @Test
86 | fun `should throw error when sending after close`() = runTest {
87 | clientTransport.close()
88 |
89 | assertFailsWith {
90 | clientTransport.send(
91 | InitializedNotification().toJSON()
92 | )
93 | }
94 | }
95 |
96 | @Test
97 | fun `should queue messages sent before start`() = runTest {
98 | val message = InitializedNotification()
99 | .toJSON()
100 |
101 | var receivedMessage: JSONRPCMessage? = null
102 | serverTransport.onMessage { msg ->
103 | receivedMessage = msg
104 | }
105 |
106 | clientTransport.send(message)
107 | serverTransport.start()
108 | assertEquals(message, receivedMessage)
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.client
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.plugins.sse.SSE
5 | import io.ktor.server.application.install
6 | import io.ktor.server.cio.CIO
7 | import io.ktor.server.engine.embeddedServer
8 | import io.ktor.server.routing.post
9 | import io.ktor.server.routing.routing
10 | import io.ktor.server.sse.sse
11 | import io.ktor.util.collections.ConcurrentMap
12 | import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport
13 | import io.modelcontextprotocol.kotlin.sdk.server.mcpPostEndpoint
14 | import io.modelcontextprotocol.kotlin.sdk.server.mcpSseTransport
15 | import kotlinx.coroutines.test.runTest
16 | import kotlin.test.Test
17 |
18 | class SseTransportTest : BaseTransportTest() {
19 | @Test
20 | fun `should start then close cleanly`() = runTest {
21 | val port = 8080
22 | val server = embeddedServer(CIO, port = port) {
23 | install(io.ktor.server.sse.SSE)
24 | val transports = ConcurrentMap()
25 | routing {
26 | sse {
27 | mcpSseTransport("", transports).start()
28 | }
29 |
30 | post {
31 | mcpPostEndpoint(transports)
32 | }
33 | }
34 | }.startSuspend(wait = false)
35 |
36 | val client = HttpClient {
37 | install(SSE)
38 | }.mcpSseTransport {
39 | url {
40 | host = "localhost"
41 | this.port = port
42 | }
43 | }
44 |
45 | testClientOpenClose(client)
46 |
47 | server.stopSuspend()
48 | }
49 |
50 | @Test
51 | fun `should read messages`() = runTest {
52 | val port = 3003
53 | val server = embeddedServer(CIO, port = port) {
54 | install(io.ktor.server.sse.SSE)
55 | val transports = ConcurrentMap()
56 | routing {
57 | sse {
58 | mcpSseTransport("", transports).apply {
59 | onMessage {
60 | send(it)
61 | }
62 |
63 | start()
64 | }
65 | }
66 |
67 | post {
68 | mcpPostEndpoint(transports)
69 | }
70 | }
71 | }.startSuspend(wait = false)
72 |
73 | val client = HttpClient {
74 | install(SSE)
75 | }.mcpSseTransport {
76 | url {
77 | host = "localhost"
78 | this.port = port
79 | }
80 | }
81 |
82 | testClientRead(client)
83 | server.stopSuspend()
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/TypesTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.client
2 |
3 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
4 | import io.modelcontextprotocol.kotlin.sdk.Request
5 | import io.modelcontextprotocol.kotlin.sdk.shared.McpJson
6 | import kotlin.test.Test
7 |
8 | class TypesTest {
9 |
10 | @Test
11 | fun testRequestResult() {
12 | val message =
13 | "{\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{\"listChanged\":true},\"resources\":{}},\"serverInfo\":{\"name\":\"jetbrains/proxy\",\"version\":\"0.1.0\"}},\"jsonrpc\":\"2.0\",\"id\":1}"
14 | McpJson.decodeFromString(message)
15 | }
16 |
17 | @Test
18 | fun testRequestError() {
19 | val message =
20 | "{\"method\":\"initialize\", \"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"experimental\":{},\"sampling\":{}},\"clientInfo\":{\"name\":\"test client\",\"version\":\"1.0\"},\"_meta\":{}}"
21 | McpJson.decodeFromString(message)
22 | }
23 |
24 | @Test
25 | fun testJSONRPCMessage() {
26 | val line = "{\"result\":{\"content\":[{\"type\":\"text\"}],\"isError\":false},\"jsonrpc\":\"2.0\",\"id\":4}"
27 | McpJson.decodeFromString(line)
28 | }
29 |
30 | @Test
31 | fun testJSONRPCMessageWithStringId() {
32 | val line = """
33 | {
34 | "jsonrpc": "2.0",
35 | "method": "initialize",
36 | "id": "ebf9f64a-0",
37 | "params": {
38 | "protocolVersion": "2024-11-05",
39 | "capabilities": {},
40 | "clientInfo": {
41 | "name": "mcp-java-client",
42 | "version": "0.2.0"
43 | }
44 | }
45 | }
46 | """.trimIndent()
47 | McpJson.decodeFromString(line)
48 | }
49 | }
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.client
2 |
3 | import io.ktor.server.testing.testApplication
4 | import io.ktor.server.websocket.WebSockets
5 | import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket
6 | import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocketTransport
7 | import kotlinx.coroutines.CompletableDeferred
8 | import kotlin.test.Ignore
9 | import kotlin.test.Test
10 |
11 | class WebSocketTransportTest : BaseTransportTest() {
12 | @Test
13 | @Ignore // "Test disabled for investigation #17"
14 | fun `should start then close cleanly`() = testApplication {
15 | install(WebSockets)
16 | routing {
17 | mcpWebSocket()
18 | }
19 |
20 | val client = createClient {
21 | install(io.ktor.client.plugins.websocket.WebSockets)
22 | }.mcpWebSocketTransport()
23 |
24 | testClientOpenClose(client)
25 | }
26 |
27 | @Test
28 | @Ignore // "Test disabled for investigation #17"
29 | fun `should read messages`() = testApplication {
30 | val clientFinished = CompletableDeferred()
31 |
32 | install(WebSockets)
33 | routing {
34 | mcpWebSocketTransport {
35 | onMessage {
36 | send(it)
37 | }
38 |
39 | clientFinished.await()
40 | }
41 | }
42 |
43 | val client = createClient {
44 | install(io.ktor.client.plugins.websocket.WebSockets)
45 | }.mcpWebSocketTransport()
46 |
47 | testClientRead(client)
48 |
49 | clientFinished.complete(Unit)
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.integration
2 |
3 | import io.ktor.client.HttpClient
4 | import io.ktor.client.plugins.sse.SSE
5 | import io.ktor.server.cio.CIOApplicationEngine
6 | import io.ktor.server.engine.EmbeddedServer
7 | import io.ktor.server.engine.embeddedServer
8 | import io.modelcontextprotocol.kotlin.sdk.Implementation
9 | import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
10 | import io.modelcontextprotocol.kotlin.sdk.client.Client
11 | import io.modelcontextprotocol.kotlin.sdk.client.mcpSse
12 | import io.modelcontextprotocol.kotlin.sdk.server.Server
13 | import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions
14 | import io.modelcontextprotocol.kotlin.sdk.server.mcp
15 | import kotlinx.coroutines.Dispatchers
16 | import kotlinx.coroutines.test.runTest
17 | import kotlinx.coroutines.withContext
18 | import kotlin.test.Test
19 | import kotlin.test.fail
20 | import io.ktor.client.engine.cio.CIO as ClientCIO
21 | import io.ktor.server.cio.CIO as ServerCIO
22 |
23 | class SseIntegrationTest {
24 | @Test
25 | fun `client should be able to connect to sse server`() = runTest {
26 | val serverEngine = initServer()
27 | try {
28 | withContext(Dispatchers.Default) {
29 | assertDoesNotThrow { initClient() }
30 | }
31 | } finally {
32 | // Make sure to stop the server
33 | serverEngine.stopSuspend(1000, 2000)
34 | }
35 | }
36 |
37 | private inline fun assertDoesNotThrow(block: () -> T): T {
38 | return try {
39 | block()
40 | } catch (e: Throwable) {
41 | fail("Expected no exception, but got: $e")
42 | }
43 | }
44 |
45 | private suspend fun initClient(): Client {
46 | return HttpClient(ClientCIO) { install(SSE) }.mcpSse("http://$URL:$PORT")
47 | }
48 |
49 | private suspend fun initServer(): EmbeddedServer {
50 | val server = Server(
51 | Implementation(name = "sse-e2e-test", version = "1.0.0"),
52 | ServerOptions(capabilities = ServerCapabilities()),
53 | )
54 |
55 | return embeddedServer(ServerCIO, host = URL, port = PORT) { mcp { server } }.startSuspend(wait = false)
56 | }
57 |
58 | companion object {
59 | private const val PORT = 3001
60 | private const val URL = "localhost"
61 | }
62 | }
--------------------------------------------------------------------------------
/src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.shared
2 |
3 | import io.ktor.utils.io.charsets.Charsets
4 | import io.ktor.utils.io.core.toByteArray
5 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
6 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification
7 | import kotlinx.serialization.encodeToString
8 | import kotlinx.serialization.json.Json
9 | import kotlin.test.Test
10 | import kotlin.test.assertEquals
11 | import kotlin.test.assertNull
12 |
13 | class ReadBufferTest {
14 | private val testMessage: JSONRPCMessage = JSONRPCNotification(method = "foobar")
15 |
16 | private val json = Json {
17 | ignoreUnknownKeys = true
18 | encodeDefaults = true
19 | }
20 |
21 | @Test
22 | fun `should have no messages after initialization`() {
23 | val readBuffer = ReadBuffer()
24 | assertNull(readBuffer.readMessage())
25 | }
26 |
27 | @Test
28 | fun `should only yield a message after a newline`() {
29 | val readBuffer = ReadBuffer()
30 |
31 | // Append message without a newline
32 | val messageBytes = json.encodeToString(testMessage).encodeToByteArray()
33 | readBuffer.append(messageBytes)
34 | assertNull(readBuffer.readMessage())
35 |
36 | // Append a newline and verify message is now available
37 | readBuffer.append("\n".encodeToByteArray())
38 | assertEquals(testMessage, readBuffer.readMessage())
39 | assertNull(readBuffer.readMessage())
40 | }
41 |
42 | @Test
43 | fun `skip empty line`() {
44 | val readBuffer = ReadBuffer()
45 | readBuffer.append("\n".toByteArray())
46 | assertNull(readBuffer.readMessage())
47 | }
48 |
49 | @Test
50 | fun `should be reusable after clearing`() {
51 | val readBuffer = ReadBuffer()
52 |
53 | readBuffer.append("foobar".toByteArray(Charsets.UTF_8))
54 | readBuffer.clear()
55 | assertNull(readBuffer.readMessage())
56 |
57 | val messageJson = serializeMessage(testMessage)
58 | readBuffer.append(messageJson.toByteArray(Charsets.UTF_8))
59 | readBuffer.append("\n".toByteArray(Charsets.UTF_8))
60 | val message = readBuffer.readMessage()
61 | assertEquals(testMessage, message)
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/iosMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.ios.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.internal
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 | import kotlinx.coroutines.IO
6 |
7 | internal actual val IODispatcher: CoroutineDispatcher
8 | get() = Dispatchers.IO
--------------------------------------------------------------------------------
/src/jsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.js.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.internal
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 |
6 | internal actual val IODispatcher: CoroutineDispatcher
7 | get() = Dispatchers.Default
--------------------------------------------------------------------------------
/src/jvmMain/java/io/modelcontextprotocol/kotlin/sdk/internal/utils.jvm.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.internal
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 |
6 | internal actual val IODispatcher: CoroutineDispatcher
7 | get() = Dispatchers.IO
--------------------------------------------------------------------------------
/src/jvmTest/kotlin/client/ClientIntegrationTest.kt:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import io.modelcontextprotocol.kotlin.sdk.Implementation
4 | import io.modelcontextprotocol.kotlin.sdk.ListToolsResult
5 | import io.modelcontextprotocol.kotlin.sdk.client.Client
6 | import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport
7 | import kotlinx.coroutines.test.runTest
8 | import kotlinx.io.asSink
9 | import kotlinx.io.asSource
10 | import kotlinx.io.buffered
11 | import org.junit.jupiter.api.Disabled
12 | import org.junit.jupiter.api.Test
13 | import java.net.Socket
14 |
15 | class ClientIntegrationTest {
16 |
17 | fun createTransport(): StdioClientTransport {
18 | val socket = Socket("localhost", 3000)
19 |
20 | return StdioClientTransport(
21 | socket.inputStream.asSource().buffered(),
22 | socket.outputStream.asSink().buffered()
23 | )
24 | }
25 |
26 | @Disabled("This test requires a running server")
27 | @Test
28 | fun testRequestTools() = runTest {
29 | val client = Client(
30 | Implementation("test", "1.0"),
31 | )
32 |
33 | val transport = createTransport()
34 | try {
35 | client.connect(transport)
36 |
37 | val response: ListToolsResult? = client.listTools()
38 | println(response?.tools)
39 |
40 | } finally {
41 | transport.close()
42 | }
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/jvmTest/kotlin/client/StdioClientTransportTest.kt:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import io.modelcontextprotocol.kotlin.sdk.client.BaseTransportTest
4 | import kotlinx.coroutines.test.runTest
5 | import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport
6 | import kotlinx.io.asSink
7 | import kotlinx.io.asSource
8 | import kotlinx.io.buffered
9 | import org.junit.jupiter.api.Test
10 |
11 | class StdioClientTransportTest : BaseTransportTest() {
12 | @Test
13 | fun `should start then close cleanly`() = runTest {
14 | // Run process "/usr/bin/tee"
15 | val processBuilder = ProcessBuilder("/usr/bin/tee")
16 | val process = processBuilder.start()
17 |
18 | val input = process.inputStream.asSource().buffered()
19 | val output = process.outputStream.asSink().buffered()
20 |
21 | val client = StdioClientTransport(
22 | input = input,
23 | output = output
24 | )
25 |
26 | testClientOpenClose(client)
27 |
28 | process.destroy()
29 | }
30 |
31 | @Test
32 | fun `should read messages`() = runTest {
33 | val processBuilder = ProcessBuilder("/usr/bin/tee")
34 | val process = processBuilder.start()
35 |
36 | val input = process.inputStream.asSource().buffered()
37 | val output = process.outputStream.asSink().buffered()
38 |
39 | val client = StdioClientTransport(
40 | input = input,
41 | output = output
42 | )
43 |
44 | testClientRead(client)
45 |
46 | process.waitFor()
47 | process.destroy()
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/jvmTest/kotlin/server/StdioServerTransportTest.kt:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import io.modelcontextprotocol.kotlin.sdk.InitializedNotification
4 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage
5 | import io.modelcontextprotocol.kotlin.sdk.PingRequest
6 | import kotlinx.coroutines.CompletableDeferred
7 | import kotlinx.coroutines.runBlocking
8 | import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport
9 | import org.junit.jupiter.api.Assertions.*
10 | import org.junit.jupiter.api.BeforeEach
11 | import org.junit.jupiter.api.Test
12 | import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer
13 | import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage
14 | import io.modelcontextprotocol.kotlin.sdk.toJSON
15 | import kotlinx.io.Sink
16 | import kotlinx.io.Source
17 | import kotlinx.io.asSink
18 | import kotlinx.io.asSource
19 | import kotlinx.io.buffered
20 | import java.io.*
21 |
22 | class StdioServerTransportTest {
23 | private lateinit var input: PipedInputStream
24 | private lateinit var inputWriter: PipedOutputStream
25 | private lateinit var outputBuffer: ReadBuffer
26 | private lateinit var output: ByteArrayOutputStream
27 |
28 | // We'll store the wrapped streams that meet the constructor requirements
29 | private lateinit var bufferedInput: Source
30 | private lateinit var printOutput: Sink
31 |
32 | @BeforeEach
33 | fun setUp() {
34 | // Simulate an input stream that we can push data into using inputWriter.
35 | input = PipedInputStream()
36 | inputWriter = PipedOutputStream(input)
37 |
38 | outputBuffer = ReadBuffer()
39 |
40 | // A custom ByteArrayOutputStream that appends all written data into outputBuffer
41 | output = object : ByteArrayOutputStream() {
42 | override fun write(b: ByteArray, off: Int, len: Int) {
43 | super.write(b, off, len)
44 | outputBuffer.append(b.copyOfRange(off, off + len))
45 | }
46 | }
47 |
48 | bufferedInput = input.asSource().buffered()
49 |
50 | printOutput = output.asSink().buffered()
51 | }
52 |
53 | @Test
54 | fun `should start then close cleanly`() {
55 | runBlocking {
56 | val server = StdioServerTransport(bufferedInput, printOutput)
57 | server.onError { error ->
58 | throw error
59 | }
60 |
61 | var didClose = false
62 | server.onClose {
63 | didClose = true
64 | }
65 |
66 | server.start()
67 | assertFalse(didClose, "Should not have closed yet")
68 |
69 | server.close()
70 | assertTrue(didClose, "Should have closed after calling close()")
71 | }
72 | }
73 |
74 | @Test
75 | fun `should not read until started`() {
76 | runBlocking {
77 | val server = StdioServerTransport(bufferedInput, printOutput)
78 | server.onError { error ->
79 | throw error
80 | }
81 |
82 | var didRead = false
83 | val readMessage = CompletableDeferred()
84 |
85 | server.onMessage { message ->
86 | didRead = true
87 | readMessage.complete(message)
88 | }
89 |
90 | val message = PingRequest().toJSON()
91 |
92 | // Push a message before the server started
93 | val serialized = serializeMessage(message)
94 | inputWriter.write(serialized)
95 | inputWriter.flush()
96 |
97 | assertFalse(didRead, "Should not have read message before start")
98 |
99 | server.start()
100 | val received = readMessage.await()
101 | assertEquals(message, received)
102 | }
103 | }
104 |
105 | @Test
106 | fun `should read multiple messages`() {
107 | runBlocking {
108 | val server = StdioServerTransport(bufferedInput, printOutput)
109 | server.onError { error ->
110 | throw error
111 | }
112 |
113 | val messages = listOf(
114 | PingRequest().toJSON(),
115 | InitializedNotification().toJSON(),
116 | )
117 |
118 | val readMessages = mutableListOf()
119 | val finished = CompletableDeferred()
120 |
121 | server.onMessage { message ->
122 | readMessages.add(message)
123 | if (message == messages[1]) {
124 | finished.complete(Unit)
125 | }
126 | }
127 |
128 | // Push both messages before starting the server
129 | for (m in messages) {
130 | inputWriter.write(serializeMessage(m))
131 | }
132 | inputWriter.flush()
133 |
134 | server.start()
135 | finished.await()
136 |
137 | assertEquals(messages, readMessages)
138 | }
139 | }
140 | }
141 |
142 | fun PipedOutputStream.write(s: String) {
143 | write(s.toByteArray())
144 | }
145 |
--------------------------------------------------------------------------------
/src/wasmJsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.wasmJs.kt:
--------------------------------------------------------------------------------
1 | package io.modelcontextprotocol.kotlin.sdk.internal
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 | import kotlinx.coroutines.Dispatchers
5 |
6 | internal actual val IODispatcher: CoroutineDispatcher
7 | get() = Dispatchers.Default
--------------------------------------------------------------------------------