├── .gitignore
├── .idea
├── .gitignore
├── gradle.xml
├── icon.svg
├── jsLibraryMappings.xml
├── kotlinc.xml
├── misc.xml
├── uiDesigner.xml
└── vcs.xml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.gradle.kts
├── docs
├── -m-c-p -kotlin -s-d-k
│ ├── org.jetbrains.kotlinx.mcp.client
│ │ ├── -client-options
│ │ │ ├── -client-options.html
│ │ │ ├── capabilities.html
│ │ │ └── index.html
│ │ ├── -client
│ │ │ ├── -client.html
│ │ │ ├── assert-capability-for-method.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
│ ├── org.jetbrains.kotlinx.mcp.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-capability-for-method.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
│ ├── org.jetbrains.kotlinx.mcp.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-capability-for-method.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
│ ├── org.jetbrains.kotlinx.mcp
│ │ ├── -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
│ │ ├── -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
│ │ ├── from-j-s-o-n.html
│ │ ├── index.html
│ │ └── to-j-s-o-n.html
│ ├── package-list
│ └── shared
│ │ ├── -l-i-b_-v-e-r-s-i-o-n.html
│ │ └── index.html
├── 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
├── cli.kt
├── connect-to-intellij.sh
├── inspector.sh
├── 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
│ │ └── main
│ │ └── kotlin
│ │ └── Main.kt
└── node-server
│ ├── build.sh
│ ├── package-lock.json
│ ├── package.json
│ ├── run_client.sh
│ ├── run_server.sh
│ ├── src
│ ├── client.ts
│ ├── server.ts
│ └── tmp.ts
│ └── tsconfig.json
├── settings.gradle.kts
└── src
├── main
└── kotlin
│ └── org
│ └── jetbrains
│ └── kotlinx
│ └── mcp
│ ├── client
│ ├── Client.kt
│ ├── SSEClientTransport.kt
│ ├── StdioClientTransport.kt
│ ├── WebSocketClientTransport.kt
│ ├── WebSocketMcpKtorClientExtensions.kt
│ └── sse.ktor.kt
│ ├── server
│ ├── McpKtorServerPlugin.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
└── test
└── kotlin
├── InMemoryTransport.kt
├── client
├── BaseTransportTest.kt
├── ClientIntegrationTest.kt
├── ClientTest.kt
├── InMemoryTransportTest.kt
├── SseTransportTest.kt
├── StdioClientTransportTest.kt
├── TypesTest.kt
└── WebSocketTransportTest.kt
├── server
└── StdioServerTransportTest.kt
├── shared
└── ReadBufferTest.kt
└── sse.ktor.kt
/.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
46 |
47 | ### Node.js ###
48 | node_modules
49 | dist
50 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 |
9 |
11 |
19 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/.idea/jsLibraryMappings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/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/JetBrains/mcp-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/JetBrains/mcp-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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # This repository is moved
2 |
3 | The new repo is located here: [https://github.com/modelcontextprotocol/kotlin-sdk](https://github.com/modelcontextprotocol/kotlin-sdk)
4 |
5 | # MCP Kotlin SDK
6 |
7 | Kotlin implementation of the [Model Context Protocol](https://modelcontextprotocol.io) (MCP), providing both client and server capabilities for integrating with LLM surfaces.
8 |
9 | ## Overview
10 |
11 | The Model Context Protocol allows applications to provide context for LLMs in a standardized way, separating the concerns of providing context from the actual LLM interaction. This Kotlin SDK implements the full MCP specification, making it easy to:
12 |
13 | - Build MCP clients that can connect to any MCP server
14 | - Create MCP servers that expose resources, prompts and tools
15 | - Use standard transports like stdio, SSE, and WebSocket
16 | - Handle all MCP protocol messages and lifecycle events
17 |
18 | ## Samples
19 |
20 | - [kotlin-mcp-server](./samples/kotlin-mcp-server): shows how to set up Kotlin MCP server with different tools and other features.
21 |
22 | ## Installation
23 |
24 | Add the new repository to your build file:
25 |
26 | ```kotlin
27 | repositories {
28 | maven(url = "https://maven.pkg.jetbrains.space/public/p/kotlin-mcp-sdk/sdk")
29 | }
30 | ```
31 |
32 | Add the dependency:
33 |
34 | ```kotlin
35 | dependencies {
36 | implementation("org.jetbrains.kotlinx:kotlinx-mcp-sdk:0.1.0")
37 | }
38 | ```
39 |
40 | ## Quick Start
41 |
42 | ### Creating a Client
43 |
44 | ```kotlin
45 | import org.jetbrains.kotlinx.mcp.client.Client
46 | import org.jetbrains.kotlinx.mcp.client.StdioClientTransport
47 | import org.jetbrains.kotlinx.mcp.Implementation
48 |
49 | val client = Client(
50 | clientInfo = Implementation(
51 | name = "example-client",
52 | version = "1.0.0"
53 | )
54 | )
55 |
56 | val transport = StdioClientTransport(
57 | inputStream = processInputStream,
58 | outputStream = processOutputStream
59 | )
60 |
61 | // Connect to server
62 | client.connect(transport)
63 |
64 | // List available resources
65 | val resources = client.listResources()
66 |
67 | // Read a specific resource
68 | val resourceContent = client.readResource(
69 | ReadResourceRequest(uri = "file:///example.txt")
70 | )
71 | ```
72 |
73 | ### Creating a Server
74 |
75 | ```kotlin
76 | import org.jetbrains.kotlinx.mcp.server.Server
77 | import org.jetbrains.kotlinx.mcp.server.ServerOptions
78 | import org.jetbrains.kotlinx.mcp.server.StdioServerTransport
79 | import org.jetbrains.kotlinx.mcp.ServerCapabilities
80 |
81 | val server = Server(
82 | serverInfo = Implementation(
83 | name = "example-server",
84 | version = "1.0.0"
85 | ),
86 | options = ServerOptions(
87 | capabilities = ServerCapabilities(
88 | resources = ServerCapabilities.Resources(
89 | subscribe = true,
90 | listChanged = true
91 | )
92 | )
93 | )
94 | )
95 |
96 | // Add a resource
97 | server.addResource(
98 | uri = "file:///example.txt",
99 | name = "Example Resource",
100 | description = "An example text file",
101 | mimeType = "text/plain"
102 | ) { request ->
103 | ReadResourceResult(
104 | contents = listOf(
105 | TextResourceContents(
106 | text = "This is the content of the example resource.",
107 | uri = request.uri,
108 | mimeType = "text/plain"
109 | )
110 | )
111 | )
112 | }
113 |
114 | // Start server with stdio transport
115 | val transport = StdioServerTransport()
116 | server.connect(transport)
117 | ```
118 |
119 | ### Using SSE Transport
120 |
121 | ```kotlin
122 | import io.ktor.server.application.*
123 | import org.jetbrains.kotlinx.mcp.server.MCP
124 |
125 | fun Application.module() {
126 | MCP {
127 | Server(
128 | serverInfo = Implementation(
129 | name = "example-sse-server",
130 | version = "1.0.0"
131 | ),
132 | options = ServerOptions(
133 | capabilities = ServerCapabilities(
134 | prompts = ServerCapabilities.Prompts(listChanged = null),
135 | resources = ServerCapabilities.Resources(subscribe = null, listChanged = null)
136 | )
137 | )
138 | )
139 | }
140 | }
141 | ```
142 |
143 | ## Contributing
144 |
145 | Please see the [contribution guide](CONTRIBUTING.md) and the [Code of conduct](CODE_OF_CONDUCT.md) before contributing.
146 |
147 | ## License
148 |
149 | This project is licensed under the MIT License—see the [LICENSE](LICENSE) file for details.
150 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/org.jetbrains.kotlinx.mcp/-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 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
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/org.jetbrains.kotlinx.mcp/-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 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
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/org.jetbrains.kotlinx.mcp/-implementation/name.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | name
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
name
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/org.jetbrains.kotlinx.mcp/-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 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
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/org.jetbrains.kotlinx.mcp/-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 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
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/org.jetbrains.kotlinx.mcp/-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 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
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/org.jetbrains.kotlinx.mcp/-j-s-o-n-r-p-c_-v-e-r-s-i-o-n.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSONRPC_VERSION
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
JSONRPC_ VERSION
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/org.jetbrains.kotlinx.mcp/-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 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
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/org.jetbrains.kotlinx.mcp/-mcp-error/data.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | data
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
data
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/org.jetbrains.kotlinx.mcp/-ping-request/-ping-request.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PingRequest
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
Ping Request
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/org.jetbrains.kotlinx.mcp/-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 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
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/org.jetbrains.kotlinx.mcp/-request/method.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | method
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
method
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/-m-c-p -kotlin -s-d-k/org.jetbrains.kotlinx.mcp/-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 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
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/shared/-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 |
41 |
42 |
43 |
44 |
49 |
50 | 1.0-SNAPSHOT
51 |
52 |
53 | jvm
54 |
55 |
56 |
57 |
switch theme
58 |
search in API
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
LIB_ VERSION
70 |
71 |
72 |
73 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/docs/images/anchor-copy-button.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/images/arrow_down.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/images/burger.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/docs/images/copy-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/images/copy-successful-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/images/footer-go-to-link.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/images/go-to-top-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/docs/images/homepage.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/docs/images/logo-icon.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/abstract-class-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/abstract-class.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/annotation-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/annotation.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/class-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/class.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/enum-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/enum.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/exception-class.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/field-value.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/field-variable.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/function.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/interface-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/interface.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/object.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/images/nav-icons/typealias-kotlin.svg:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/images/theme-toggle.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 |
3 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled
4 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true
5 |
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | # plugins version
3 | kotlin = "2.0.21"
4 | dokka = "2.0.0-Beta"
5 |
6 | # libraries version
7 | serialization = "1.7.3"
8 | coroutines = "1.9.0"
9 | ktor = "3.0.2"
10 | mockk = "1.13.13"
11 | logging = "7.0.0"
12 |
13 | [libraries]
14 | # Kotlinx libraries
15 | kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
16 | kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging-jvm", version.ref = "logging" }
17 |
18 | # Ktor
19 | ktor-client-apache = { group = "io.ktor", name = "ktor-client-apache", version.ref = "ktor" }
20 | ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" }
21 | ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" }
22 | ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" }
23 | ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" }
24 |
25 | # Testing
26 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
27 | kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" }
28 | kotlinx-coroutines-debug = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-debug", version.ref = "coroutines" }
29 | ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" }
30 | mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
31 |
32 |
33 |
34 | [plugins]
35 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
36 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
37 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JetBrains/mcp-kotlin-sdk/ceffdfc1b7c394743f65e18a0ef604338521f6cc/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Dec 12 15:35:46 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 |
--------------------------------------------------------------------------------
/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/connect-to-intellij.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ../gradlew clean localJar; java -jar ../build/libs/kotlinx-mcp-sdk.jar --demo
4 |
--------------------------------------------------------------------------------
/samples/inspector.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ../gradlew clean localJar && npx @modelcontextprotocol/inspector java -jar ../build/libs/kotlinx-mcp-sdk.jar --server
4 |
--------------------------------------------------------------------------------
/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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/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 configurations and transport methods.
4 |
5 | ## Features
6 |
7 | - Multiple server operation modes:
8 | - Standard I/O server
9 | - SSE (Server-Sent Events) server with plain configuration
10 | - SSE server using Ktor plugin
11 | - Built-in capabilities for:
12 | - Prompts management
13 | - Resources handling
14 | - Tools integration
15 |
16 | ## Getting Started
17 |
18 | ### Running the Server
19 |
20 | To run the server in SSE mode on the port 3001, run:
21 |
22 | ```bash
23 | ./gradlew run
24 | ```
25 |
26 | ### Connecting to the Server
27 |
28 | For SSE servers (both plain and Ktor plugin versions):
29 | 1. Start the server
30 | 2. Use the MCP inspector to connect to `http://localhost:/sse`
31 |
32 | ## Server Capabilities
33 |
34 | - **Prompts**: Supports prompt management with list change notifications
35 | - **Resources**: Includes subscription support and list change notifications
36 | - **Tools**: Supports tool management with list change notifications
37 |
38 | ## Implementation Details
39 |
40 | The server is implemented using:
41 | - Ktor for HTTP server functionality
42 | - Kotlin coroutines for asynchronous operations
43 | - SSE for real-time communication
44 | - Standard I/O for command-line interface
45 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | kotlin("jvm") version "2.1.0"
3 | kotlin("plugin.serialization") version "2.1.0"
4 | application
5 | }
6 |
7 | application {
8 | mainClass.set("MainKt")
9 | }
10 |
11 |
12 | group = "org.example"
13 | version = "0.1.0"
14 |
15 | dependencies {
16 | implementation("org.jetbrains.kotlinx:kotlinx-mcp-sdk:0.1.0")
17 | implementation("org.slf4j:slf4j-nop:2.0.9")
18 |
19 | testImplementation(kotlin("test"))
20 | }
21 |
22 | tasks.test {
23 | useJUnitPlatform()
24 | }
25 | kotlin {
26 | jvmToolchain(21)
27 | }
28 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 |
--------------------------------------------------------------------------------
/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JetBrains/mcp-kotlin-sdk/ceffdfc1b7c394743f65e18a0ef604338521f6cc/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 |
6 | dependencyResolutionManagement {
7 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
8 | repositories {
9 | mavenCentral()
10 | maven("https://maven.pkg.jetbrains.space/public/p/kotlin-mcp-sdk/sdk")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/samples/node-server/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | pnpm build
--------------------------------------------------------------------------------
/samples/node-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "main": "dist/server.js",
3 | "type": "module",
4 | "bin": {
5 | "node-server": "dist/server.js"
6 | },
7 | "files": [
8 | "dist"
9 | ],
10 | "scripts": {
11 | "build": "tsc --outDir dist && shx chmod +x dist/*.js",
12 | "prepare": "npm run build",
13 | "watch": "tsc --watch --outDir dist",
14 | "run_server": "dist/server.js",
15 | "run_client": "dist/client.js"
16 | },
17 | "dependencies": {
18 | "@modelcontextprotocol/sdk": "1.0.3",
19 | "node-fetch": "^3.3.2"
20 | },
21 | "devDependencies": {
22 | "@types/node": "^20.11.0",
23 | "shx": "^0.3.4",
24 | "typescript": "^5.3.3"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/samples/node-server/run_client.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./dist/client.js
--------------------------------------------------------------------------------
/samples/node-server/run_server.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | ./dist/server.js
--------------------------------------------------------------------------------
/samples/node-server/src/client.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import * as net from 'net';
4 | import { Readable, Writable } from 'stream';
5 |
6 | const client = new net.Socket();
7 | const PORT = 3000;
8 | const HOST = '127.0.0.1';
9 |
10 | // If startClient is supposed to be asynchronous and do something later,
11 | // keep async. Otherwise, remove async if not needed.
12 | function startClient(input: Readable, output: Writable) {
13 | // TODO: Implement client logic here if needed
14 | // For now, it's just a placeholder.
15 | }
16 |
17 | const input = new Readable({
18 | read() {
19 | // No-op: We'll push data into this stream externally.
20 | }
21 | });
22 |
23 | const output = new Writable({
24 | write(chunk, encoding, callback) {
25 | console.log('Writing to output (and pushing to input):', chunk.toString('utf-8'));
26 | input.push(chunk);
27 | callback();
28 | }
29 | });
30 |
31 | client.connect(PORT, HOST, () => {
32 | console.log('Connected to server');
33 | startClient(input, output);
34 | });
35 |
36 | client.on('data', (data) => {
37 | // When we receive data from the server socket, push it into `input`.
38 | input.push(data);
39 | });
40 |
41 | client.on('close', () => {
42 | input.push(null); // Signal the end of the input stream
43 | console.log('Connection closed by server');
44 | });
45 |
46 | client.on('error', (err) => {
47 | console.error('Client error:', err);
48 | });
49 |
50 | // Push initial data into `input`. If no reader is attached, this might not be visible.
51 | // Consider adding `input.on('data', ...)` somewhere else if you want to see this data.
52 | input.push("Hello");
--------------------------------------------------------------------------------
/samples/node-server/src/server.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import * as net from 'net';
4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5 | import { Readable, Writable, WritableOptions } from 'stream';
6 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
7 | import { ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
8 |
9 | const PORT = 3000;
10 |
11 | const mcpServer = new Server(
12 | {
13 | name: "jetbrains/proxy",
14 | version: "0.1.0",
15 | },
16 | {
17 | capabilities: {
18 | tools: {
19 | listChanged: true,
20 | },
21 | resources: {},
22 | },
23 | },
24 | );
25 |
26 | mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
27 | console.log("ListToolsRequest received");
28 | return {
29 | tools: []
30 | };
31 | });
32 |
33 | async function handleConnection(stdin: Readable, stdout: Writable) {
34 | const transport = new StdioServerTransport(stdin, stdout);
35 | try {
36 | await mcpServer.connect(transport);
37 | } catch (err) {
38 | console.error('Error connecting MCP server:', err);
39 | }
40 | }
41 |
42 | // A Writable stream class that writes data back to the socket
43 | class SocketWritable extends Writable {
44 | private socket: net.Socket;
45 |
46 | constructor(socket: net.Socket, options?: WritableOptions) {
47 | super(options);
48 | this.socket = socket;
49 | }
50 |
51 | _write(chunk: any, encoding: BufferEncoding, callback: (error?: Error | null) => void) {
52 | console.log("Socket write:", chunk.toString());
53 | this.socket.write(chunk, encoding, callback);
54 | }
55 | }
56 |
57 | const server = net.createServer((socket) => {
58 | console.log('Client connected');
59 |
60 | // Create a Readable stream that we will push data into from the socket
61 | const stdin = new Readable({
62 | read() {
63 | // We'll use stdin.push() from socket data events
64 | },
65 | });
66 |
67 | // Create a Writable that writes back to the socket
68 | const stdout = new SocketWritable(socket);
69 |
70 | socket.on('data', (chunk) => {
71 | const decoded = chunk.toString("utf-8");
72 | console.log("Socket data:", decoded);
73 | // Push the raw buffer into stdin so the server can read it
74 | stdin.push(chunk);
75 | });
76 |
77 | socket.on('end', () => {
78 | stdin.push(null); // Signal EOF to the stdin stream
79 | console.log('Client disconnected');
80 | });
81 |
82 | socket.on('error', (err) => {
83 | console.error('Socket error:', err);
84 | });
85 |
86 | handleConnection(stdin, stdout);
87 | });
88 |
89 | server.listen(PORT, () => {
90 | console.log(`MCP proxy server listening on port ${PORT}`);
91 | });
92 |
93 | server.on('error', (err) => {
94 | console.error('Server error:', err);
95 | });
--------------------------------------------------------------------------------
/samples/node-server/src/tmp.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4 | import {
5 | CallToolRequestSchema,
6 | CallToolResult,
7 | ListToolsRequestSchema,
8 | } from "@modelcontextprotocol/sdk/types.js";
9 |
10 | interface IDEResponseOk {
11 | status: string;
12 | error: null;
13 | }
14 |
15 | interface IDEResponseErr {
16 | status: null;
17 | error: string;
18 | }
19 |
20 | type IDEResponse = IDEResponseOk | IDEResponseErr;
21 |
22 | /**
23 | * Try to find a working IDE endpoint.
24 | * Logic:
25 | * 1. If process.env.IDE_PORT is set, use that port directly.
26 | * 2. If not set, try ports from 63342 to 63352.
27 | * 3. For each port, send a test request to /mcp/list_tools. If it works (res.ok), use that port.
28 | * 4. If no port works, throw an error.
29 | */
30 | async function findWorkingIDEEndpoint(): Promise {
31 | // If user specified a port, just use that
32 | if (process.env.IDE_PORT) {
33 | const testEndpoint = `http://localhost:${process.env.IDE_PORT}/api`;
34 | if (await testListTools(testEndpoint)) {
35 | return testEndpoint;
36 | } else {
37 | throw new Error(`Specified IDE_PORT=${process.env.IDE_PORT} but it is not responding correctly.`);
38 | }
39 | }
40 |
41 | for (let port = 63342; port <= 63352; port++) {
42 | const candidateEndpoint = `http://localhost:${port}/api`;
43 | if (await testListTools(candidateEndpoint)) {
44 | return candidateEndpoint;
45 | }
46 | }
47 | sendToolsChanged()
48 | previousResponse = ""
49 | throw new Error("No working IDE endpoint found in range 63342-63352");
50 | }
51 |
52 | let previousResponse: string | null = null;
53 |
54 | function sendToolsChanged() {
55 | try {
56 | server.notification({ method: "notifications/tools/list_changed" })
57 | } catch (_) {
58 | }
59 | }
60 |
61 | async function testListTools(endpoint: string): Promise {
62 | try {
63 | const res = await fetch(`${endpoint}/mcp/list_tools`);
64 |
65 | if (!res.ok) {
66 | return false;
67 | }
68 |
69 | const currentResponse = await res.text();
70 | if (previousResponse !== null && previousResponse !== currentResponse) {
71 | sendToolsChanged()
72 | }
73 | previousResponse = currentResponse;
74 | return true;
75 | } catch {
76 | return false;
77 | }
78 | }
79 |
80 |
81 | const server = new Server(
82 | {
83 | name: "jetbrains/proxy",
84 | version: "0.1.0",
85 | },
86 | {
87 | capabilities: {
88 | tools: {
89 | listChanged: true,
90 | },
91 | resources: {},
92 | },
93 | },
94 | );
95 |
96 | server.setRequestHandler(ListToolsRequestSchema, async () => {
97 | const endpoint = await findWorkingIDEEndpoint();
98 | return {
99 | tools: await fetch(`${endpoint}/mcp/list_tools`)
100 | .then(res => res.ok ? res.json() : Promise.reject(new Error("Unable to list tools")))
101 | }
102 | });
103 |
104 | async function handleToolCall(name: string, args: any): Promise {
105 | try {
106 | const endPoint = await findWorkingIDEEndpoint();
107 | const response = await fetch(`${endPoint}/mcp/${name}`, {
108 | method: 'POST',
109 | headers: {
110 | "Content-Type": "application/json",
111 | },
112 | body: JSON.stringify(args)
113 | });
114 |
115 | if (!response.ok) {
116 | throw new Error(`Response failed: ${response.status}`);
117 | }
118 |
119 | const { status, error }: IDEResponse = await response.json();
120 | const isError = !!error;
121 | const text = status ?? error;
122 | return {
123 | content: [{ type: "text", text: text }],
124 | isError,
125 | };
126 | } catch (error: any) {
127 | return {
128 | content: [{
129 | type: "text",
130 | text: error instanceof Error ? error.message : "Unknown error",
131 | }],
132 | isError: true,
133 | };
134 | }
135 | }
136 |
137 | server.setRequestHandler(CallToolRequestSchema, async (request) =>
138 | handleToolCall(request.params.name, request.params.arguments ?? {})
139 | );
140 |
141 | async function runServer() {
142 | const transport = new StdioServerTransport();
143 | await server.connect(transport);
144 | const checkEndpoint = () => findWorkingIDEEndpoint().catch();
145 | // We need to recheck the IDE endpoint every 10 seconds since IDE might be closed or restarted
146 | setInterval(checkEndpoint, 10000);
147 | await checkEndpoint();
148 | console.error("JetBrains Proxy MCP Server running on stdio");
149 | }
150 |
151 | runServer().catch(console.error);
--------------------------------------------------------------------------------
/samples/node-server/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "resolveJsonModule": true,
11 | "noImplicitAny": false
12 | },
13 | "include": [
14 | "src/**/*"
15 | ],
16 | "exclude": [
17 | "node_modules"
18 | ]
19 | }
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
3 | }
4 | rootProject.name = "kotlinx-mcp-sdk"
5 |
6 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/jetbrains/kotlinx/mcp/client/SSEClientTransport.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.client
2 |
3 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
4 | import io.ktor.client.*
5 | import io.ktor.client.plugins.sse.*
6 | import io.ktor.client.request.*
7 | import io.ktor.client.statement.*
8 | import io.ktor.http.*
9 | import kotlinx.coroutines.*
10 | import kotlinx.serialization.encodeToString
11 | import org.jetbrains.kotlinx.mcp.shared.McpJson
12 | import org.jetbrains.kotlinx.mcp.shared.Transport
13 | import java.util.concurrent.atomic.AtomicBoolean
14 | import kotlin.properties.Delegates
15 | import kotlin.time.Duration
16 |
17 | /**
18 | * Client transport for SSE: this will connect to a server using Server-Sent Events for receiving
19 | * messages and make separate POST requests for sending messages.
20 | */
21 | public class SSEClientTransport(
22 | private val client: HttpClient,
23 | private val urlString: String?,
24 | private val reconnectionTime: Duration? = null,
25 | private val requestBuilder: HttpRequestBuilder.() -> Unit = {},
26 | ) : Transport {
27 | private val scope by lazy {
28 | CoroutineScope(session.coroutineContext + SupervisorJob())
29 | }
30 |
31 | private val initialized = AtomicBoolean(false)
32 | private var session: ClientSSESession by Delegates.notNull()
33 | private val endpoint = CompletableDeferred()
34 |
35 | override var onClose: (() -> Unit)? = null
36 | override var onError: ((Throwable) -> Unit)? = null
37 | override var onMessage: (suspend ((JSONRPCMessage) -> Unit))? = null
38 |
39 | private var job: Job? = null
40 |
41 | private val baseUrl by lazy {
42 | session.call.request.url.toString().removeSuffix("/")
43 | }
44 |
45 | override suspend fun start() {
46 | if (!initialized.compareAndSet(false, true)) {
47 | error(
48 | "SSEClientTransport already started! " +
49 | "If using Client class, note that connect() calls start() automatically.",
50 | )
51 | }
52 |
53 | session = urlString?.let {
54 | client.sseSession(
55 | urlString = it,
56 | reconnectionTime = reconnectionTime,
57 | block = requestBuilder,
58 | )
59 | } ?: client.sseSession(
60 | reconnectionTime = reconnectionTime,
61 | block = requestBuilder,
62 | )
63 |
64 | job = scope.launch(CoroutineName("SseMcpClientTransport.collect#${hashCode()}")) {
65 | session.incoming.collect { event ->
66 | when (event.event) {
67 | "error" -> {
68 | val e = IllegalStateException("SSE error: ${event.data}")
69 | onError?.invoke(e)
70 | throw e
71 | }
72 |
73 | "open" -> {
74 | // The connection is open, but we need to wait for the endpoint to be received.
75 | }
76 |
77 | "endpoint" -> {
78 | try {
79 | val eventData = event.data ?: ""
80 |
81 | // check url correctness
82 | val maybeEndpoint = Url("$baseUrl/$eventData")
83 |
84 | endpoint.complete(maybeEndpoint.toString())
85 | } catch (e: Exception) {
86 | onError?.invoke(e)
87 | close()
88 | error(e)
89 | }
90 | }
91 |
92 | else -> {
93 | try {
94 | val message = McpJson.decodeFromString(event.data ?: "")
95 | onMessage?.invoke(message)
96 | } catch (e: Exception) {
97 | onError?.invoke(e)
98 | }
99 | }
100 | }
101 | }
102 | }
103 |
104 | endpoint.await()
105 | }
106 |
107 | @OptIn(ExperimentalCoroutinesApi::class)
108 | override suspend fun send(message: JSONRPCMessage) {
109 | if (!endpoint.isCompleted) {
110 | error("Not connected")
111 | }
112 |
113 | try {
114 | val response = client.post(endpoint.getCompleted()) {
115 | headers.append(HttpHeaders.ContentType, ContentType.Application.Json)
116 | setBody(McpJson.encodeToString(message))
117 | }
118 |
119 | if (!response.status.isSuccess()) {
120 | val text = response.bodyAsText()
121 | error("Error POSTing to endpoint (HTTP ${response.status}): $text")
122 | }
123 | } catch (e: Exception) {
124 | onError?.invoke(e)
125 | throw e
126 | }
127 | }
128 |
129 | override suspend fun close() {
130 | if (!initialized.get()) {
131 | error("SSEClientTransport is not initialized!")
132 | }
133 |
134 | session.cancel()
135 | onClose?.invoke()
136 | job?.cancelAndJoin()
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/jetbrains/kotlinx/mcp/client/StdioClientTransport.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.client
2 |
3 | import io.github.oshai.kotlinlogging.KotlinLogging
4 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
5 | import kotlinx.coroutines.*
6 | import kotlinx.coroutines.channels.Channel
7 | import kotlinx.coroutines.channels.consumeEach
8 | import org.jetbrains.kotlinx.mcp.shared.ReadBuffer
9 | import org.jetbrains.kotlinx.mcp.shared.Transport
10 | import org.jetbrains.kotlinx.mcp.shared.serializeMessage
11 | import java.io.InputStream
12 | import java.io.OutputStream
13 | import java.util.concurrent.atomic.AtomicBoolean
14 | import kotlin.coroutines.CoroutineContext
15 | import kotlin.text.Charsets.UTF_8
16 |
17 | /**
18 | * A transport implementation for JSON-RPC communication that leverages standard input and output streams.
19 | *
20 | * This class reads from an input stream to process incoming JSON-RPC messages and writes JSON-RPC messages
21 | * to an output stream.
22 | *
23 | * @param input The input stream where messages are received.
24 | * @param output The output stream where messages are sent.
25 | */
26 | public class StdioClientTransport(
27 | private val input: InputStream,
28 | private val output: OutputStream
29 | ) : Transport {
30 | private val logger = KotlinLogging.logger {}
31 | private val ioCoroutineContext: CoroutineContext = Dispatchers.IO
32 | private val scope by lazy {
33 | CoroutineScope(ioCoroutineContext + SupervisorJob())
34 | }
35 | private var job: Job? = null
36 | private var initialized = AtomicBoolean(false)
37 | private val sendChannel = Channel(Channel.UNLIMITED)
38 | private val readBuffer = ReadBuffer()
39 |
40 | override var onClose: (() -> Unit)? = null
41 | override var onError: ((Throwable) -> Unit)? = null
42 | override var onMessage: (suspend ((JSONRPCMessage) -> Unit))? = null
43 |
44 | override suspend fun start() {
45 | if (!initialized.compareAndSet(false, true)) {
46 | error("StdioClientTransport already started!")
47 | }
48 |
49 | logger.debug { "Starting StdioClientTransport..." }
50 |
51 | val outputStream = output.bufferedWriter(UTF_8)
52 |
53 | job = scope.launch(CoroutineName("StdioClientTransport.IO#${hashCode()}")) {
54 | val readJob = launch {
55 | logger.debug { "Read coroutine started." }
56 | try {
57 | val buffer = ByteArray(8192)
58 | while (isActive) {
59 | val bytesRead = input.read(buffer)
60 | if (bytesRead == -1) break
61 | if (bytesRead > 0) {
62 | readBuffer.append(buffer.copyOf(bytesRead))
63 | processReadBuffer()
64 | }
65 | }
66 | } catch (e: CancellationException) {
67 | throw e
68 | } catch (e: Exception) {
69 | onError?.invoke(e)
70 | logger.error(e) { "Error reading from input stream" }
71 | } finally {
72 | input.close()
73 | }
74 | }
75 |
76 | val writeJob = launch {
77 | logger.debug { "Write coroutine started." }
78 | try {
79 | sendChannel.consumeEach { message ->
80 | val json = serializeMessage(message)
81 | outputStream.write(json)
82 | outputStream.flush()
83 | }
84 | } catch (e: Throwable) {
85 | if (isActive) {
86 | onError?.invoke(e)
87 | logger.error(e) { "Error writing to output stream" }
88 | }
89 | } finally {
90 | output.close()
91 | }
92 | }
93 |
94 | readJob.join()
95 | writeJob.cancelAndJoin()
96 | onClose?.invoke()
97 | }
98 | }
99 |
100 | override suspend fun send(message: JSONRPCMessage) {
101 | if (!initialized.get()) {
102 | error("Transport not started")
103 | }
104 |
105 | sendChannel.send(message)
106 | }
107 |
108 | override suspend fun close() {
109 | if (!initialized.compareAndSet(true, false)) {
110 | error("Transport is already closed")
111 | }
112 | job?.cancelAndJoin()
113 | input.close()
114 | output.close()
115 | readBuffer.clear()
116 | sendChannel.close()
117 | onClose?.invoke()
118 | }
119 |
120 | private suspend fun processReadBuffer() {
121 | while (true) {
122 | val msg = readBuffer.readMessage() ?: break
123 | try {
124 | onMessage?.invoke(msg)
125 | } catch (e: Throwable) {
126 | onError?.invoke(e)
127 | logger.error(e) { "Error processing message." }
128 | }
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/jetbrains/kotlinx/mcp/client/WebSocketClientTransport.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.client
2 |
3 | import io.ktor.client.*
4 | import io.ktor.client.plugins.websocket.*
5 | import io.ktor.client.request.*
6 | import io.ktor.http.*
7 | import io.ktor.websocket.*
8 | import org.jetbrains.kotlinx.mcp.shared.MCP_SUBPROTOCOL
9 | import org.jetbrains.kotlinx.mcp.shared.WebSocketMcpTransport
10 | import kotlin.properties.Delegates
11 |
12 | /**
13 | * Client transport for WebSocket: this will connect to a server over the WebSocket protocol.
14 | */
15 | public class WebSocketClientTransport(
16 | private val client: HttpClient,
17 | private val urlString: String?,
18 | private val requestBuilder: HttpRequestBuilder.() -> Unit = {},
19 | ) : WebSocketMcpTransport() {
20 | override var session: WebSocketSession by Delegates.notNull()
21 |
22 | override suspend fun initializeSession() {
23 | session = urlString?.let {
24 | client.webSocketSession(it) {
25 | requestBuilder()
26 |
27 | header(HttpHeaders.SecWebSocketProtocol, MCP_SUBPROTOCOL)
28 | }
29 | } ?: client.webSocketSession {
30 | requestBuilder()
31 |
32 | header(HttpHeaders.SecWebSocketProtocol, MCP_SUBPROTOCOL)
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/jetbrains/kotlinx/mcp/client/WebSocketMcpKtorClientExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.client
2 |
3 | import org.jetbrains.kotlinx.mcp.Implementation
4 | import io.ktor.client.HttpClient
5 | import io.ktor.client.request.HttpRequestBuilder
6 | import org.jetbrains.kotlinx.mcp.LIB_VERSION
7 | import org.jetbrains.kotlinx.mcp.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/main/kotlin/org/jetbrains/kotlinx/mcp/client/sse.ktor.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.client
2 |
3 | import org.jetbrains.kotlinx.mcp.Implementation
4 | import io.ktor.client.HttpClient
5 | import io.ktor.client.request.HttpRequestBuilder
6 | import org.jetbrains.kotlinx.mcp.LIB_VERSION
7 | import org.jetbrains.kotlinx.mcp.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/main/kotlin/org/jetbrains/kotlinx/mcp/server/McpKtorServerPlugin.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.server
2 |
3 | import io.github.oshai.kotlinlogging.KotlinLogging
4 | import io.ktor.http.*
5 | import io.ktor.server.application.*
6 | import io.ktor.server.response.*
7 | import io.ktor.server.routing.*
8 | import io.ktor.server.sse.*
9 | import io.ktor.util.collections.*
10 |
11 | private val logger = KotlinLogging.logger {}
12 |
13 | /**
14 | * Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE).
15 | */
16 | public fun Application.MCP(block: () -> Server) {
17 | val servers = ConcurrentMap()
18 |
19 | install(SSE)
20 | routing {
21 | sse("/sse") {
22 | val transport = SSEServerTransport("/message", this)
23 | logger.info { "New SSE connection established with sessionId: ${transport.sessionId}" }
24 |
25 | val server = block()
26 |
27 | servers[transport.sessionId] = server
28 | logger.debug { "Server instance created and stored for sessionId: ${transport.sessionId}" }
29 |
30 | server.onCloseCallback = {
31 | logger.info { "Server connection closed for sessionId: ${transport.sessionId}" }
32 | servers.remove(transport.sessionId)
33 | }
34 |
35 | server.connect(transport)
36 | logger.debug { "Server connected to transport for sessionId: ${transport.sessionId}" }
37 | }
38 |
39 | post("/message") {
40 | val sessionId: String = call.request.queryParameters["sessionId"]!!
41 | logger.debug { "Received message for sessionId: $sessionId" }
42 |
43 | val transport = servers[sessionId]?.transport as? SSEServerTransport
44 | if (transport == null) {
45 | logger.warn { "Session not found for sessionId: $sessionId" }
46 | call.respond(HttpStatusCode.NotFound, "Session not found")
47 | return@post
48 | }
49 |
50 | transport.handlePostMessage(call)
51 | logger.trace { "Message handled for sessionId: $sessionId" }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/jetbrains/kotlinx/mcp/server/SSEServerTransport.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.server
2 |
3 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
4 | import io.ktor.http.*
5 | import io.ktor.server.application.*
6 | import io.ktor.server.request.*
7 | import io.ktor.server.response.*
8 | import io.ktor.server.sse.*
9 | import kotlinx.coroutines.job
10 | import kotlinx.serialization.encodeToString
11 | import org.jetbrains.kotlinx.mcp.shared.McpJson
12 | import org.jetbrains.kotlinx.mcp.shared.Transport
13 | import java.util.concurrent.atomic.AtomicBoolean
14 | import kotlin.uuid.ExperimentalUuidApi
15 | import kotlin.uuid.Uuid
16 |
17 | internal const val SESSION_ID_PARAM = "sessionId"
18 |
19 | /**
20 | * Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests.
21 | *
22 | * Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`.
23 | */
24 | public class SSEServerTransport(
25 | private val endpoint: String,
26 | private val session: ServerSSESession,
27 | ) : Transport {
28 | private val initialized = AtomicBoolean(false)
29 |
30 | @OptIn(ExperimentalUuidApi::class)
31 | public val sessionId: String = Uuid.random().toString()
32 |
33 | override var onClose: (() -> Unit)? = null
34 | override var onError: ((Throwable) -> Unit)? = null
35 | override var onMessage: (suspend ((JSONRPCMessage) -> Unit))? = null
36 |
37 | /**
38 | * Handles the initial SSE connection request.
39 | *
40 | * This should be called when a GET request is made to establish the SSE stream.
41 | */
42 | override suspend fun start() {
43 | if (!initialized.compareAndSet(false, true)) {
44 | throw error("SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.")
45 | }
46 |
47 | // Send the endpoint event
48 | session.send(
49 | event = "endpoint",
50 | data = "${endpoint.encodeURLPath()}?$SESSION_ID_PARAM=${sessionId}",
51 | )
52 |
53 | try {
54 | session.coroutineContext.job.join()
55 | } finally {
56 | onClose?.invoke()
57 | }
58 | }
59 |
60 | /**
61 | * Handles incoming POST messages.
62 | *
63 | * This should be called when a POST request is made to send a message to the server.
64 | */
65 | public suspend fun handlePostMessage(call: ApplicationCall) {
66 | if (!initialized.get()) {
67 | val message = "SSE connection not established"
68 | call.respondText(message, status = HttpStatusCode.InternalServerError)
69 | onError?.invoke(IllegalStateException(message))
70 | }
71 |
72 | val body = try {
73 | val ct = call.request.contentType()
74 | if (ct != ContentType.Application.Json) {
75 | error("Unsupported content-type: $ct")
76 | }
77 |
78 | call.receiveText()
79 | } catch (e: Exception) {
80 | call.respondText("Invalid message: ${e.message}", status = HttpStatusCode.BadRequest)
81 | onError?.invoke(e)
82 | return
83 | }
84 |
85 | try {
86 | handleMessage(body)
87 | } catch (e: Exception) {
88 | call.respondText("Error handling message $body: ${e.message}", status = HttpStatusCode.BadRequest)
89 | return
90 | }
91 |
92 | call.respondText("Accepted", status = HttpStatusCode.Accepted)
93 | }
94 |
95 | /**
96 | * Handle a client message, regardless of how it arrived.
97 | * This can be used to inform the server of messages that arrive via a means different from HTTP POST.
98 | */
99 | public suspend fun handleMessage(message: String) {
100 | try {
101 | val parsedMessage = McpJson.decodeFromString(message)
102 | onMessage?.invoke(parsedMessage)
103 | } catch (e: Exception) {
104 | onError?.invoke(e)
105 | throw e
106 | }
107 | }
108 |
109 | override suspend fun close() {
110 | session.close()
111 | onClose?.invoke()
112 | }
113 |
114 | override suspend fun send(message: JSONRPCMessage) {
115 | if (!initialized.get()) {
116 | throw error("Not connected")
117 | }
118 |
119 | session.send(
120 | event = "message",
121 | data = McpJson.encodeToString(message),
122 | )
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/jetbrains/kotlinx/mcp/server/StdioServerTransport.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.server
2 |
3 | import io.github.oshai.kotlinlogging.KotlinLogging
4 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
5 | import kotlinx.coroutines.*
6 | import kotlinx.coroutines.channels.Channel
7 | import org.jetbrains.kotlinx.mcp.shared.ReadBuffer
8 | import org.jetbrains.kotlinx.mcp.shared.Transport
9 | import org.jetbrains.kotlinx.mcp.shared.serializeMessage
10 | import java.io.BufferedInputStream
11 | import java.io.PrintStream
12 | import java.util.concurrent.atomic.AtomicBoolean
13 | import kotlin.coroutines.CoroutineContext
14 |
15 | /**
16 | * A server transport that communicates with a client via standard I/O.
17 | *
18 | * Reads from System.in and writes to System.out.
19 | */
20 | public class StdioServerTransport(
21 | private val inputStream: BufferedInputStream = BufferedInputStream(System.`in`),
22 | outputStream: PrintStream = System.out
23 | ) : Transport {
24 | private val logger = KotlinLogging.logger {}
25 | override var onClose: (() -> Unit)? = null
26 | override var onError: ((Throwable) -> Unit)? = null
27 | override var onMessage: (suspend (JSONRPCMessage) -> Unit)? = null
28 |
29 | private val readBuffer = ReadBuffer()
30 | private var initialized = AtomicBoolean(false)
31 | private var readingJob: Job? = null
32 |
33 | private val coroutineContext: CoroutineContext = Dispatchers.IO + SupervisorJob()
34 | private val scope = CoroutineScope(coroutineContext)
35 | private val readChannel = Channel(Channel.UNLIMITED)
36 | private val outputWriter = outputStream.bufferedWriter()
37 |
38 | override suspend fun start() {
39 | if (!initialized.compareAndSet(false, true)) {
40 | error("StdioServerTransport already started!")
41 | }
42 |
43 | // Launch a coroutine to read from stdin
44 | readingJob = scope.launch {
45 | val buf = ByteArray(8192)
46 | try {
47 | while (isActive) {
48 | val bytesRead = inputStream.read(buf)
49 | if (bytesRead == -1) {
50 | // EOF reached
51 | break
52 | }
53 | if (bytesRead > 0) {
54 | val chunk = buf.copyOf(bytesRead)
55 | readChannel.send(chunk)
56 | }
57 | }
58 | } catch (e: Throwable) {
59 | logger.error(e) { "Error reading from stdin" }
60 | onError?.invoke(e)
61 | } finally {
62 | // Reached EOF or error, close connection
63 | close()
64 | }
65 | }
66 |
67 | // Launch a coroutine to process messages from readChannel
68 | scope.launch {
69 | try {
70 | for (chunk in readChannel) {
71 | readBuffer.append(chunk)
72 | processReadBuffer()
73 | }
74 | } catch (e: Throwable) {
75 | onError?.invoke(e)
76 | }
77 | }
78 | }
79 |
80 | private suspend fun processReadBuffer() {
81 | while (true) {
82 | val message = try {
83 | readBuffer.readMessage()
84 | } catch (e: Throwable) {
85 | onError?.invoke(e)
86 | null
87 | }
88 |
89 | if (message == null) break
90 | // Async invocation broke delivery order
91 | try {
92 | onMessage?.invoke(message)
93 | } catch (e: Throwable) {
94 | onError?.invoke(e)
95 | }
96 | }
97 | }
98 |
99 | override suspend fun close() {
100 | if (!initialized.compareAndSet(true, false)) return
101 |
102 | // Cancel reading job and close channel
103 | readingJob?.cancel() // ToDO("was cancel and join")
104 | readChannel.close()
105 | readBuffer.clear()
106 |
107 | onClose?.invoke()
108 | }
109 |
110 | override suspend fun send(message: JSONRPCMessage) {
111 | val json = serializeMessage(message)
112 | synchronized(outputWriter) {
113 | // You may need to add Content-Length headers before the message if using the LSP framing protocol
114 | outputWriter.write(json)
115 | outputWriter.flush()
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/jetbrains/kotlinx/mcp/server/WebSocketMcpKtorServerExtensions.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.server
2 |
3 | import org.jetbrains.kotlinx.mcp.Implementation
4 | import org.jetbrains.kotlinx.mcp.ServerCapabilities
5 | import io.ktor.server.routing.*
6 | import io.ktor.server.websocket.*
7 | import org.jetbrains.kotlinx.mcp.LIB_VERSION
8 | import org.jetbrains.kotlinx.mcp.shared.IMPLEMENTATION_NAME
9 |
10 | /**
11 | * Registers a WebSocket route that establishes an MCP (Model Context Protocol) server session.
12 | *
13 | * @param options Optional server configuration settings for the MCP server.
14 | * @param handler A suspend function that defines the server's behavior.
15 | */
16 | public fun Route.mcpWebSocket(
17 | options: ServerOptions? = null,
18 | handler: suspend Server.() -> Unit = {},
19 | ) {
20 | webSocket {
21 | createMcpServer(this, options, handler)
22 | }
23 | }
24 |
25 | /**
26 | * Registers a WebSocket route at the specified [path] that establishes an MCP server session.
27 | *
28 | * @param path The URL path at which to register the WebSocket route.
29 | * @param options Optional server configuration settings for the MCP server.
30 | * @param handler A suspend function that defines the server's behavior.
31 | */
32 | public fun Route.mcpWebSocket(
33 | path: String,
34 | options: ServerOptions? = null,
35 | handler: suspend Server.() -> Unit = {},
36 | ) {
37 | webSocket(path) {
38 | createMcpServer(this, options, handler)
39 | }
40 | }
41 |
42 | /**
43 | * Registers a WebSocket route that creates an MCP server transport layer.
44 | *
45 | * @param handler A suspend function that defines the behavior of the transport layer.
46 | */
47 | public fun Route.mcpWebSocketTransport(
48 | handler: suspend WebSocketMcpServerTransport.() -> Unit = {},
49 | ) {
50 | webSocket {
51 | val transport = createMcpTransport(this)
52 | transport.start()
53 | handler(transport)
54 | transport.close()
55 | }
56 | }
57 |
58 | /**
59 | * Registers a WebSocket route at the specified [path] that creates an MCP server transport layer.
60 | *
61 | * @param path The URL path at which to register the WebSocket route.
62 | * @param handler A suspend function that defines the behavior of the transport layer.
63 | */
64 | public fun Route.mcpWebSocketTransport(
65 | path: String,
66 | handler: suspend WebSocketMcpServerTransport.() -> Unit = {},
67 | ) {
68 | webSocket(path) {
69 | val transport = createMcpTransport(this)
70 | transport.start()
71 | handler(transport)
72 | transport.close()
73 | }
74 | }
75 |
76 |
77 | private suspend fun Route.createMcpServer(
78 | session: WebSocketServerSession,
79 | options: ServerOptions?,
80 | handler: suspend Server.() -> Unit,
81 | ) {
82 | val transport = createMcpTransport(session)
83 |
84 | val server = Server(
85 | serverInfo = Implementation(
86 | name = IMPLEMENTATION_NAME,
87 | version = LIB_VERSION,
88 | ),
89 | options = options ?: ServerOptions(
90 | capabilities = ServerCapabilities(
91 | prompts = ServerCapabilities.Prompts(listChanged = null),
92 | resources = ServerCapabilities.Resources(subscribe = null, listChanged = null),
93 | tools = ServerCapabilities.Tools(listChanged = null),
94 | )
95 | ),
96 | )
97 |
98 | server.connect(transport)
99 | handler(server)
100 | server.close()
101 | }
102 |
103 | private fun createMcpTransport(
104 | session: WebSocketServerSession,
105 | ): WebSocketMcpServerTransport {
106 | return WebSocketMcpServerTransport(session)
107 | }
108 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/jetbrains/kotlinx/mcp/server/WebSocketMcpServerTransport.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.server
2 |
3 | import io.ktor.http.*
4 | import io.ktor.server.websocket.*
5 | import org.jetbrains.kotlinx.mcp.shared.MCP_SUBPROTOCOL
6 | import org.jetbrains.kotlinx.mcp.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/main/kotlin/org/jetbrains/kotlinx/mcp/shared/ReadBuffer.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.shared
2 |
3 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
4 | import io.ktor.utils.io.core.*
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 | ""
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/main/kotlin/org/jetbrains/kotlinx/mcp/shared/Transport.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.shared
2 |
3 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
4 |
5 | /**
6 | * Describes the minimal contract for a MCP transport that a client or server can communicate over.
7 | */
8 | public interface Transport {
9 | /**
10 | * Starts processing messages on the transport, including any connection steps that might need to be taken.
11 | *
12 | * This method should only be called after callbacks are installed, or else messages may be lost.
13 | *
14 | * NOTE: This method should not be called explicitly when using Client, Server, or Protocol classes,
15 | * as they will implicitly call start().
16 | */
17 | public suspend fun start()
18 |
19 | /**
20 | * Sends a JSON-RPC message (request or response).
21 | */
22 | public suspend fun send(message: JSONRPCMessage)
23 |
24 | /**
25 | * Closes the connection.
26 | */
27 | public suspend fun close()
28 |
29 | /**
30 | * Callback for when the connection is closed for any reason.
31 | *
32 | * This should be invoked when close() is called as well.
33 | */
34 | public var onClose: (() -> Unit)?
35 |
36 | /**
37 | * Callback for when an error occurs.
38 | *
39 | * Note that errors are not necessarily fatal; they are used for reporting any kind of
40 | * exceptional condition out of band.
41 | */
42 | public var onError: ((Throwable) -> Unit)?
43 |
44 | /**
45 | * Callback for when a message (request or response) is received over the connection.
46 | */
47 | public var onMessage: (suspend ((JSONRPCMessage) -> Unit))?
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/kotlin/org/jetbrains/kotlinx/mcp/shared/WebSocketMcpTransport.kt:
--------------------------------------------------------------------------------
1 | package org.jetbrains.kotlinx.mcp.shared
2 |
3 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
4 | import io.ktor.websocket.Frame
5 | import io.ktor.websocket.WebSocketSession
6 | import io.ktor.websocket.close
7 | import io.ktor.websocket.readText
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 java.util.concurrent.atomic.AtomicBoolean
17 |
18 | internal const val MCP_SUBPROTOCOL = "mcp"
19 |
20 | /**
21 | * Abstract class representing a WebSocket transport for the Model Context Protocol (MCP).
22 | * Handles communication over a WebSocket session.
23 | */
24 | public abstract class WebSocketMcpTransport : Transport {
25 | private val scope by lazy {
26 | CoroutineScope(session.coroutineContext + SupervisorJob())
27 | }
28 |
29 | private val initialized = AtomicBoolean(false)
30 | /**
31 | * The WebSocket session used for communication.
32 | */
33 | protected abstract val session: WebSocketSession
34 |
35 | override var onClose: (() -> Unit)? = null
36 | override var onError: ((Throwable) -> Unit)? = null
37 | override var onMessage: (suspend ((JSONRPCMessage) -> Unit))? = null
38 |
39 | /**
40 | * Initializes the WebSocket session
41 | */
42 | protected abstract suspend fun initializeSession()
43 |
44 | override suspend fun start() {
45 | if (!initialized.compareAndSet(false, true)) {
46 | error(
47 | "WebSocketClientTransport already started! " +
48 | "If using Client class, note that connect() calls start() automatically.",
49 | )
50 | }
51 |
52 | initializeSession()
53 |
54 | scope.launch(CoroutineName("WebSocketMcpTransport.collect#${hashCode()}")) {
55 | while (true) {
56 | val message = try {
57 | session.incoming.receive()
58 | } catch (_: ClosedReceiveChannelException) {
59 | return@launch
60 | }
61 |
62 | if (message !is Frame.Text) {
63 | val e = IllegalArgumentException("Expected text frame, got ${message::class.simpleName}: $message")
64 | onError?.invoke(e)
65 | throw e
66 | }
67 |
68 | try {
69 | val message = McpJson.decodeFromString(message.readText())
70 | onMessage?.invoke(message)
71 | } catch (e: Exception) {
72 | onError?.invoke(e)
73 | throw e
74 | }
75 | }
76 | }
77 |
78 | @OptIn(InternalCoroutinesApi::class)
79 | session.coroutineContext.job.invokeOnCompletion {
80 | if (it != null) {
81 | onError?.invoke(it)
82 | } else {
83 | onClose?.invoke()
84 | }
85 | }
86 | }
87 |
88 | override suspend fun send(message: JSONRPCMessage) {
89 | if (!initialized.get()) {
90 | error("Not connected")
91 | }
92 |
93 | session.outgoing.send(Frame.Text(McpJson.encodeToString(message)))
94 | }
95 |
96 | override suspend fun close() {
97 | if (!initialized.get()) {
98 | error("Not connected")
99 | }
100 |
101 | session.close()
102 | session.coroutineContext.job.join()
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/test/kotlin/InMemoryTransport.kt:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
2 | import org.jetbrains.kotlinx.mcp.shared.Transport
3 |
4 | /**
5 | * In-memory transport for creating clients and servers that talk to each other within the same process.
6 | */
7 | class InMemoryTransport : Transport {
8 | private var otherTransport: InMemoryTransport? = null
9 | private val messageQueue: MutableList = mutableListOf()
10 |
11 | override var onClose: (() -> Unit)? = null
12 | override var onError: ((Throwable) -> Unit)? = null
13 | override var onMessage: (suspend ((JSONRPCMessage) -> Unit))? = null
14 |
15 | /**
16 | * Creates a pair of linked in-memory transports that can communicate with each other.
17 | * One should be passed to a Client and one to a Server.
18 | */
19 | companion object {
20 | fun createLinkedPair(): Pair {
21 | val clientTransport = InMemoryTransport()
22 | val serverTransport = InMemoryTransport()
23 | clientTransport.otherTransport = serverTransport
24 | serverTransport.otherTransport = clientTransport
25 | return Pair(clientTransport, serverTransport)
26 | }
27 | }
28 |
29 | override suspend fun start() {
30 | // Process any messages that were queued before start was called
31 | while (messageQueue.isNotEmpty()) {
32 | messageQueue.removeFirstOrNull()?.let { message ->
33 | onMessage?.invoke(message) // todo?
34 | }
35 | }
36 | }
37 |
38 | override suspend fun close() {
39 | val other = otherTransport
40 | otherTransport = null
41 | other?.close()
42 | onClose?.invoke()
43 | }
44 |
45 | override suspend fun send(message: JSONRPCMessage) {
46 | val other = otherTransport ?: throw IllegalStateException("Not connected")
47 |
48 | if (other.onMessage != null) {
49 | other.onMessage?.invoke(message) // todo?
50 | } else {
51 | other.messageQueue.add(message)
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/kotlin/client/BaseTransportTest.kt:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import org.jetbrains.kotlinx.mcp.InitializedNotification
4 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
5 | import org.jetbrains.kotlinx.mcp.PingRequest
6 | import kotlinx.coroutines.CompletableDeferred
7 | import org.jetbrains.kotlinx.mcp.shared.Transport
8 | import org.jetbrains.kotlinx.mcp.toJSON
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(),
38 | InitializedNotification().toJSON()
39 | )
40 |
41 | val readMessages = mutableListOf()
42 | val finished = CompletableDeferred()
43 |
44 | client.onMessage = { message ->
45 | readMessages.add(message)
46 | if (message == messages.last()) {
47 | finished.complete(Unit)
48 | }
49 | }
50 |
51 | client.start()
52 |
53 | for (message in messages) {
54 | client.send(message)
55 | }
56 |
57 | finished.await()
58 |
59 | assertEquals(messages, readMessages, "Assert messages received")
60 |
61 | client.close()
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/kotlin/client/ClientIntegrationTest.kt:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import org.jetbrains.kotlinx.mcp.Implementation
4 | import org.jetbrains.kotlinx.mcp.ListToolsResult
5 | import kotlinx.coroutines.test.runTest
6 | import org.jetbrains.kotlinx.mcp.client.Client
7 | import org.jetbrains.kotlinx.mcp.client.StdioClientTransport
8 | import org.junit.jupiter.api.Disabled
9 | import org.junit.jupiter.api.Test
10 | import java.net.Socket
11 |
12 | class ClientIntegrationTest {
13 |
14 | fun createTransport(): StdioClientTransport {
15 | val socket = Socket("localhost", 3000)
16 |
17 | return StdioClientTransport(socket.inputStream, socket.outputStream)
18 | }
19 |
20 | @Disabled("This test requires a running server")
21 | @Test
22 | fun testRequestTools() = runTest {
23 | val client = Client(
24 | Implementation("test", "1.0"),
25 | )
26 |
27 | val transport = createTransport()
28 | try {
29 | client.connect(transport)
30 |
31 | val response: ListToolsResult? = client.listTools()
32 | println(response?.tools)
33 |
34 | } finally {
35 | transport.close()
36 | }
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/test/kotlin/client/InMemoryTransportTest.kt:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import InMemoryTransport
4 | import org.jetbrains.kotlinx.mcp.InitializedNotification
5 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
6 | import kotlinx.coroutines.runBlocking
7 | import org.jetbrains.kotlinx.mcp.toJSON
8 | import org.junit.jupiter.api.BeforeEach
9 | import org.junit.jupiter.api.Test
10 | import org.junit.jupiter.api.assertThrows
11 | import kotlin.test.assertEquals
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 | @BeforeEach
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`() {
34 | runBlocking {
35 | clientTransport.start()
36 | serverTransport.start()
37 | // If no exception is thrown, the test passes
38 | }
39 | }
40 |
41 | @Test
42 | fun `should send message from client to server`() {
43 | runBlocking {
44 | val message = InitializedNotification()
45 |
46 | var receivedMessage: JSONRPCMessage? = null
47 | serverTransport.onMessage = { msg ->
48 | receivedMessage = msg
49 | }
50 |
51 | val rpcNotification = message.toJSON()
52 | clientTransport.send(rpcNotification)
53 | assertEquals(rpcNotification, receivedMessage)
54 | }
55 | }
56 |
57 | @Test
58 | fun `should send message from server to client`() {
59 | runBlocking {
60 | val message = InitializedNotification()
61 | .toJSON()
62 |
63 | var receivedMessage: JSONRPCMessage? = null
64 | clientTransport.onMessage = { msg ->
65 | receivedMessage = msg
66 | }
67 |
68 | serverTransport.send(message)
69 | assertEquals(message, receivedMessage)
70 | }
71 | }
72 |
73 | @Test
74 | fun `should handle close`() {
75 | runBlocking {
76 | var clientClosed = false
77 | var serverClosed = false
78 |
79 | clientTransport.onClose = {
80 | clientClosed = true
81 | }
82 |
83 | serverTransport.onClose = {
84 | serverClosed = true
85 | }
86 |
87 | clientTransport.close()
88 | assertTrue(clientClosed)
89 | assertTrue(serverClosed)
90 | }
91 | }
92 |
93 | @Test
94 | fun `should throw error when sending after close`() {
95 | runBlocking {
96 | clientTransport.close()
97 |
98 | assertThrows {
99 | runBlocking {
100 | clientTransport.send(
101 | InitializedNotification().toJSON()
102 | )
103 | }
104 | }
105 | }
106 | }
107 |
108 | @Test
109 | fun `should queue messages sent before start`() {
110 | runBlocking {
111 | val message = InitializedNotification()
112 | .toJSON()
113 |
114 | var receivedMessage: JSONRPCMessage? = null
115 | serverTransport.onMessage = { msg ->
116 | receivedMessage = msg
117 | }
118 |
119 | clientTransport.send(message)
120 | serverTransport.start()
121 | assertEquals(message, receivedMessage)
122 | }
123 | }
124 | }
--------------------------------------------------------------------------------
/src/test/kotlin/client/SseTransportTest.kt:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import io.ktor.client.*
4 | import io.ktor.client.plugins.sse.*
5 | import io.ktor.server.application.*
6 | import io.ktor.server.cio.*
7 | import io.ktor.server.engine.*
8 | import io.ktor.server.routing.*
9 | import kotlinx.coroutines.CompletableDeferred
10 | import kotlinx.coroutines.test.runTest
11 | import mcpSse
12 | import mcpSseTransport
13 | import org.jetbrains.kotlinx.mcp.client.mcpSseTransport
14 | import org.junit.jupiter.api.Test
15 |
16 | private const val PORT = 8080
17 |
18 | class SseTransportTest : BaseTransportTest() {
19 | @Test
20 | fun `should start then close cleanly`() = runTest {
21 | val server = embeddedServer(CIO, port = PORT) {
22 | install(io.ktor.server.sse.SSE)
23 | routing {
24 | mcpSse()
25 | }
26 | }.start(wait = false)
27 |
28 | val client = HttpClient {
29 | install(SSE)
30 | }.mcpSseTransport {
31 | url {
32 | host = "localhost"
33 | port = PORT
34 | }
35 | }
36 |
37 | testClientOpenClose(client)
38 |
39 | server.stop()
40 | }
41 |
42 | @Test
43 | fun `should read messages`() = runTest {
44 | val clientFinished = CompletableDeferred()
45 |
46 | val server = embeddedServer(CIO, port = PORT) {
47 | install(io.ktor.server.sse.SSE)
48 | routing {
49 | mcpSseTransport {
50 | onMessage = {
51 | send(it)
52 | }
53 | }
54 | }
55 | }.start(wait = false)
56 |
57 | val client = HttpClient {
58 | install(SSE)
59 | }.mcpSseTransport {
60 | url {
61 | host = "localhost"
62 | port = PORT
63 | }
64 | }
65 |
66 | testClientRead(client)
67 | server.stop()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/test/kotlin/client/StdioClientTransportTest.kt:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import kotlinx.coroutines.test.runTest
4 | import org.jetbrains.kotlinx.mcp.client.StdioClientTransport
5 | import org.junit.jupiter.api.Test
6 |
7 | class StdioClientTransportTest : BaseTransportTest() {
8 | @Test
9 | fun `should start then close cleanly`() = runTest {
10 | // Run process "/usr/bin/tee"
11 | val processBuilder = ProcessBuilder("/usr/bin/tee")
12 | val process = processBuilder.start()
13 |
14 | val input = process.inputStream
15 | val output = process.outputStream
16 |
17 | val client = StdioClientTransport(
18 | input = input,
19 | output = output
20 | )
21 |
22 | testClientOpenClose(client)
23 |
24 | process.destroy()
25 | }
26 |
27 | @Test
28 | fun `should read messages`() = runTest {
29 | val processBuilder = ProcessBuilder("/usr/bin/tee")
30 | val process = processBuilder.start()
31 |
32 | val input = process.inputStream
33 | val output = process.outputStream
34 |
35 | val client = StdioClientTransport(
36 | input = input,
37 | output = output
38 | )
39 |
40 | testClientRead(client)
41 |
42 | process.waitFor()
43 | process.destroy()
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/test/kotlin/client/TypesTest.kt:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
4 | import org.jetbrains.kotlinx.mcp.Request
5 | import org.junit.jupiter.api.Test
6 | import org.jetbrains.kotlinx.mcp.shared.McpJson
7 |
8 | class TypesTest {
9 |
10 | @Test
11 | fun testRequestResult() {
12 | val message = "{\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{\"listChanged\":true},\"resources\":{}},\"serverInfo\":{\"name\":\"jetbrains/proxy\",\"version\":\"0.1.0\"}},\"jsonrpc\":\"2.0\",\"id\":1}"
13 | McpJson.decodeFromString(message)
14 | }
15 |
16 | @Test
17 | fun testRequestError() {
18 | val message = "{\"method\":\"initialize\", \"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"experimental\":{},\"sampling\":{}},\"clientInfo\":{\"name\":\"test client\",\"version\":\"1.0\"},\"_meta\":{}}"
19 | McpJson.decodeFromString(message)
20 | }
21 |
22 | @Test
23 | fun testJSONRPCMessage() {
24 | val line = "{\"result\":{\"content\":[{\"type\":\"text\"}],\"isError\":false},\"jsonrpc\":\"2.0\",\"id\":4}"
25 | McpJson.decodeFromString(line)
26 | }
27 | }
--------------------------------------------------------------------------------
/src/test/kotlin/client/WebSocketTransportTest.kt:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import io.ktor.server.testing.*
4 | import io.ktor.server.websocket.*
5 | import kotlinx.coroutines.CompletableDeferred
6 | import org.jetbrains.kotlinx.mcp.client.mcpWebSocketTransport
7 | import org.junit.jupiter.api.Test
8 | import org.jetbrains.kotlinx.mcp.server.mcpWebSocket
9 | import org.jetbrains.kotlinx.mcp.server.mcpWebSocketTransport
10 |
11 | class WebSocketTransportTest : BaseTransportTest() {
12 | @Test
13 | fun `should start then close cleanly`() = testApplication {
14 | install(WebSockets)
15 | routing {
16 | mcpWebSocket()
17 | }
18 |
19 | val client = createClient {
20 | install(io.ktor.client.plugins.websocket.WebSockets)
21 | }.mcpWebSocketTransport()
22 |
23 | testClientOpenClose(client)
24 | }
25 |
26 | @Test
27 | fun `should read messages`() = testApplication {
28 | val clientFinished = CompletableDeferred()
29 |
30 | install(WebSockets)
31 | routing {
32 | mcpWebSocketTransport {
33 | onMessage = {
34 | send(it)
35 | }
36 |
37 | clientFinished.await()
38 | }
39 | }
40 |
41 | val client = createClient {
42 | install(io.ktor.client.plugins.websocket.WebSockets)
43 | }.mcpWebSocketTransport()
44 |
45 | testClientRead(client)
46 |
47 | clientFinished.complete(Unit)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/kotlin/server/StdioServerTransportTest.kt:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import org.jetbrains.kotlinx.mcp.InitializedNotification
4 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
5 | import org.jetbrains.kotlinx.mcp.PingRequest
6 | import kotlinx.coroutines.CompletableDeferred
7 | import kotlinx.coroutines.runBlocking
8 | import org.jetbrains.kotlinx.mcp.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 org.jetbrains.kotlinx.mcp.shared.ReadBuffer
13 | import org.jetbrains.kotlinx.mcp.shared.serializeMessage
14 | import org.jetbrains.kotlinx.mcp.toJSON
15 | import java.io.*
16 |
17 | class StdioServerTransportTest {
18 | private lateinit var input: PipedInputStream
19 | private lateinit var inputWriter: PipedOutputStream
20 | private lateinit var outputBuffer: ReadBuffer
21 | private lateinit var output: ByteArrayOutputStream
22 |
23 | // We'll store the wrapped streams that meet the constructor requirements
24 | private lateinit var bufferedInput: BufferedInputStream
25 | private lateinit var printOutput: PrintStream
26 |
27 | @BeforeEach
28 | fun setUp() {
29 | // Simulate an input stream that we can push data into using inputWriter.
30 | input = PipedInputStream()
31 | inputWriter = PipedOutputStream(input)
32 |
33 | outputBuffer = ReadBuffer()
34 |
35 | // A custom ByteArrayOutputStream that appends all written data into outputBuffer
36 | output = object : ByteArrayOutputStream() {
37 | override fun write(b: ByteArray, off: Int, len: Int) {
38 | super.write(b, off, len)
39 | outputBuffer.append(b.copyOfRange(off, off + len))
40 | }
41 | }
42 |
43 | // Wrap input in BufferedInputStream
44 | bufferedInput = BufferedInputStream(input)
45 |
46 | // Wrap output in PrintStream
47 | printOutput = PrintStream(output, true)
48 | }
49 |
50 | @Test
51 | fun `should start then close cleanly`() {
52 | runBlocking {
53 | val server = StdioServerTransport(bufferedInput, printOutput)
54 | server.onError = { error ->
55 | throw error
56 | }
57 |
58 | var didClose = false
59 | server.onClose = {
60 | didClose = true
61 | }
62 |
63 | server.start()
64 | assertFalse(didClose, "Should not have closed yet")
65 |
66 | server.close()
67 | assertTrue(didClose, "Should have closed after calling close()")
68 | }
69 | }
70 |
71 | @Test
72 | fun `should not read until started`() {
73 | runBlocking {
74 | val server = StdioServerTransport(bufferedInput, printOutput)
75 | server.onError = { error ->
76 | throw error
77 | }
78 |
79 | var didRead = false
80 | val readMessage = CompletableDeferred()
81 |
82 | server.onMessage = { message ->
83 | didRead = true
84 | readMessage.complete(message)
85 | }
86 |
87 | val message = PingRequest().toJSON()
88 |
89 | // Push message before the server started
90 | val serialized = serializeMessage(message)
91 | inputWriter.write(serialized)
92 | inputWriter.flush()
93 |
94 | assertFalse(didRead, "Should not have read message before start")
95 |
96 | server.start()
97 | val received = readMessage.await()
98 | assertEquals(message, received)
99 | }
100 | }
101 |
102 | @Test
103 | fun `should read multiple messages`() {
104 | runBlocking {
105 | val server = StdioServerTransport(bufferedInput, printOutput)
106 | server.onError = { error ->
107 | throw error
108 | }
109 |
110 | val messages = listOf(
111 | PingRequest().toJSON(),
112 | InitializedNotification().toJSON(),
113 | )
114 |
115 | val readMessages = mutableListOf()
116 | val finished = CompletableDeferred()
117 |
118 | server.onMessage = { message ->
119 | readMessages.add(message)
120 | if (message == messages[1]) {
121 | finished.complete(Unit)
122 | }
123 | }
124 |
125 | // Push both messages before starting the server
126 | for (m in messages) {
127 | inputWriter.write(serializeMessage(m))
128 | }
129 | inputWriter.flush()
130 |
131 | server.start()
132 | finished.await()
133 |
134 | assertEquals(messages, readMessages)
135 | }
136 | }
137 | }
138 |
139 | fun PipedOutputStream.write(s: String) {
140 | write(s.toByteArray())
141 | }
--------------------------------------------------------------------------------
/src/test/kotlin/shared/ReadBufferTest.kt:
--------------------------------------------------------------------------------
1 | package shared
2 |
3 | import org.jetbrains.kotlinx.mcp.JSONRPCMessage
4 | import org.jetbrains.kotlinx.mcp.JSONRPCNotification
5 | import kotlinx.serialization.encodeToString
6 | import kotlinx.serialization.json.Json
7 | import org.jetbrains.kotlinx.mcp.shared.ReadBuffer
8 | import org.jetbrains.kotlinx.mcp.shared.serializeMessage
9 | import org.junit.jupiter.api.Assertions.assertEquals
10 | import org.junit.jupiter.api.Assertions.assertNull
11 | import org.junit.jupiter.api.Test
12 | import java.nio.charset.StandardCharsets
13 |
14 | class ReadBufferTest {
15 | private val testMessage: JSONRPCMessage = JSONRPCNotification(method = "foobar")
16 |
17 | private val json = Json {
18 | ignoreUnknownKeys = true
19 | encodeDefaults = true
20 | }
21 |
22 | @Test
23 | fun `should have no messages after initialization`() {
24 | val readBuffer = ReadBuffer()
25 | assertNull(readBuffer.readMessage())
26 | }
27 |
28 | @Test
29 | fun `should only yield a message after a newline`() {
30 | val readBuffer = ReadBuffer()
31 |
32 | // Append message without newline
33 | val messageBytes = json.encodeToString(testMessage)
34 | .toByteArray(StandardCharsets.UTF_8)
35 | readBuffer.append(messageBytes)
36 | assertNull(readBuffer.readMessage())
37 |
38 | // Append newline and verify message is now available
39 | readBuffer.append("\n".toByteArray(StandardCharsets.UTF_8))
40 | assertEquals(testMessage, readBuffer.readMessage())
41 | assertNull(readBuffer.readMessage())
42 | }
43 |
44 | @Test
45 | fun `should be reusable after clearing`() {
46 | val readBuffer = ReadBuffer()
47 |
48 | readBuffer.append("foobar".toByteArray(Charsets.UTF_8))
49 | readBuffer.clear()
50 | assertNull(readBuffer.readMessage())
51 |
52 | val messageJson = serializeMessage(testMessage)
53 | readBuffer.append(messageJson.toByteArray(Charsets.UTF_8))
54 | readBuffer.append("\n".toByteArray(Charsets.UTF_8))
55 | val message = readBuffer.readMessage()
56 | assertEquals(testMessage, message)
57 | }
58 | }
59 |
--------------------------------------------------------------------------------