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 /** 112 * Provides the generator used for MCP session IDs. 113 * 114 * @return the session ID generator 115 */ 116 @NonNull 117 IdGenerator<String> getSessionIdGenerator(); 118 119 @Override 120 default void close() throws Exception { 121 stop(); 122 } 123 124 /** 125 * Request callback used by {@link Soklet} to hand transport requests to an initialized MCP server. 126 */ 127 @FunctionalInterface 128 interface RequestHandler { 129 /** 130 * Handles an MCP transport request. 131 * 132 * @param request the transport request 133 * @param requestResultConsumer the consumer that should receive the completed request result 134 */ 135 void handleRequest(@NonNull Request request, 136 @NonNull Consumer<HttpRequestResult> requestResultConsumer); 137 } 138 139 /** 140 * Acquires a builder configured with the given port. 141 * 142 * @param port the port to bind 143 * @return a new MCP server builder 144 */ 145 @NonNull 146 static Builder withPort(@NonNull Integer port) { 147 requireNonNull(port); 148 return new Builder(port); 149 } 150 151 /** 152 * Creates a default MCP server bound to the given port. 153 * 154 * @param port the port to bind 155 * @return the built MCP server 156 */ 157 @NonNull 158 static McpServer fromPort(@NonNull Integer port) { 159 requireNonNull(port); 160 return withPort(port).build(); 161 } 162 163 /** 164 * Builder for {@link McpServer} instances. 165 */ 166 @NotThreadSafe 167 final class Builder { 168 @NonNull 169 Integer port; 170 @Nullable 171 String host; 172 @Nullable 173 McpHandlerResolver handlerResolver; 174 @Nullable 175 McpRequestAdmissionPolicy requestAdmissionPolicy; 176 @Nullable 177 McpRequestInterceptor requestInterceptor; 178 @Nullable 179 McpResponseMarshaler responseMarshaler; 180 @Nullable 181 McpCorsAuthorizer corsAuthorizer; 182 @Nullable 183 McpSessionStore sessionStore; 184 @Nullable 185 Duration requestHeaderTimeout; 186 @Nullable 187 Duration requestBodyTimeout; 188 @Nullable 189 Duration requestHandlerTimeout; 190 @Nullable 191 Integer requestHandlerConcurrency; 192 @Nullable 193 Integer requestHandlerQueueCapacity; 194 @Nullable 195 Supplier<ExecutorService> requestHandlerExecutorServiceSupplier; 196 @Nullable 197 Integer maximumRequestSizeInBytes; 198 @Nullable 199 Integer maximumHeaderCount; 200 @Nullable 201 Integer maximumRequestTargetLengthInBytes; 202 @Nullable 203 Integer requestReadBufferSizeInBytes; 204 @Nullable 205 Integer concurrentConnectionLimit; 206 @Nullable 207 Integer connectionQueueCapacity; 208 @Nullable 209 Duration shutdownTimeout; 210 @Nullable 211 Duration writeTimeout; 212 @Nullable 213 Duration heartbeatInterval; 214 @Nullable 215 IdGenerator<String> sessionIdGenerator; 216 217 private Builder(@NonNull Integer port) { 218 requireNonNull(port); 219 this.port = port; 220 } 221 222 /** 223 * Sets the port to bind. 224 * 225 * @param port the port 226 * @return this builder 227 */ 228 @NonNull 229 public Builder port(@NonNull Integer port) { 230 requireNonNull(port); 231 this.port = port; 232 return this; 233 } 234 235 /** 236 * Sets the host to bind, or {@code null} to use the server default. 237 * 238 * @param host the host to bind 239 * @return this builder 240 */ 241 @NonNull 242 public Builder host(@Nullable String host) { 243 this.host = host; 244 return this; 245 } 246 247 /** 248 * Sets the handler resolver. 249 * 250 * @param handlerResolver the handler resolver 251 * @return this builder 252 */ 253 @NonNull 254 public Builder handlerResolver(@Nullable McpHandlerResolver handlerResolver) { 255 this.handlerResolver = handlerResolver; 256 return this; 257 } 258 259 /** 260 * Sets the request admission policy. 261 * 262 * @param requestAdmissionPolicy the admission policy 263 * @return this builder 264 */ 265 @NonNull 266 public Builder requestAdmissionPolicy(@Nullable McpRequestAdmissionPolicy requestAdmissionPolicy) { 267 this.requestAdmissionPolicy = requestAdmissionPolicy; 268 return this; 269 } 270 271 /** 272 * Sets the request interceptor. 273 * 274 * @param requestInterceptor the request interceptor 275 * @return this builder 276 */ 277 @NonNull 278 public Builder requestInterceptor(@Nullable McpRequestInterceptor requestInterceptor) { 279 this.requestInterceptor = requestInterceptor; 280 return this; 281 } 282 283 /** 284 * Sets the response marshaler used for tool structured content. 285 * 286 * @param responseMarshaler the response marshaler 287 * @return this builder 288 */ 289 @NonNull 290 public Builder responseMarshaler(@Nullable McpResponseMarshaler responseMarshaler) { 291 this.responseMarshaler = responseMarshaler; 292 return this; 293 } 294 295 /** 296 * Sets the MCP CORS authorizer. 297 * 298 * @param corsAuthorizer the CORS authorizer 299 * @return this builder 300 */ 301 @NonNull 302 public Builder corsAuthorizer(@Nullable McpCorsAuthorizer corsAuthorizer) { 303 this.corsAuthorizer = corsAuthorizer; 304 return this; 305 } 306 307 /** 308 * Sets the session store. 309 * 310 * @param sessionStore the session store 311 * @return this builder 312 */ 313 @NonNull 314 public Builder sessionStore(@Nullable McpSessionStore sessionStore) { 315 this.sessionStore = sessionStore; 316 return this; 317 } 318 319 /** 320 * Sets the maximum duration for reading the MCP HTTP request line and headers. 321 * <p> 322 * If this value is not specified, Soklet uses the server default. 323 * 324 * @param requestHeaderTimeout the request header timeout, or {@code null} for the default 325 * @return this builder 326 */ 327 @NonNull 328 public Builder requestHeaderTimeout(@Nullable Duration requestHeaderTimeout) { 329 this.requestHeaderTimeout = requestHeaderTimeout; 330 return this; 331 } 332 333 /** 334 * Sets the maximum duration for reading the MCP HTTP request body after the request 335 * line and headers have been received. 336 * <p> 337 * If this value is not specified, Soklet uses the server default. 338 * 339 * @param requestBodyTimeout the request body timeout, or {@code null} for the default 340 * @return this builder 341 */ 342 @NonNull 343 public Builder requestBodyTimeout(@Nullable Duration requestBodyTimeout) { 344 this.requestBodyTimeout = requestBodyTimeout; 345 return this; 346 } 347 348 /** 349 * Sets the timeout for MCP handler execution, including framework-managed 350 * {@link McpEndpoint#initialize(McpInitializationContext, McpSessionContext)} 351 * calls. 352 * 353 * @param requestHandlerTimeout the handler timeout 354 * @return this builder 355 */ 356 @NonNull 357 public Builder requestHandlerTimeout(@Nullable Duration requestHandlerTimeout) { 358 this.requestHandlerTimeout = requestHandlerTimeout; 359 return this; 360 } 361 362 /** 363 * Sets the handler concurrency level. 364 * 365 * @param requestHandlerConcurrency the handler concurrency 366 * @return this builder 367 */ 368 @NonNull 369 public Builder requestHandlerConcurrency(@Nullable Integer requestHandlerConcurrency) { 370 this.requestHandlerConcurrency = requestHandlerConcurrency; 371 return this; 372 } 373 374 /** 375 * Sets the handler queue capacity. 376 * 377 * @param requestHandlerQueueCapacity the handler queue capacity 378 * @return this builder 379 */ 380 @NonNull 381 public Builder requestHandlerQueueCapacity(@Nullable Integer requestHandlerQueueCapacity) { 382 this.requestHandlerQueueCapacity = requestHandlerQueueCapacity; 383 return this; 384 } 385 386 /** 387 * Sets the supplier used to create the handler executor service. 388 * 389 * @param requestHandlerExecutorServiceSupplier the executor service supplier 390 * @return this builder 391 */ 392 @NonNull 393 public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) { 394 this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier; 395 return this; 396 } 397 398 /** 399 * Sets the maximum accepted MCP request size in bytes. 400 * <p> 401 * The MCP transport rejects a header section larger than this limit and rejects 402 * requests whose declared body size is larger than this limit. 403 * 404 * @param maximumRequestSizeInBytes the maximum request size, or {@code null} for the default 405 * @return this builder 406 */ 407 @NonNull 408 public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) { 409 this.maximumRequestSizeInBytes = maximumRequestSizeInBytes; 410 return this; 411 } 412 413 /** 414 * Sets the maximum number of HTTP header fields accepted in one MCP request. 415 * 416 * @param maximumHeaderCount the maximum header count, or {@code null} for the default 417 * @return this builder 418 */ 419 @NonNull 420 public Builder maximumHeaderCount(@Nullable Integer maximumHeaderCount) { 421 this.maximumHeaderCount = maximumHeaderCount; 422 return this; 423 } 424 425 /** 426 * Sets the maximum MCP HTTP request-target length accepted in bytes. 427 * 428 * @param maximumRequestTargetLengthInBytes the maximum request-target length, or {@code null} for the default 429 * @return this builder 430 */ 431 @NonNull 432 public Builder maximumRequestTargetLengthInBytes(@Nullable Integer maximumRequestTargetLengthInBytes) { 433 this.maximumRequestTargetLengthInBytes = maximumRequestTargetLengthInBytes; 434 return this; 435 } 436 437 /** 438 * Sets the read buffer size used while reading MCP requests. 439 * 440 * @param requestReadBufferSizeInBytes the read buffer size 441 * @return this builder 442 */ 443 @NonNull 444 public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) { 445 this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes; 446 return this; 447 } 448 449 /** 450 * Sets the concurrent connection limit for the MCP server. 451 * 452 * @param concurrentConnectionLimit the concurrent connection limit 453 * @return this builder 454 */ 455 @NonNull 456 public Builder concurrentConnectionLimit(@Nullable Integer concurrentConnectionLimit) { 457 this.concurrentConnectionLimit = concurrentConnectionLimit; 458 return this; 459 } 460 461 /** 462 * Sets the outbound queue capacity for live MCP streams. 463 * 464 * @param connectionQueueCapacity the outbound queue capacity 465 * @return this builder 466 */ 467 @NonNull 468 public Builder connectionQueueCapacity(@Nullable Integer connectionQueueCapacity) { 469 this.connectionQueueCapacity = connectionQueueCapacity; 470 return this; 471 } 472 473 /** 474 * Sets the shutdown timeout. 475 * 476 * @param shutdownTimeout the shutdown timeout 477 * @return this builder 478 */ 479 @NonNull 480 public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) { 481 this.shutdownTimeout = shutdownTimeout; 482 return this; 483 } 484 485 /** 486 * Sets the transport write timeout. 487 * 488 * @param writeTimeout the write timeout 489 * @return this builder 490 */ 491 @NonNull 492 public Builder writeTimeout(@Nullable Duration writeTimeout) { 493 this.writeTimeout = writeTimeout; 494 return this; 495 } 496 497 /** 498 * Sets the heartbeat interval for long-lived MCP event streams. 499 * 500 * @param heartbeatInterval the heartbeat interval 501 * @return this builder 502 */ 503 @NonNull 504 public Builder heartbeatInterval(@Nullable Duration heartbeatInterval) { 505 this.heartbeatInterval = heartbeatInterval; 506 return this; 507 } 508 509 /** 510 * Sets the generator used for MCP session IDs. 511 * <p> 512 * Custom generators must return globally unique, cryptographically strong, 513 * visible-ASCII IDs suitable for {@code MCP-Session-Id} header values. 514 * 515 * @param sessionIdGenerator the session ID generator 516 * @return this builder 517 */ 518 @NonNull 519 public Builder sessionIdGenerator(@Nullable IdGenerator<String> sessionIdGenerator) { 520 this.sessionIdGenerator = sessionIdGenerator; 521 return this; 522 } 523 524 /** 525 * Builds the MCP server. 526 * 527 * @return the built MCP server 528 */ 529 @NonNull 530 public McpServer build() { 531 return new DefaultMcpServer(this); 532 } 533 } 534}