001/* 002 * Copyright 2022-2026 Revetware LLC. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.soklet; 018 019import org.jspecify.annotations.NonNull; 020import org.jspecify.annotations.Nullable; 021 022import javax.annotation.concurrent.NotThreadSafe; 023import java.time.Duration; 024import java.util.concurrent.ExecutorService; 025import java.util.function.Consumer; 026import java.util.function.Supplier; 027 028import static java.util.Objects.requireNonNull; 029 030/** 031 * Contract for MCP server implementations that are designed to be managed by a {@link Soklet} instance. 032 * 033 * @author <a href="https://www.revetkn.com">Mark Allen</a> 034 */ 035public interface McpServer extends AutoCloseable { 036 /** 037 * Starts the MCP server. 038 */ 039 void start(); 040 041 /** 042 * Stops the MCP server. 043 */ 044 void stop(); 045 046 /** 047 * Indicates whether the server has been started. 048 * 049 * @return {@code true} if the server is started 050 */ 051 @NonNull 052 Boolean isStarted(); 053 054 /** 055 * Initializes the server with Soklet-owned infrastructure. 056 * 057 * @param sokletConfig the owning Soklet configuration 058 * @param requestHandler the request handler callback Soklet will invoke for MCP requests 059 */ 060 void initialize(@NonNull SokletConfig sokletConfig, 061 @NonNull RequestHandler requestHandler); 062 063 /** 064 * Provides the MCP handler resolver. 065 * 066 * @return the handler resolver 067 */ 068 @NonNull 069 McpHandlerResolver getHandlerResolver(); 070 071 /** 072 * Provides the request admission policy. 073 * 074 * @return the request admission policy 075 */ 076 @NonNull 077 McpRequestAdmissionPolicy getRequestAdmissionPolicy(); 078 079 /** 080 * Provides the request interceptor. 081 * 082 * @return the request interceptor 083 */ 084 @NonNull 085 McpRequestInterceptor getRequestInterceptor(); 086 087 /** 088 * Provides the response marshaler used for tool structured content. 089 * 090 * @return the response marshaler 091 */ 092 @NonNull 093 McpResponseMarshaler getResponseMarshaler(); 094 095 /** 096 * Provides the MCP CORS authorizer. 097 * 098 * @return the CORS authorizer 099 */ 100 @NonNull 101 McpCorsAuthorizer getCorsAuthorizer(); 102 103 /** 104 * Provides the session store. 105 * 106 * @return the session store 107 */ 108 @NonNull 109 McpSessionStore getSessionStore(); 110 111 @Override 112 default void close() throws Exception { 113 stop(); 114 } 115 116 /** 117 * Request callback used by {@link Soklet} to hand transport requests to an initialized MCP server. 118 */ 119 @FunctionalInterface 120 interface RequestHandler { 121 /** 122 * Handles an MCP transport request. 123 * 124 * @param request the transport request 125 * @param requestResultConsumer the consumer that should receive the completed request result 126 */ 127 void handleRequest(@NonNull Request request, 128 @NonNull Consumer<HttpRequestResult> requestResultConsumer); 129 } 130 131 /** 132 * Acquires a builder configured with the given port. 133 * 134 * @param port the port to bind 135 * @return a new MCP server builder 136 */ 137 @NonNull 138 static Builder withPort(@NonNull Integer port) { 139 requireNonNull(port); 140 return new Builder(port); 141 } 142 143 /** 144 * Creates a default MCP server bound to the given port. 145 * 146 * @param port the port to bind 147 * @return the built MCP server 148 */ 149 @NonNull 150 static McpServer fromPort(@NonNull Integer port) { 151 requireNonNull(port); 152 return withPort(port).build(); 153 } 154 155 /** 156 * Builder for {@link McpServer} instances. 157 */ 158 @NotThreadSafe 159 final class Builder { 160 @NonNull 161 Integer port; 162 @Nullable 163 String host; 164 @Nullable 165 McpHandlerResolver handlerResolver; 166 @Nullable 167 McpRequestAdmissionPolicy requestAdmissionPolicy; 168 @Nullable 169 McpRequestInterceptor requestInterceptor; 170 @Nullable 171 McpResponseMarshaler responseMarshaler; 172 @Nullable 173 McpCorsAuthorizer corsAuthorizer; 174 @Nullable 175 McpSessionStore sessionStore; 176 @Nullable 177 Duration requestHeaderTimeout; 178 @Nullable 179 Duration requestBodyTimeout; 180 @Nullable 181 Duration requestHandlerTimeout; 182 @Nullable 183 Integer requestHandlerConcurrency; 184 @Nullable 185 Integer requestHandlerQueueCapacity; 186 @Nullable 187 Supplier<ExecutorService> requestHandlerExecutorServiceSupplier; 188 @Nullable 189 Integer maximumRequestSizeInBytes; 190 @Nullable 191 Integer maximumHeaderCount; 192 @Nullable 193 Integer maximumHeadersSizeInBytes; 194 @Nullable 195 Integer maximumRequestTargetLengthInBytes; 196 @Nullable 197 Integer requestReadBufferSizeInBytes; 198 @Nullable 199 Integer concurrentConnectionLimit; 200 @Nullable 201 Integer connectionQueueCapacity; 202 @Nullable 203 Duration shutdownTimeout; 204 @Nullable 205 Duration writeTimeout; 206 @Nullable 207 Duration heartbeatInterval; 208 209 private Builder(@NonNull Integer port) { 210 requireNonNull(port); 211 this.port = port; 212 } 213 214 /** 215 * Sets the port to bind. 216 * 217 * @param port the port 218 * @return this builder 219 */ 220 @NonNull 221 public Builder port(@NonNull Integer port) { 222 requireNonNull(port); 223 this.port = port; 224 return this; 225 } 226 227 /** 228 * Sets the host to bind, or {@code null} to use the server default. 229 * 230 * @param host the host to bind 231 * @return this builder 232 */ 233 @NonNull 234 public Builder host(@Nullable String host) { 235 this.host = host; 236 return this; 237 } 238 239 /** 240 * Sets the handler resolver. 241 * 242 * @param handlerResolver the handler resolver 243 * @return this builder 244 */ 245 @NonNull 246 public Builder handlerResolver(@Nullable McpHandlerResolver handlerResolver) { 247 this.handlerResolver = handlerResolver; 248 return this; 249 } 250 251 /** 252 * Sets the request admission policy. 253 * 254 * @param requestAdmissionPolicy the admission policy 255 * @return this builder 256 */ 257 @NonNull 258 public Builder requestAdmissionPolicy(@Nullable McpRequestAdmissionPolicy requestAdmissionPolicy) { 259 this.requestAdmissionPolicy = requestAdmissionPolicy; 260 return this; 261 } 262 263 /** 264 * Sets the request interceptor. 265 * 266 * @param requestInterceptor the request interceptor 267 * @return this builder 268 */ 269 @NonNull 270 public Builder requestInterceptor(@Nullable McpRequestInterceptor requestInterceptor) { 271 this.requestInterceptor = requestInterceptor; 272 return this; 273 } 274 275 /** 276 * Sets the response marshaler used for tool structured content. 277 * 278 * @param responseMarshaler the response marshaler 279 * @return this builder 280 */ 281 @NonNull 282 public Builder responseMarshaler(@Nullable McpResponseMarshaler responseMarshaler) { 283 this.responseMarshaler = responseMarshaler; 284 return this; 285 } 286 287 /** 288 * Sets the MCP CORS authorizer. 289 * 290 * @param corsAuthorizer the CORS authorizer 291 * @return this builder 292 */ 293 @NonNull 294 public Builder corsAuthorizer(@Nullable McpCorsAuthorizer corsAuthorizer) { 295 this.corsAuthorizer = corsAuthorizer; 296 return this; 297 } 298 299 /** 300 * Sets the session store. 301 * 302 * @param sessionStore the session store 303 * @return this builder 304 */ 305 @NonNull 306 public Builder sessionStore(@Nullable McpSessionStore sessionStore) { 307 this.sessionStore = sessionStore; 308 return this; 309 } 310 311 /** 312 * Sets the maximum duration for reading the MCP HTTP request line and headers. 313 * <p> 314 * If this value is not specified, Soklet uses the server default. 315 * 316 * @param requestHeaderTimeout the request header timeout, or {@code null} for the default 317 * @return this builder 318 */ 319 @NonNull 320 public Builder requestHeaderTimeout(@Nullable Duration requestHeaderTimeout) { 321 this.requestHeaderTimeout = requestHeaderTimeout; 322 return this; 323 } 324 325 /** 326 * Sets the maximum duration for reading the MCP HTTP request body after the request 327 * line and headers have been received. 328 * <p> 329 * If this value is not specified, Soklet uses the server default. 330 * 331 * @param requestBodyTimeout the request body timeout, or {@code null} for the default 332 * @return this builder 333 */ 334 @NonNull 335 public Builder requestBodyTimeout(@Nullable Duration requestBodyTimeout) { 336 this.requestBodyTimeout = requestBodyTimeout; 337 return this; 338 } 339 340 /** 341 * Sets the timeout for MCP handler execution, including framework-managed 342 * {@link McpEndpoint#initialize(McpInitializationContext, McpSessionContext)} 343 * calls. 344 * 345 * @param requestHandlerTimeout the handler timeout 346 * @return this builder 347 */ 348 @NonNull 349 public Builder requestHandlerTimeout(@Nullable Duration requestHandlerTimeout) { 350 this.requestHandlerTimeout = requestHandlerTimeout; 351 return this; 352 } 353 354 /** 355 * Sets the handler concurrency level for MCP JSON-RPC request handling. 356 * <p> 357 * On runtimes with virtual threads, established MCP live {@code GET} streams are 358 * processed on a virtual-thread-per-stream executor and are not limited by this 359 * value. On runtimes without virtual threads, the live-stream processor uses the 360 * bounded fallback executor and this value also bounds active live-stream 361 * processing. 362 * 363 * @param requestHandlerConcurrency the handler concurrency 364 * @return this builder 365 */ 366 @NonNull 367 public Builder requestHandlerConcurrency(@Nullable Integer requestHandlerConcurrency) { 368 this.requestHandlerConcurrency = requestHandlerConcurrency; 369 return this; 370 } 371 372 /** 373 * Sets the handler queue capacity for MCP JSON-RPC request handling. 374 * 375 * @param requestHandlerQueueCapacity the handler queue capacity 376 * @return this builder 377 */ 378 @NonNull 379 public Builder requestHandlerQueueCapacity(@Nullable Integer requestHandlerQueueCapacity) { 380 this.requestHandlerQueueCapacity = requestHandlerQueueCapacity; 381 return this; 382 } 383 384 /** 385 * Sets the supplier used to create the handler executor service. 386 * 387 * @param requestHandlerExecutorServiceSupplier the executor service supplier 388 * @return this builder 389 */ 390 @NonNull 391 public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) { 392 this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier; 393 return this; 394 } 395 396 /** 397 * Sets the maximum accepted MCP request size in bytes. 398 * <p> 399 * The MCP transport rejects requests whose declared body size is larger than 400 * this limit. Header-section size is governed by 401 * {@link #maximumHeadersSizeInBytes(Integer)}. 402 * 403 * @param maximumRequestSizeInBytes the maximum request size, or {@code null} for the default 404 * @return this builder 405 */ 406 @NonNull 407 public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) { 408 this.maximumRequestSizeInBytes = maximumRequestSizeInBytes; 409 return this; 410 } 411 412 /** 413 * Sets the maximum number of HTTP header fields accepted in one MCP request. 414 * 415 * @param maximumHeaderCount the maximum header count, or {@code null} for the default 416 * @return this builder 417 */ 418 @NonNull 419 public Builder maximumHeaderCount(@Nullable Integer maximumHeaderCount) { 420 this.maximumHeaderCount = maximumHeaderCount; 421 return this; 422 } 423 424 /** 425 * Sets the maximum accepted MCP HTTP header-section size in bytes. 426 * <p> 427 * This limit applies to the header bytes after the request line, including 428 * header-field line endings and the terminating blank line. 429 * 430 * @param maximumHeadersSizeInBytes the maximum headers size, or {@code null} for the default 431 * @return this builder 432 */ 433 @NonNull 434 public Builder maximumHeadersSizeInBytes(@Nullable Integer maximumHeadersSizeInBytes) { 435 this.maximumHeadersSizeInBytes = maximumHeadersSizeInBytes; 436 return this; 437 } 438 439 /** 440 * Sets the maximum MCP HTTP request-target length accepted in bytes. 441 * 442 * @param maximumRequestTargetLengthInBytes the maximum request-target length, or {@code null} for the default 443 * @return this builder 444 */ 445 @NonNull 446 public Builder maximumRequestTargetLengthInBytes(@Nullable Integer maximumRequestTargetLengthInBytes) { 447 this.maximumRequestTargetLengthInBytes = maximumRequestTargetLengthInBytes; 448 return this; 449 } 450 451 /** 452 * Sets the read buffer size used while reading MCP requests. 453 * 454 * @param requestReadBufferSizeInBytes the read buffer size 455 * @return this builder 456 */ 457 @NonNull 458 public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) { 459 this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes; 460 return this; 461 } 462 463 /** 464 * Sets the concurrent connection limit for the MCP server. 465 * <p> 466 * A value of {@code 0} disables Soklet's MCP live-stream connection cap entirely. 467 * Use {@code 0} only when an external proxy, load balancer, operating-system limit, 468 * or custom admission policy provides the intended production cap. 469 * 470 * @param concurrentConnectionLimit the concurrent connection limit 471 * @return this builder 472 */ 473 @NonNull 474 public Builder concurrentConnectionLimit(@Nullable Integer concurrentConnectionLimit) { 475 this.concurrentConnectionLimit = concurrentConnectionLimit; 476 return this; 477 } 478 479 /** 480 * Sets the outbound queue capacity for live MCP streams. 481 * <p> 482 * This controls each established stream's outbound message queue. On runtimes 483 * without virtual threads, the same value is also used by the bounded fallback 484 * executor for queued live-stream processing tasks. 485 * 486 * @param connectionQueueCapacity the outbound queue capacity 487 * @return this builder 488 */ 489 @NonNull 490 public Builder connectionQueueCapacity(@Nullable Integer connectionQueueCapacity) { 491 this.connectionQueueCapacity = connectionQueueCapacity; 492 return this; 493 } 494 495 /** 496 * Sets the shutdown timeout. 497 * 498 * @param shutdownTimeout the shutdown timeout 499 * @return this builder 500 */ 501 @NonNull 502 public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) { 503 this.shutdownTimeout = shutdownTimeout; 504 return this; 505 } 506 507 /** 508 * Sets the transport write timeout for established MCP event streams. 509 * <p> 510 * If this value is not specified, Soklet uses the server default. Use 511 * {@link Duration#ZERO} to disable MCP event-stream write timeouts. 512 * 513 * @param writeTimeout the write timeout, or {@code null} for the default 514 * @return this builder 515 */ 516 @NonNull 517 public Builder writeTimeout(@Nullable Duration writeTimeout) { 518 this.writeTimeout = writeTimeout; 519 return this; 520 } 521 522 /** 523 * Sets the heartbeat interval for long-lived MCP event streams. 524 * 525 * @param heartbeatInterval the heartbeat interval 526 * @return this builder 527 */ 528 @NonNull 529 public Builder heartbeatInterval(@Nullable Duration heartbeatInterval) { 530 this.heartbeatInterval = heartbeatInterval; 531 return this; 532 } 533 534 /** 535 * Builds the MCP server. 536 * 537 * @return the built MCP server 538 */ 539 @NonNull 540 public McpServer build() { 541 return new DefaultMcpServer(this); 542 } 543 } 544}