├── .github └── workflows │ ├── gradle-publish.yml │ └── validate-pr.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── api └── kotlin-sdk.api ├── build.gradle.kts ├── docs ├── -m-c-p -kotlin -s-d-k │ ├── io.modelcontextprotocol.kotlin.sdk.client │ │ ├── -client-options │ │ │ ├── -client-options.html │ │ │ ├── capabilities.html │ │ │ └── index.html │ │ ├── -client │ │ │ ├── -client.html │ │ │ ├── assert-request-handler-capability.html │ │ │ ├── call-tool.html │ │ │ ├── complete.html │ │ │ ├── connect.html │ │ │ ├── get-prompt.html │ │ │ ├── get-server-capabilities.html │ │ │ ├── get-server-version.html │ │ │ ├── index.html │ │ │ ├── list-prompts.html │ │ │ ├── list-resource-templates.html │ │ │ ├── list-resources.html │ │ │ ├── list-tools.html │ │ │ ├── ping.html │ │ │ ├── read-resource.html │ │ │ ├── send-roots-list-changed.html │ │ │ ├── set-logging-level.html │ │ │ ├── subscribe-resource.html │ │ │ └── unsubscribe-resource.html │ │ ├── -s-s-e-client-transport │ │ │ ├── -s-s-e-client-transport.html │ │ │ ├── close.html │ │ │ ├── index.html │ │ │ ├── on-close.html │ │ │ ├── on-error.html │ │ │ ├── on-message.html │ │ │ ├── send.html │ │ │ └── start.html │ │ ├── -stdio-client-transport │ │ │ ├── -stdio-client-transport.html │ │ │ ├── close.html │ │ │ ├── index.html │ │ │ ├── on-close.html │ │ │ ├── on-error.html │ │ │ ├── on-message.html │ │ │ ├── send.html │ │ │ └── start.html │ │ ├── -web-socket-client-transport │ │ │ ├── -web-socket-client-transport.html │ │ │ ├── index.html │ │ │ └── session.html │ │ ├── index.html │ │ ├── mcp-sse-transport.html │ │ ├── mcp-sse.html │ │ ├── mcp-web-socket-transport.html │ │ └── mcp-web-socket.html │ ├── io.modelcontextprotocol.kotlin.sdk.server │ │ ├── -m-c-p.html │ │ ├── -registered-prompt │ │ │ ├── -registered-prompt.html │ │ │ ├── index.html │ │ │ ├── message-provider.html │ │ │ └── prompt.html │ │ ├── -registered-resource │ │ │ ├── -registered-resource.html │ │ │ ├── index.html │ │ │ ├── read-handler.html │ │ │ └── resource.html │ │ ├── -registered-tool │ │ │ ├── -registered-tool.html │ │ │ ├── handler.html │ │ │ ├── index.html │ │ │ └── tool.html │ │ ├── -s-s-e-server-transport │ │ │ ├── -s-s-e-server-transport.html │ │ │ ├── close.html │ │ │ ├── handle-message.html │ │ │ ├── handle-post-message.html │ │ │ ├── index.html │ │ │ ├── on-close.html │ │ │ ├── on-error.html │ │ │ ├── on-message.html │ │ │ ├── send.html │ │ │ ├── session-id.html │ │ │ └── start.html │ │ ├── -server-options │ │ │ ├── -server-options.html │ │ │ ├── capabilities.html │ │ │ └── index.html │ │ ├── -server │ │ │ ├── -server.html │ │ │ ├── add-prompt.html │ │ │ ├── add-prompts.html │ │ │ ├── add-resource.html │ │ │ ├── add-resources.html │ │ │ ├── add-tool.html │ │ │ ├── add-tools.html │ │ │ ├── assert-request-handler-capability.html │ │ │ ├── client-capabilities.html │ │ │ ├── client-version.html │ │ │ ├── create-message.html │ │ │ ├── index.html │ │ │ ├── list-roots.html │ │ │ ├── on-close-callback.html │ │ │ ├── on-initialized.html │ │ │ ├── onclose.html │ │ │ ├── ping.html │ │ │ ├── send-logging-message.html │ │ │ ├── send-prompt-list-changed.html │ │ │ ├── send-resource-list-changed.html │ │ │ ├── send-resource-updated.html │ │ │ └── send-tool-list-changed.html │ │ ├── -stdio-server-transport │ │ │ ├── -stdio-server-transport.html │ │ │ ├── close.html │ │ │ ├── index.html │ │ │ ├── on-close.html │ │ │ ├── on-error.html │ │ │ ├── on-message.html │ │ │ ├── send.html │ │ │ └── start.html │ │ ├── -web-socket-mcp-server-transport │ │ │ ├── -web-socket-mcp-server-transport.html │ │ │ └── index.html │ │ ├── index.html │ │ ├── mcp-web-socket-transport.html │ │ └── mcp-web-socket.html │ ├── io.modelcontextprotocol.kotlin.sdk.shared │ │ ├── -d-e-f-a-u-l-t_-r-e-q-u-e-s-t_-t-i-m-e-o-u-t.html │ │ ├── -progress-callback │ │ │ └── index.html │ │ ├── -protocol-options │ │ │ ├── -protocol-options.html │ │ │ ├── enforce-strict-capabilities.html │ │ │ ├── index.html │ │ │ └── timeout.html │ │ ├── -protocol │ │ │ ├── -protocol.html │ │ │ ├── assert-request-handler-capability.html │ │ │ ├── close.html │ │ │ ├── connect.html │ │ │ ├── fallback-notification-handler.html │ │ │ ├── fallback-request-handler.html │ │ │ ├── index.html │ │ │ ├── notification-handlers.html │ │ │ ├── notification.html │ │ │ ├── onclose.html │ │ │ ├── onerror.html │ │ │ ├── remove-notification-handler.html │ │ │ ├── remove-request-handler.html │ │ │ ├── request.html │ │ │ ├── set-notification-handler.html │ │ │ ├── set-request-handler.html │ │ │ └── transport.html │ │ ├── -read-buffer │ │ │ ├── -read-buffer.html │ │ │ ├── append.html │ │ │ ├── clear.html │ │ │ ├── index.html │ │ │ └── read-message.html │ │ ├── -request-handler-extra │ │ │ ├── -request-handler-extra.html │ │ │ └── index.html │ │ ├── -request-options │ │ │ ├── -request-options.html │ │ │ ├── index.html │ │ │ ├── on-progress.html │ │ │ └── timeout.html │ │ ├── -transport │ │ │ ├── close.html │ │ │ ├── index.html │ │ │ ├── on-close.html │ │ │ ├── on-error.html │ │ │ ├── on-message.html │ │ │ ├── send.html │ │ │ └── start.html │ │ ├── -web-socket-mcp-transport │ │ │ ├── -web-socket-mcp-transport.html │ │ │ ├── close.html │ │ │ ├── index.html │ │ │ ├── on-close.html │ │ │ ├── on-error.html │ │ │ ├── on-message.html │ │ │ ├── send.html │ │ │ └── start.html │ │ └── index.html │ ├── io.modelcontextprotocol.kotlin.sdk │ │ ├── -blob-resource-contents │ │ │ ├── -blob-resource-contents.html │ │ │ ├── blob.html │ │ │ ├── index.html │ │ │ ├── mime-type.html │ │ │ └── uri.html │ │ ├── -call-tool-request │ │ │ ├── -call-tool-request.html │ │ │ ├── _meta.html │ │ │ ├── arguments.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ └── name.html │ │ ├── -call-tool-result-base │ │ │ ├── content.html │ │ │ ├── index.html │ │ │ └── is-error.html │ │ ├── -call-tool-result │ │ │ ├── -call-tool-result.html │ │ │ ├── _meta.html │ │ │ ├── content.html │ │ │ ├── index.html │ │ │ └── is-error.html │ │ ├── -cancelled-notification │ │ │ ├── -cancelled-notification.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ ├── reason.html │ │ │ └── request-id.html │ │ ├── -client-capabilities │ │ │ ├── -client-capabilities.html │ │ │ ├── -roots │ │ │ │ ├── -roots.html │ │ │ │ ├── index.html │ │ │ │ └── list-changed.html │ │ │ ├── experimental.html │ │ │ ├── index.html │ │ │ ├── roots.html │ │ │ └── sampling.html │ │ ├── -client-notification │ │ │ └── index.html │ │ ├── -client-request │ │ │ └── index.html │ │ ├── -client-result │ │ │ └── index.html │ │ ├── -compatibility-call-tool-result │ │ │ ├── -compatibility-call-tool-result.html │ │ │ ├── _meta.html │ │ │ ├── content.html │ │ │ ├── index.html │ │ │ ├── is-error.html │ │ │ └── tool-result.html │ │ ├── -complete-request │ │ │ ├── -argument │ │ │ │ ├── -argument.html │ │ │ │ ├── index.html │ │ │ │ ├── name.html │ │ │ │ └── value.html │ │ │ ├── -complete-request.html │ │ │ ├── _meta.html │ │ │ ├── argument.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ └── ref.html │ │ ├── -complete-result │ │ │ ├── -complete-result.html │ │ │ ├── -completion │ │ │ │ ├── -completion.html │ │ │ │ ├── has-more.html │ │ │ │ ├── index.html │ │ │ │ ├── total.html │ │ │ │ └── values.html │ │ │ ├── _meta.html │ │ │ ├── completion.html │ │ │ └── index.html │ │ ├── -create-message-request │ │ │ ├── -create-message-request.html │ │ │ ├── -include-context │ │ │ │ ├── all-servers │ │ │ │ │ └── index.html │ │ │ │ ├── entries.html │ │ │ │ ├── index.html │ │ │ │ ├── none │ │ │ │ │ └── index.html │ │ │ │ ├── this-server │ │ │ │ │ └── index.html │ │ │ │ ├── value-of.html │ │ │ │ └── values.html │ │ │ ├── _meta.html │ │ │ ├── include-context.html │ │ │ ├── index.html │ │ │ ├── max-tokens.html │ │ │ ├── messages.html │ │ │ ├── metadata.html │ │ │ ├── method.html │ │ │ ├── model-preferences.html │ │ │ ├── stop-sequences.html │ │ │ ├── system-prompt.html │ │ │ └── temperature.html │ │ ├── -create-message-result │ │ │ ├── -create-message-result.html │ │ │ ├── _meta.html │ │ │ ├── content.html │ │ │ ├── index.html │ │ │ ├── model.html │ │ │ ├── role.html │ │ │ └── stop-reason.html │ │ ├── -cursor │ │ │ └── index.html │ │ ├── -custom-meta │ │ │ ├── -custom-meta.html │ │ │ ├── _meta.html │ │ │ └── index.html │ │ ├── -custom-request │ │ │ ├── -custom-request.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -embedded-resource │ │ │ ├── -companion │ │ │ │ ├── -t-y-p-e.html │ │ │ │ └── index.html │ │ │ ├── -embedded-resource.html │ │ │ ├── index.html │ │ │ ├── resource.html │ │ │ └── type.html │ │ ├── -empty-request-result │ │ │ ├── -empty-request-result.html │ │ │ ├── _meta.html │ │ │ └── index.html │ │ ├── -error-code │ │ │ ├── -defined │ │ │ │ ├── -connection-closed │ │ │ │ │ └── index.html │ │ │ │ ├── -internal-error │ │ │ │ │ └── index.html │ │ │ │ ├── -invalid-params │ │ │ │ │ └── index.html │ │ │ │ ├── -invalid-request │ │ │ │ │ └── index.html │ │ │ │ ├── -method-not-found │ │ │ │ │ └── index.html │ │ │ │ ├── -parse-error │ │ │ │ │ └── index.html │ │ │ │ ├── -request-timeout │ │ │ │ │ └── index.html │ │ │ │ ├── code.html │ │ │ │ ├── entries.html │ │ │ │ ├── index.html │ │ │ │ ├── value-of.html │ │ │ │ └── values.html │ │ │ ├── -unknown │ │ │ │ ├── -unknown.html │ │ │ │ ├── code.html │ │ │ │ └── index.html │ │ │ ├── code.html │ │ │ └── index.html │ │ ├── -get-prompt-request │ │ │ ├── -get-prompt-request.html │ │ │ ├── _meta.html │ │ │ ├── arguments.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ └── name.html │ │ ├── -get-prompt-result │ │ │ ├── -get-prompt-result.html │ │ │ ├── _meta.html │ │ │ ├── description.html │ │ │ ├── index.html │ │ │ └── messages.html │ │ ├── -image-content │ │ │ ├── -companion │ │ │ │ ├── -t-y-p-e.html │ │ │ │ └── index.html │ │ │ ├── -image-content.html │ │ │ ├── data.html │ │ │ ├── index.html │ │ │ ├── mime-type.html │ │ │ └── type.html │ │ ├── -implementation │ │ │ ├── -implementation.html │ │ │ ├── index.html │ │ │ ├── name.html │ │ │ └── version.html │ │ ├── -initialize-request │ │ │ ├── -initialize-request.html │ │ │ ├── _meta.html │ │ │ ├── capabilities.html │ │ │ ├── client-info.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ └── protocol-version.html │ │ ├── -initialize-result │ │ │ ├── -initialize-result.html │ │ │ ├── _meta.html │ │ │ ├── capabilities.html │ │ │ ├── index.html │ │ │ ├── protocol-version.html │ │ │ └── server-info.html │ │ ├── -initialized-notification │ │ │ ├── -initialized-notification.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -j-s-o-n-r-p-c-error │ │ │ ├── -j-s-o-n-r-p-c-error.html │ │ │ ├── code.html │ │ │ ├── data.html │ │ │ ├── index.html │ │ │ └── message.html │ │ ├── -j-s-o-n-r-p-c-message │ │ │ └── index.html │ │ ├── -j-s-o-n-r-p-c-notification │ │ │ ├── -j-s-o-n-r-p-c-notification.html │ │ │ ├── index.html │ │ │ ├── jsonrpc.html │ │ │ ├── method.html │ │ │ └── params.html │ │ ├── -j-s-o-n-r-p-c-request │ │ │ ├── -j-s-o-n-r-p-c-request.html │ │ │ ├── id.html │ │ │ ├── index.html │ │ │ ├── jsonrpc.html │ │ │ ├── method.html │ │ │ └── params.html │ │ ├── -j-s-o-n-r-p-c-response │ │ │ ├── -j-s-o-n-r-p-c-response.html │ │ │ ├── error.html │ │ │ ├── id.html │ │ │ ├── index.html │ │ │ ├── jsonrpc.html │ │ │ └── result.html │ │ ├── -j-s-o-n-r-p-c_-v-e-r-s-i-o-n.html │ │ ├── -l-a-t-e-s-t_-p-r-o-t-o-c-o-l_-v-e-r-s-i-o-n.html │ │ ├── -l-i-b_-v-e-r-s-i-o-n.html │ │ ├── -list-prompts-request │ │ │ ├── -list-prompts-request.html │ │ │ ├── _meta.html │ │ │ ├── cursor.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -list-prompts-result │ │ │ ├── -list-prompts-result.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── next-cursor.html │ │ │ └── prompts.html │ │ ├── -list-resource-templates-request │ │ │ ├── -list-resource-templates-request.html │ │ │ ├── _meta.html │ │ │ ├── cursor.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -list-resource-templates-result │ │ │ ├── -list-resource-templates-result.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── next-cursor.html │ │ │ └── resource-templates.html │ │ ├── -list-resources-request │ │ │ ├── -list-resources-request.html │ │ │ ├── _meta.html │ │ │ ├── cursor.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -list-resources-result │ │ │ ├── -list-resources-result.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── next-cursor.html │ │ │ └── resources.html │ │ ├── -list-roots-request │ │ │ ├── -list-roots-request.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -list-roots-result │ │ │ ├── -list-roots-result.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ └── roots.html │ │ ├── -list-tools-request │ │ │ ├── -list-tools-request.html │ │ │ ├── _meta.html │ │ │ ├── cursor.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -list-tools-result │ │ │ ├── -list-tools-result.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── next-cursor.html │ │ │ └── tools.html │ │ ├── -logging-level │ │ │ ├── alert │ │ │ │ └── index.html │ │ │ ├── critical │ │ │ │ └── index.html │ │ │ ├── debug │ │ │ │ └── index.html │ │ │ ├── emergency │ │ │ │ └── index.html │ │ │ ├── entries.html │ │ │ ├── error │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ ├── info │ │ │ │ └── index.html │ │ │ ├── notice │ │ │ │ └── index.html │ │ │ ├── value-of.html │ │ │ ├── values.html │ │ │ └── warning │ │ │ │ └── index.html │ │ ├── -logging-message-notification │ │ │ ├── -logging-message-notification.html │ │ │ ├── -set-level-request │ │ │ │ ├── -set-level-request.html │ │ │ │ ├── _meta.html │ │ │ │ ├── index.html │ │ │ │ ├── level.html │ │ │ │ └── method.html │ │ │ ├── _meta.html │ │ │ ├── data.html │ │ │ ├── index.html │ │ │ ├── level.html │ │ │ ├── logger.html │ │ │ └── method.html │ │ ├── -mcp-error │ │ │ ├── -mcp-error.html │ │ │ ├── code.html │ │ │ ├── data.html │ │ │ ├── index.html │ │ │ └── message.html │ │ ├── -method │ │ │ ├── -custom │ │ │ │ ├── -custom.html │ │ │ │ ├── index.html │ │ │ │ └── value.html │ │ │ ├── -defined │ │ │ │ ├── -completion-complete │ │ │ │ │ └── index.html │ │ │ │ ├── -initialize │ │ │ │ │ └── index.html │ │ │ │ ├── -logging-set-level │ │ │ │ │ └── index.html │ │ │ │ ├── -notifications-cancelled │ │ │ │ │ └── index.html │ │ │ │ ├── -notifications-initialized │ │ │ │ │ └── index.html │ │ │ │ ├── -notifications-message │ │ │ │ │ └── index.html │ │ │ │ ├── -notifications-progress │ │ │ │ │ └── index.html │ │ │ │ ├── -notifications-prompts-list-changed │ │ │ │ │ └── index.html │ │ │ │ ├── -notifications-resources-list-changed │ │ │ │ │ └── index.html │ │ │ │ ├── -notifications-resources-updated │ │ │ │ │ └── index.html │ │ │ │ ├── -notifications-roots-list-changed │ │ │ │ │ └── index.html │ │ │ │ ├── -notifications-tools-list-changed │ │ │ │ │ └── index.html │ │ │ │ ├── -ping │ │ │ │ │ └── index.html │ │ │ │ ├── -prompts-get │ │ │ │ │ └── index.html │ │ │ │ ├── -prompts-list │ │ │ │ │ └── index.html │ │ │ │ ├── -resources-list │ │ │ │ │ └── index.html │ │ │ │ ├── -resources-read │ │ │ │ │ └── index.html │ │ │ │ ├── -resources-subscribe │ │ │ │ │ └── index.html │ │ │ │ ├── -resources-templates-list │ │ │ │ │ └── index.html │ │ │ │ ├── -resources-unsubscribe │ │ │ │ │ └── index.html │ │ │ │ ├── -roots-list │ │ │ │ │ └── index.html │ │ │ │ ├── -sampling-create-message │ │ │ │ │ └── index.html │ │ │ │ ├── -tools-call │ │ │ │ │ └── index.html │ │ │ │ ├── -tools-list │ │ │ │ │ └── index.html │ │ │ │ ├── entries.html │ │ │ │ ├── index.html │ │ │ │ ├── value-of.html │ │ │ │ ├── value.html │ │ │ │ └── values.html │ │ │ ├── index.html │ │ │ └── value.html │ │ ├── -model-hint │ │ │ ├── -model-hint.html │ │ │ ├── index.html │ │ │ └── name.html │ │ ├── -model-preferences │ │ │ ├── -model-preferences.html │ │ │ ├── cost-priority.html │ │ │ ├── hints.html │ │ │ ├── index.html │ │ │ ├── intelligence-priority.html │ │ │ └── speed-priority.html │ │ ├── -notification │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -paginated-request │ │ │ ├── _meta.html │ │ │ ├── cursor.html │ │ │ └── index.html │ │ ├── -paginated-result │ │ │ ├── index.html │ │ │ └── next-cursor.html │ │ ├── -ping-request │ │ │ ├── -ping-request.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -progress-base │ │ │ ├── index.html │ │ │ ├── progress.html │ │ │ └── total.html │ │ ├── -progress-notification │ │ │ ├── -progress-notification.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ ├── progress-token.html │ │ │ ├── progress.html │ │ │ └── total.html │ │ ├── -progress-token │ │ │ └── index.html │ │ ├── -progress │ │ │ ├── -progress.html │ │ │ ├── index.html │ │ │ ├── progress.html │ │ │ └── total.html │ │ ├── -prompt-argument │ │ │ ├── -prompt-argument.html │ │ │ ├── description.html │ │ │ ├── index.html │ │ │ ├── name.html │ │ │ └── required.html │ │ ├── -prompt-list-changed-notification │ │ │ ├── -prompt-list-changed-notification.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -prompt-message-content-text-or-image │ │ │ └── index.html │ │ ├── -prompt-message-content │ │ │ ├── index.html │ │ │ └── type.html │ │ ├── -prompt-message │ │ │ ├── -prompt-message.html │ │ │ ├── content.html │ │ │ ├── index.html │ │ │ └── role.html │ │ ├── -prompt-reference │ │ │ ├── -companion │ │ │ │ ├── -t-y-p-e.html │ │ │ │ └── index.html │ │ │ ├── -prompt-reference.html │ │ │ ├── index.html │ │ │ ├── name.html │ │ │ └── type.html │ │ ├── -prompt │ │ │ ├── -prompt.html │ │ │ ├── arguments.html │ │ │ ├── description.html │ │ │ ├── index.html │ │ │ └── name.html │ │ ├── -read-resource-request │ │ │ ├── -read-resource-request.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ └── uri.html │ │ ├── -read-resource-result │ │ │ ├── -read-resource-result.html │ │ │ ├── _meta.html │ │ │ ├── contents.html │ │ │ └── index.html │ │ ├── -reference │ │ │ ├── index.html │ │ │ └── type.html │ │ ├── -request-id │ │ │ └── index.html │ │ ├── -request-result │ │ │ └── index.html │ │ ├── -request │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -resource-contents │ │ │ ├── index.html │ │ │ ├── mime-type.html │ │ │ └── uri.html │ │ ├── -resource-list-changed-notification │ │ │ ├── -resource-list-changed-notification.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -resource-reference │ │ │ ├── -companion │ │ │ │ ├── -t-y-p-e.html │ │ │ │ └── index.html │ │ │ ├── -resource-reference.html │ │ │ ├── index.html │ │ │ ├── type.html │ │ │ └── uri.html │ │ ├── -resource-template │ │ │ ├── -resource-template.html │ │ │ ├── description.html │ │ │ ├── index.html │ │ │ ├── mime-type.html │ │ │ ├── name.html │ │ │ └── uri-template.html │ │ ├── -resource-updated-notification │ │ │ ├── -resource-updated-notification.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ └── uri.html │ │ ├── -resource │ │ │ ├── -resource.html │ │ │ ├── description.html │ │ │ ├── index.html │ │ │ ├── mime-type.html │ │ │ ├── name.html │ │ │ └── uri.html │ │ ├── -role │ │ │ ├── assistant │ │ │ │ └── index.html │ │ │ ├── entries.html │ │ │ ├── index.html │ │ │ ├── user │ │ │ │ └── index.html │ │ │ ├── value-of.html │ │ │ └── values.html │ │ ├── -root │ │ │ ├── -root.html │ │ │ ├── index.html │ │ │ ├── name.html │ │ │ └── uri.html │ │ ├── -roots-list-changed-notification │ │ │ ├── -roots-list-changed-notification.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -s-u-p-p-o-r-t-e-d_-p-r-o-t-o-c-o-l_-v-e-r-s-i-o-n-s.html │ │ ├── -sampling-message │ │ │ ├── -sampling-message.html │ │ │ ├── content.html │ │ │ ├── index.html │ │ │ └── role.html │ │ ├── -server-capabilities │ │ │ ├── -prompts │ │ │ │ ├── -prompts.html │ │ │ │ ├── index.html │ │ │ │ └── list-changed.html │ │ │ ├── -resources │ │ │ │ ├── -resources.html │ │ │ │ ├── index.html │ │ │ │ ├── list-changed.html │ │ │ │ └── subscribe.html │ │ │ ├── -server-capabilities.html │ │ │ ├── -tools │ │ │ │ ├── -tools.html │ │ │ │ ├── index.html │ │ │ │ └── list-changed.html │ │ │ ├── experimental.html │ │ │ ├── index.html │ │ │ ├── logging.html │ │ │ ├── prompts.html │ │ │ ├── resources.html │ │ │ ├── sampling.html │ │ │ └── tools.html │ │ ├── -server-notification │ │ │ └── index.html │ │ ├── -server-request │ │ │ └── index.html │ │ ├── -server-result │ │ │ └── index.html │ │ ├── -stop-reason │ │ │ ├── -end-turn │ │ │ │ ├── index.html │ │ │ │ └── value.html │ │ │ ├── -max-tokens │ │ │ │ ├── index.html │ │ │ │ └── value.html │ │ │ ├── -other │ │ │ │ ├── -other.html │ │ │ │ ├── index.html │ │ │ │ └── value.html │ │ │ ├── -stop-sequence │ │ │ │ ├── index.html │ │ │ │ └── value.html │ │ │ ├── index.html │ │ │ └── value.html │ │ ├── -subscribe-request │ │ │ ├── -subscribe-request.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ └── uri.html │ │ ├── -text-content │ │ │ ├── -companion │ │ │ │ ├── -t-y-p-e.html │ │ │ │ └── index.html │ │ │ ├── -text-content.html │ │ │ ├── index.html │ │ │ ├── text.html │ │ │ └── type.html │ │ ├── -text-resource-contents │ │ │ ├── -text-resource-contents.html │ │ │ ├── index.html │ │ │ ├── mime-type.html │ │ │ ├── text.html │ │ │ └── uri.html │ │ ├── -tool-list-changed-notification │ │ │ ├── -tool-list-changed-notification.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -tool │ │ │ ├── -input │ │ │ │ ├── -input.html │ │ │ │ ├── index.html │ │ │ │ ├── properties.html │ │ │ │ └── type.html │ │ │ ├── -tool.html │ │ │ ├── description.html │ │ │ ├── index.html │ │ │ ├── input-schema.html │ │ │ └── name.html │ │ ├── -unknown-content │ │ │ ├── -unknown-content.html │ │ │ ├── index.html │ │ │ └── type.html │ │ ├── -unknown-method-request-or-notification │ │ │ ├── -unknown-method-request-or-notification.html │ │ │ ├── index.html │ │ │ └── method.html │ │ ├── -unknown-reference │ │ │ ├── -unknown-reference.html │ │ │ ├── index.html │ │ │ └── type.html │ │ ├── -unknown-resource-contents │ │ │ ├── -unknown-resource-contents.html │ │ │ ├── index.html │ │ │ ├── mime-type.html │ │ │ └── uri.html │ │ ├── -unsubscribe-request │ │ │ ├── -unsubscribe-request.html │ │ │ ├── _meta.html │ │ │ ├── index.html │ │ │ ├── method.html │ │ │ └── uri.html │ │ ├── -with-meta │ │ │ ├── -companion │ │ │ │ ├── -empty.html │ │ │ │ └── index.html │ │ │ ├── _meta.html │ │ │ └── index.html │ │ └── index.html │ └── package-list ├── images │ ├── anchor-copy-button.svg │ ├── arrow_down.svg │ ├── burger.svg │ ├── copy-icon.svg │ ├── copy-successful-icon.svg │ ├── footer-go-to-link.svg │ ├── go-to-top-icon.svg │ ├── homepage.svg │ ├── logo-icon.svg │ ├── nav-icons │ │ ├── abstract-class-kotlin.svg │ │ ├── abstract-class.svg │ │ ├── annotation-kotlin.svg │ │ ├── annotation.svg │ │ ├── class-kotlin.svg │ │ ├── class.svg │ │ ├── enum-kotlin.svg │ │ ├── enum.svg │ │ ├── exception-class.svg │ │ ├── field-value.svg │ │ ├── field-variable.svg │ │ ├── function.svg │ │ ├── interface-kotlin.svg │ │ ├── interface.svg │ │ ├── object.svg │ │ └── typealias-kotlin.svg │ └── theme-toggle.svg ├── index.html ├── navigation.html ├── scripts │ ├── clipboard.js │ ├── main.js │ ├── navigation-loader.js │ ├── pages.json │ ├── platform-content-handler.js │ ├── prism.js │ ├── sourceset_dependencies.js │ └── symbol-parameters-wrapper_deferred.js ├── styles │ ├── font-jb-sans-auto.css │ ├── logo-styles.css │ ├── main.css │ ├── prism.css │ └── style.css └── ui-kit │ ├── ui-kit.min.css │ └── ui-kit.min.js ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── samples ├── kotlin-mcp-client │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── io │ │ └── modelcontextprotocol │ │ └── sample │ │ └── client │ │ ├── MCPClient.kt │ │ └── main.kt ├── kotlin-mcp-server │ ├── .gitignore │ ├── .idea │ │ ├── .gitignore │ │ ├── gradle.xml │ │ ├── kotlinc.xml │ │ ├── misc.xml │ │ └── vcs.xml │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ ├── commonMain │ │ └── kotlin │ │ │ └── server.kt │ │ ├── jvmMain │ │ └── kotlin │ │ │ └── main.jvm.kt │ │ └── wasmJsMain │ │ └── kotlin │ │ └── main.wasmJs.kt └── weather-stdio-server │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ ├── main │ └── kotlin │ │ └── io │ │ └── modelcontextprotocol │ │ └── sample │ │ └── server │ │ ├── McpWeatherServer.kt │ │ ├── WeatherApi.kt │ │ └── main.kt │ └── test │ └── kotlin │ └── io │ └── modelcontextprotocol │ └── sample │ └── client │ └── ClientStdio.kt ├── settings.gradle.kts └── src ├── commonMain └── kotlin │ └── io │ └── modelcontextprotocol │ └── kotlin │ └── sdk │ ├── client │ ├── Client.kt │ ├── KtorClient.kt │ ├── SSEClientTransport.kt │ ├── StdioClientTransport.kt │ ├── WebSocketClientTransport.kt │ └── WebSocketMcpKtorClientExtensions.kt │ ├── internal │ └── utils.kt │ ├── server │ ├── KtorServer.kt │ ├── SSEServerTransport.kt │ ├── Server.kt │ ├── StdioServerTransport.kt │ ├── WebSocketMcpKtorServerExtensions.kt │ └── WebSocketMcpServerTransport.kt │ ├── shared │ ├── Protocol.kt │ ├── ReadBuffer.kt │ ├── Transport.kt │ └── WebSocketMcpTransport.kt │ ├── types.kt │ └── types.util.kt ├── commonTest └── kotlin │ ├── AudioContentSerializationTest.kt │ ├── CallToolResultUtilsTest.kt │ └── io │ └── modelcontextprotocol │ └── kotlin │ └── sdk │ ├── InMemoryTransport.kt │ ├── ToolSerializationTest.kt │ ├── client │ ├── BaseTransportTest.kt │ ├── InMemoryTransportTest.kt │ ├── SseTransportTest.kt │ ├── TypesTest.kt │ └── WebSocketTransportTest.kt │ ├── integration │ └── SseIntegrationTest.kt │ └── shared │ └── ReadBufferTest.kt ├── iosMain └── kotlin │ └── io │ └── modelcontextprotocol │ └── kotlin │ └── sdk │ └── internal │ └── utils.ios.kt ├── jsMain └── kotlin │ └── io │ └── modelcontextprotocol │ └── kotlin │ └── sdk │ └── internal │ └── utils.js.kt ├── jvmMain └── java │ └── io │ └── modelcontextprotocol │ └── kotlin │ └── sdk │ └── internal │ └── utils.jvm.kt ├── jvmTest └── kotlin │ ├── client │ ├── ClientIntegrationTest.kt │ ├── ClientTest.kt │ └── StdioClientTransportTest.kt │ └── server │ ├── ServerTest.kt │ └── StdioServerTransportTest.kt └── wasmJsMain └── kotlin └── io └── modelcontextprotocol └── kotlin └── sdk └── internal └── utils.wasmJs.kt /.github/workflows/gradle-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created 6 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle 7 | 8 | name: Release 9 | 10 | on: 11 | release: 12 | types: [created] 13 | 14 | jobs: 15 | build: 16 | runs-on: macos-latest 17 | environment: release 18 | 19 | permissions: 20 | contents: write 21 | packages: write 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - name: Set up JDK 21 26 | uses: actions/setup-java@v4 27 | with: 28 | java-version: '21' 29 | distribution: 'temurin' 30 | server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml 31 | server-username: JRELEASER_MAVENCENTRAL_USERNAME 32 | server-password: JRELEASER_MAVENCENTRAL_PASSWORD 33 | 34 | - name: Setup Gradle 35 | uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 36 | 37 | - name: Verify publication configuration 38 | run: ./gradlew jreleaserConfig 39 | env: 40 | JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.OSSRH_USERNAME }} 41 | JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 42 | JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }} 43 | JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }} 44 | JRELEASER_GPG_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} 45 | JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | - name: Clean Build with Gradle 48 | run: ./gradlew clean build 49 | env: 50 | JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.OSSRH_USERNAME }} 51 | JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 52 | JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }} 53 | JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }} 54 | JRELEASER_GPG_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} 55 | 56 | - name: Publish to OSSRH repository 57 | run: ./gradlew publish jreleaserFullRelease 58 | env: 59 | JRELEASER_MAVENCENTRAL_USERNAME: ${{ secrets.OSSRH_USERNAME }} 60 | JRELEASER_MAVENCENTRAL_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 61 | JRELEASER_GPG_PUBLIC_KEY: ${{ secrets.GPG_PUBLIC_KEY }} 62 | JRELEASER_GPG_SECRET_KEY: ${{ secrets.GPG_SECRET_KEY }} 63 | JRELEASER_GPG_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} 64 | JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 65 | -------------------------------------------------------------------------------- /.github/workflows/validate-pr.yml: -------------------------------------------------------------------------------- 1 | name: Validate PR 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | types: [auto_merge_enabled] 7 | 8 | jobs: 9 | validate-pr: 10 | runs-on: macos-latest 11 | name: Validate PR 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Set up JDK 21 15 | uses: actions/setup-java@v4 16 | with: 17 | java-version: '21' 18 | distribution: 'temurin' 19 | - name: Setup Gradle 20 | uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 # v4.0.0 21 | 22 | - name: Clean Build with Gradle 23 | run: ./gradlew clean build 24 | 25 | - name: Disable Auto-Merge on Fail 26 | if: failure() 27 | run: gh pr merge --disable-auto "$PR_URL" 28 | env: 29 | PR_URL: ${{github.event.pull_request.html_url}} 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/misc.xml 10 | .idea/jarRepositories.xml 11 | .idea/compiler.xml 12 | .idea/libraries/ 13 | .idea 14 | *.iws 15 | *.iml 16 | *.ipr 17 | out/ 18 | !**/src/main/**/out/ 19 | !**/src/test/**/out/ 20 | 21 | ### Kotlin ### 22 | .kotlin 23 | yarn.lock 24 | 25 | ### Eclipse ### 26 | .apt_generated 27 | .classpath 28 | .factorypath 29 | .project 30 | .settings 31 | .springBeans 32 | .sts4-cache 33 | bin/ 34 | !**/src/main/**/bin/ 35 | !**/src/test/**/bin/ 36 | 37 | ### NetBeans ### 38 | /nbproject/private/ 39 | /nbbuild/ 40 | /dist/ 41 | /nbdist/ 42 | /.nb-gradle/ 43 | 44 | ### VS Code ### 45 | .vscode/ 46 | 47 | ### Mac OS ### 48 | .DS_Store 49 | 50 | ### Node.js ### 51 | node_modules 52 | dist 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | There are two main ways to contribute to the project — submitting issues and submitting 4 | fixes/changes/improvements via pull requests. 5 | 6 | ## Submitting issues 7 | 8 | Both bug reports and feature requests are welcome. 9 | Submit issues [here](https://github.com/modelcontextprotocol/kotlin-sdk/issues). 10 | 11 | * Search for existing issues to avoid reporting duplicates. 12 | * When submitting a bug report: 13 | * Test it against the most recently released version. It might have already been fixed. 14 | * Include the code that reproduces the problem. Provide a minimal, complete, and reproducible example. 15 | * However, don't put off reporting any weird or rarely appearing issues just because you cannot consistently 16 | reproduce them. 17 | * If the bug is related to behavior, explain what behavior you expected and what you got. 18 | * When submitting a feature request: 19 | * Explain why you need the feature — what's your use-case, what's your domain. 20 | * Explaining the problem you face is more important than suggesting a solution. 21 | Even if you don't have a proposed solution, please report your problem. 22 | * If there is an alternative way to do what you need, then show the code of the alternative. 23 | 24 | ## Submitting PRs 25 | 26 | We love PRs. Submit PRs [here](https://github.com/modelcontextprotocol/kotlin-sdk/pulls). 27 | However, please keep in mind that maintainers will have to support the resulting code of the project, 28 | so do familiarize yourself with the following guidelines. 29 | 30 | * All development (both new features and bug fixes) is performed in the `main` branch. 31 | * Please base your PRs on the `main` branch. 32 | * PR should be linked with the issue, excluding minor documentation changes, adding unit tests, and fixing typos. 33 | * If you make any code changes: 34 | * Follow the [Kotlin Coding Conventions](https://kotlinlang.org/docs/reference/coding-conventions.html). 35 | * [Build the project](#building) to ensure it all works and passes the tests. 36 | * If you fix a bug: 37 | * Write the test that reproduces the bug. 38 | * Fixes without tests are accepted only in exceptional circumstances if it can be shown that writing the 39 | corresponding test is too hard or otherwise impractical. 40 | * If you introduce any new public APIs: 41 | * All new APIs must come with documentation and tests. 42 | * If you plan API additions, please start by submitting an issue with the proposed API design to gather community 43 | feedback. 44 | * [Contact the maintainers](#contacting-maintainers) to coordinate any great work in advance via submitting an 45 | issue. 46 | * If you fix documentation: 47 | * If you plan extensive rewrites/additions to the docs, then 48 | please [contact the maintainers](#contacting-maintainers) to coordinate the work in advance. 49 | 50 | ## Style guides 51 | 52 | A few things to remember: 53 | 54 | * Your code should conform to 55 | the official [Kotlin code style guide](https://kotlinlang.org/docs/reference/coding-conventions.html). 56 | Code style is managed by [EditorConfig](https://www.jetbrains.com/help/idea/editorconfig.html), 57 | so make sure the EditorConfig plugin is enabled in the IDE. 58 | * Every public API (including functions, classes, objects and so on) should be documented, 59 | every parameter, property, return types and exceptions should be described properly. 60 | 61 | ## Commit messages 62 | 63 | * Commit messages should be written in English 64 | * They should be written in present tense using imperative mood 65 | ("Fix" instead of "Fixes", "Improve" instead of "Improved"). 66 | Add the related bug reference to a commit message (bug number after a hash character between round braces). 67 | 68 | See [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/) 69 | 70 | ## Building 71 | 72 | ### Requirements 73 | 74 | * To build MCP Kotlin SDK, JDK version 21 or higher is required. Make sure this is your default JDK (`JAVA_HOME` is set 75 | accordingly) 76 | * The project can be opened in IntelliJ IDEA without additional prerequisites. 77 | 78 | ### Building MCP Kotlin SDK from source 79 | 80 | * Run `./gradlew assemble` to build the project and produce the corresponding artifacts. 81 | * Run `./gradlew test` to test the module and speed up development. 82 | * Run `./gradlew build` to build the project, which also runs all the tests. 83 | 84 | ## Contacting maintainers 85 | 86 | * If something cannot be done, not convenient, or does not work — submit an [issue](#submitting-issues). 87 | * "How to do something" questions — [StackOverflow](https://stackoverflow.com). 88 | * Discussions and general inquiries — use [KotlinLang Slack](https://kotl.in/slack). 89 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Anthropic, PBC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-complete-request/ref.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ref 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 61 |
62 | 65 |
66 |
67 | 68 |
69 |

