@toolfactory.dev/core 1.0.0-rc
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.
- package/LICENSE +21 -0
- package/README.md +88 -0
- package/out/codegen/access-stubs.d.ts +2 -0
- package/out/codegen/access-stubs.js +6 -0
- package/out/codegen/auth-module-names.d.ts +11 -0
- package/out/codegen/auth-module-names.js +34 -0
- package/out/codegen/auth-pipeline-render.d.ts +10 -0
- package/out/codegen/auth-pipeline-render.js +157 -0
- package/out/codegen/auth-stub-bootstrap.d.ts +42 -0
- package/out/codegen/auth-stub-bootstrap.js +252 -0
- package/out/codegen/document-validation.d.ts +22 -0
- package/out/codegen/document-validation.js +76 -0
- package/out/codegen/generated-layout.d.ts +15 -0
- package/out/codegen/generated-layout.js +53 -0
- package/out/codegen/index.d.ts +20 -0
- package/out/codegen/index.js +20 -0
- package/out/codegen/langium-cli-types.d.ts +45 -0
- package/out/codegen/langium-cli-types.js +1 -0
- package/out/codegen/logging-adapter-bootstrap.d.ts +6 -0
- package/out/codegen/logging-adapter-bootstrap.js +69 -0
- package/out/codegen/mcp-host-credential-validation.d.ts +5 -0
- package/out/codegen/mcp-host-credential-validation.js +15 -0
- package/out/codegen/mcp-host-product-runtime.d.ts +22 -0
- package/out/codegen/mcp-host-product-runtime.js +413 -0
- package/out/codegen/project-bootstrap.d.ts +29 -0
- package/out/codegen/project-bootstrap.js +153 -0
- package/out/codegen/render-http-mcp-server.d.ts +3 -0
- package/out/codegen/render-http-mcp-server.js +194 -0
- package/out/codegen/render-mcp-host-shared.d.ts +7 -0
- package/out/codegen/render-mcp-host-shared.js +671 -0
- package/out/codegen/render-oauth-http-mcp-server.d.ts +5 -0
- package/out/codegen/render-oauth-http-mcp-server.js +220 -0
- package/out/codegen/render-stdio-mcp-server.d.ts +5 -0
- package/out/codegen/render-stdio-mcp-server.js +58 -0
- package/out/codegen/write-demos-test-support.d.ts +2 -0
- package/out/codegen/write-demos-test-support.js +28 -0
- package/out/codegen/zod-codegen.d.ts +9 -0
- package/out/codegen/zod-codegen.js +149 -0
- package/out/scripts/generated-scripts-banner.d.ts +2 -0
- package/out/scripts/generated-scripts-banner.js +2 -0
- package/out/scripts/render-kill-listeners-on-port.mjs.d.ts +1 -0
- package/out/scripts/render-kill-listeners-on-port.mjs.js +81 -0
- package/out/scripts/render-load-env-local.mjs.d.ts +1 -0
- package/out/scripts/render-load-env-local.mjs.js +67 -0
- package/out/scripts/render-require-env.mjs.d.ts +1 -0
- package/out/scripts/render-require-env.mjs.js +36 -0
- package/out/scripts/write-generated-scripts.d.ts +4 -0
- package/out/scripts/write-generated-scripts.js +24 -0
- package/package.json +58 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { renderMcpHostSharedSource } from './render-mcp-host-shared.js';
|
|
2
|
+
import { requireBaseUrlEnvArgvCheck } from './mcp-host-product-runtime.js';
|
|
3
|
+
const PROFILE_LOG_LABEL = {
|
|
4
|
+
public: 'public HTTP',
|
|
5
|
+
passthrough: 'passthrough HTTP'
|
|
6
|
+
};
|
|
7
|
+
const PROFILE_FILE = {
|
|
8
|
+
public: 'public-http-mcp-server',
|
|
9
|
+
passthrough: 'passthrough-http-mcp-server'
|
|
10
|
+
};
|
|
11
|
+
function renderHttpMcpServerSourceForProfile(profile, product = 'api2ai', loggingImport) {
|
|
12
|
+
const mode = profile === 'public' ? 'public-http' : 'passthrough-http';
|
|
13
|
+
const shared = renderMcpHostSharedSource(mode, product);
|
|
14
|
+
const fileBase = PROFILE_FILE[profile];
|
|
15
|
+
const logLabel = PROFILE_LOG_LABEL[profile];
|
|
16
|
+
const credentialHeaderExpr = profile === 'public' ? 'undefined' : 'readAuthHeaderNameFromEnv()';
|
|
17
|
+
return `#!/usr/bin/env node
|
|
18
|
+
/**
|
|
19
|
+
* Generated ${logLabel} MCP Streamable HTTP host (static runtime — no @toolfactory.dev/core).
|
|
20
|
+
*/
|
|
21
|
+
import { randomUUID } from 'node:crypto';
|
|
22
|
+
import * as fs from 'node:fs';
|
|
23
|
+
import * as http from 'node:http';
|
|
24
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
25
|
+
import * as path from 'node:path';
|
|
26
|
+
import { pathToFileURL } from 'node:url';
|
|
27
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
28
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
29
|
+
import { ListToolsRequestSchema, type ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
|
|
30
|
+
import * as z from 'zod/v4';
|
|
31
|
+
import { loggingAdapter } from '${loggingImport}';
|
|
32
|
+
|
|
33
|
+
${shared}
|
|
34
|
+
|
|
35
|
+
type SessionEntry = {
|
|
36
|
+
transport: StreamableHTTPServerTransport;
|
|
37
|
+
server: McpServer;
|
|
38
|
+
sessionId: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const sessionEntries = new Map<string, SessionEntry>();
|
|
42
|
+
const sessionHeaders = new Map<string, Record<string, string | string[] | undefined>>();
|
|
43
|
+
|
|
44
|
+
function isInitializeRequestBody(body: unknown): boolean {
|
|
45
|
+
if (Array.isArray(body)) {
|
|
46
|
+
return body.some((item) => isInitializeRequestBody(item));
|
|
47
|
+
}
|
|
48
|
+
if (!body || typeof body !== 'object') {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
const record = body as Record<string, unknown>;
|
|
52
|
+
return record.jsonrpc === '2.0' && record.method === 'initialize';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readSessionId(req: IncomingMessage): string | undefined {
|
|
56
|
+
const raw = req.headers['mcp-session-id'];
|
|
57
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
58
|
+
return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function createMcpServerForSession(
|
|
62
|
+
generated: GeneratedHostModule,
|
|
63
|
+
httpHostConfig: HttpMcpHostRuntimeConfig,
|
|
64
|
+
sessionId: string,
|
|
65
|
+
headers: Record<string, string | string[] | undefined>
|
|
66
|
+
): Promise<SessionEntry> {
|
|
67
|
+
const { name, version } = requireMcpServerIdentity(generated);
|
|
68
|
+
const server = new McpServer({ name, version });
|
|
69
|
+
sessionHeaders.set(sessionId, headers);
|
|
70
|
+
await registerMcpTools(server, generated, {
|
|
71
|
+
envDirs: httpHostConfig.envDirs,
|
|
72
|
+
resolveContext: () =>
|
|
73
|
+
resolveHostContextForHttpCall(
|
|
74
|
+
httpHostConfig,
|
|
75
|
+
generated,
|
|
76
|
+
sessionHeaders.get(sessionId) ?? headers
|
|
77
|
+
)
|
|
78
|
+
});
|
|
79
|
+
const transport = new StreamableHTTPServerTransport({
|
|
80
|
+
sessionIdGenerator: () => sessionId
|
|
81
|
+
});
|
|
82
|
+
transport.onclose = () => {
|
|
83
|
+
sessionEntries.delete(sessionId);
|
|
84
|
+
sessionHeaders.delete(sessionId);
|
|
85
|
+
// Transport already closed (onclose runs from transport.close). Do not call server.close()
|
|
86
|
+
// here — that re-enters transport.close() and overflows the stack.
|
|
87
|
+
};
|
|
88
|
+
await server.connect(transport);
|
|
89
|
+
return { transport, server, sessionId };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function handleHttpMcpRequest(
|
|
93
|
+
req: IncomingMessage,
|
|
94
|
+
res: ServerResponse,
|
|
95
|
+
generated: GeneratedHostModule,
|
|
96
|
+
httpHostConfig: HttpMcpHostRuntimeConfig
|
|
97
|
+
): Promise<void> {
|
|
98
|
+
const headers = req.headers as Record<string, string | string[] | undefined>;
|
|
99
|
+
const sessionIdHeader = readSessionId(req);
|
|
100
|
+
const parsedBody = req.method === 'POST' ? await readMcpHttpJsonBody(req) : undefined;
|
|
101
|
+
|
|
102
|
+
let entry: SessionEntry | undefined;
|
|
103
|
+
if (sessionIdHeader && sessionEntries.has(sessionIdHeader)) {
|
|
104
|
+
entry = sessionEntries.get(sessionIdHeader);
|
|
105
|
+
} else if (req.method === 'POST' && isInitializeRequestBody(parsedBody)) {
|
|
106
|
+
const newSessionId = randomUUID();
|
|
107
|
+
entry = await createMcpServerForSession(generated, httpHostConfig, newSessionId, headers);
|
|
108
|
+
sessionEntries.set(newSessionId, entry);
|
|
109
|
+
} else if (sessionIdHeader) {
|
|
110
|
+
writeJsonRpcError(res, 404, -32_001, 'Session not found');
|
|
111
|
+
return;
|
|
112
|
+
} else if (req.method === 'POST') {
|
|
113
|
+
writeJsonRpcError(res, 400, -32_000, 'Bad Request: Session ID required');
|
|
114
|
+
return;
|
|
115
|
+
} else {
|
|
116
|
+
writeJsonRpcMethodNotAllowed(res);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!entry) {
|
|
121
|
+
writeJsonRpcInternalError(res);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
sessionHeaders.set(entry.sessionId, headers);
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await entry.transport.handleRequest(req, res, parsedBody);
|
|
129
|
+
} catch (err) {
|
|
130
|
+
loggingAdapter.error('[mcp] ${logLabel} request failed', {
|
|
131
|
+
error: err instanceof Error ? err.message : String(err)
|
|
132
|
+
});
|
|
133
|
+
if (!res.headersSent) {
|
|
134
|
+
writeJsonRpcInternalError(res);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async function runHttpMcpStandaloneFromArgv(argv: string[]): Promise<void> {
|
|
140
|
+
const modulePath = argv[0];
|
|
141
|
+
if (!modulePath) {
|
|
142
|
+
throw new Error(
|
|
143
|
+
'Usage: node ${fileBase}.js <path-to-*-tools.js> [--base-url-env ENV] --port N [--host HOST] [--path /mcp]'
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
const envDirs = [process.cwd(), path.dirname(path.resolve(modulePath))];
|
|
147
|
+
loadLocalEnvFiles(envDirs);
|
|
148
|
+
const imported = await import(pathToFileURL(path.resolve(modulePath)).href);
|
|
149
|
+
if (!imported || typeof imported !== 'object') {
|
|
150
|
+
throw new Error(\`Generated module "\${modulePath}" did not export an object.\`);
|
|
151
|
+
}
|
|
152
|
+
const generated = readGeneratedModule(imported as Record<string, unknown>);
|
|
153
|
+
const httpHostConfig = parseHttpMcpHostArgv(argv.slice(1), envDirs);
|
|
154
|
+
${requireBaseUrlEnvArgvCheck(product, 'httpHostConfig.baseUrlEnvKey')}
|
|
155
|
+
validateHttpMcpHostAtStartup(httpHostConfig, generated);
|
|
156
|
+
loggingAdapter.info('[mcp] ${logLabel} listening', {
|
|
157
|
+
url:
|
|
158
|
+
'http://' +
|
|
159
|
+
httpHostConfig.listenHost +
|
|
160
|
+
':' +
|
|
161
|
+
httpHostConfig.port +
|
|
162
|
+
httpHostConfig.mcpPath,
|
|
163
|
+
profile: '${profile}',
|
|
164
|
+
credentialHeader: ${credentialHeaderExpr}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
168
|
+
const url = new URL(req.url ?? '/', 'http://' + (req.headers.host ?? 'localhost'));
|
|
169
|
+
if (url.pathname !== httpHostConfig.mcpPath) {
|
|
170
|
+
res.writeHead(404).end('Not found');
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
if (req.method === 'POST' || req.method === 'GET' || req.method === 'DELETE') {
|
|
174
|
+
await handleHttpMcpRequest(req, res, generated, httpHostConfig);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
res.writeHead(405).end('Method not allowed');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
await new Promise<void>((resolve, reject) => {
|
|
181
|
+
httpServer.once('error', reject);
|
|
182
|
+
httpServer.listen(httpHostConfig.port, httpHostConfig.listenHost, () => resolve());
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await runHttpMcpStandaloneFromArgv(process.argv.slice(2));
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
export function renderPublicHttpMcpServerSource(product = 'api2ai', loggingImport) {
|
|
190
|
+
return renderHttpMcpServerSourceForProfile('public', product, loggingImport);
|
|
191
|
+
}
|
|
192
|
+
export function renderPassthroughHttpMcpServerSource(product = 'api2ai', loggingImport) {
|
|
193
|
+
return renderHttpMcpServerSourceForProfile('passthrough', product, loggingImport);
|
|
194
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type McpHostProduct, type HttpMcpHostProfile } from './mcp-host-product-runtime.js';
|
|
2
|
+
export type { McpHostProduct, HttpMcpHostProfile };
|
|
3
|
+
export type McpHostSharedMode = 'stdio' | 'public-http' | 'passthrough-http' | 'oauth-http';
|
|
4
|
+
/**
|
|
5
|
+
* Shared generated MCP host runtime (env loading, host config, tool registration).
|
|
6
|
+
*/
|
|
7
|
+
export declare function renderMcpHostSharedSource(mode: McpHostSharedMode, product?: McpHostProduct): string;
|