@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Annette Pohl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# core2ai
|
|
2
|
+
|
|
3
|
+
> Shared runtime, code generation infrastructure, and documentation for the api2ai ecosystem.
|
|
4
|
+
|
|
5
|
+
## Ecosystem
|
|
6
|
+
|
|
7
|
+
| Repository | Purpose |
|
|
8
|
+
| ----------------------------------------------------- | ------------------------------------------------------ |
|
|
9
|
+
| [core2ai](https://github.com/annettedorothea/core2ai) | Shared runtime, architecture, and documentation |
|
|
10
|
+
| [api2ai](https://github.com/annettedorothea/api2ai) | Generate curated MCP tools from OpenAPI specifications |
|
|
11
|
+
| [db2ai](https://github.com/annettedorothea/db2ai) | Generate curated MCP tools from relational databases |
|
|
12
|
+
|
|
13
|
+
`core2ai` provides the common foundation used by the other projects in the ecosystem.
|
|
14
|
+
|
|
15
|
+
It contains the shared infrastructure for:
|
|
16
|
+
|
|
17
|
+
- code generation
|
|
18
|
+
- validation
|
|
19
|
+
- MCP host generation
|
|
20
|
+
- authentication bootstrap
|
|
21
|
+
- runtime components
|
|
22
|
+
- architecture and documentation
|
|
23
|
+
|
|
24
|
+
If you want to generate MCP tools, you will usually start with one of the sibling projects instead.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Documentation
|
|
29
|
+
|
|
30
|
+
The shared documentation for the ecosystem lives in this repository.
|
|
31
|
+
|
|
32
|
+
Topics include:
|
|
33
|
+
|
|
34
|
+
- Architecture
|
|
35
|
+
- Tool Factory
|
|
36
|
+
- Tool Authoring
|
|
37
|
+
- AI Runtime
|
|
38
|
+
- Personas
|
|
39
|
+
- Authentication
|
|
40
|
+
- Integrations
|
|
41
|
+
- Development Guides
|
|
42
|
+
|
|
43
|
+
Start here:
|
|
44
|
+
|
|
45
|
+
- [Documentation](docs/README.md)
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Related Projects
|
|
50
|
+
|
|
51
|
+
- [api2ai](https://github.com/annettedorothea/api2ai) — Generate curated MCP tools from OpenAPI specifications.
|
|
52
|
+
- [db2ai](https://github.com/annettedorothea/db2ai) — Generate curated MCP tools from relational databases.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Development
|
|
57
|
+
|
|
58
|
+
`core2ai` is currently consumed as a local dependency by `api2ai` and `db2ai` and is not published to npm.
|
|
59
|
+
|
|
60
|
+
Install dependencies:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npm install
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Whenever you make changes, rebuild the package so the dependent repositories can pick up the latest version:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npm run build
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Although a watch mode exists, rebuilding after changes is currently the recommended workflow.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT — see [LICENSE](LICENSE).
|
|
79
|
+
|
|
80
|
+
Questions, ideas, bug reports, and feature requests are always welcome through GitHub Discussions or Issues.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
> _Whatever you do, work heartily, as for the Lord and not for men._
|
|
85
|
+
>
|
|
86
|
+
> **— Colossians 3:23**
|
|
87
|
+
>
|
|
88
|
+
> _Created by Annette Pohl_
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ModuleCredentialNames = {
|
|
2
|
+
pascalBase: string;
|
|
3
|
+
className: string;
|
|
4
|
+
toFunctionName: string;
|
|
5
|
+
verifyFunctionName: string;
|
|
6
|
+
fileBase: string;
|
|
7
|
+
};
|
|
8
|
+
/** Derive per-module credential type/file names from `generated/{product}/tools/<module>-tools.ts`. */
|
|
9
|
+
export declare function resolveModuleCredentialNames(toolsModuleTsPath: string): ModuleCredentialNames;
|
|
10
|
+
export declare function resolveVerifyCredentialsStubFileName(toolsModuleTsPath: string): string;
|
|
11
|
+
export declare function resolveVerifyCredentialsStubPath(projectRoot: string, toolsModuleTsPath: string): string;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import * as path from 'node:path';
|
|
2
|
+
import { resolveHostProductFromGeneratedToolsPath } from './generated-layout.js';
|
|
3
|
+
function kebabSegmentToPascal(segment) {
|
|
4
|
+
if (segment.length === 0) {
|
|
5
|
+
return '';
|
|
6
|
+
}
|
|
7
|
+
return segment.charAt(0).toUpperCase() + segment.slice(1);
|
|
8
|
+
}
|
|
9
|
+
function kebabToPascal(kebab) {
|
|
10
|
+
return kebab.split('-').map(kebabSegmentToPascal).join('');
|
|
11
|
+
}
|
|
12
|
+
/** Derive per-module credential type/file names from `generated/{product}/tools/<module>-tools.ts`. */
|
|
13
|
+
export function resolveModuleCredentialNames(toolsModuleTsPath) {
|
|
14
|
+
let base = path.parse(toolsModuleTsPath).name;
|
|
15
|
+
if (base.endsWith('-tools')) {
|
|
16
|
+
base = base.slice(0, -'-tools'.length);
|
|
17
|
+
}
|
|
18
|
+
const pascalBase = kebabToPascal(base);
|
|
19
|
+
return {
|
|
20
|
+
pascalBase,
|
|
21
|
+
className: `${pascalBase}Credentials`,
|
|
22
|
+
toFunctionName: `to${pascalBase}Credentials`,
|
|
23
|
+
verifyFunctionName: `verify${pascalBase}Credentials`,
|
|
24
|
+
fileBase: `verify${pascalBase}Credentials`
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function resolveVerifyCredentialsStubFileName(toolsModuleTsPath) {
|
|
28
|
+
return `${resolveModuleCredentialNames(toolsModuleTsPath).fileBase}.ts`;
|
|
29
|
+
}
|
|
30
|
+
export function resolveVerifyCredentialsStubPath(projectRoot, toolsModuleTsPath) {
|
|
31
|
+
const hostProduct = resolveHostProductFromGeneratedToolsPath(toolsModuleTsPath);
|
|
32
|
+
const mcpModuleName = path.parse(toolsModuleTsPath).name;
|
|
33
|
+
return path.join(projectRoot, 'src', 'auth', hostProduct, mcpModuleName, resolveVerifyCredentialsStubFileName(toolsModuleTsPath));
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type AuthPipelineTier = 'none' | 'credential' | 'full';
|
|
2
|
+
export type HookStubMaps = {
|
|
3
|
+
authorizers: boolean;
|
|
4
|
+
preparers: boolean;
|
|
5
|
+
};
|
|
6
|
+
/** @deprecated Use HookStubMaps */
|
|
7
|
+
export type AuthStubMaps = HookStubMaps;
|
|
8
|
+
export type AuthPipelineProfile = 'api2ai' | 'db2ai';
|
|
9
|
+
export declare function resolveAuthPipelineTier(hasAuthPipeline: boolean, authorizeToolNames: readonly string[], prepareToolNames: readonly string[]): AuthPipelineTier;
|
|
10
|
+
export declare function renderInvokeAuthPipeline(profile: AuthPipelineProfile, tier: AuthPipelineTier, hasVerifyCredential: boolean, stubMaps: HookStubMaps): string;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const MISSING_CREDENTIAL_ERROR = `
|
|
2
|
+
throw new Error(
|
|
3
|
+
'Missing host credential. stdio: set env for --auth-env on stdio-mcp-server; passthrough HTTP: MCP auth header (e.g. x-api-token); OAuth HTTP: complete MCP login (Authorization Bearer from Cursor).'
|
|
4
|
+
);`;
|
|
5
|
+
function renderUrlAndHeadersPreamble() {
|
|
6
|
+
return `
|
|
7
|
+
const normalizedBaseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
8
|
+
const pathParams = { ...(optionsResolved.pathParams ?? {}) };
|
|
9
|
+
let resolvedPath = tool.path;
|
|
10
|
+
for (const [key, value] of Object.entries(pathParams)) {
|
|
11
|
+
resolvedPath = resolvedPath.split('{' + key + '}').join(encodeURIComponent(String(value)));
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const url = new URL(normalizedBaseUrl + resolvedPath);
|
|
15
|
+
appendSerializedQueryParams(url.searchParams, tool.toolName, optionsResolved.query);
|
|
16
|
+
const requestHeaders: Record<string, string> = {
|
|
17
|
+
'content-type': 'application/json',
|
|
18
|
+
...(optionsResolved.headers ?? {})
|
|
19
|
+
};`;
|
|
20
|
+
}
|
|
21
|
+
function renderInvokeCredentialPipeline(profile, hasVerifyCredential) {
|
|
22
|
+
if (profile === 'api2ai') {
|
|
23
|
+
const verifyBlock = hasVerifyCredential
|
|
24
|
+
? `
|
|
25
|
+
if (upstreamCredential === undefined) {
|
|
26
|
+
const verified = await verifyCredential({ inboundCredential: String(inbound).trim() });
|
|
27
|
+
upstreamCredential = verified.upstreamCredential;
|
|
28
|
+
}`
|
|
29
|
+
: '';
|
|
30
|
+
return `
|
|
31
|
+
let upstreamCredential = host.upstreamCredential;
|
|
32
|
+
let authCredential = host.credential;
|
|
33
|
+
|
|
34
|
+
if (tool.access === 'protected') {
|
|
35
|
+
const inbound = host.credential;
|
|
36
|
+
if (!inbound || !String(inbound).trim()) {${MISSING_CREDENTIAL_ERROR}
|
|
37
|
+
}${verifyBlock}
|
|
38
|
+
authCredential = upstreamCredential ?? String(inbound).trim();
|
|
39
|
+
}${renderUrlAndHeadersPreamble()}`;
|
|
40
|
+
}
|
|
41
|
+
const verifyBlock = hasVerifyCredential
|
|
42
|
+
? `
|
|
43
|
+
await verifyCredential({ inboundCredential: String(inbound).trim() });`
|
|
44
|
+
: '';
|
|
45
|
+
return `
|
|
46
|
+
if (toolMeta.access === 'protected') {
|
|
47
|
+
const inbound = host.credential;
|
|
48
|
+
if (!inbound || !String(inbound).trim()) {${MISSING_CREDENTIAL_ERROR}
|
|
49
|
+
}${verifyBlock}
|
|
50
|
+
}`;
|
|
51
|
+
}
|
|
52
|
+
export function resolveAuthPipelineTier(hasAuthPipeline, authorizeToolNames, prepareToolNames) {
|
|
53
|
+
if (!hasAuthPipeline) {
|
|
54
|
+
return 'none';
|
|
55
|
+
}
|
|
56
|
+
if (authorizeToolNames.length > 0 || prepareToolNames.length > 0) {
|
|
57
|
+
return 'full';
|
|
58
|
+
}
|
|
59
|
+
return 'credential';
|
|
60
|
+
}
|
|
61
|
+
export function renderInvokeAuthPipeline(profile, tier, hasVerifyCredential, stubMaps) {
|
|
62
|
+
if (tier === 'credential') {
|
|
63
|
+
return renderInvokeCredentialPipeline(profile, hasVerifyCredential);
|
|
64
|
+
}
|
|
65
|
+
if (tier !== 'full') {
|
|
66
|
+
throw new Error('renderInvokeAuthPipeline: tier must be credential or full');
|
|
67
|
+
}
|
|
68
|
+
const toolRef = profile === 'api2ai' ? 'tool' : 'toolMeta';
|
|
69
|
+
const verifyBlock = profile === 'api2ai'
|
|
70
|
+
? hasVerifyCredential
|
|
71
|
+
? `
|
|
72
|
+
if (credentialsForStubs === undefined || upstreamCredential === undefined) {
|
|
73
|
+
const verified = await verifyCredential({ inboundCredential: String(inbound).trim() });
|
|
74
|
+
upstreamCredential = verified.upstreamCredential;
|
|
75
|
+
credentialsForStubs = verified.credentials;
|
|
76
|
+
}`
|
|
77
|
+
: ''
|
|
78
|
+
: hasVerifyCredential
|
|
79
|
+
? `
|
|
80
|
+
if (credentialsForStubs === undefined) {
|
|
81
|
+
const verified = await verifyCredential({ inboundCredential: String(inbound).trim() });
|
|
82
|
+
credentialsForStubs = verified.credentials;
|
|
83
|
+
}`
|
|
84
|
+
: '';
|
|
85
|
+
const authorizeBlock = stubMaps.authorizers
|
|
86
|
+
? `
|
|
87
|
+
if (${toolRef}.hasAuthorize) {
|
|
88
|
+
const authorize = authorizers[toolName];
|
|
89
|
+
if (typeof authorize !== 'function') {
|
|
90
|
+
throw new Error('No authorizer for tool: ' + toolName);
|
|
91
|
+
}
|
|
92
|
+
await Promise.resolve(authorize(credentialsForStubs!));
|
|
93
|
+
}`
|
|
94
|
+
: '';
|
|
95
|
+
const needsCredentials = hasVerifyCredential || stubMaps.authorizers;
|
|
96
|
+
const prepareBlock = stubMaps.preparers
|
|
97
|
+
? needsCredentials
|
|
98
|
+
? `
|
|
99
|
+
if (${toolRef}.hasPrepare) {
|
|
100
|
+
const prepare = preparers[toolName];
|
|
101
|
+
if (typeof prepare !== 'function') {
|
|
102
|
+
throw new Error('No preparer for tool: ' + toolName);
|
|
103
|
+
}
|
|
104
|
+
if (${toolRef}.access === 'protected') {
|
|
105
|
+
if (credentialsForStubs === undefined) {
|
|
106
|
+
throw new Error('Prepare requires credentials; verify credential or pass host.credentials.');
|
|
107
|
+
}
|
|
108
|
+
optionsResolved = await Promise.resolve(prepare(optionsResolved, credentialsForStubs));
|
|
109
|
+
} else {
|
|
110
|
+
optionsResolved = await Promise.resolve(prepare(optionsResolved));
|
|
111
|
+
}
|
|
112
|
+
}`
|
|
113
|
+
: `
|
|
114
|
+
if (${toolRef}.hasPrepare) {
|
|
115
|
+
const prepare = preparers[toolName];
|
|
116
|
+
if (typeof prepare !== 'function') {
|
|
117
|
+
throw new Error('No preparer for tool: ' + toolName);
|
|
118
|
+
}
|
|
119
|
+
optionsResolved = await Promise.resolve(prepare(optionsResolved));
|
|
120
|
+
}`
|
|
121
|
+
: '';
|
|
122
|
+
const api2aiCredentialsPreamble = needsCredentials
|
|
123
|
+
? `
|
|
124
|
+
let upstreamCredential = host.upstreamCredential;
|
|
125
|
+
const credentialsPlain = host.credentials;
|
|
126
|
+
let credentialsForStubs: ModuleCredentials | undefined =
|
|
127
|
+
credentialsPlain != null
|
|
128
|
+
? toModuleCredentials(credentialsPlain as Record<string, unknown>)
|
|
129
|
+
: undefined;
|
|
130
|
+
let authCredential = host.credential;
|
|
131
|
+
|
|
132
|
+
if (tool.access === 'protected') {
|
|
133
|
+
const inbound = host.credential;
|
|
134
|
+
if (!inbound || !String(inbound).trim()) {${MISSING_CREDENTIAL_ERROR}
|
|
135
|
+
}${verifyBlock}
|
|
136
|
+
authCredential = upstreamCredential ?? String(inbound).trim();${authorizeBlock}
|
|
137
|
+
}${prepareBlock}${renderUrlAndHeadersPreamble()}`
|
|
138
|
+
: `
|
|
139
|
+
${prepareBlock}${renderUrlAndHeadersPreamble()}`;
|
|
140
|
+
const db2aiCredentialsPreamble = needsCredentials
|
|
141
|
+
? `
|
|
142
|
+
const credentialsPlain = host.credentials;
|
|
143
|
+
let credentialsForStubs: ModuleCredentials | undefined =
|
|
144
|
+
credentialsPlain != null
|
|
145
|
+
? toModuleCredentials(credentialsPlain as Record<string, unknown>)
|
|
146
|
+
: undefined;
|
|
147
|
+
|
|
148
|
+
if (toolMeta.access === 'protected') {
|
|
149
|
+
const inbound = host.credential;
|
|
150
|
+
if (!inbound || !String(inbound).trim()) {${MISSING_CREDENTIAL_ERROR}
|
|
151
|
+
}${verifyBlock}${authorizeBlock}
|
|
152
|
+
}${prepareBlock}`
|
|
153
|
+
: `
|
|
154
|
+
${prepareBlock}`;
|
|
155
|
+
const api2aiPreamble = profile === 'api2ai' ? api2aiCredentialsPreamble : db2aiCredentialsPreamble;
|
|
156
|
+
return api2aiPreamble;
|
|
157
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type ToolHookStubSpec = {
|
|
2
|
+
toolName: string;
|
|
3
|
+
authorize: boolean;
|
|
4
|
+
prepare: boolean;
|
|
5
|
+
access: 'public' | 'protected';
|
|
6
|
+
};
|
|
7
|
+
/** @deprecated Use ToolHookStubSpec */
|
|
8
|
+
export type ToolAuthStubSpec = ToolHookStubSpec;
|
|
9
|
+
/** Basename of `generated/{product}/tools/<name>-tools.ts` — matches exported `mcpServerName`. */
|
|
10
|
+
export declare function resolveMcpModuleNameFromToolsModule(toolsModuleTsPath: string): string;
|
|
11
|
+
export declare function resolveHookStubDir(projectRoot: string, toolsModuleTsPath: string): string;
|
|
12
|
+
/** @deprecated Use resolveHookStubDir */
|
|
13
|
+
export declare const resolveAuthStubDir: typeof resolveHookStubDir;
|
|
14
|
+
export declare function renderToolHookStubFileContent(toolName: string, spec: ToolHookStubSpec, hookStubTsPath: string, toolsModuleTsPath: string): string;
|
|
15
|
+
/** @deprecated Use renderToolHookStubFileContent */
|
|
16
|
+
export declare const renderToolAuthStubFileContent: typeof renderToolHookStubFileContent;
|
|
17
|
+
export declare function ensureToolHookStubsAtProjectRoot(projectRoot: string, toolSpecs: readonly ToolHookStubSpec[], toolsModuleTsPath: string): Promise<Map<string, string>>;
|
|
18
|
+
/** @deprecated Use ensureToolHookStubsAtProjectRoot */
|
|
19
|
+
export declare const ensureToolAuthStubsAtProjectRoot: typeof ensureToolHookStubsAtProjectRoot;
|
|
20
|
+
export declare function ensureToolHookStubsFromSource(source: string, toolSpecs: readonly ToolHookStubSpec[], toolsModuleTsPath: string): Promise<Map<string, string>>;
|
|
21
|
+
/** @deprecated Use ensureToolHookStubsFromSource */
|
|
22
|
+
export declare const ensureToolAuthStubsFromSource: typeof ensureToolHookStubsFromSource;
|
|
23
|
+
export declare function renderVerifyCredentialsStubFileContent(toolsModuleTsPath: string): string;
|
|
24
|
+
export declare function resolveVerifyCredentialStubPath(projectRoot: string, toolsModuleTsPath: string): string;
|
|
25
|
+
/** Write-once \`src/hooks/{product}/<module>/verify*Credentials.ts\` when DSL has auth. */
|
|
26
|
+
export declare function ensureVerifyCredentialStubAtProjectRoot(projectRoot: string, toolsModuleTsPath: string): Promise<string | undefined>;
|
|
27
|
+
export declare function ensureVerifyCredentialStubFromSource(source: string, toolsModuleTsPath: string): Promise<string | undefined>;
|
|
28
|
+
export declare function renderVerifyCredentialReExport(toolsModuleTsPath: string, verifyStubTsPath: string): string;
|
|
29
|
+
export declare function renderVerifyCredentialImport(toolsModuleTsPath: string, verifyStubTsPath: string, options?: {
|
|
30
|
+
includeVerify?: boolean;
|
|
31
|
+
includeModuleCredentials?: boolean;
|
|
32
|
+
}): string;
|
|
33
|
+
export declare function renderAuthorizersMap(toolNames: readonly string[]): string;
|
|
34
|
+
export declare function renderPreparersMap(toolNames: readonly string[], options?: {
|
|
35
|
+
includeCredentials?: boolean;
|
|
36
|
+
}): string;
|
|
37
|
+
/** @deprecated Use renderPreparersMap */
|
|
38
|
+
export declare const renderValidatorsMap: typeof renderPreparersMap;
|
|
39
|
+
export declare function renderAuthorizerImports(tsPath: string, stubPaths: Map<string, string>, authorizeToolNames: readonly string[]): string;
|
|
40
|
+
export declare function renderPreparerImports(tsPath: string, stubPaths: Map<string, string>): string;
|
|
41
|
+
/** @deprecated Use renderPreparerImports */
|
|
42
|
+
export declare const renderValidatorImports: typeof renderPreparerImports;
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { resolveModuleCredentialNames } from './auth-module-names.js';
|
|
4
|
+
import { authorizeExportName, prepareInputExportName } from './access-stubs.js';
|
|
5
|
+
import { relativeJsImportPath, resolveHostProductFromGeneratedToolsPath } from './generated-layout.js';
|
|
6
|
+
import { resolveBootstrapProjectRootFromSource } from './project-bootstrap.js';
|
|
7
|
+
/** Basename of `generated/{product}/tools/<name>-tools.ts` — matches exported `mcpServerName`. */
|
|
8
|
+
export function resolveMcpModuleNameFromToolsModule(toolsModuleTsPath) {
|
|
9
|
+
return path.parse(toolsModuleTsPath).name;
|
|
10
|
+
}
|
|
11
|
+
export function resolveHookStubDir(projectRoot, toolsModuleTsPath) {
|
|
12
|
+
const hostProduct = resolveHostProductFromGeneratedToolsPath(toolsModuleTsPath);
|
|
13
|
+
const mcpModuleName = resolveMcpModuleNameFromToolsModule(toolsModuleTsPath);
|
|
14
|
+
return path.join(projectRoot, 'src', 'hooks', hostProduct, mcpModuleName);
|
|
15
|
+
}
|
|
16
|
+
/** @deprecated Use resolveHookStubDir */
|
|
17
|
+
export const resolveAuthStubDir = resolveHookStubDir;
|
|
18
|
+
function hookStubRelativePath(toolsModuleTsPath, fileBase) {
|
|
19
|
+
const hostProduct = resolveHostProductFromGeneratedToolsPath(toolsModuleTsPath);
|
|
20
|
+
const mcpModuleName = resolveMcpModuleNameFromToolsModule(toolsModuleTsPath);
|
|
21
|
+
return `src/hooks/${hostProduct}/${mcpModuleName}/${fileBase}.ts`;
|
|
22
|
+
}
|
|
23
|
+
function resolveVerifyStubRelPath(toolsModuleTsPath) {
|
|
24
|
+
const names = resolveModuleCredentialNames(toolsModuleTsPath);
|
|
25
|
+
return hookStubRelativePath(toolsModuleTsPath, names.fileBase);
|
|
26
|
+
}
|
|
27
|
+
function renderToolHookStubBody(toolName, spec, toolsModuleTsPath) {
|
|
28
|
+
const lines = [];
|
|
29
|
+
if (spec.authorize) {
|
|
30
|
+
const fn = authorizeExportName(toolName);
|
|
31
|
+
lines.push(`export function ${fn}(credentials: ModuleCredentials): void {
|
|
32
|
+
void credentials;
|
|
33
|
+
}`);
|
|
34
|
+
}
|
|
35
|
+
if (spec.prepare) {
|
|
36
|
+
const fn = prepareInputExportName(toolName);
|
|
37
|
+
if (spec.access === 'public') {
|
|
38
|
+
lines.push(`export function ${fn}(options: InvokeOptions): InvokeOptions {
|
|
39
|
+
void options;
|
|
40
|
+
throw new Error('Implement ${fn} in ${hookStubRelativePath(toolsModuleTsPath, toolName)}');
|
|
41
|
+
}`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
lines.push(`export function ${fn}(options: InvokeOptions, credentials?: ModuleCredentials): InvokeOptions {
|
|
45
|
+
void options;
|
|
46
|
+
void credentials;
|
|
47
|
+
throw new Error('Implement ${fn} in ${hookStubRelativePath(toolsModuleTsPath, toolName)}');
|
|
48
|
+
}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return lines.join('\n\n');
|
|
52
|
+
}
|
|
53
|
+
export function renderToolHookStubFileContent(toolName, spec, hookStubTsPath, toolsModuleTsPath) {
|
|
54
|
+
const importSpec = relativeJsImportPath(hookStubTsPath, toolsModuleTsPath);
|
|
55
|
+
const hookDir = path.dirname(hookStubTsPath);
|
|
56
|
+
const verifyStubPath = path.join(hookDir, `${resolveModuleCredentialNames(toolsModuleTsPath).fileBase}.ts`);
|
|
57
|
+
const verifyImportSpec = relativeJsImportPath(hookStubTsPath, verifyStubPath);
|
|
58
|
+
const header = spec.authorize && spec.prepare
|
|
59
|
+
? `/**
|
|
60
|
+
* Authorize + prepare hooks for "${toolName}" (write-once — implement authorize / prepareInput).
|
|
61
|
+
*/`
|
|
62
|
+
: spec.authorize
|
|
63
|
+
? `/**
|
|
64
|
+
* Authorize hook for "${toolName}" (write-once — override ${authorizeExportName(toolName)} for role gates).
|
|
65
|
+
*/`
|
|
66
|
+
: `/**
|
|
67
|
+
* Prepare hook for "${toolName}" (write-once — implement ${prepareInputExportName(toolName)}).
|
|
68
|
+
*/`;
|
|
69
|
+
const needsModuleCredentials = spec.authorize || (spec.prepare && spec.access === 'protected');
|
|
70
|
+
const credentialsImport = needsModuleCredentials
|
|
71
|
+
? `import type { ModuleCredentials } from '${verifyImportSpec}';\n`
|
|
72
|
+
: '';
|
|
73
|
+
return `${header}
|
|
74
|
+
${credentialsImport}import type { InvokeOptions } from '${importSpec}';
|
|
75
|
+
|
|
76
|
+
${renderToolHookStubBody(toolName, spec, toolsModuleTsPath)}
|
|
77
|
+
`;
|
|
78
|
+
}
|
|
79
|
+
/** @deprecated Use renderToolHookStubFileContent */
|
|
80
|
+
export const renderToolAuthStubFileContent = renderToolHookStubFileContent;
|
|
81
|
+
export async function ensureToolHookStubsAtProjectRoot(projectRoot, toolSpecs, toolsModuleTsPath) {
|
|
82
|
+
const hookDir = resolveHookStubDir(projectRoot, toolsModuleTsPath);
|
|
83
|
+
if (!fs.existsSync(hookDir)) {
|
|
84
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
85
|
+
}
|
|
86
|
+
const importPaths = new Map();
|
|
87
|
+
for (const spec of toolSpecs) {
|
|
88
|
+
if (!spec.authorize && !spec.prepare) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const tsPath = path.join(hookDir, `${spec.toolName}.ts`);
|
|
92
|
+
if (!fs.existsSync(tsPath)) {
|
|
93
|
+
fs.writeFileSync(tsPath, renderToolHookStubFileContent(spec.toolName, spec, tsPath, toolsModuleTsPath), 'utf-8');
|
|
94
|
+
}
|
|
95
|
+
importPaths.set(spec.toolName, tsPath);
|
|
96
|
+
}
|
|
97
|
+
return importPaths;
|
|
98
|
+
}
|
|
99
|
+
/** @deprecated Use ensureToolHookStubsAtProjectRoot */
|
|
100
|
+
export const ensureToolAuthStubsAtProjectRoot = ensureToolHookStubsAtProjectRoot;
|
|
101
|
+
export async function ensureToolHookStubsFromSource(source, toolSpecs, toolsModuleTsPath) {
|
|
102
|
+
const projectRoot = resolveBootstrapProjectRootFromSource(source);
|
|
103
|
+
return ensureToolHookStubsAtProjectRoot(projectRoot, toolSpecs, toolsModuleTsPath);
|
|
104
|
+
}
|
|
105
|
+
/** @deprecated Use ensureToolHookStubsFromSource */
|
|
106
|
+
export const ensureToolAuthStubsFromSource = ensureToolHookStubsFromSource;
|
|
107
|
+
export function renderVerifyCredentialsStubFileContent(toolsModuleTsPath) {
|
|
108
|
+
const names = resolveModuleCredentialNames(toolsModuleTsPath);
|
|
109
|
+
const verifyPath = resolveVerifyStubRelPath(toolsModuleTsPath);
|
|
110
|
+
return `/**
|
|
111
|
+
* MCP credential verification (write-once — implement ${names.verifyFunctionName}).
|
|
112
|
+
* Used by oauth-http gate and by invokeTool for protected tools (stdio/relay/OAuth).
|
|
113
|
+
*/
|
|
114
|
+
export type ModuleCredentials = Record<string, unknown>;
|
|
115
|
+
|
|
116
|
+
export class ${names.className} implements ModuleCredentials {
|
|
117
|
+
[key: string]: unknown;
|
|
118
|
+
|
|
119
|
+
constructor(init: ModuleCredentials) {
|
|
120
|
+
Object.assign(this, init);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
toString(): string {
|
|
124
|
+
return '[${names.pascalBase} credentials]';
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function ${names.toFunctionName}(data: ModuleCredentials | Record<string, unknown>): ${names.className} {
|
|
129
|
+
return new ${names.className}(data as ModuleCredentials);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export type VerifyCredentialInput = {
|
|
133
|
+
inboundCredential: string;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export type VerifyCredentialResult = {
|
|
137
|
+
upstreamCredential: string;
|
|
138
|
+
credentials: ${names.className};
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
export async function ${names.verifyFunctionName}(input: VerifyCredentialInput): Promise<VerifyCredentialResult> {
|
|
142
|
+
void input;
|
|
143
|
+
throw new Error('Implement ${names.verifyFunctionName} in ${verifyPath}');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export { ${names.verifyFunctionName} as verifyCredential, ${names.toFunctionName} as toModuleCredentials };
|
|
147
|
+
`;
|
|
148
|
+
}
|
|
149
|
+
export function resolveVerifyCredentialStubPath(projectRoot, toolsModuleTsPath) {
|
|
150
|
+
const hookDir = resolveHookStubDir(projectRoot, toolsModuleTsPath);
|
|
151
|
+
const names = resolveModuleCredentialNames(toolsModuleTsPath);
|
|
152
|
+
return path.join(hookDir, `${names.fileBase}.ts`);
|
|
153
|
+
}
|
|
154
|
+
/** Write-once \`src/hooks/{product}/<module>/verify*Credentials.ts\` when DSL has auth. */
|
|
155
|
+
export async function ensureVerifyCredentialStubAtProjectRoot(projectRoot, toolsModuleTsPath) {
|
|
156
|
+
const hookDir = resolveHookStubDir(projectRoot, toolsModuleTsPath);
|
|
157
|
+
if (!fs.existsSync(hookDir)) {
|
|
158
|
+
fs.mkdirSync(hookDir, { recursive: true });
|
|
159
|
+
}
|
|
160
|
+
const tsPath = resolveVerifyCredentialStubPath(projectRoot, toolsModuleTsPath);
|
|
161
|
+
if (!fs.existsSync(tsPath)) {
|
|
162
|
+
fs.writeFileSync(tsPath, renderVerifyCredentialsStubFileContent(toolsModuleTsPath), 'utf-8');
|
|
163
|
+
}
|
|
164
|
+
return tsPath;
|
|
165
|
+
}
|
|
166
|
+
export async function ensureVerifyCredentialStubFromSource(source, toolsModuleTsPath) {
|
|
167
|
+
const projectRoot = resolveBootstrapProjectRootFromSource(source);
|
|
168
|
+
return ensureVerifyCredentialStubAtProjectRoot(projectRoot, toolsModuleTsPath);
|
|
169
|
+
}
|
|
170
|
+
export function renderVerifyCredentialReExport(toolsModuleTsPath, verifyStubTsPath) {
|
|
171
|
+
const rel = relativeJsImportPath(toolsModuleTsPath, verifyStubTsPath);
|
|
172
|
+
const names = resolveModuleCredentialNames(toolsModuleTsPath);
|
|
173
|
+
return `export {
|
|
174
|
+
verifyCredential,
|
|
175
|
+
toModuleCredentials
|
|
176
|
+
} from '${rel}';
|
|
177
|
+
export type {
|
|
178
|
+
VerifyCredentialInput,
|
|
179
|
+
VerifyCredentialResult,
|
|
180
|
+
ModuleCredentials,
|
|
181
|
+
${names.className}
|
|
182
|
+
} from '${rel}';`;
|
|
183
|
+
}
|
|
184
|
+
export function renderVerifyCredentialImport(toolsModuleTsPath, verifyStubTsPath, options) {
|
|
185
|
+
const rel = relativeJsImportPath(toolsModuleTsPath, verifyStubTsPath);
|
|
186
|
+
const includeVerify = options?.includeVerify !== false;
|
|
187
|
+
const includeModuleCredentials = options?.includeModuleCredentials !== false;
|
|
188
|
+
if (!includeVerify) {
|
|
189
|
+
return `import { toModuleCredentials, type ModuleCredentials } from '${rel}';`;
|
|
190
|
+
}
|
|
191
|
+
if (!includeModuleCredentials) {
|
|
192
|
+
return `import { verifyCredential } from '${rel}';`;
|
|
193
|
+
}
|
|
194
|
+
return `import { verifyCredential, toModuleCredentials, type ModuleCredentials } from '${rel}';`;
|
|
195
|
+
}
|
|
196
|
+
export function renderAuthorizersMap(toolNames) {
|
|
197
|
+
const typeAnnotation = ': Record<string, (credentials: ModuleCredentials) => void | Promise<void>>';
|
|
198
|
+
if (toolNames.length === 0) {
|
|
199
|
+
return `const authorizers${typeAnnotation} = {};`;
|
|
200
|
+
}
|
|
201
|
+
const entries = toolNames.map((toolName) => {
|
|
202
|
+
const fn = authorizeExportName(toolName);
|
|
203
|
+
return ` ${JSON.stringify(toolName)}: ${fn}`;
|
|
204
|
+
});
|
|
205
|
+
return `const authorizers${typeAnnotation} = {\n${entries.join(',\n')}\n};`;
|
|
206
|
+
}
|
|
207
|
+
export function renderPreparersMap(toolNames, options) {
|
|
208
|
+
const includeCredentials = options?.includeCredentials !== false;
|
|
209
|
+
const typeAnnotation = includeCredentials
|
|
210
|
+
? ': Record<string, (options: InvokeOptions, credentials?: ModuleCredentials) => InvokeOptions | Promise<InvokeOptions>>'
|
|
211
|
+
: ': Record<string, (options: InvokeOptions) => InvokeOptions | Promise<InvokeOptions>>';
|
|
212
|
+
if (toolNames.length === 0) {
|
|
213
|
+
return `const preparers${typeAnnotation} = {};`;
|
|
214
|
+
}
|
|
215
|
+
const entries = toolNames.map((toolName) => {
|
|
216
|
+
const fn = prepareInputExportName(toolName);
|
|
217
|
+
return ` ${JSON.stringify(toolName)}: ${fn}`;
|
|
218
|
+
});
|
|
219
|
+
return `const preparers${typeAnnotation} = {\n${entries.join(',\n')}\n};`;
|
|
220
|
+
}
|
|
221
|
+
/** @deprecated Use renderPreparersMap */
|
|
222
|
+
export const renderValidatorsMap = renderPreparersMap;
|
|
223
|
+
export function renderAuthorizerImports(tsPath, stubPaths, authorizeToolNames) {
|
|
224
|
+
const allowed = new Set(authorizeToolNames);
|
|
225
|
+
const lines = [];
|
|
226
|
+
for (const [toolName, absStub] of stubPaths) {
|
|
227
|
+
if (!allowed.has(toolName)) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const rel = relativeJsImportPath(tsPath, absStub);
|
|
231
|
+
const fn = authorizeExportName(toolName);
|
|
232
|
+
const stubContent = fs.existsSync(absStub) ? fs.readFileSync(absStub, 'utf-8') : '';
|
|
233
|
+
if (stubContent.includes(`export function ${fn}`)) {
|
|
234
|
+
lines.push(`import { ${fn} } from '${rel}';`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return lines.join('\n');
|
|
238
|
+
}
|
|
239
|
+
export function renderPreparerImports(tsPath, stubPaths) {
|
|
240
|
+
const lines = [];
|
|
241
|
+
for (const [toolName, absStub] of stubPaths) {
|
|
242
|
+
const rel = relativeJsImportPath(tsPath, absStub);
|
|
243
|
+
const fn = prepareInputExportName(toolName);
|
|
244
|
+
const stubContent = fs.existsSync(absStub) ? fs.readFileSync(absStub, 'utf-8') : '';
|
|
245
|
+
if (stubContent.includes(`export function ${fn}`)) {
|
|
246
|
+
lines.push(`import { ${fn} } from '${rel}';`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return lines.join('\n');
|
|
250
|
+
}
|
|
251
|
+
/** @deprecated Use renderPreparerImports */
|
|
252
|
+
export const renderValidatorImports = renderPreparerImports;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { CliLangiumDocument, CliLangiumServices } from './langium-cli-types.js';
|
|
2
|
+
export type CliLangiumValidationDiagnostic = {
|
|
3
|
+
severity?: number;
|
|
4
|
+
message: string;
|
|
5
|
+
range: {
|
|
6
|
+
start: {
|
|
7
|
+
line: number;
|
|
8
|
+
character: number;
|
|
9
|
+
};
|
|
10
|
+
end: {
|
|
11
|
+
line: number;
|
|
12
|
+
character: number;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
export type AssertDocumentValidOptions = {
|
|
17
|
+
beforeBuild?: () => void;
|
|
18
|
+
extraErrors?: (document: CliLangiumDocument) => Promise<CliLangiumValidationDiagnostic[]> | CliLangiumValidationDiagnostic[];
|
|
19
|
+
};
|
|
20
|
+
export declare function collectLangiumDocumentErrors(document: CliLangiumDocument): CliLangiumValidationDiagnostic[];
|
|
21
|
+
export declare function printDocumentValidationErrors(document: CliLangiumDocument, errors: CliLangiumValidationDiagnostic[]): void;
|
|
22
|
+
export declare function assertDocumentValidForGenerate(fileName: string, services: CliLangiumServices, options?: AssertDocumentValidOptions): Promise<CliLangiumDocument>;
|