ref

70 |
71 | 72 |
73 | 83 |
84 |
85 |
86 | 87 | -------------------------------------------------------------------------------- /docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-create-message-result/role.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | role 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 61 |
62 | 65 |
66 |
67 | 68 |
69 |

role

70 |
71 | 72 |
73 | 83 |
84 |
85 |
86 | 87 | -------------------------------------------------------------------------------- /docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-j-s-o-n-r-p-c-error/code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | code 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 61 |
62 | 65 |
66 |
67 | 68 |
69 |

code

70 |
71 | 72 |
73 | 83 |
84 |
85 |
86 | 87 | -------------------------------------------------------------------------------- /docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-j-s-o-n-r-p-c-request/id.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | id 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 61 |
62 | 65 |
66 |
67 | 68 |
69 |

id

70 |
71 | 72 |
73 | 83 |
84 |
85 |
86 | 87 | -------------------------------------------------------------------------------- /docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-j-s-o-n-r-p-c-response/id.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | id 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 61 |
62 | 65 |
66 |
67 | 68 |
69 |

id

70 |
71 | 72 |
73 | 83 |
84 |
85 |
86 | 87 | -------------------------------------------------------------------------------- /docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-l-i-b_-v-e-r-s-i-o-n.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | LIB_VERSION 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 61 |
62 | 65 |
66 |
67 | 68 |
69 |

