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> getIdGenerator();
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 requestTimeout;
186                @Nullable
187                Duration requestHandlerTimeout;
188                @Nullable
189                Integer requestHandlerConcurrency;
190                @Nullable
191                Integer requestHandlerQueueCapacity;
192                @Nullable
193                Supplier<ExecutorService> requestHandlerExecutorServiceSupplier;
194                @Nullable
195                Integer maximumRequestSizeInBytes;
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                @Nullable
209                IdGenerator<String> idGenerator;
210
211                private Builder(@NonNull Integer port) {
212                        requireNonNull(port);
213                        this.port = port;
214                }
215
216                /**
217                 * Sets the port to bind.
218                 *
219                 * @param port the port
220                 * @return this builder
221                 */
222                @NonNull
223                public Builder port(@NonNull Integer port) {
224                        requireNonNull(port);
225                        this.port = port;
226                        return this;
227                }
228
229                /**
230                 * Sets the host to bind, or {@code null} to use the server default.
231                 *
232                 * @param host the host to bind
233                 * @return this builder
234                 */
235                @NonNull
236                public Builder host(@Nullable String host) {
237                        this.host = host;
238                        return this;
239                }
240
241                /**
242                 * Sets the handler resolver.
243                 *
244                 * @param handlerResolver the handler resolver
245                 * @return this builder
246                 */
247                @NonNull
248                public Builder handlerResolver(@Nullable McpHandlerResolver handlerResolver) {
249                        this.handlerResolver = handlerResolver;
250                        return this;
251                }
252
253                /**
254                 * Sets the request admission policy.
255                 *
256                 * @param requestAdmissionPolicy the admission policy
257                 * @return this builder
258                 */
259                @NonNull
260                public Builder requestAdmissionPolicy(@Nullable McpRequestAdmissionPolicy requestAdmissionPolicy) {
261                        this.requestAdmissionPolicy = requestAdmissionPolicy;
262                        return this;
263                }
264
265                /**
266                 * Sets the request interceptor.
267                 *
268                 * @param requestInterceptor the request interceptor
269                 * @return this builder
270                 */
271                @NonNull
272                public Builder requestInterceptor(@Nullable McpRequestInterceptor requestInterceptor) {
273                        this.requestInterceptor = requestInterceptor;
274                        return this;
275                }
276
277                /**
278                 * Sets the response marshaler used for tool structured content.
279                 *
280                 * @param responseMarshaler the response marshaler
281                 * @return this builder
282                 */
283                @NonNull
284                public Builder responseMarshaler(@Nullable McpResponseMarshaler responseMarshaler) {
285                        this.responseMarshaler = responseMarshaler;
286                        return this;
287                }
288
289                /**
290                 * Sets the MCP CORS authorizer.
291                 *
292                 * @param corsAuthorizer the CORS authorizer
293                 * @return this builder
294                 */
295                @NonNull
296                public Builder corsAuthorizer(@Nullable McpCorsAuthorizer corsAuthorizer) {
297                        this.corsAuthorizer = corsAuthorizer;
298                        return this;
299                }
300
301                /**
302                 * Sets the session store.
303                 *
304                 * @param sessionStore the session store
305                 * @return this builder
306                 */
307                @NonNull
308                public Builder sessionStore(@Nullable McpSessionStore sessionStore) {
309                        this.sessionStore = sessionStore;
310                        return this;
311                }
312
313                /**
314                 * Sets the end-to-end request timeout.
315                 *
316                 * @param requestTimeout the request timeout
317                 * @return this builder
318                 */
319                @NonNull
320                public Builder requestTimeout(@Nullable Duration requestTimeout) {
321                        this.requestTimeout = requestTimeout;
322                        return this;
323                }
324
325                /**
326                 * Sets the timeout for MCP handler execution.
327                 *
328                 * @param requestHandlerTimeout the handler timeout
329                 * @return this builder
330                 */
331                @NonNull
332                public Builder requestHandlerTimeout(@Nullable Duration requestHandlerTimeout) {
333                        this.requestHandlerTimeout = requestHandlerTimeout;
334                        return this;
335                }
336
337                /**
338                 * Sets the handler concurrency level.
339                 *
340                 * @param requestHandlerConcurrency the handler concurrency
341                 * @return this builder
342                 */
343                @NonNull
344                public Builder requestHandlerConcurrency(@Nullable Integer requestHandlerConcurrency) {
345                        this.requestHandlerConcurrency = requestHandlerConcurrency;
346                        return this;
347                }
348
349                /**
350                 * Sets the handler queue capacity.
351                 *
352                 * @param requestHandlerQueueCapacity the handler queue capacity
353                 * @return this builder
354                 */
355                @NonNull
356                public Builder requestHandlerQueueCapacity(@Nullable Integer requestHandlerQueueCapacity) {
357                        this.requestHandlerQueueCapacity = requestHandlerQueueCapacity;
358                        return this;
359                }
360
361                /**
362                 * Sets the supplier used to create the handler executor service.
363                 *
364                 * @param requestHandlerExecutorServiceSupplier the executor service supplier
365                 * @return this builder
366                 */
367                @NonNull
368                public Builder requestHandlerExecutorServiceSupplier(@Nullable Supplier<ExecutorService> requestHandlerExecutorServiceSupplier) {
369                        this.requestHandlerExecutorServiceSupplier = requestHandlerExecutorServiceSupplier;
370                        return this;
371                }
372
373                /**
374                 * Sets the maximum accepted MCP request size in bytes.
375                 *
376                 * @param maximumRequestSizeInBytes the maximum request size
377                 * @return this builder
378                 */
379                @NonNull
380                public Builder maximumRequestSizeInBytes(@Nullable Integer maximumRequestSizeInBytes) {
381                        this.maximumRequestSizeInBytes = maximumRequestSizeInBytes;
382                        return this;
383                }
384
385                /**
386                 * Sets the read buffer size used while reading MCP requests.
387                 *
388                 * @param requestReadBufferSizeInBytes the read buffer size
389                 * @return this builder
390                 */
391                @NonNull
392                public Builder requestReadBufferSizeInBytes(@Nullable Integer requestReadBufferSizeInBytes) {
393                        this.requestReadBufferSizeInBytes = requestReadBufferSizeInBytes;
394                        return this;
395                }
396
397                /**
398                 * Sets the concurrent connection limit for the MCP server.
399                 *
400                 * @param concurrentConnectionLimit the concurrent connection limit
401                 * @return this builder
402                 */
403                @NonNull
404                public Builder concurrentConnectionLimit(@Nullable Integer concurrentConnectionLimit) {
405                        this.concurrentConnectionLimit = concurrentConnectionLimit;
406                        return this;
407                }
408
409                /**
410                 * Sets the outbound queue capacity for live MCP streams.
411                 *
412                 * @param connectionQueueCapacity the outbound queue capacity
413                 * @return this builder
414                 */
415                @NonNull
416                public Builder connectionQueueCapacity(@Nullable Integer connectionQueueCapacity) {
417                        this.connectionQueueCapacity = connectionQueueCapacity;
418                        return this;
419                }
420
421                /**
422                 * Sets the shutdown timeout.
423                 *
424                 * @param shutdownTimeout the shutdown timeout
425                 * @return this builder
426                 */
427                @NonNull
428                public Builder shutdownTimeout(@Nullable Duration shutdownTimeout) {
429                        this.shutdownTimeout = shutdownTimeout;
430                        return this;
431                }
432
433                /**
434                 * Sets the transport write timeout.
435                 *
436                 * @param writeTimeout the write timeout
437                 * @return this builder
438                 */
439                @NonNull
440                public Builder writeTimeout(@Nullable Duration writeTimeout) {
441                        this.writeTimeout = writeTimeout;
442                        return this;
443                }
444
445                /**
446                 * Sets the heartbeat interval for long-lived MCP event streams.
447                 *
448                 * @param heartbeatInterval the heartbeat interval
449                 * @return this builder
450                 */
451                @NonNull
452                public Builder heartbeatInterval(@Nullable Duration heartbeatInterval) {
453                        this.heartbeatInterval = heartbeatInterval;
454                        return this;
455                }
456
457                /**
458                 * Sets the generator used for MCP session IDs.
459                 *
460                 * @param idGenerator the session ID generator
461                 * @return this builder
462                 */
463                @NonNull
464                public Builder idGenerator(@Nullable IdGenerator<String> idGenerator) {
465                        this.idGenerator = idGenerator;
466                        return this;
467                }
468
469                /**
470                 * Builds the MCP server.
471                 *
472                 * @return the built MCP server
473                 */
474                @NonNull
475                public McpServer build() {
476                        return new DefaultMcpServer(this);
477                }
478        }
479}