tiny-http-mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +778 -0
  3. package/dist/auth.d.ts +69 -0
  4. package/dist/auth.js +261 -0
  5. package/dist/cli.d.ts +20 -0
  6. package/dist/cli.js +465 -0
  7. package/dist/composition.json +25 -0
  8. package/dist/express-middleware.d.ts +18 -0
  9. package/dist/express-middleware.js +91 -0
  10. package/dist/http-server.d.ts +48 -0
  11. package/dist/http-server.js +263 -0
  12. package/dist/http-transport.d.ts +164 -0
  13. package/dist/http-transport.js +897 -0
  14. package/dist/index.d.ts +13 -0
  15. package/dist/index.js +6 -0
  16. package/dist/load-oauth-verifier.d.ts +6 -0
  17. package/dist/load-oauth-verifier.js +43 -0
  18. package/dist/parse-body.d.ts +22 -0
  19. package/dist/parse-body.js +150 -0
  20. package/dist/session.d.ts +18 -0
  21. package/dist/session.js +37 -0
  22. package/dist/sse.d.ts +11 -0
  23. package/dist/sse.js +22 -0
  24. package/dist/test-support.d.ts +10 -0
  25. package/dist/test-support.js +398 -0
  26. package/dist/testing.d.ts +59 -0
  27. package/dist/testing.js +191 -0
  28. package/node_modules/auth-store/LICENSE +21 -0
  29. package/node_modules/auth-store/README.md +62 -0
  30. package/node_modules/auth-store/dist/create-secret-store.d.ts +2 -0
  31. package/node_modules/auth-store/dist/create-secret-store.js +44 -0
  32. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +47 -0
  33. package/node_modules/auth-store/dist/encrypted-file-store.js +303 -0
  34. package/node_modules/auth-store/dist/error-codes.d.ts +1 -0
  35. package/node_modules/auth-store/dist/error-codes.js +5 -0
  36. package/node_modules/auth-store/dist/index.d.ts +7 -0
  37. package/node_modules/auth-store/dist/index.js +4 -0
  38. package/node_modules/auth-store/dist/keychain-store.d.ts +25 -0
  39. package/node_modules/auth-store/dist/keychain-store.js +154 -0
  40. package/node_modules/auth-store/dist/provider-store.d.ts +14 -0
  41. package/node_modules/auth-store/dist/provider-store.js +78 -0
  42. package/node_modules/auth-store/dist/types.d.ts +22 -0
  43. package/node_modules/auth-store/dist/types.js +1 -0
  44. package/node_modules/auth-store/package.json +27 -0
  45. package/node_modules/mcp-oauth/LICENSE +21 -0
  46. package/node_modules/mcp-oauth/README.md +70 -0
  47. package/node_modules/mcp-oauth/dist/client/auth-store-session-store.d.ts +14 -0
  48. package/node_modules/mcp-oauth/dist/client/auth-store-session-store.js +169 -0
  49. package/node_modules/mcp-oauth/dist/client/authorization-state.d.ts +8 -0
  50. package/node_modules/mcp-oauth/dist/client/authorization-state.js +47 -0
  51. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.d.ts +3 -0
  52. package/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +627 -0
  53. package/node_modules/mcp-oauth/dist/client/loopback-authorization.d.ts +20 -0
  54. package/node_modules/mcp-oauth/dist/client/loopback-authorization.js +207 -0
  55. package/node_modules/mcp-oauth/dist/client/pkce.d.ts +2 -0
  56. package/node_modules/mcp-oauth/dist/client/pkce.js +7 -0
  57. package/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +40 -0
  58. package/node_modules/mcp-oauth/dist/client/token-endpoint.js +164 -0
  59. package/node_modules/mcp-oauth/dist/client/types.d.ts +113 -0
  60. package/node_modules/mcp-oauth/dist/client/types.js +1 -0
  61. package/node_modules/mcp-oauth/dist/index.d.ts +10 -0
  62. package/node_modules/mcp-oauth/dist/index.js +7 -0
  63. package/node_modules/mcp-oauth/dist/resource-indicator.d.ts +1 -0
  64. package/node_modules/mcp-oauth/dist/resource-indicator.js +11 -0
  65. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.d.ts +32 -0
  66. package/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +388 -0
  67. package/node_modules/mcp-oauth/dist/types.compile-check.d.ts +1 -0
  68. package/node_modules/mcp-oauth/dist/types.compile-check.js +22 -0
  69. package/node_modules/mcp-oauth/package.json +33 -0
  70. package/node_modules/tiny-mcp-client/LICENSE +21 -0
  71. package/node_modules/tiny-mcp-client/README.md +104 -0
  72. package/node_modules/tiny-mcp-client/dist/index.d.ts +660 -0
  73. package/node_modules/tiny-mcp-client/dist/index.js +3870 -0
  74. package/node_modules/tiny-mcp-client/package.json +30 -0
  75. package/package.json +63 -0
