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}