LIB_VERSION

70 |
71 |
const val LIB_VERSION: String
72 |
73 | 83 |
84 |
85 |
86 | 87 | -------------------------------------------------------------------------------- /docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-mcp-error/code.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | code 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 61 |
62 | 65 |
66 |
67 | 68 |
69 |

code

70 |
71 | 72 |
73 | 83 |
84 |
85 |
86 | 87 | -------------------------------------------------------------------------------- /docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-prompt-message/role.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | role 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 61 |
62 | 65 |
66 |
67 | 68 |
69 |

role

70 |
71 | 72 |
73 | 83 |
84 |
85 |
86 | 87 | -------------------------------------------------------------------------------- /docs/-m-c-p -kotlin -s-d-k/io.modelcontextprotocol.kotlin.sdk/-sampling-message/role.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | role 6 | 7 | 8 | 9 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 | 61 |
62 | 65 |
66 |
67 | 68 |
69 |

role

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 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 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 | org.gradle.jvmargs=-XX:+UseParallelGC 3 | org.gradle.parallel=true 4 | org.gradle.configuration-cache.parallel=true 5 | 6 | org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled 7 | org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true 8 | 9 | kotlin.daemon.jvmargs=-Xmx2G -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # plugins version 3 | kotlin = "2.1.20" 4 | dokka = "2.0.0" 5 | 6 | # libraries version 7 | serialization = "1.7.3" 8 | coroutines = "1.10.2" 9 | ktor = "3.1.2" 10 | mockk = "1.13.13" 11 | logging = "7.0.0" 12 | jreleaser = "1.17.0" 13 | binaryCompatibilityValidatorPlugin = "0.17.0" 14 | slf4j = "2.0.16" 15 | kotest = "5.9.1" 16 | 17 | [libraries] 18 | # Kotlinx libraries 19 | kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" } 20 | kotlin-logging = { group = "io.github.oshai", name = "kotlin-logging", version.ref = "logging" } 21 | 22 | # Ktor 23 | ktor-client-cio = { group = "io.ktor", name = "ktor-client-cio", version.ref = "ktor" } 24 | ktor-server-sse = { group = "io.ktor", name = "ktor-server-sse", version.ref = "ktor" } 25 | ktor-server-websockets = { group = "io.ktor", name = "ktor-server-websockets", version.ref = "ktor" } 26 | ktor-server-cio = { group = "io.ktor", name = "ktor-server-cio", version.ref = "ktor" } 27 | 28 | # Testing 29 | kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 30 | kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines" } 31 | ktor-server-test-host = { group = "io.ktor", name = "ktor-server-test-host", version.ref = "ktor" } 32 | mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" } 33 | slf4j-simple = { group = "org.slf4j", name = "slf4j-simple", version.ref = "slf4j" } 34 | kotest-assertions-json = { group = "io.kotest", name = "kotest-assertions-json", version.ref = "kotest" } 35 | 36 | [plugins] 37 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } 38 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } 39 | dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" } 40 | jreleaser = { id = "org.jreleaser", version.ref = "jreleaser"} 41 | kotlinx-binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binaryCompatibilityValidatorPlugin" } 42 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelcontextprotocol/kotlin-sdk/0cff2ca71828857f998aa08c06fe73e206e7a8f5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-client/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/ 9 | *.iws 10 | *.iml 11 | *.ipr 12 | out/ 13 | !**/src/main/**/out/ 14 | !**/src/test/**/out/ 15 | 16 | ### Kotlin ### 17 | .kotlin 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /samples/kotlin-mcp-client/README.md: -------------------------------------------------------------------------------- 1 | # Kotlin MCP Client 2 | 3 | This project demonstrates how to build a Model Context Protocol (MCP) client in Kotlin that interacts with an MCP server 4 | via a STDIO transport layer while leveraging Anthropic's API for natural language processing. The client uses the MCP 5 | Kotlin SDK to communicate with an MCP server that exposes various tools, and it uses Anthropic's API to process user 6 | queries and integrate tool responses into the conversation. 7 | 8 | For more information about the MCP SDK and protocol, please refer to 9 | the [MCP documentation](https://modelcontextprotocol.io/introduction). 10 | 11 | ## Prerequisites 12 | 13 | - **Java 17 or later** 14 | - **Gradle** (or the Gradle wrapper provided with the project) 15 | - An Anthropic API key set in your environment variable `ANTHROPIC_API_KEY` 16 | - Basic understanding of MCP concepts and Kotlin programming 17 | 18 | ## Overview 19 | 20 | The client application performs the following tasks: 21 | 22 | - **Connecting to an MCP server** — 23 | launches an MCP server process (implemented in JavaScript, Python, or Java) using STDIO transport. 24 | It connects to the server, retrieves available tools, and converts them to Anthropic’s tool format. 25 | - **Processing queries** — 26 | accepts user queries, sends them to Anthropic’s API along with the registered tools, and handles responses. 27 | If the response indicates a tool should be called, it invokes the corresponding MCP tool and continues the 28 | conversation based on the tool’s result. 29 | - **Interactive chat loop** — 30 | runs an interactive command-line loop, allowing users to continuously submit queries and receive responses. 31 | 32 | ## Building and Running 33 | 34 | Use the Gradle wrapper to build the application. In a terminal, run: 35 | 36 | ```shell 37 | ./gradlew clean build -x test 38 | ``` 39 | 40 | To run the client, execute the jar file and provide the path to your MCP server script. 41 | 42 | To run the client with any MCP server: 43 | 44 | ```shell 45 | java -jar build/libs/.jar path/to/server.jar # jvm server 46 | java -jar build/libs/.jar path/to/server.py # python server 47 | java -jar build/libs/.jar path/to/build/index.js # node server 48 | ``` 49 | 50 | > [!NOTE] 51 | > The client uses STDIO transport, so it launches the MCP server as a separate process. 52 | > Ensure the server script is executable and is a valid `.js`, `.py`, or `.jar` file. 53 | 54 | ## Configuration for Anthropic 55 | 56 | Ensure your Anthropic API key is available in your environment: 57 | 58 | ```shell 59 | export ANTHROPIC_API_KEY=your_anthropic_api_key_here 60 | ``` 61 | 62 | The client uses `AnthropicOkHttpClient.fromEnv()` to automatically load the API key from `ANTHROPIC_API_KEY` and 63 | `ANTHROPIC_AUTH_TOKEN` environment variables. 64 | 65 | ## Additional Resources 66 | 67 | - [MCP Specification](https://spec.modelcontextprotocol.io/) 68 | - [Kotlin MCP SDK](https://github.com/modelcontextprotocol/kotlin-sdk) 69 | - [Anthropic Java SDK](https://github.com/anthropics/anthropic-sdk-java/tree/main) 70 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-client/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.10" 3 | application 4 | id("com.github.johnrengelman.shadow") version "8.1.1" 5 | } 6 | 7 | application { 8 | mainClass.set("io.modelcontextprotocol.sample.client.MainKt") 9 | } 10 | 11 | 12 | group = "org.example" 13 | version = "0.1.0" 14 | 15 | val mcpVersion = "0.5.0" 16 | val slf4jVersion = "2.0.9" 17 | val anthropicVersion = "0.8.0" 18 | 19 | dependencies { 20 | implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") 21 | implementation("org.slf4j:slf4j-nop:$slf4jVersion") 22 | implementation("com.anthropic:anthropic-java:$anthropicVersion") 23 | } 24 | 25 | tasks.test { 26 | useJUnitPlatform() 27 | } 28 | 29 | kotlin { 30 | jvmToolchain(17) 31 | } 32 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-client/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelcontextprotocol/kotlin-sdk/0cff2ca71828857f998aa08c06fe73e206e7a8f5/samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/kotlin-mcp-client/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-client/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-client/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "kotlin-mcp-client" 2 | 3 | plugins { 4 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-client/src/main/kotlin/io/modelcontextprotocol/sample/client/main.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.sample.client 2 | 3 | import kotlinx.coroutines.runBlocking 4 | 5 | fun main(args: Array) = runBlocking { 6 | if (args.isEmpty()) throw IllegalArgumentException("Usage: java -jar /build/libs/kotlin-mcp-client-0.1.0-all.jar ") 7 | val serverPath = args.first() 8 | val client = MCPClient() 9 | client.use { 10 | client.connectToServer(serverPath) 11 | client.chatLoop() 12 | } 13 | } -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/modules.xml 9 | .idea/jarRepositories.xml 10 | .idea/compiler.xml 11 | .idea/libraries/ 12 | *.iws 13 | *.iml 14 | *.ipr 15 | out/ 16 | !**/src/main/**/out/ 17 | !**/src/test/**/out/ 18 | 19 | ### Kotlin ### 20 | .kotlin 21 | 22 | ### Eclipse ### 23 | .apt_generated 24 | .classpath 25 | .factorypath 26 | .project 27 | .settings 28 | .springBeans 29 | .sts4-cache 30 | bin/ 31 | !**/src/main/**/bin/ 32 | !**/src/test/**/bin/ 33 | 34 | ### NetBeans ### 35 | /nbproject/private/ 36 | /nbbuild/ 37 | /dist/ 38 | /nbdist/ 39 | /.nb-gradle/ 40 | 41 | ### VS Code ### 42 | .vscode/ 43 | 44 | ### Mac OS ### 45 | .DS_Store -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/.idea/kotlinc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/README.md: -------------------------------------------------------------------------------- 1 | # MCP Kotlin Server Sample 2 | 3 | A sample implementation of an MCP (Model Communication Protocol) server in Kotlin that demonstrates different server 4 | configurations and transport methods for both JVM and WASM targets. 5 | 6 | ## Features 7 | 8 | - Multiple server operation modes: 9 | - Standard I/O server (JVM only) 10 | - SSE (Server-Sent Events) server with plain configuration (JVM, WASM) 11 | - SSE server using Ktor plugin (JVM, WASM) 12 | - Multiplatform support 13 | - Built-in capabilities for: 14 | - Prompts management 15 | - Resources handling 16 | - Tools integration 17 | 18 | ## Getting Started 19 | 20 | ### Running the Server 21 | 22 | You can run the server on the JVM or using Kotlin/WASM on Node.js. 23 | 24 | 25 | #### JVM: 26 | 27 | To run the server on the JVM (defaults to SSE mode with Ktor plugin on port 3001): 28 | 29 | ```bash 30 | ./gradlew runJvm 31 | ``` 32 | 33 | #### WASM: 34 | 35 | To run the server using Kotlin/WASM on Node.js (defaults to SSE mode with Ktor plugin on port 3001): 36 | 37 | ```bash 38 | ./gradlew wasmJsNodeDevelopmentRun 39 | ``` 40 | 41 | ### Connecting to the Server 42 | 43 | For servers on JVM or WASM: 44 | 1. Start the server 45 | 2. Use the [MCP inspector](https://modelcontextprotocol.io/docs/tools/inspector) to connect to `http://localhost:/sse` 46 | 47 | ## Server Capabilities 48 | 49 | - **Prompts**: Supports prompt management with list change notifications 50 | - **Resources**: Includes subscription support and list change notifications 51 | - **Tools**: Supports tool management with list change notifications 52 | 53 | ## Implementation Details 54 | 55 | The server is implemented using: 56 | - Ktor for HTTP server functionality 57 | - Kotlin coroutines for asynchronous operations 58 | - SSE for real-time communication 59 | - Standard I/O for command-line interface 60 | - Common Kotlin code shared between JVM and WASM targets 61 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/build.gradle.kts: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalWasmDsl::class, ExperimentalKotlinGradlePluginApi::class) 2 | 3 | import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi 4 | import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl 5 | 6 | plugins { 7 | kotlin("multiplatform") version "2.1.20" 8 | kotlin("plugin.serialization") version "2.1.20" 9 | } 10 | 11 | group = "org.example" 12 | version = "0.1.0" 13 | 14 | repositories { 15 | mavenCentral() 16 | } 17 | 18 | val jvmMainClass = "Main_jvmKt" 19 | 20 | kotlin { 21 | jvmToolchain(17) 22 | jvm { 23 | binaries { 24 | executable { 25 | mainClass.set(jvmMainClass) 26 | } 27 | } 28 | val jvmJar by tasks.getting(org.gradle.jvm.tasks.Jar::class) { 29 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 30 | doFirst { 31 | manifest { 32 | attributes["Main-Class"] = jvmMainClass 33 | } 34 | 35 | from(configurations["jvmRuntimeClasspath"].map { if (it.isDirectory) it else zipTree(it) }) 36 | } 37 | } 38 | } 39 | wasmJs { 40 | nodejs() 41 | binaries.executable() 42 | } 43 | 44 | sourceSets { 45 | commonMain.dependencies { 46 | implementation("io.modelcontextprotocol:kotlin-sdk:0.5.0") 47 | } 48 | jvmMain.dependencies { 49 | implementation("org.slf4j:slf4j-nop:2.0.9") 50 | } 51 | wasmJsMain.dependencies {} 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | kotlin.daemon.jvmargs=-Xmx2G 4 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelcontextprotocol/kotlin-sdk/0cff2ca71828857f998aa08c06fe73e206e7a8f5/samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 16 12:27:09 CET 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 3 | } 4 | rootProject.name = "kotlin-mcp-server" 5 | -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/src/jvmMain/kotlin/main.jvm.kt: -------------------------------------------------------------------------------- 1 | import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport 2 | import kotlinx.coroutines.Job 3 | import kotlinx.coroutines.runBlocking 4 | import kotlinx.io.asSink 5 | import kotlinx.io.asSource 6 | import kotlinx.io.buffered 7 | import shared.configureServer 8 | import shared.runSseMcpServerUsingKtorPlugin 9 | import shared.runSseMcpServerWithPlainConfiguration 10 | 11 | /** 12 | * Start sse-server mcp on port 3001. 13 | * 14 | * @param args 15 | * - "--stdio": Runs an MCP server using standard input/output. 16 | * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided). 17 | * - "--sse-server ": Runs an SSE MCP server with a plain configuration. 18 | */ 19 | fun main(args: Array): Unit = runBlocking { 20 | val command = args.firstOrNull() ?: "--sse-server-ktor" 21 | val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 22 | when (command) { 23 | "--stdio" -> runMcpServerUsingStdio() 24 | "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port) 25 | "--sse-server" -> runSseMcpServerWithPlainConfiguration(port) 26 | else -> { 27 | System.err.println("Unknown command: $command") 28 | } 29 | } 30 | } 31 | 32 | fun runMcpServerUsingStdio() { 33 | // Note: The server will handle listing prompts, tools, and resources automatically. 34 | // The handleListResourceTemplates will return empty as defined in the Server code. 35 | val server = configureServer() 36 | val transport = StdioServerTransport( 37 | inputStream = System.`in`.asSource().buffered(), 38 | outputStream = System.out.asSink().buffered() 39 | ) 40 | 41 | runBlocking { 42 | server.connect(transport) 43 | val done = Job() 44 | server.onClose { 45 | done.complete() 46 | } 47 | done.join() 48 | println("Server closed") 49 | } 50 | } -------------------------------------------------------------------------------- /samples/kotlin-mcp-server/src/wasmJsMain/kotlin/main.wasmJs.kt: -------------------------------------------------------------------------------- 1 | import shared.runSseMcpServerUsingKtorPlugin 2 | import shared.runSseMcpServerWithPlainConfiguration 3 | 4 | /** 5 | * Start sse-server mcp on port 3001. 6 | * 7 | * @param args 8 | * - "--sse-server-ktor ": Runs an SSE MCP server using Ktor plugin (default if no argument is provided). 9 | * - "--sse-server ": Runs an SSE MCP server with a plain configuration. 10 | */ 11 | suspend fun main(args: Array) { 12 | val command = args.firstOrNull() ?: "--sse-server-ktor" 13 | val port = args.getOrNull(1)?.toIntOrNull() ?: 3001 14 | when (command) { 15 | "--sse-server-ktor" -> runSseMcpServerUsingKtorPlugin(port) 16 | "--sse-server" -> runSseMcpServerWithPlainConfiguration(port) 17 | else -> { 18 | error("Unknown command: $command") 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /samples/weather-stdio-server/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | !gradle/wrapper/gradle-wrapper.jar 4 | !**/src/main/**/build/ 5 | !**/src/test/**/build/ 6 | 7 | ### IntelliJ IDEA ### 8 | .idea/ 9 | *.iws 10 | *.iml 11 | *.ipr 12 | out/ 13 | !**/src/main/**/out/ 14 | !**/src/test/**/out/ 15 | 16 | ### Kotlin ### 17 | .kotlin 18 | 19 | ### Eclipse ### 20 | .apt_generated 21 | .classpath 22 | .factorypath 23 | .project 24 | .settings 25 | .springBeans 26 | .sts4-cache 27 | bin/ 28 | !**/src/main/**/bin/ 29 | !**/src/test/**/bin/ 30 | 31 | ### NetBeans ### 32 | /nbproject/private/ 33 | /nbbuild/ 34 | /dist/ 35 | /nbdist/ 36 | /.nb-gradle/ 37 | 38 | ### VS Code ### 39 | .vscode/ 40 | 41 | ### Mac OS ### 42 | .DS_Store -------------------------------------------------------------------------------- /samples/weather-stdio-server/README.md: -------------------------------------------------------------------------------- 1 | # Kotlin MCP Weather STDIO Server 2 | 3 | This project demonstrates how to build a Model Context Protocol (MCP) server in Kotlin that provides weather-related 4 | tools by consuming the National Weather Service (weather.gov) API. The server uses STDIO as the transport layer and 5 | leverages the Kotlin MCP SDK to expose weather forecast and alert tools. 6 | 7 | For more information about the MCP SDK and protocol, please refer to 8 | the [MCP documentation](https://modelcontextprotocol.io/introduction). 9 | 10 | ## Prerequisites 11 | 12 | - Java 17 or later 13 | - Gradle (or the Gradle wrapper provided with the project) 14 | - Basic understanding of MCP concepts 15 | - Basic understanding of Kotlin and Kotlin ecosystems (sush as kotlinx-serialization, coroutines, ktor) 16 | 17 | ## MCP Weather Server 18 | 19 | The project provides: 20 | 21 | - A lightweight MCP server built with Kotlin. 22 | - STDIO transport layer implementation for server-client communication. 23 | - Two weather tools: 24 | - **Weather Forecast Tool** — returns details such as temperature, wind information, and a detailed forecast for a 25 | given latitude/longitude. 26 | - **Weather Alerts Tool** — returns active weather alerts for a given US state. 27 | 28 | ## Building and running 29 | 30 | Use the Gradle wrapper to build the application. In a terminal run: 31 | 32 | ```shell 33 | ./gradlew clean build -x test 34 | ``` 35 | 36 | To run the server: 37 | 38 | ```shell 39 | java -jar build/libs/.jar 40 | ``` 41 | 42 | > [!NOTE] 43 | > The server uses STDIO transport, so it is typically launched in an environment where the client connects via standard 44 | > input/output. 45 | 46 | ## Tool Implementation 47 | 48 | The project registers two MCP tools using the Kotlin MCP SDK. Below is an overview of the core tool implementations: 49 | 50 | ### 1. Weather Forecast Tool 51 | 52 | This tool fetches the weather forecast for a specific latitude and longitude using the `weather.gov` API. 53 | 54 | Example tool registration in Kotlin: 55 | 56 | ```kotlin 57 | server.addTool( 58 | name = "get_forecast", 59 | description = """ 60 | Get weather forecast for a specific latitude/longitude 61 | """.trimIndent(), 62 | inputSchema = Tool.Input( 63 | properties = JsonObject( 64 | mapOf( 65 | "latitude" to JsonObject(mapOf("type" to JsonPrimitive("number"))), 66 | "longitude" to JsonObject(mapOf("type" to JsonPrimitive("number"))), 67 | ) 68 | ), 69 | required = listOf("latitude", "longitude") 70 | ) 71 | ) { request -> 72 | // Implementation tool 73 | } 74 | ``` 75 | 76 | ### 2. Weather Alerts Tool 77 | 78 | This tool retrieves active weather alerts for a US state. 79 | 80 | Example tool registration in Kotlin: 81 | 82 | ```kotlin 83 | server.addTool( 84 | name = "get_alerts", 85 | description = """ 86 | Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY) 87 | """.trimIndent(), 88 | inputSchema = Tool.Input( 89 | properties = JsonObject( 90 | mapOf( 91 | "state" to JsonObject( 92 | mapOf( 93 | "type" to JsonPrimitive("string"), 94 | "description" to JsonPrimitive("Two-letter US state code (e.g. CA, NY)") 95 | ) 96 | ), 97 | ) 98 | ), 99 | required = listOf("state") 100 | ) 101 | ) { request -> 102 | // Implementation tool 103 | } 104 | ``` 105 | 106 | ## Client Integration 107 | 108 | ### Kotlin Client Example 109 | 110 | Since the server uses STDIO for transport, the client typically connects via standard input/output streams. A sample 111 | client implementation can be found in the tests, demonstrating how to send tool requests and process responses. 112 | 113 | ### Claude for Desktop 114 | 115 | To integrate with Claude Desktop, add the following configuration to your Claude Desktop settings: 116 | 117 | ```json 118 | { 119 | "mcpServers": { 120 | "weather": { 121 | "command": "java", 122 | "args": [ 123 | "-jar", 124 | "/absolute/path/to/.jar" 125 | ] 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | > [!NOTE] 132 | > Replace `/absolute/path/to/.jar` with the actual absolute path to your built jar file. 133 | 134 | ## Additional Resources 135 | 136 | - [MCP Specification](https://spec.modelcontextprotocol.io/) 137 | - [Kotlin MCP SDK](https://github.com/modelcontextprotocol/kotlin-sdk) 138 | - [Ktor Client Documentation](https://ktor.io/docs/welcome.html) 139 | - [Kotlinx Serialization](https://kotlinlang.org/docs/serialization.html) 140 | 141 | -------------------------------------------------------------------------------- /samples/weather-stdio-server/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("jvm") version "2.1.10" 3 | kotlin("plugin.serialization") version "2.1.10" 4 | id("com.github.johnrengelman.shadow") version "8.1.1" 5 | application 6 | } 7 | 8 | application { 9 | mainClass.set("io.modelcontextprotocol.sample.server.MainKt") 10 | } 11 | 12 | 13 | group = "org.example" 14 | version = "0.1.0" 15 | 16 | val mcpVersion = "0.5.0" 17 | val slf4jVersion = "2.0.9" 18 | val ktorVersion = "3.1.1" 19 | 20 | dependencies { 21 | implementation("io.modelcontextprotocol:kotlin-sdk:$mcpVersion") 22 | implementation("org.slf4j:slf4j-nop:$slf4jVersion") 23 | implementation("io.ktor:ktor-client-content-negotiation:$ktorVersion") 24 | implementation("io.ktor:ktor-serialization-kotlinx-json:$ktorVersion") 25 | testImplementation(kotlin("test")) 26 | testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.1") 27 | } 28 | 29 | tasks.test { 30 | useJUnitPlatform() 31 | } 32 | 33 | kotlin { 34 | jvmToolchain(17) 35 | } 36 | -------------------------------------------------------------------------------- /samples/weather-stdio-server/gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | -------------------------------------------------------------------------------- /samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelcontextprotocol/kotlin-sdk/0cff2ca71828857f998aa08c06fe73e206e7a8f5/samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /samples/weather-stdio-server/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /samples/weather-stdio-server/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /samples/weather-stdio-server/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "weather-stdio-server" 2 | 3 | plugins { 4 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 5 | } 6 | 7 | dependencyResolutionManagement { 8 | repositories { 9 | mavenCentral() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/McpWeatherServer.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.sample.server 2 | 3 | import io.ktor.client.* 4 | import io.ktor.client.plugins.* 5 | import io.ktor.client.plugins.contentnegotiation.* 6 | import io.ktor.http.* 7 | import io.ktor.serialization.kotlinx.json.* 8 | import io.ktor.utils.io.streams.* 9 | import io.modelcontextprotocol.kotlin.sdk.* 10 | import io.modelcontextprotocol.kotlin.sdk.server.Server 11 | import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions 12 | import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport 13 | import kotlinx.coroutines.Job 14 | import kotlinx.coroutines.runBlocking 15 | import kotlinx.io.asSink 16 | import kotlinx.io.buffered 17 | import kotlinx.serialization.json.* 18 | 19 | // Main function to run the MCP server 20 | fun `run mcp server`() { 21 | // Base URL for the Weather API 22 | val baseUrl = "https://api.weather.gov" 23 | 24 | // Create an HTTP client with a default request configuration and JSON content negotiation 25 | val httpClient = HttpClient { 26 | defaultRequest { 27 | url(baseUrl) 28 | headers { 29 | append("Accept", "application/geo+json") 30 | append("User-Agent", "WeatherApiClient/1.0") 31 | } 32 | contentType(ContentType.Application.Json) 33 | } 34 | // Install content negotiation plugin for JSON serialization/deserialization 35 | install(ContentNegotiation) { 36 | json(Json { 37 | ignoreUnknownKeys = true 38 | prettyPrint = true 39 | }) 40 | } 41 | } 42 | 43 | // Create the MCP Server instance with a basic implementation 44 | val server = Server( 45 | Implementation( 46 | name = "weather", // Tool name is "weather" 47 | version = "1.0.0" // Version of the implementation 48 | ), 49 | ServerOptions( 50 | capabilities = ServerCapabilities(tools = ServerCapabilities.Tools(listChanged = true)) 51 | ) 52 | ) 53 | 54 | // Register a tool to fetch weather alerts by state 55 | server.addTool( 56 | name = "get_alerts", 57 | description = """ 58 | Get weather alerts for a US state. Input is Two-letter US state code (e.g. CA, NY) 59 | """.trimIndent(), 60 | inputSchema = Tool.Input( 61 | properties = buildJsonObject { 62 | putJsonObject("state") { 63 | put("type", "string") 64 | put("description", "Two-letter US state code (e.g. CA, NY)") 65 | } 66 | }, 67 | required = listOf("state") 68 | ) 69 | ) { request -> 70 | val state = request.arguments["state"]?.jsonPrimitive?.content ?: return@addTool CallToolResult( 71 | content = listOf(TextContent("The 'state' parameter is required.")) 72 | ) 73 | 74 | val alerts = httpClient.getAlerts(state) 75 | 76 | CallToolResult(content = alerts.map { TextContent(it) }) 77 | } 78 | 79 | // Register a tool to fetch weather forecast by latitude and longitude 80 | server.addTool( 81 | name = "get_forecast", 82 | description = """ 83 | Get weather forecast for a specific latitude/longitude 84 | """.trimIndent(), 85 | inputSchema = Tool.Input( 86 | properties = buildJsonObject { 87 | putJsonObject("latitude") { 88 | put("type", "number") 89 | } 90 | putJsonObject("longitude") { 91 | put("type", "number") 92 | } 93 | }, 94 | required = listOf("latitude", "longitude") 95 | ) 96 | ) { request -> 97 | val latitude = request.arguments["latitude"]?.jsonPrimitive?.doubleOrNull 98 | val longitude = request.arguments["longitude"]?.jsonPrimitive?.doubleOrNull 99 | if (latitude == null || longitude == null) { 100 | return@addTool CallToolResult( 101 | content = listOf(TextContent("The 'latitude' and 'longitude' parameters are required.")) 102 | ) 103 | } 104 | 105 | val forecast = httpClient.getForecast(latitude, longitude) 106 | 107 | CallToolResult(content = forecast.map { TextContent(it) }) 108 | } 109 | 110 | // Create a transport using standard IO for server communication 111 | val transport = StdioServerTransport( 112 | System.`in`.asInput(), 113 | System.out.asSink().buffered() 114 | ) 115 | 116 | runBlocking { 117 | server.connect(transport) 118 | val done = Job() 119 | server.onClose { 120 | done.complete() 121 | } 122 | done.join() 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/WeatherApi.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.sample.server 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.call.body 5 | import io.ktor.client.request.get 6 | import kotlinx.serialization.Serializable 7 | import kotlinx.serialization.json.JsonObject 8 | 9 | // Extension function to fetch forecast information for given latitude and longitude 10 | suspend fun HttpClient.getForecast(latitude: Double, longitude: Double): List { 11 | // Build the URI using provided latitude and longitude 12 | val uri = "/points/$latitude,$longitude" 13 | // Request the points data from the API 14 | val points = this.get(uri).body() 15 | 16 | // Request the forecast using the URL provided in the points response 17 | val forecast = this.get(points.properties.forecast).body() 18 | 19 | // Map each forecast period to a formatted string 20 | return forecast.properties.periods.map { period -> 21 | """ 22 | ${period.name}: 23 | Temperature: ${period.temperature} ${period.temperatureUnit} 24 | Wind: ${period.windSpeed} ${period.windDirection} 25 | Forecast: ${period.detailedForecast} 26 | """.trimIndent() 27 | } 28 | } 29 | 30 | // Extension function to fetch weather alerts for a given state 31 | suspend fun HttpClient.getAlerts(state: String): List { 32 | // Build the URI using the given state code 33 | val uri = "/alerts/active/area/$state" 34 | // Request the alerts data from the API 35 | val alerts = this.get(uri).body() 36 | 37 | // Map each alert feature to a formatted string 38 | return alerts.features.map { feature -> 39 | """ 40 | Event: ${feature.properties.event} 41 | Area: ${feature.properties.areaDesc} 42 | Severity: ${feature.properties.severity} 43 | Description: ${feature.properties.description} 44 | Instruction: ${feature.properties.instruction} 45 | """.trimIndent() 46 | } 47 | } 48 | 49 | // Data class representing the points response from the API 50 | @Serializable 51 | data class Points( 52 | val properties: Properties 53 | ) { 54 | @Serializable 55 | data class Properties(val forecast: String) 56 | } 57 | 58 | // Data class representing the forecast response from the API 59 | @Serializable 60 | data class Forecast( 61 | val properties: Properties 62 | ) { 63 | @Serializable 64 | data class Properties(val periods: List) 65 | 66 | @Serializable 67 | data class Period( 68 | val number: Int, val name: String, val startTime: String, val endTime: String, 69 | val isDaytime: Boolean, val temperature: Int, val temperatureUnit: String, 70 | val temperatureTrend: String, val probabilityOfPrecipitation: JsonObject, 71 | val windSpeed: String, val windDirection: String, 72 | val shortForecast: String, val detailedForecast: String, 73 | ) 74 | } 75 | 76 | // Data class representing the alerts response from the API 77 | @Serializable 78 | data class Alert( 79 | val features: List 80 | ) { 81 | @Serializable 82 | data class Feature( 83 | val properties: Properties 84 | ) 85 | 86 | @Serializable 87 | data class Properties( 88 | val event: String, val areaDesc: String, val severity: String, 89 | val description: String, val instruction: String?, 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /samples/weather-stdio-server/src/main/kotlin/io/modelcontextprotocol/sample/server/main.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.sample.server 2 | 3 | fun main() = `run mcp server`() -------------------------------------------------------------------------------- /samples/weather-stdio-server/src/test/kotlin/io/modelcontextprotocol/sample/client/ClientStdio.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.sample.client 2 | 3 | import io.modelcontextprotocol.kotlin.sdk.CallToolRequest 4 | import io.modelcontextprotocol.kotlin.sdk.Implementation 5 | import io.modelcontextprotocol.kotlin.sdk.TextContent 6 | import io.modelcontextprotocol.kotlin.sdk.client.Client 7 | import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport 8 | import kotlinx.coroutines.runBlocking 9 | import kotlinx.io.asSink 10 | import kotlinx.io.asSource 11 | import kotlinx.io.buffered 12 | import kotlinx.serialization.json.JsonObject 13 | import kotlinx.serialization.json.JsonPrimitive 14 | 15 | 16 | fun main(): Unit = runBlocking { 17 | val process = ProcessBuilder("java", "-jar", "build/libs/weather-stdio-server-0.1.0-all.jar") 18 | .start() 19 | 20 | val transport = StdioClientTransport( 21 | input = process.inputStream.asSource().buffered(), 22 | output = process.outputStream.asSink().buffered() 23 | ) 24 | 25 | // Initialize the MCP client with client information 26 | val client = Client( 27 | clientInfo = Implementation(name = "weather", version = "1.0.0"), 28 | ) 29 | 30 | client.connect(transport) 31 | 32 | 33 | val toolsList = client.listTools()?.tools?.map { it.name } 34 | println("Available Tools = $toolsList") 35 | 36 | val weatherForecastResult = client.callTool( 37 | CallToolRequest( 38 | name = "get_forecast", 39 | arguments = JsonObject(mapOf("latitude" to JsonPrimitive(38.5816), "longitude" to JsonPrimitive(-121.4944))) 40 | ) 41 | )?.content?.map { if (it is TextContent) it.text else it.toString() } 42 | 43 | println("Weather Forcast: ${weatherForecastResult?.joinToString(separator = "\n", prefix = "\n", postfix = "\n")}") 44 | 45 | val alertResult = 46 | client.callTool( 47 | CallToolRequest( 48 | name = "get_alerts", 49 | arguments = JsonObject(mapOf("state" to JsonPrimitive("TX"))) 50 | ) 51 | )?.content?.map { if (it is TextContent) it.text else it.toString() } 52 | 53 | println("Alert Response = $alertResult") 54 | 55 | client.close() 56 | } -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | gradlePluginPortal() 5 | } 6 | 7 | plugins { 8 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0" 9 | } 10 | } 11 | 12 | dependencyResolutionManagement { 13 | repositories { 14 | mavenCentral() 15 | } 16 | } 17 | 18 | rootProject.name = "kotlin-sdk" 19 | 20 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/KtorClient.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.client 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.request.HttpRequestBuilder 5 | import io.modelcontextprotocol.kotlin.sdk.Implementation 6 | import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION 7 | import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME 8 | import kotlin.time.Duration 9 | 10 | /** 11 | * Returns a new SSE transport for the Model Context Protocol using the provided HttpClient. 12 | * 13 | * @param urlString Optional URL of the MCP server. 14 | * @param reconnectionTime Optional duration to wait before attempting to reconnect. 15 | * @param requestBuilder Optional lambda to configure the HTTP request. 16 | * @return A [SSEClientTransport] configured for MCP communication. 17 | */ 18 | public fun HttpClient.mcpSseTransport( 19 | urlString: String? = null, 20 | reconnectionTime: Duration? = null, 21 | requestBuilder: HttpRequestBuilder.() -> Unit = {}, 22 | ): SseClientTransport = SseClientTransport(this, urlString, reconnectionTime, requestBuilder) 23 | 24 | /** 25 | * Creates and connects an MCP client over SSE using the provided HttpClient. 26 | * 27 | * @param urlString Optional URL of the MCP server. 28 | * @param reconnectionTime Optional duration to wait before attempting to reconnect. 29 | * @param requestBuilder Optional lambda to configure the HTTP request. 30 | * @return A connected [Client] ready for MCP communication. 31 | */ 32 | public suspend fun HttpClient.mcpSse( 33 | urlString: String? = null, 34 | reconnectionTime: Duration? = null, 35 | requestBuilder: HttpRequestBuilder.() -> Unit = {}, 36 | ): Client { 37 | val transport = mcpSseTransport(urlString, reconnectionTime, requestBuilder) 38 | val client = Client( 39 | Implementation( 40 | name = IMPLEMENTATION_NAME, 41 | version = LIB_VERSION 42 | ) 43 | ) 44 | client.connect(transport) 45 | return client 46 | } 47 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketClientTransport.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.client 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.plugins.websocket.webSocketSession 5 | import io.ktor.client.request.HttpRequestBuilder 6 | import io.ktor.client.request.header 7 | import io.ktor.http.HttpHeaders 8 | import io.ktor.websocket.WebSocketSession 9 | import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL 10 | import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport 11 | import kotlin.properties.Delegates 12 | 13 | /** 14 | * Client transport for WebSocket: this will connect to a server over the WebSocket protocol. 15 | */ 16 | public class WebSocketClientTransport( 17 | private val client: HttpClient, 18 | private val urlString: String?, 19 | private val requestBuilder: HttpRequestBuilder.() -> Unit = {}, 20 | ) : WebSocketMcpTransport() { 21 | override var session: WebSocketSession by Delegates.notNull() 22 | 23 | override suspend fun initializeSession() { 24 | session = urlString?.let { 25 | client.webSocketSession(it) { 26 | requestBuilder() 27 | 28 | header(HttpHeaders.SecWebSocketProtocol, MCP_SUBPROTOCOL) 29 | } 30 | } ?: client.webSocketSession { 31 | requestBuilder() 32 | 33 | header(HttpHeaders.SecWebSocketProtocol, MCP_SUBPROTOCOL) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketMcpKtorClientExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.client 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.request.HttpRequestBuilder 5 | import io.modelcontextprotocol.kotlin.sdk.Implementation 6 | import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION 7 | import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME 8 | 9 | /** 10 | * Returns a new WebSocket transport for the Model Context Protocol using the provided HttpClient. 11 | * 12 | * @param urlString Optional URL of the MCP server. 13 | * @param requestBuilder Optional lambda to configure the HTTP request. 14 | * @return A [WebSocketClientTransport] configured for MCP communication. 15 | */ 16 | public fun HttpClient.mcpWebSocketTransport( 17 | urlString: String? = null, 18 | requestBuilder: HttpRequestBuilder.() -> Unit = {}, 19 | ): WebSocketClientTransport = WebSocketClientTransport(this, urlString, requestBuilder) 20 | 21 | /** 22 | * Creates and connects an MCP client over WebSocket using the provided HttpClient. 23 | * 24 | * @param urlString Optional URL of the MCP server. 25 | * @param requestBuilder Optional lambda to configure the HTTP request. 26 | * @return A connected [Client] ready for MCP communication. 27 | */ 28 | public suspend fun HttpClient.mcpWebSocket( 29 | urlString: String? = null, 30 | requestBuilder: HttpRequestBuilder.() -> Unit = {}, 31 | ): Client { 32 | val transport = mcpWebSocketTransport(urlString, requestBuilder) 33 | val client = Client( 34 | Implementation( 35 | name = IMPLEMENTATION_NAME, 36 | version = LIB_VERSION 37 | ) 38 | ) 39 | client.connect(transport) 40 | return client 41 | } 42 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.internal 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | 5 | internal expect val IODispatcher: CoroutineDispatcher -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/KtorServer.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.server 2 | 3 | import io.github.oshai.kotlinlogging.KotlinLogging 4 | import io.ktor.http.HttpStatusCode 5 | import io.ktor.server.application.Application 6 | import io.ktor.server.application.install 7 | import io.ktor.server.response.respond 8 | import io.ktor.server.routing.Routing 9 | import io.ktor.server.routing.RoutingContext 10 | import io.ktor.server.routing.post 11 | import io.ktor.server.routing.route 12 | import io.ktor.server.routing.routing 13 | import io.ktor.server.sse.SSE 14 | import io.ktor.server.sse.ServerSSESession 15 | import io.ktor.server.sse.sse 16 | import io.ktor.util.collections.ConcurrentMap 17 | import io.ktor.utils.io.KtorDsl 18 | 19 | private val logger = KotlinLogging.logger {} 20 | 21 | @KtorDsl 22 | public fun Routing.mcp(path: String, block: () -> Server) { 23 | route(path) { 24 | mcp(block) 25 | } 26 | } 27 | 28 | /** 29 | * Configures the Ktor Application to handle Model Context Protocol (MCP) over Server-Sent Events (SSE). 30 | */ 31 | @KtorDsl 32 | public fun Routing.mcp(block: () -> Server) { 33 | val transports = ConcurrentMap() 34 | 35 | sse { 36 | mcpSseEndpoint("", transports, block) 37 | } 38 | 39 | post { 40 | mcpPostEndpoint(transports) 41 | } 42 | } 43 | 44 | @Suppress("FunctionName") 45 | @Deprecated("Use mcp() instead", ReplaceWith("mcp(block)"), DeprecationLevel.WARNING) 46 | public fun Application.MCP(block: () -> Server) { 47 | mcp(block) 48 | } 49 | 50 | @KtorDsl 51 | public fun Application.mcp(block: () -> Server) { 52 | val transports = ConcurrentMap() 53 | 54 | install(SSE) 55 | 56 | routing { 57 | sse("/sse") { 58 | mcpSseEndpoint("/message", transports, block) 59 | } 60 | 61 | post("/message") { 62 | mcpPostEndpoint(transports) 63 | } 64 | } 65 | } 66 | 67 | private suspend fun ServerSSESession.mcpSseEndpoint( 68 | postEndpoint: String, 69 | transports: ConcurrentMap, 70 | block: () -> Server, 71 | ) { 72 | val transport = mcpSseTransport(postEndpoint, transports) 73 | 74 | val server = block() 75 | 76 | server.onClose { 77 | logger.info { "Server connection closed for sessionId: ${transport.sessionId}" } 78 | transports.remove(transport.sessionId) 79 | } 80 | 81 | server.connect(transport) 82 | logger.debug { "Server connected to transport for sessionId: ${transport.sessionId}" } 83 | } 84 | 85 | internal fun ServerSSESession.mcpSseTransport( 86 | postEndpoint: String, 87 | transports: ConcurrentMap, 88 | ): SseServerTransport { 89 | val transport = SseServerTransport(postEndpoint, this) 90 | transports[transport.sessionId] = transport 91 | 92 | logger.info { "New SSE connection established and stored with sessionId: ${transport.sessionId}" } 93 | 94 | return transport 95 | } 96 | 97 | internal suspend fun RoutingContext.mcpPostEndpoint( 98 | transports: ConcurrentMap, 99 | ) { 100 | val sessionId: String = call.request.queryParameters["sessionId"] 101 | ?: run { 102 | call.respond(HttpStatusCode.BadRequest, "sessionId query parameter is not provided") 103 | return 104 | } 105 | 106 | logger.debug { "Received message for sessionId: $sessionId" } 107 | 108 | val transport = transports[sessionId] 109 | if (transport == null) { 110 | logger.warn { "Session not found for sessionId: $sessionId" } 111 | call.respond(HttpStatusCode.NotFound, "Session not found") 112 | return 113 | } 114 | 115 | transport.handlePostMessage(call) 116 | logger.trace { "Message handled for sessionId: $sessionId" } 117 | } 118 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/SSEServerTransport.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.server 2 | 3 | import io.ktor.http.ContentType 4 | import io.ktor.http.HttpStatusCode 5 | import io.ktor.http.encodeURLPath 6 | import io.ktor.server.application.ApplicationCall 7 | import io.ktor.server.request.contentType 8 | import io.ktor.server.request.receiveText 9 | import io.ktor.server.response.respondText 10 | import io.ktor.server.sse.ServerSSESession 11 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 12 | import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport 13 | import io.modelcontextprotocol.kotlin.sdk.shared.McpJson 14 | import kotlinx.coroutines.job 15 | import kotlinx.serialization.encodeToString 16 | import kotlin.concurrent.atomics.AtomicBoolean 17 | import kotlin.concurrent.atomics.ExperimentalAtomicApi 18 | import kotlin.uuid.ExperimentalUuidApi 19 | import kotlin.uuid.Uuid 20 | 21 | internal const val SESSION_ID_PARAM = "sessionId" 22 | 23 | @Deprecated("Use SseServerTransport instead", ReplaceWith("SseServerTransport"), DeprecationLevel.WARNING) 24 | public typealias SSEServerTransport = SseServerTransport 25 | 26 | /** 27 | * Server transport for SSE: this will send messages over an SSE connection and receive messages from HTTP POST requests. 28 | * 29 | * Creates a new SSE server transport, which will direct the client to POST messages to the relative or absolute URL identified by `_endpoint`. 30 | */ 31 | @OptIn(ExperimentalAtomicApi::class) 32 | public class SseServerTransport( 33 | private val endpoint: String, 34 | private val session: ServerSSESession, 35 | ) : AbstractTransport() { 36 | private val initialized: AtomicBoolean = AtomicBoolean(false) 37 | 38 | @OptIn(ExperimentalUuidApi::class) 39 | public val sessionId: String = Uuid.random().toString() 40 | 41 | /** 42 | * Handles the initial SSE connection request. 43 | * 44 | * This should be called when a GET request is made to establish the SSE stream. 45 | */ 46 | override suspend fun start() { 47 | if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { 48 | error("SSEServerTransport already started! If using Server class, note that connect() calls start() automatically.") 49 | } 50 | 51 | // Send the endpoint event 52 | session.send( 53 | event = "endpoint", 54 | data = "${endpoint.encodeURLPath()}?$SESSION_ID_PARAM=${sessionId}", 55 | ) 56 | 57 | try { 58 | session.coroutineContext.job.join() 59 | } finally { 60 | _onClose.invoke() 61 | } 62 | } 63 | 64 | /** 65 | * Handles incoming POST messages. 66 | * 67 | * This should be called when a POST request is made to send a message to the server. 68 | */ 69 | public suspend fun handlePostMessage(call: ApplicationCall) { 70 | if (!initialized.load()) { 71 | val message = "SSE connection not established" 72 | call.respondText(message, status = HttpStatusCode.InternalServerError) 73 | _onError.invoke(IllegalStateException(message)) 74 | } 75 | 76 | val body = try { 77 | val ct = call.request.contentType() 78 | if (ct != ContentType.Application.Json) { 79 | error("Unsupported content-type: $ct") 80 | } 81 | 82 | call.receiveText() 83 | } catch (e: Exception) { 84 | call.respondText("Invalid message: ${e.message}", status = HttpStatusCode.BadRequest) 85 | _onError.invoke(e) 86 | return 87 | } 88 | 89 | try { 90 | handleMessage(body) 91 | } catch (e: Exception) { 92 | call.respondText("Error handling message $body: ${e.message}", status = HttpStatusCode.BadRequest) 93 | return 94 | } 95 | 96 | call.respondText("Accepted", status = HttpStatusCode.Accepted) 97 | } 98 | 99 | /** 100 | * Handle a client message, regardless of how it arrived. 101 | * This can be used to inform the server of messages that arrive via a means different from HTTP POST. 102 | */ 103 | public suspend fun handleMessage(message: String) { 104 | try { 105 | val parsedMessage = McpJson.decodeFromString(message) 106 | _onMessage.invoke(parsedMessage) 107 | } catch (e: Exception) { 108 | _onError.invoke(e) 109 | throw e 110 | } 111 | } 112 | 113 | override suspend fun close() { 114 | session.close() 115 | _onClose.invoke() 116 | } 117 | 118 | override suspend fun send(message: JSONRPCMessage) { 119 | if (!initialized.load()) { 120 | error("Not connected") 121 | } 122 | 123 | session.send( 124 | event = "message", 125 | data = McpJson.encodeToString(message), 126 | ) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpKtorServerExtensions.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.server 2 | 3 | import io.ktor.server.routing.Route 4 | import io.ktor.server.websocket.WebSocketServerSession 5 | import io.ktor.server.websocket.webSocket 6 | import io.modelcontextprotocol.kotlin.sdk.Implementation 7 | import io.modelcontextprotocol.kotlin.sdk.LIB_VERSION 8 | import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities 9 | import io.modelcontextprotocol.kotlin.sdk.shared.IMPLEMENTATION_NAME 10 | 11 | /** 12 | * Registers a WebSocket route that establishes an MCP (Model Context Protocol) server session. 13 | * 14 | * @param options Optional server configuration settings for the MCP server. 15 | * @param handler A suspend function that defines the server's behavior. 16 | */ 17 | public fun Route.mcpWebSocket( 18 | options: ServerOptions? = null, 19 | handler: suspend Server.() -> Unit = {}, 20 | ) { 21 | webSocket { 22 | createMcpServer(this, options, handler) 23 | } 24 | } 25 | 26 | /** 27 | * Registers a WebSocket route at the specified [path] that establishes an MCP server session. 28 | * 29 | * @param path The URL path at which to register the WebSocket route. 30 | * @param options Optional server configuration settings for the MCP server. 31 | * @param handler A suspend function that defines the server's behavior. 32 | */ 33 | public fun Route.mcpWebSocket( 34 | path: String, 35 | options: ServerOptions? = null, 36 | handler: suspend Server.() -> Unit = {}, 37 | ) { 38 | webSocket(path) { 39 | createMcpServer(this, options, handler) 40 | } 41 | } 42 | 43 | /** 44 | * Registers a WebSocket route that creates an MCP server transport layer. 45 | * 46 | * @param handler A suspend function that defines the behavior of the transport layer. 47 | */ 48 | public fun Route.mcpWebSocketTransport( 49 | handler: suspend WebSocketMcpServerTransport.() -> Unit = {}, 50 | ) { 51 | webSocket { 52 | val transport = createMcpTransport(this) 53 | transport.start() 54 | handler(transport) 55 | transport.close() 56 | } 57 | } 58 | 59 | /** 60 | * Registers a WebSocket route at the specified [path] that creates an MCP server transport layer. 61 | * 62 | * @param path The URL path at which to register the WebSocket route. 63 | * @param handler A suspend function that defines the behavior of the transport layer. 64 | */ 65 | public fun Route.mcpWebSocketTransport( 66 | path: String, 67 | handler: suspend WebSocketMcpServerTransport.() -> Unit = {}, 68 | ) { 69 | webSocket(path) { 70 | val transport = createMcpTransport(this) 71 | transport.start() 72 | handler(transport) 73 | transport.close() 74 | } 75 | } 76 | 77 | 78 | private suspend fun Route.createMcpServer( 79 | session: WebSocketServerSession, 80 | options: ServerOptions?, 81 | handler: suspend Server.() -> Unit, 82 | ) { 83 | val transport = createMcpTransport(session) 84 | 85 | val server = Server( 86 | serverInfo = Implementation( 87 | name = IMPLEMENTATION_NAME, 88 | version = LIB_VERSION 89 | ), 90 | options = options ?: ServerOptions( 91 | capabilities = ServerCapabilities( 92 | prompts = ServerCapabilities.Prompts(listChanged = null), 93 | resources = ServerCapabilities.Resources(subscribe = null, listChanged = null), 94 | tools = ServerCapabilities.Tools(listChanged = null), 95 | ) 96 | ), 97 | ) 98 | 99 | server.connect(transport) 100 | handler(server) 101 | server.close() 102 | } 103 | 104 | private fun createMcpTransport( 105 | session: WebSocketServerSession, 106 | ): WebSocketMcpServerTransport { 107 | return WebSocketMcpServerTransport(session) 108 | } 109 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/server/WebSocketMcpServerTransport.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.server 2 | 3 | import io.ktor.http.HttpHeaders 4 | import io.ktor.server.websocket.WebSocketServerSession 5 | import io.modelcontextprotocol.kotlin.sdk.shared.MCP_SUBPROTOCOL 6 | import io.modelcontextprotocol.kotlin.sdk.shared.WebSocketMcpTransport 7 | 8 | /** 9 | * Server-side implementation of the MCP (Model Context Protocol) transport over WebSocket. 10 | * 11 | * @property session The WebSocket server session used for communication. 12 | */ 13 | public class WebSocketMcpServerTransport( 14 | override val session: WebSocketServerSession, 15 | ) : WebSocketMcpTransport() { 16 | override suspend fun initializeSession() { 17 | val subprotocol = session.call.request.headers[HttpHeaders.SecWebSocketProtocol] 18 | if (subprotocol != MCP_SUBPROTOCOL) { 19 | error("Invalid subprotocol: $subprotocol, expected $MCP_SUBPROTOCOL") 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBuffer.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.shared 2 | 3 | import io.ktor.utils.io.core.writeFully 4 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 5 | import kotlinx.io.Buffer 6 | import kotlinx.io.indexOf 7 | import kotlinx.io.readString 8 | import kotlinx.serialization.encodeToString 9 | 10 | /** 11 | * Buffers a continuous stdio stream into discrete JSON-RPC messages. 12 | */ 13 | public class ReadBuffer { 14 | private val buffer: Buffer = Buffer() 15 | 16 | public fun append(chunk: ByteArray) { 17 | buffer.writeFully(chunk) 18 | } 19 | 20 | public fun readMessage(): JSONRPCMessage? { 21 | if (buffer.exhausted()) return null 22 | var lfIndex = buffer.indexOf('\n'.code.toByte()) 23 | val line = when (lfIndex) { 24 | -1L -> return null 25 | 0L -> { 26 | buffer.skip(1) 27 | return null 28 | } 29 | 30 | else -> { 31 | var skipBytes = 1 32 | if (buffer[lfIndex - 1] == '\r'.code.toByte()) { 33 | lfIndex -= 1 34 | skipBytes += 1 35 | } 36 | val string = buffer.readString(lfIndex) 37 | buffer.skip(skipBytes.toLong()) 38 | string 39 | } 40 | } 41 | return deserializeMessage(line) 42 | } 43 | 44 | public fun clear() { 45 | buffer.clear() 46 | } 47 | } 48 | 49 | internal fun deserializeMessage(line: String): JSONRPCMessage { 50 | return McpJson.decodeFromString(line) 51 | } 52 | 53 | internal fun serializeMessage(message: JSONRPCMessage): String { 54 | return McpJson.encodeToString(message) + "\n" 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/Transport.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.shared 2 | 3 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 4 | import kotlinx.coroutines.CompletableDeferred 5 | 6 | /** 7 | * Describes the minimal contract for MCP transport that a client or server can communicate over. 8 | */ 9 | public interface Transport { 10 | /** 11 | * Starts processing messages on the transport, including any connection steps that might need to be taken. 12 | * 13 | * This method should only be called after callbacks are installed, or else messages may be lost. 14 | * 15 | * NOTE: This method should not be called explicitly when using Client, Server, or Protocol classes, 16 | * as they will implicitly call start(). 17 | */ 18 | public suspend fun start() 19 | 20 | /** 21 | * Sends a JSON-RPC message (request or response). 22 | */ 23 | public suspend fun send(message: JSONRPCMessage) 24 | 25 | /** 26 | * Closes the connection. 27 | */ 28 | public suspend fun close() 29 | 30 | /** 31 | * Callback for when the connection is closed for any reason. 32 | * 33 | * This should be invoked when close() is called as well. 34 | */ 35 | public fun onClose(block: () -> Unit) 36 | 37 | /** 38 | * Callback for when an error occurs. 39 | * 40 | * Note that errors are not necessarily fatal; they are used for reporting any kind of 41 | * exceptional condition out of a band. 42 | */ 43 | public fun onError(block: (Throwable) -> Unit) 44 | 45 | /** 46 | * Callback for when a message (request or response) is received over the connection. 47 | */ 48 | public fun onMessage(block: suspend (JSONRPCMessage) -> Unit) 49 | } 50 | 51 | /** 52 | * Implements [onClose], [onError] and [onMessage] functions of [Transport] providing 53 | * corresponding [_onClose], [_onError] and [_onMessage] properties to use for an implementation. 54 | */ 55 | @Suppress("PropertyName") 56 | public abstract class AbstractTransport : Transport { 57 | protected var _onClose: (() -> Unit) = {} 58 | private set 59 | protected var _onError: ((Throwable) -> Unit) = {} 60 | private set 61 | 62 | // to not skip messages 63 | private val _onMessageInitialized = CompletableDeferred() 64 | protected var _onMessage: (suspend ((JSONRPCMessage) -> Unit)) = { 65 | _onMessageInitialized.await() 66 | _onMessage.invoke(it) 67 | } 68 | private set 69 | 70 | override fun onClose(block: () -> Unit) { 71 | val old = _onClose 72 | _onClose = { 73 | old() 74 | block() 75 | } 76 | } 77 | 78 | override fun onError(block: (Throwable) -> Unit) { 79 | val old = _onError 80 | _onError = { e -> 81 | old(e) 82 | block(e) 83 | } 84 | } 85 | 86 | override fun onMessage(block: suspend (JSONRPCMessage) -> Unit) { 87 | val old: suspend (JSONRPCMessage) -> Unit = when (_onMessageInitialized.isCompleted) { 88 | true -> _onMessage 89 | false -> { _ -> } 90 | } 91 | 92 | _onMessage = { message -> 93 | old(message) 94 | block(message) 95 | } 96 | 97 | _onMessageInitialized.complete(Unit) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/commonMain/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/WebSocketMcpTransport.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.shared 2 | 3 | import io.ktor.websocket.Frame 4 | import io.ktor.websocket.WebSocketSession 5 | import io.ktor.websocket.close 6 | import io.ktor.websocket.readText 7 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 8 | import kotlinx.coroutines.CoroutineName 9 | import kotlinx.coroutines.CoroutineScope 10 | import kotlinx.coroutines.InternalCoroutinesApi 11 | import kotlinx.coroutines.SupervisorJob 12 | import kotlinx.coroutines.channels.ClosedReceiveChannelException 13 | import kotlinx.coroutines.job 14 | import kotlinx.coroutines.launch 15 | import kotlinx.serialization.encodeToString 16 | import kotlin.concurrent.atomics.AtomicBoolean 17 | import kotlin.concurrent.atomics.ExperimentalAtomicApi 18 | 19 | internal const val MCP_SUBPROTOCOL = "mcp" 20 | 21 | /** 22 | * Abstract class representing a WebSocket transport for the Model Context Protocol (MCP). 23 | * Handles communication over a WebSocket session. 24 | */ 25 | @OptIn(ExperimentalAtomicApi::class) 26 | public abstract class WebSocketMcpTransport : AbstractTransport() { 27 | private val scope by lazy { 28 | CoroutineScope(session.coroutineContext + SupervisorJob()) 29 | } 30 | 31 | private val initialized: AtomicBoolean = AtomicBoolean(false) 32 | 33 | /** 34 | * The WebSocket session used for communication. 35 | */ 36 | protected abstract val session: WebSocketSession 37 | 38 | /** 39 | * Initializes the WebSocket session 40 | */ 41 | protected abstract suspend fun initializeSession() 42 | 43 | override suspend fun start() { 44 | if (!initialized.compareAndSet(expectedValue = false, newValue = true)) { 45 | error( 46 | "WebSocketClientTransport already started! " + 47 | "If using Client class, note that connect() calls start() automatically.", 48 | ) 49 | } 50 | 51 | initializeSession() 52 | 53 | scope.launch(CoroutineName("WebSocketMcpTransport.collect#${hashCode()}")) { 54 | while (true) { 55 | val message = try { 56 | session.incoming.receive() 57 | } catch (_: ClosedReceiveChannelException) { 58 | return@launch 59 | } 60 | 61 | if (message !is Frame.Text) { 62 | val e = IllegalArgumentException("Expected text frame, got ${message::class.simpleName}: $message") 63 | _onError.invoke(e) 64 | throw e 65 | } 66 | 67 | try { 68 | val message = McpJson.decodeFromString(message.readText()) 69 | _onMessage.invoke(message) 70 | } catch (e: Exception) { 71 | _onError.invoke(e) 72 | throw e 73 | } 74 | } 75 | } 76 | 77 | @OptIn(InternalCoroutinesApi::class) 78 | session.coroutineContext.job.invokeOnCompletion { 79 | if (it != null) { 80 | _onError.invoke(it) 81 | } else { 82 | _onClose.invoke() 83 | } 84 | } 85 | } 86 | 87 | override suspend fun send(message: JSONRPCMessage) { 88 | if (!initialized.load()) { 89 | error("Not connected") 90 | } 91 | 92 | session.outgoing.send(Frame.Text(McpJson.encodeToString(message))) 93 | } 94 | 95 | override suspend fun close() { 96 | if (!initialized.load()) { 97 | error("Not connected") 98 | } 99 | 100 | session.close() 101 | session.coroutineContext.job.join() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/AudioContentSerializationTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk 2 | 3 | import io.kotest.assertions.json.shouldEqualJson 4 | import io.modelcontextprotocol.kotlin.sdk.shared.McpJson 5 | import kotlinx.serialization.encodeToString 6 | import kotlin.test.Test 7 | import kotlin.test.assertEquals 8 | 9 | class AudioContentSerializationTest { 10 | 11 | private val audioContentJson = """ 12 | { 13 | "data": "base64-encoded-audio-data", 14 | "mimeType": "audio/wav", 15 | "type": "audio" 16 | } 17 | """.trimIndent() 18 | 19 | private val audioContent = AudioContent( 20 | data = "base64-encoded-audio-data", 21 | mimeType = "audio/wav" 22 | ) 23 | 24 | @Test 25 | fun `should serialize audio content`() { 26 | McpJson.encodeToString(audioContent) shouldEqualJson audioContentJson 27 | } 28 | 29 | @Test 30 | fun `should deserialize audio content`() { 31 | val content = McpJson.decodeFromString(audioContentJson) 32 | assertEquals(expected = audioContent, actual = content) 33 | } 34 | } -------------------------------------------------------------------------------- /src/commonTest/kotlin/CallToolResultUtilsTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk 2 | 3 | import kotlinx.serialization.json.JsonPrimitive 4 | import kotlinx.serialization.json.buildJsonObject 5 | import kotlin.test.Test 6 | import kotlin.test.assertEquals 7 | import kotlin.test.assertFalse 8 | import kotlin.test.assertTrue 9 | 10 | class CallToolResultUtilsTest { 11 | 12 | @Test 13 | fun testOkWithOnlyText() { 14 | val content = "TextMessage" 15 | val result = CallToolResult.Companion.ok(content) 16 | 17 | assertEquals(1, result.content.size) 18 | assertEquals(content, (result.content[0] as TextContent).text) 19 | assertFalse(result.isError == true) 20 | assertEquals(EmptyJsonObject, result._meta) 21 | } 22 | 23 | @Test 24 | fun testOkWithMeta() { 25 | val content = "TextMessageWithMeta" 26 | val meta = buildJsonObject { 27 | put("key1", JsonPrimitive("value1")) 28 | put("key2", JsonPrimitive(42)) 29 | } 30 | val result = CallToolResult.Companion.ok(content, meta) 31 | 32 | assertEquals(1, result.content.size) 33 | assertEquals(content, (result.content[0] as TextContent).text) 34 | assertFalse(result.isError == true) 35 | assertEquals(meta, result._meta) 36 | } 37 | 38 | @Test 39 | fun testErrorWithOnlyText() { 40 | val content = "ErrorMessage" 41 | val result = CallToolResult.Companion.error(content) 42 | 43 | assertEquals(1, result.content.size) 44 | assertEquals(content, (result.content[0] as TextContent).text) 45 | assertTrue(result.isError == true) 46 | assertEquals(EmptyJsonObject, result._meta) 47 | } 48 | 49 | @Test 50 | fun testErrorWithMeta() { 51 | val content = "ErrorMessageWithMeta" 52 | val meta = buildJsonObject { 53 | put("errorCode", JsonPrimitive(404)) 54 | put("errorDetail", JsonPrimitive("资源未找到")) 55 | } 56 | val result = CallToolResult.Companion.error(content, meta) 57 | 58 | assertEquals(1, result.content.size) 59 | assertEquals(content, (result.content[0] as TextContent).text) 60 | assertTrue(result.isError == true) 61 | assertEquals(meta, result._meta) 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/InMemoryTransport.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk 2 | 3 | import io.modelcontextprotocol.kotlin.sdk.shared.AbstractTransport 4 | 5 | /** 6 | * In-memory transport for creating clients and servers that talk to each other within the same process. 7 | */ 8 | class InMemoryTransport : AbstractTransport() { 9 | private var otherTransport: InMemoryTransport? = null 10 | private val messageQueue: MutableList = mutableListOf() 11 | 12 | /** 13 | * Creates a pair of linked in-memory transports that can communicate with each other. 14 | * One should be passed to a Client and one to a Server. 15 | */ 16 | companion object { 17 | fun createLinkedPair(): Pair { 18 | val clientTransport = InMemoryTransport() 19 | val serverTransport = InMemoryTransport() 20 | clientTransport.otherTransport = serverTransport 21 | serverTransport.otherTransport = clientTransport 22 | return Pair(clientTransport, serverTransport) 23 | } 24 | } 25 | 26 | override suspend fun start() { 27 | // Process any messages that were queued before start was called 28 | while (messageQueue.isNotEmpty()) { 29 | messageQueue.removeFirstOrNull()?.let { message -> 30 | _onMessage.invoke(message) // todo? 31 | } 32 | } 33 | } 34 | 35 | override suspend fun close() { 36 | val other = otherTransport 37 | otherTransport = null 38 | other?.close() 39 | _onClose.invoke() 40 | } 41 | 42 | override suspend fun send(message: JSONRPCMessage) { 43 | val other = otherTransport ?: throw IllegalStateException("Not connected") 44 | 45 | other._onMessage.invoke(message) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/ToolSerializationTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk 2 | 3 | import io.kotest.assertions.json.shouldEqualJson 4 | import io.modelcontextprotocol.kotlin.sdk.shared.McpJson 5 | import kotlinx.serialization.encodeToString 6 | import kotlinx.serialization.json.Json 7 | import kotlinx.serialization.json.JsonPrimitive 8 | import kotlinx.serialization.json.buildJsonObject 9 | import kotlin.test.Test 10 | import kotlin.test.assertEquals 11 | 12 | class ToolSerializationTest { 13 | 14 | // see https://docs.anthropic.com/en/docs/build-with-claude/tool-use 15 | /* language=json */ 16 | private val getWeatherToolJson = """ 17 | { 18 | "name": "get_weather", 19 | "description": "Get the current weather in a given location", 20 | "inputSchema": { 21 | "type": "object", 22 | "properties": { 23 | "location": { 24 | "type": "string", 25 | "description": "The city and state, e.g. San Francisco, CA" 26 | } 27 | }, 28 | "required": ["location"] 29 | } 30 | } 31 | """.trimIndent() 32 | 33 | val getWeatherTool = Tool( 34 | name = "get_weather", 35 | description = "Get the current weather in a given location", 36 | inputSchema = Tool.Input( 37 | properties = buildJsonObject { 38 | put("location", buildJsonObject { 39 | put("type", JsonPrimitive("string")) 40 | put("description", JsonPrimitive("The city and state, e.g. San Francisco, CA")) 41 | }) 42 | }, 43 | required = listOf("location") 44 | ) 45 | ) 46 | 47 | @Test 48 | fun `should serialize get_weather tool`() { 49 | McpJson.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson 50 | } 51 | 52 | @Test 53 | fun `should deserialize get_weather tool`() { 54 | val tool = McpJson.decodeFromString(getWeatherToolJson) 55 | assertEquals(expected = getWeatherTool, actual = tool) 56 | } 57 | 58 | @Test 59 | fun `should always serialize default value`() { 60 | val json = Json(from = McpJson) { 61 | encodeDefaults = false 62 | } 63 | json.encodeToString(getWeatherTool) shouldEqualJson getWeatherToolJson 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/BaseTransportTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.client 2 | 3 | import io.modelcontextprotocol.kotlin.sdk.InitializedNotification 4 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 5 | import io.modelcontextprotocol.kotlin.sdk.PingRequest 6 | import io.modelcontextprotocol.kotlin.sdk.shared.Transport 7 | import io.modelcontextprotocol.kotlin.sdk.toJSON 8 | import kotlinx.coroutines.CompletableDeferred 9 | import kotlin.test.assertEquals 10 | import kotlin.test.assertFalse 11 | import kotlin.test.assertTrue 12 | import kotlin.test.fail 13 | 14 | abstract class BaseTransportTest { 15 | protected suspend fun testClientOpenClose(client: Transport) { 16 | client.onError { error -> 17 | fail("Unexpected error: $error") 18 | } 19 | 20 | var didClose = false 21 | client.onClose { didClose = true } 22 | 23 | client.start() 24 | assertFalse(didClose, "Transport should not be closed immediately after start") 25 | 26 | client.close() 27 | assertTrue(didClose, "Transport should be closed after close() call") 28 | } 29 | 30 | protected suspend fun testClientRead(client: Transport) { 31 | client.onError { error -> 32 | error.printStackTrace() 33 | fail("Unexpected error: $error") 34 | } 35 | 36 | val messages = listOf( 37 | PingRequest().toJSON(), InitializedNotification().toJSON() 38 | ) 39 | 40 | val readMessages = mutableListOf() 41 | val finished = CompletableDeferred() 42 | 43 | client.onMessage { message -> 44 | readMessages.add(message) 45 | if (message == messages.last()) { 46 | finished.complete(Unit) 47 | } 48 | } 49 | 50 | client.start() 51 | 52 | for (message in messages) { 53 | client.send(message) 54 | } 55 | 56 | finished.await() 57 | 58 | assertEquals(messages, readMessages, "Assert messages received") 59 | 60 | client.close() 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/InMemoryTransportTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.client 2 | 3 | import io.modelcontextprotocol.kotlin.sdk.InMemoryTransport 4 | import io.modelcontextprotocol.kotlin.sdk.InitializedNotification 5 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 6 | import io.modelcontextprotocol.kotlin.sdk.toJSON 7 | import kotlinx.coroutines.test.runTest 8 | import kotlin.test.BeforeTest 9 | import kotlin.test.Test 10 | import kotlin.test.assertEquals 11 | import kotlin.test.assertFailsWith 12 | import kotlin.test.assertNotNull 13 | import kotlin.test.assertTrue 14 | 15 | class InMemoryTransportTest { 16 | private lateinit var clientTransport: InMemoryTransport 17 | private lateinit var serverTransport: InMemoryTransport 18 | 19 | @BeforeTest 20 | fun setUp() { 21 | val (client, server) = InMemoryTransport.createLinkedPair() 22 | clientTransport = client 23 | serverTransport = server 24 | } 25 | 26 | @Test 27 | fun `should create linked pair`() { 28 | assertNotNull(clientTransport) 29 | assertNotNull(serverTransport) 30 | } 31 | 32 | @Test 33 | fun `should start without error`() = runTest { 34 | clientTransport.start() 35 | serverTransport.start() 36 | // If no exception is thrown, the test passes 37 | } 38 | 39 | @Test 40 | fun `should send message from client to server`() = runTest { 41 | val message = InitializedNotification() 42 | 43 | var receivedMessage: JSONRPCMessage? = null 44 | serverTransport.onMessage { msg -> 45 | receivedMessage = msg 46 | } 47 | 48 | val rpcNotification = message.toJSON() 49 | clientTransport.send(rpcNotification) 50 | assertEquals(rpcNotification, receivedMessage) 51 | } 52 | 53 | @Test 54 | fun `should send message from server to client`() = runTest { 55 | val message = InitializedNotification() 56 | .toJSON() 57 | 58 | var receivedMessage: JSONRPCMessage? = null 59 | clientTransport.onMessage { msg -> 60 | receivedMessage = msg 61 | } 62 | 63 | serverTransport.send(message) 64 | assertEquals(message, receivedMessage) 65 | } 66 | 67 | @Test 68 | fun `should handle close`() = runTest { 69 | var clientClosed = false 70 | var serverClosed = false 71 | 72 | clientTransport.onClose { 73 | clientClosed = true 74 | } 75 | 76 | serverTransport.onClose { 77 | serverClosed = true 78 | } 79 | 80 | clientTransport.close() 81 | assertTrue(clientClosed) 82 | assertTrue(serverClosed) 83 | } 84 | 85 | @Test 86 | fun `should throw error when sending after close`() = runTest { 87 | clientTransport.close() 88 | 89 | assertFailsWith { 90 | clientTransport.send( 91 | InitializedNotification().toJSON() 92 | ) 93 | } 94 | } 95 | 96 | @Test 97 | fun `should queue messages sent before start`() = runTest { 98 | val message = InitializedNotification() 99 | .toJSON() 100 | 101 | var receivedMessage: JSONRPCMessage? = null 102 | serverTransport.onMessage { msg -> 103 | receivedMessage = msg 104 | } 105 | 106 | clientTransport.send(message) 107 | serverTransport.start() 108 | assertEquals(message, receivedMessage) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/SseTransportTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.client 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.plugins.sse.SSE 5 | import io.ktor.server.application.install 6 | import io.ktor.server.cio.CIO 7 | import io.ktor.server.engine.embeddedServer 8 | import io.ktor.server.routing.post 9 | import io.ktor.server.routing.routing 10 | import io.ktor.server.sse.sse 11 | import io.ktor.util.collections.ConcurrentMap 12 | import io.modelcontextprotocol.kotlin.sdk.server.SseServerTransport 13 | import io.modelcontextprotocol.kotlin.sdk.server.mcpPostEndpoint 14 | import io.modelcontextprotocol.kotlin.sdk.server.mcpSseTransport 15 | import kotlinx.coroutines.test.runTest 16 | import kotlin.test.Test 17 | 18 | class SseTransportTest : BaseTransportTest() { 19 | @Test 20 | fun `should start then close cleanly`() = runTest { 21 | val port = 8080 22 | val server = embeddedServer(CIO, port = port) { 23 | install(io.ktor.server.sse.SSE) 24 | val transports = ConcurrentMap() 25 | routing { 26 | sse { 27 | mcpSseTransport("", transports).start() 28 | } 29 | 30 | post { 31 | mcpPostEndpoint(transports) 32 | } 33 | } 34 | }.startSuspend(wait = false) 35 | 36 | val client = HttpClient { 37 | install(SSE) 38 | }.mcpSseTransport { 39 | url { 40 | host = "localhost" 41 | this.port = port 42 | } 43 | } 44 | 45 | testClientOpenClose(client) 46 | 47 | server.stopSuspend() 48 | } 49 | 50 | @Test 51 | fun `should read messages`() = runTest { 52 | val port = 3003 53 | val server = embeddedServer(CIO, port = port) { 54 | install(io.ktor.server.sse.SSE) 55 | val transports = ConcurrentMap() 56 | routing { 57 | sse { 58 | mcpSseTransport("", transports).apply { 59 | onMessage { 60 | send(it) 61 | } 62 | 63 | start() 64 | } 65 | } 66 | 67 | post { 68 | mcpPostEndpoint(transports) 69 | } 70 | } 71 | }.startSuspend(wait = false) 72 | 73 | val client = HttpClient { 74 | install(SSE) 75 | }.mcpSseTransport { 76 | url { 77 | host = "localhost" 78 | this.port = port 79 | } 80 | } 81 | 82 | testClientRead(client) 83 | server.stopSuspend() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/TypesTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.client 2 | 3 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 4 | import io.modelcontextprotocol.kotlin.sdk.Request 5 | import io.modelcontextprotocol.kotlin.sdk.shared.McpJson 6 | import kotlin.test.Test 7 | 8 | class TypesTest { 9 | 10 | @Test 11 | fun testRequestResult() { 12 | val message = 13 | "{\"result\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{\"listChanged\":true},\"resources\":{}},\"serverInfo\":{\"name\":\"jetbrains/proxy\",\"version\":\"0.1.0\"}},\"jsonrpc\":\"2.0\",\"id\":1}" 14 | McpJson.decodeFromString(message) 15 | } 16 | 17 | @Test 18 | fun testRequestError() { 19 | val message = 20 | "{\"method\":\"initialize\", \"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"experimental\":{},\"sampling\":{}},\"clientInfo\":{\"name\":\"test client\",\"version\":\"1.0\"},\"_meta\":{}}" 21 | McpJson.decodeFromString(message) 22 | } 23 | 24 | @Test 25 | fun testJSONRPCMessage() { 26 | val line = "{\"result\":{\"content\":[{\"type\":\"text\"}],\"isError\":false},\"jsonrpc\":\"2.0\",\"id\":4}" 27 | McpJson.decodeFromString(line) 28 | } 29 | 30 | @Test 31 | fun testJSONRPCMessageWithStringId() { 32 | val line = """ 33 | { 34 | "jsonrpc": "2.0", 35 | "method": "initialize", 36 | "id": "ebf9f64a-0", 37 | "params": { 38 | "protocolVersion": "2024-11-05", 39 | "capabilities": {}, 40 | "clientInfo": { 41 | "name": "mcp-java-client", 42 | "version": "0.2.0" 43 | } 44 | } 45 | } 46 | """.trimIndent() 47 | McpJson.decodeFromString(line) 48 | } 49 | } -------------------------------------------------------------------------------- /src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/client/WebSocketTransportTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.client 2 | 3 | import io.ktor.server.testing.testApplication 4 | import io.ktor.server.websocket.WebSockets 5 | import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocket 6 | import io.modelcontextprotocol.kotlin.sdk.server.mcpWebSocketTransport 7 | import kotlinx.coroutines.CompletableDeferred 8 | import kotlin.test.Ignore 9 | import kotlin.test.Test 10 | 11 | class WebSocketTransportTest : BaseTransportTest() { 12 | @Test 13 | @Ignore // "Test disabled for investigation #17" 14 | fun `should start then close cleanly`() = testApplication { 15 | install(WebSockets) 16 | routing { 17 | mcpWebSocket() 18 | } 19 | 20 | val client = createClient { 21 | install(io.ktor.client.plugins.websocket.WebSockets) 22 | }.mcpWebSocketTransport() 23 | 24 | testClientOpenClose(client) 25 | } 26 | 27 | @Test 28 | @Ignore // "Test disabled for investigation #17" 29 | fun `should read messages`() = testApplication { 30 | val clientFinished = CompletableDeferred() 31 | 32 | install(WebSockets) 33 | routing { 34 | mcpWebSocketTransport { 35 | onMessage { 36 | send(it) 37 | } 38 | 39 | clientFinished.await() 40 | } 41 | } 42 | 43 | val client = createClient { 44 | install(io.ktor.client.plugins.websocket.WebSockets) 45 | }.mcpWebSocketTransport() 46 | 47 | testClientRead(client) 48 | 49 | clientFinished.complete(Unit) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/integration/SseIntegrationTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.integration 2 | 3 | import io.ktor.client.HttpClient 4 | import io.ktor.client.plugins.sse.SSE 5 | import io.ktor.server.cio.CIOApplicationEngine 6 | import io.ktor.server.engine.EmbeddedServer 7 | import io.ktor.server.engine.embeddedServer 8 | import io.modelcontextprotocol.kotlin.sdk.Implementation 9 | import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities 10 | import io.modelcontextprotocol.kotlin.sdk.client.Client 11 | import io.modelcontextprotocol.kotlin.sdk.client.mcpSse 12 | import io.modelcontextprotocol.kotlin.sdk.server.Server 13 | import io.modelcontextprotocol.kotlin.sdk.server.ServerOptions 14 | import io.modelcontextprotocol.kotlin.sdk.server.mcp 15 | import kotlinx.coroutines.Dispatchers 16 | import kotlinx.coroutines.test.runTest 17 | import kotlinx.coroutines.withContext 18 | import kotlin.test.Test 19 | import kotlin.test.fail 20 | import io.ktor.client.engine.cio.CIO as ClientCIO 21 | import io.ktor.server.cio.CIO as ServerCIO 22 | 23 | class SseIntegrationTest { 24 | @Test 25 | fun `client should be able to connect to sse server`() = runTest { 26 | val serverEngine = initServer() 27 | try { 28 | withContext(Dispatchers.Default) { 29 | assertDoesNotThrow { initClient() } 30 | } 31 | } finally { 32 | // Make sure to stop the server 33 | serverEngine.stopSuspend(1000, 2000) 34 | } 35 | } 36 | 37 | private inline fun assertDoesNotThrow(block: () -> T): T { 38 | return try { 39 | block() 40 | } catch (e: Throwable) { 41 | fail("Expected no exception, but got: $e") 42 | } 43 | } 44 | 45 | private suspend fun initClient(): Client { 46 | return HttpClient(ClientCIO) { install(SSE) }.mcpSse("http://$URL:$PORT") 47 | } 48 | 49 | private suspend fun initServer(): EmbeddedServer { 50 | val server = Server( 51 | Implementation(name = "sse-e2e-test", version = "1.0.0"), 52 | ServerOptions(capabilities = ServerCapabilities()), 53 | ) 54 | 55 | return embeddedServer(ServerCIO, host = URL, port = PORT) { mcp { server } }.startSuspend(wait = false) 56 | } 57 | 58 | companion object { 59 | private const val PORT = 3001 60 | private const val URL = "localhost" 61 | } 62 | } -------------------------------------------------------------------------------- /src/commonTest/kotlin/io/modelcontextprotocol/kotlin/sdk/shared/ReadBufferTest.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.shared 2 | 3 | import io.ktor.utils.io.charsets.Charsets 4 | import io.ktor.utils.io.core.toByteArray 5 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 6 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCNotification 7 | import kotlinx.serialization.encodeToString 8 | import kotlinx.serialization.json.Json 9 | import kotlin.test.Test 10 | import kotlin.test.assertEquals 11 | import kotlin.test.assertNull 12 | 13 | class ReadBufferTest { 14 | private val testMessage: JSONRPCMessage = JSONRPCNotification(method = "foobar") 15 | 16 | private val json = Json { 17 | ignoreUnknownKeys = true 18 | encodeDefaults = true 19 | } 20 | 21 | @Test 22 | fun `should have no messages after initialization`() { 23 | val readBuffer = ReadBuffer() 24 | assertNull(readBuffer.readMessage()) 25 | } 26 | 27 | @Test 28 | fun `should only yield a message after a newline`() { 29 | val readBuffer = ReadBuffer() 30 | 31 | // Append message without a newline 32 | val messageBytes = json.encodeToString(testMessage).encodeToByteArray() 33 | readBuffer.append(messageBytes) 34 | assertNull(readBuffer.readMessage()) 35 | 36 | // Append a newline and verify message is now available 37 | readBuffer.append("\n".encodeToByteArray()) 38 | assertEquals(testMessage, readBuffer.readMessage()) 39 | assertNull(readBuffer.readMessage()) 40 | } 41 | 42 | @Test 43 | fun `skip empty line`() { 44 | val readBuffer = ReadBuffer() 45 | readBuffer.append("\n".toByteArray()) 46 | assertNull(readBuffer.readMessage()) 47 | } 48 | 49 | @Test 50 | fun `should be reusable after clearing`() { 51 | val readBuffer = ReadBuffer() 52 | 53 | readBuffer.append("foobar".toByteArray(Charsets.UTF_8)) 54 | readBuffer.clear() 55 | assertNull(readBuffer.readMessage()) 56 | 57 | val messageJson = serializeMessage(testMessage) 58 | readBuffer.append(messageJson.toByteArray(Charsets.UTF_8)) 59 | readBuffer.append("\n".toByteArray(Charsets.UTF_8)) 60 | val message = readBuffer.readMessage() 61 | assertEquals(testMessage, message) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/iosMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.ios.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.internal 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.IO 6 | 7 | internal actual val IODispatcher: CoroutineDispatcher 8 | get() = Dispatchers.IO -------------------------------------------------------------------------------- /src/jsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.js.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.internal 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | internal actual val IODispatcher: CoroutineDispatcher 7 | get() = Dispatchers.Default -------------------------------------------------------------------------------- /src/jvmMain/java/io/modelcontextprotocol/kotlin/sdk/internal/utils.jvm.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.internal 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | internal actual val IODispatcher: CoroutineDispatcher 7 | get() = Dispatchers.IO -------------------------------------------------------------------------------- /src/jvmTest/kotlin/client/ClientIntegrationTest.kt: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import io.modelcontextprotocol.kotlin.sdk.Implementation 4 | import io.modelcontextprotocol.kotlin.sdk.ListToolsResult 5 | import io.modelcontextprotocol.kotlin.sdk.client.Client 6 | import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport 7 | import kotlinx.coroutines.test.runTest 8 | import kotlinx.io.asSink 9 | import kotlinx.io.asSource 10 | import kotlinx.io.buffered 11 | import org.junit.jupiter.api.Disabled 12 | import org.junit.jupiter.api.Test 13 | import java.net.Socket 14 | 15 | class ClientIntegrationTest { 16 | 17 | fun createTransport(): StdioClientTransport { 18 | val socket = Socket("localhost", 3000) 19 | 20 | return StdioClientTransport( 21 | socket.inputStream.asSource().buffered(), 22 | socket.outputStream.asSink().buffered() 23 | ) 24 | } 25 | 26 | @Disabled("This test requires a running server") 27 | @Test 28 | fun testRequestTools() = runTest { 29 | val client = Client( 30 | Implementation("test", "1.0"), 31 | ) 32 | 33 | val transport = createTransport() 34 | try { 35 | client.connect(transport) 36 | 37 | val response: ListToolsResult? = client.listTools() 38 | println(response?.tools) 39 | 40 | } finally { 41 | transport.close() 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/jvmTest/kotlin/client/StdioClientTransportTest.kt: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import io.modelcontextprotocol.kotlin.sdk.client.BaseTransportTest 4 | import kotlinx.coroutines.test.runTest 5 | import io.modelcontextprotocol.kotlin.sdk.client.StdioClientTransport 6 | import kotlinx.io.asSink 7 | import kotlinx.io.asSource 8 | import kotlinx.io.buffered 9 | import org.junit.jupiter.api.Test 10 | 11 | class StdioClientTransportTest : BaseTransportTest() { 12 | @Test 13 | fun `should start then close cleanly`() = runTest { 14 | // Run process "/usr/bin/tee" 15 | val processBuilder = ProcessBuilder("/usr/bin/tee") 16 | val process = processBuilder.start() 17 | 18 | val input = process.inputStream.asSource().buffered() 19 | val output = process.outputStream.asSink().buffered() 20 | 21 | val client = StdioClientTransport( 22 | input = input, 23 | output = output 24 | ) 25 | 26 | testClientOpenClose(client) 27 | 28 | process.destroy() 29 | } 30 | 31 | @Test 32 | fun `should read messages`() = runTest { 33 | val processBuilder = ProcessBuilder("/usr/bin/tee") 34 | val process = processBuilder.start() 35 | 36 | val input = process.inputStream.asSource().buffered() 37 | val output = process.outputStream.asSink().buffered() 38 | 39 | val client = StdioClientTransport( 40 | input = input, 41 | output = output 42 | ) 43 | 44 | testClientRead(client) 45 | 46 | process.waitFor() 47 | process.destroy() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/jvmTest/kotlin/server/StdioServerTransportTest.kt: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import io.modelcontextprotocol.kotlin.sdk.InitializedNotification 4 | import io.modelcontextprotocol.kotlin.sdk.JSONRPCMessage 5 | import io.modelcontextprotocol.kotlin.sdk.PingRequest 6 | import kotlinx.coroutines.CompletableDeferred 7 | import kotlinx.coroutines.runBlocking 8 | import io.modelcontextprotocol.kotlin.sdk.server.StdioServerTransport 9 | import org.junit.jupiter.api.Assertions.* 10 | import org.junit.jupiter.api.BeforeEach 11 | import org.junit.jupiter.api.Test 12 | import io.modelcontextprotocol.kotlin.sdk.shared.ReadBuffer 13 | import io.modelcontextprotocol.kotlin.sdk.shared.serializeMessage 14 | import io.modelcontextprotocol.kotlin.sdk.toJSON 15 | import kotlinx.io.Sink 16 | import kotlinx.io.Source 17 | import kotlinx.io.asSink 18 | import kotlinx.io.asSource 19 | import kotlinx.io.buffered 20 | import java.io.* 21 | 22 | class StdioServerTransportTest { 23 | private lateinit var input: PipedInputStream 24 | private lateinit var inputWriter: PipedOutputStream 25 | private lateinit var outputBuffer: ReadBuffer 26 | private lateinit var output: ByteArrayOutputStream 27 | 28 | // We'll store the wrapped streams that meet the constructor requirements 29 | private lateinit var bufferedInput: Source 30 | private lateinit var printOutput: Sink 31 | 32 | @BeforeEach 33 | fun setUp() { 34 | // Simulate an input stream that we can push data into using inputWriter. 35 | input = PipedInputStream() 36 | inputWriter = PipedOutputStream(input) 37 | 38 | outputBuffer = ReadBuffer() 39 | 40 | // A custom ByteArrayOutputStream that appends all written data into outputBuffer 41 | output = object : ByteArrayOutputStream() { 42 | override fun write(b: ByteArray, off: Int, len: Int) { 43 | super.write(b, off, len) 44 | outputBuffer.append(b.copyOfRange(off, off + len)) 45 | } 46 | } 47 | 48 | bufferedInput = input.asSource().buffered() 49 | 50 | printOutput = output.asSink().buffered() 51 | } 52 | 53 | @Test 54 | fun `should start then close cleanly`() { 55 | runBlocking { 56 | val server = StdioServerTransport(bufferedInput, printOutput) 57 | server.onError { error -> 58 | throw error 59 | } 60 | 61 | var didClose = false 62 | server.onClose { 63 | didClose = true 64 | } 65 | 66 | server.start() 67 | assertFalse(didClose, "Should not have closed yet") 68 | 69 | server.close() 70 | assertTrue(didClose, "Should have closed after calling close()") 71 | } 72 | } 73 | 74 | @Test 75 | fun `should not read until started`() { 76 | runBlocking { 77 | val server = StdioServerTransport(bufferedInput, printOutput) 78 | server.onError { error -> 79 | throw error 80 | } 81 | 82 | var didRead = false 83 | val readMessage = CompletableDeferred() 84 | 85 | server.onMessage { message -> 86 | didRead = true 87 | readMessage.complete(message) 88 | } 89 | 90 | val message = PingRequest().toJSON() 91 | 92 | // Push a message before the server started 93 | val serialized = serializeMessage(message) 94 | inputWriter.write(serialized) 95 | inputWriter.flush() 96 | 97 | assertFalse(didRead, "Should not have read message before start") 98 | 99 | server.start() 100 | val received = readMessage.await() 101 | assertEquals(message, received) 102 | } 103 | } 104 | 105 | @Test 106 | fun `should read multiple messages`() { 107 | runBlocking { 108 | val server = StdioServerTransport(bufferedInput, printOutput) 109 | server.onError { error -> 110 | throw error 111 | } 112 | 113 | val messages = listOf( 114 | PingRequest().toJSON(), 115 | InitializedNotification().toJSON(), 116 | ) 117 | 118 | val readMessages = mutableListOf() 119 | val finished = CompletableDeferred() 120 | 121 | server.onMessage { message -> 122 | readMessages.add(message) 123 | if (message == messages[1]) { 124 | finished.complete(Unit) 125 | } 126 | } 127 | 128 | // Push both messages before starting the server 129 | for (m in messages) { 130 | inputWriter.write(serializeMessage(m)) 131 | } 132 | inputWriter.flush() 133 | 134 | server.start() 135 | finished.await() 136 | 137 | assertEquals(messages, readMessages) 138 | } 139 | } 140 | } 141 | 142 | fun PipedOutputStream.write(s: String) { 143 | write(s.toByteArray()) 144 | } 145 | -------------------------------------------------------------------------------- /src/wasmJsMain/kotlin/io/modelcontextprotocol/kotlin/sdk/internal/utils.wasmJs.kt: -------------------------------------------------------------------------------- 1 | package io.modelcontextprotocol.kotlin.sdk.internal 2 | 3 | import kotlinx.coroutines.CoroutineDispatcher 4 | import kotlinx.coroutines.Dispatchers 5 | 6 | internal actual val IODispatcher: CoroutineDispatcher 7 | get() = Dispatchers.Default --------------------------------------------------------------------------------