@@ -0,0 +1,263 @@
1
+ import http from "node:http";
2
+ import { AsyncLocalStorage } from "node:async_hooks";
3
+ import { createServer } from "tiny-stdio-mcp-server";
4
+ import { PROTECTED_RESOURCE_METADATA_CACHE_CONTROL, PROTECTED_RESOURCE_METADATA_PATH, authorizeBearerRequest } from "./auth.js";
5
+ import { StreamableHttpTransport } from "./http-transport.js";
6
+ function normalizePath(path) {
7
+ if (path.length === 0 || path === "/") {
8
+ return "/mcp";
9
+ }
10
+ if (path.includes("?") || path.includes("#")) {
11
+ throw new Error("path must not include a query or fragment");
12
+ }
13
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
14
+ return normalizedPath.length > 1 && normalizedPath.endsWith("/")
15
+ ? normalizedPath.slice(0, -1)
16
+ : normalizedPath;
17
+ }
18
+ function getProtectedResourceMetadataPaths(path) {
19
+ if (path === "/") {
20
+ return [PROTECTED_RESOURCE_METADATA_PATH];
21
+ }
22
+ return [`${PROTECTED_RESOURCE_METADATA_PATH}${path}`];
23
+ }
24
+ function formatHostnameForUrl(hostname) {
25
+ if (hostname.includes(":") && !hostname.startsWith("[")) {
26
+ return `[${hostname}]`;
27
+ }
28
+ return hostname;
29
+ }
30
+ function buildUrl(hostname, port, path) {
31
+ const url = new URL("http://127.0.0.1");
32
+ url.hostname = formatHostnameForUrl(hostname);
33
+ url.port = String(port);
34
+ url.pathname = path;
35
+ url.search = "";
36
+ url.hash = "";
37
+ return url.toString();
38
+ }
39
+ function listen(server, port, hostname) {
40
+ return new Promise((resolve, reject) => {
41
+ const onError = (error) => {
42
+ server.off("listening", onListening);
43
+ reject(error);
44
+ };
45
+ const onListening = () => {
46
+ server.off("error", onError);
47
+ resolve();
48
+ };
49
+ server.once("error", onError);
50
+ server.once("listening", onListening);
51
+ server.listen(port, hostname);
52
+ });
53
+ }
54
+ function closeServer(server) {
55
+ if (!server.listening) {
56
+ return Promise.resolve();
57
+ }
58
+ return new Promise((resolve, reject) => {
59
+ server.close((error) => {
60
+ if (error !== undefined) {
61
+ reject(error);
62
+ return;
63
+ }
64
+ resolve();
65
+ });
66
+ server.closeIdleConnections?.();
67
+ });
68
+ }
69
+ function toUrlString(value) {
70
+ return value instanceof URL ? value.toString() : value;
71
+ }
72
+ export function createProtectedResourceMetadataDocument(options) {
73
+ return {
74
+ resource: toUrlString(options.resource),
75
+ authorization_servers: options.authorizationServers.map(toUrlString),
76
+ ...(options.bearerMethodsSupported !== undefined
77
+ ? {
78
+ bearer_methods_supported: [...options.bearerMethodsSupported]
79
+ }
80
+ : {}),
81
+ ...(options.scopesSupported !== undefined
82
+ ? {
83
+ scopes_supported: [...options.scopesSupported]
84
+ }
85
+ : {})
86
+ };
87
+ }
88
+ export function createHttpServer(options) {
89
+ const requestContextStorage = new AsyncLocalStorage();
90
+ const supportsSessions = !hasOwnProperty(options, "sessionIdGenerator") || options.sessionIdGenerator !== undefined;
91
+ const server = createServer({
92
+ ...options,
93
+ supportNotifications: supportsSessions,
94
+ supportResourceSubscriptions: supportsSessions
95
+ });
96
+ const transport = new StreamableHttpTransport(server, options, async (req, callback) => requestContextStorage.run({
97
+ request: req,
98
+ sessionId: Array.isArray(req.headers["mcp-session-id"])
99
+ ? req.headers["mcp-session-id"][0]
100
+ : req.headers["mcp-session-id"],
101
+ auth: req.auth
102
+ }, callback));
103
+ const protectedResourceMetadataBody = options.oauth === undefined
104
+ ? undefined
105
+ : JSON.stringify(createProtectedResourceMetadataDocument(options.oauth));
106
+ const httpServer = server;
107
+ const registerTool = server.tool.bind(server);
108
+ const registerRichTool = server.registerTool.bind(server);
109
+ const defaultContext = {
110
+ request: { headers: {}, socket: {} }
111
+ };
112
+ const authorizeHttpRequest = async (req, res, protectedResourcePath) => {
113
+ if (options.oauth === undefined || req.method === "OPTIONS") {
114
+ return true;
115
+ }
116
+ const authenticatedRequest = req;
117
+ if (authenticatedRequest.auth !== undefined) {
118
+ return true;
119
+ }
120
+ const authorization = await authorizeBearerRequest(authenticatedRequest, {
121
+ ...options.oauth,
122
+ protectedResourcePath,
123
+ trustedProxy: options.trustedProxy
124
+ });
125
+ if (!authorization.ok) {
126
+ options.observability?.onEvent?.({
127
+ type: "auth.failure",
128
+ statusCode: authorization.statusCode,
129
+ ...(authorization.statusCode === 503 ? {} : { challenge: authorization.challenge }),
130
+ sessionId: Array.isArray(req.headers["mcp-session-id"])
131
+ ? req.headers["mcp-session-id"][0]
132
+ : req.headers["mcp-session-id"]
133
+ });
134
+ res.writeHead(authorization.statusCode, {
135
+ ...(authorization.statusCode === 503
136
+ ? {}
137
+ : { "WWW-Authenticate": authorization.challenge }),
138
+ "X-Content-Type-Options": "nosniff",
139
+ "Referrer-Policy": "no-referrer"
140
+ });
141
+ res.end();
142
+ return false;
143
+ }
144
+ return true;
145
+ };
146
+ httpServer.tool = (name, description, inputSchema, handler, outputSchema) => {
147
+ registerTool(name, description, inputSchema, (args) => handler(args, requestContextStorage.getStore() ?? defaultContext), outputSchema);
148
+ return httpServer;
149
+ };
150
+ httpServer.registerTool = (definition, handler) => {
151
+ registerRichTool(definition, (args) => handler(args, requestContextStorage.getStore() ?? defaultContext));
152
+ return httpServer;
153
+ };
154
+ httpServer.listenHttp = async (listenOptions = {}) => {
155
+ const { port = 0, hostname = "127.0.0.1", path: requestedPath = "/mcp", signal, requestTimeoutMs, headersTimeoutMs, keepAliveTimeoutMs } = listenOptions;
156
+ const path = normalizePath(requestedPath);
157
+ const nodeServer = http.createServer(async (req, res) => {
158
+ const requestUrl = new URL(req.url ?? "/", "http://127.0.0.1");
159
+ try {
160
+ if (protectedResourceMetadataBody !== undefined &&
161
+ req.method === "GET" &&
162
+ getProtectedResourceMetadataPaths(path).includes(requestUrl.pathname)) {
163
+ res.writeHead(200, {
164
+ "Cache-Control": PROTECTED_RESOURCE_METADATA_CACHE_CONTROL,
165
+ "Content-Type": "application/json; charset=utf-8",
166
+ "X-Content-Type-Options": "nosniff",
167
+ "Referrer-Policy": "no-referrer"
168
+ });
169
+ res.end(protectedResourceMetadataBody);
170
+ return;
171
+ }
172
+ if (requestUrl.pathname !== path) {
173
+ res.writeHead(404, {
174
+ "X-Content-Type-Options": "nosniff",
175
+ "Referrer-Policy": "no-referrer"
176
+ });
177
+ res.end();
178
+ return;
179
+ }
180
+ if (!(await authorizeHttpRequest(req, res, path))) {
181
+ return;
182
+ }
183
+ await transport.handleRequest(req, res);
184
+ }
185
+ catch {
186
+ if (!res.headersSent) {
187
+ res.writeHead(500, {
188
+ "X-Content-Type-Options": "nosniff",
189
+ "Referrer-Policy": "no-referrer"
190
+ });
191
+ }
192
+ if (!res.writableEnded) {
193
+ res.end();
194
+ }
195
+ }
196
+ });
197
+ if (requestTimeoutMs !== undefined) {
198
+ nodeServer.requestTimeout = requestTimeoutMs;
199
+ }
200
+ if (headersTimeoutMs !== undefined) {
201
+ nodeServer.headersTimeout = headersTimeoutMs;
202
+ }
203
+ if (keepAliveTimeoutMs !== undefined) {
204
+ nodeServer.keepAliveTimeout = keepAliveTimeoutMs;
205
+ }
206
+ await listen(nodeServer, port, hostname);
207
+ const address = nodeServer.address();
208
+ if (address === null || typeof address === "string") {
209
+ throw new Error("Expected Node HTTP server to bind to a TCP port");
210
+ }
211
+ const resolvedPort = address.port;
212
+ let closePromise;
213
+ let removeAbortListener = () => undefined;
214
+ const close = async () => {
215
+ if (closePromise !== undefined) {
216
+ return closePromise;
217
+ }
218
+ closePromise = (async () => {
219
+ removeAbortListener();
220
+ await transport.close();
221
+ await closeServer(nodeServer);
222
+ })().catch((error) => {
223
+ closePromise = undefined;
224
+ throw error;
225
+ });
226
+ return closePromise;
227
+ };
228
+ if (signal !== undefined) {
229
+ const onAbort = () => {
230
+ void close();
231
+ };
232
+ if (signal.aborted) {
233
+ onAbort();
234
+ }
235
+ else {
236
+ signal.addEventListener("abort", onAbort, { once: true });
237
+ removeAbortListener = () => {
238
+ signal.removeEventListener("abort", onAbort);
239
+ };
240
+ }
241
+ }
242
+ return {
243
+ url: buildUrl(hostname, resolvedPort, path),
244
+ port: resolvedPort,
245
+ close,
246
+ closeAllConnections: () => {
247
+ nodeServer.closeAllConnections();
248
+ }
249
+ };
250
+ };
251
+ httpServer.handleRequest = async (req, res) => {
252
+ const { baseUrl } = req;
253
+ const protectedResourcePath = typeof baseUrl === "string" && baseUrl.length > 0 ? baseUrl : "/mcp";
254
+ if (!(await authorizeHttpRequest(req, res, protectedResourcePath))) {
255
+ return;
256
+ }
257
+ await transport.handleRequest(req, res);
258
+ };
259
+ return httpServer;
260
+ }
261
+ function hasOwnProperty(value, key) {
262
+ return Object.prototype.hasOwnProperty.call(value, key);
263
+ }
@@ -0,0 +1,164 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ import type { Server } from "tiny-stdio-mcp-server";
3
+ import { type SessionStore } from "./session.js";
4
+ export type HttpObservabilityEvent = {
5
+ type: "request.start";
6
+ requestId: string;
7
+ method: string;
8
+ path: string;
9
+ sessionId?: string;
10
+ } | {
11
+ type: "request.end";
12
+ requestId: string;
13
+ method: string;
14
+ statusCode: number;
15
+ durationMs: number;
16
+ sessionId?: string;
17
+ reason?: string;
18
+ } | {
19
+ type: "request.error";
20
+ requestId: string;
21
+ method: string;
22
+ durationMs: number;
23
+ error: unknown;
24
+ sessionId?: string;
25
+ } | {
26
+ type: "auth.failure";
27
+ statusCode: number;
28
+ challenge?: string;
29
+ sessionId?: string;
30
+ } | {
31
+ type: "session.created";
32
+ sessionId: string;
33
+ } | {
34
+ type: "session.deleted";
35
+ sessionId: string;
36
+ reason: "client" | "expired" | "closed";
37
+ } | {
38
+ type: "stream.opened";
39
+ sessionId: string;
40
+ streamCount: number;
41
+ } | {
42
+ type: "stream.closed";
43
+ sessionId: string;
44
+ streamCount: number;
45
+ } | {
46
+ type: "tool.start";
47
+ requestId: string;
48
+ sessionId?: string;
49
+ toolName?: string;
50
+ } | {
51
+ type: "tool.end";
52
+ requestId: string;
53
+ sessionId?: string;
54
+ toolName?: string;
55
+ ok: boolean;
56
+ durationMs: number;
57
+ };
58
+ export interface HttpObservabilityOptions {
59
+ onEvent?(event: HttpObservabilityEvent): void;
60
+ }
61
+ export interface StreamableHttpTransportOptions {
62
+ sessionIdGenerator?: (() => string) | undefined;
63
+ enableJsonResponse?: boolean;
64
+ allowedOrigins?: readonly string[];
65
+ allowedHosts?: readonly string[];
66
+ maxRequestBytes?: number;
67
+ maxBatchSize?: number;
68
+ maxSessions?: number;
69
+ sessionTtlMs?: number;
70
+ maxStreamsPerSession?: number;
71
+ maxStreamBufferBytes?: number;
72
+ maxSseEventHistory?: number;
73
+ sseKeepAliveMs?: number;
74
+ maxConcurrentToolCalls?: number;
75
+ sessionStore?: SessionStore;
76
+ requestIdGenerator?: () => string;
77
+ observability?: HttpObservabilityOptions;
78
+ trustedProxy?: boolean;
79
+ }
80
+ type RequestContextRunner = <T>(req: IncomingMessage, callback: () => Promise<T>) => Promise<T>;
81
+ export declare class StreamableHttpTransport {
82
+ private readonly server;
83
+ private readonly runWithRequestContext;
84
+ private readonly sessionIdGenerator;
85
+ private readonly enableJsonResponse;
86
+ private readonly allowedOrigins;
87
+ private readonly allowedHosts;
88
+ private readonly maxRequestBytes;
89
+ private readonly maxBatchSize;
90
+ private readonly maxSessions;
91
+ private readonly sessionTtlMs;
92
+ private readonly maxStreamsPerSession;
93
+ private readonly maxStreamBufferBytes;
94
+ private readonly maxSseEventHistory;
95
+ private readonly sseKeepAliveMs;
96
+ private readonly maxConcurrentToolCalls;
97
+ private readonly sessionStore;
98
+ private readonly requestIdGenerator;
99
+ private readonly observability;
100
+ private readonly trustedProxy;
101
+ private readonly sessionMessages;
102
+ private readonly sseStreams;
103
+ private readonly sseExpiryTimers;
104
+ private readonly sseEventHistory;
105
+ private readonly responseRequestIds;
106
+ private readonly responseOrigins;
107
+ private readonly responseRejectionReasons;
108
+ private nextNotificationEventId;
109
+ private nextRequestId;
110
+ private activeToolCalls;
111
+ private closed;
112
+ private sessionExpiryInterval;
113
+ private sseKeepAliveInterval;
114
+ constructor(server: Server, options?: StreamableHttpTransportOptions, runWithRequestContext?: RequestContextRunner);
115
+ handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void>;
116
+ close(): Promise<void>;
117
+ private handlePost;
118
+ private handleGet;
119
+ private handleDelete;
120
+ private handleOptions;
121
+ private readSessionId;
122
+ private acceptsProtocolVersion;
123
+ private readRequestId;
124
+ private readOrigin;
125
+ private readToolName;
126
+ private getActiveSession;
127
+ private readAuthSubject;
128
+ private touchSession;
129
+ private isExpired;
130
+ private purgeExpiredSessions;
131
+ private sessionCount;
132
+ private deleteSession;
133
+ private createLocalMessageSession;
134
+ private ensureLocalMessageSession;
135
+ private closeStreamsForSession;
136
+ private scheduleSseExpiry;
137
+ private clearSseExpiryTimer;
138
+ private startSseKeepAlive;
139
+ private stopSseKeepAliveIfIdle;
140
+ private stopSseKeepAlive;
141
+ private sendNotificationToSession;
142
+ private writeToLiveGetStream;
143
+ private recordSseEvent;
144
+ private replaySseEvents;
145
+ private isJsonRequest;
146
+ private acceptsOrigin;
147
+ private acceptsOriginValue;
148
+ private readRequestProtocol;
149
+ private readRequestHost;
150
+ private readForwardedHeader;
151
+ private acceptsHost;
152
+ private normalizeHost;
153
+ private acceptsConfiguredResponse;
154
+ private acceptsResponseType;
155
+ private isValidNewSessionId;
156
+ private isRequest;
157
+ private isToolCallOk;
158
+ private respondWithJsonRpcError;
159
+ private respondWithRejection;
160
+ private respondWithStatus;
161
+ private withSessionHeader;
162
+ private emit;
163
+ }
164
+ export {};