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}