@mcpmake/core 0.2.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.
- package/dist/analyzer/auth-detector.d.ts +13 -0
- package/dist/analyzer/auth-detector.d.ts.map +1 -0
- package/dist/analyzer/auth-detector.js +143 -0
- package/dist/analyzer/auth-detector.js.map +1 -0
- package/dist/analyzer/dom-parser.d.ts +11 -0
- package/dist/analyzer/dom-parser.d.ts.map +1 -0
- package/dist/analyzer/dom-parser.js +260 -0
- package/dist/analyzer/dom-parser.js.map +1 -0
- package/dist/analyzer/goal-crawler.d.ts +26 -0
- package/dist/analyzer/goal-crawler.d.ts.map +1 -0
- package/dist/analyzer/goal-crawler.js +178 -0
- package/dist/analyzer/goal-crawler.js.map +1 -0
- package/dist/analyzer/hybrid-detector.d.ts +29 -0
- package/dist/analyzer/hybrid-detector.d.ts.map +1 -0
- package/dist/analyzer/hybrid-detector.js +97 -0
- package/dist/analyzer/hybrid-detector.js.map +1 -0
- package/dist/analyzer/index.d.ts +13 -0
- package/dist/analyzer/index.d.ts.map +1 -0
- package/dist/analyzer/index.js +9 -0
- package/dist/analyzer/index.js.map +1 -0
- package/dist/analyzer/screenshot-capture.d.ts +30 -0
- package/dist/analyzer/screenshot-capture.d.ts.map +1 -0
- package/dist/analyzer/screenshot-capture.js +43 -0
- package/dist/analyzer/screenshot-capture.js.map +1 -0
- package/dist/analyzer/selector-builder.d.ts +20 -0
- package/dist/analyzer/selector-builder.d.ts.map +1 -0
- package/dist/analyzer/selector-builder.js +200 -0
- package/dist/analyzer/selector-builder.js.map +1 -0
- package/dist/analyzer/semantic-analyzer.d.ts +14 -0
- package/dist/analyzer/semantic-analyzer.d.ts.map +1 -0
- package/dist/analyzer/semantic-analyzer.js +146 -0
- package/dist/analyzer/semantic-analyzer.js.map +1 -0
- package/dist/analyzer/site-crawler.d.ts +39 -0
- package/dist/analyzer/site-crawler.d.ts.map +1 -0
- package/dist/analyzer/site-crawler.js +236 -0
- package/dist/analyzer/site-crawler.js.map +1 -0
- package/dist/config/configurable-command.d.ts +14 -0
- package/dist/config/configurable-command.d.ts.map +1 -0
- package/dist/config/configurable-command.js +71 -0
- package/dist/config/configurable-command.js.map +1 -0
- package/dist/config/mcpmake-config.d.ts +69 -0
- package/dist/config/mcpmake-config.d.ts.map +1 -0
- package/dist/config/mcpmake-config.js +209 -0
- package/dist/config/mcpmake-config.js.map +1 -0
- package/dist/emitter/code-writer.d.ts +9 -0
- package/dist/emitter/code-writer.d.ts.map +1 -0
- package/dist/emitter/code-writer.js +26 -0
- package/dist/emitter/code-writer.js.map +1 -0
- package/dist/emitter/index.d.ts +33 -0
- package/dist/emitter/index.d.ts.map +1 -0
- package/dist/emitter/index.js +297 -0
- package/dist/emitter/index.js.map +1 -0
- package/dist/emitter/mcpb-bundler.d.ts +32 -0
- package/dist/emitter/mcpb-bundler.d.ts.map +1 -0
- package/dist/emitter/mcpb-bundler.js +173 -0
- package/dist/emitter/mcpb-bundler.js.map +1 -0
- package/dist/emitter/project-scaffolder.d.ts +5 -0
- package/dist/emitter/project-scaffolder.d.ts.map +1 -0
- package/dist/emitter/project-scaffolder.js +101 -0
- package/dist/emitter/project-scaffolder.js.map +1 -0
- package/dist/emitter/python-template-loader.d.ts +5 -0
- package/dist/emitter/python-template-loader.d.ts.map +1 -0
- package/dist/emitter/python-template-loader.js +31 -0
- package/dist/emitter/python-template-loader.js.map +1 -0
- package/dist/emitter/python-templates/dockerfile.hbs +14 -0
- package/dist/emitter/python-templates/env.example.hbs +12 -0
- package/dist/emitter/python-templates/requirements.txt.hbs +4 -0
- package/dist/emitter/python-templates/server.py.hbs +93 -0
- package/dist/emitter/site-scaffolder.d.ts +14 -0
- package/dist/emitter/site-scaffolder.d.ts.map +1 -0
- package/dist/emitter/site-scaffolder.js +71 -0
- package/dist/emitter/site-scaffolder.js.map +1 -0
- package/dist/emitter/site-template-loader.d.ts +6 -0
- package/dist/emitter/site-template-loader.d.ts.map +1 -0
- package/dist/emitter/site-template-loader.js +48 -0
- package/dist/emitter/site-template-loader.js.map +1 -0
- package/dist/emitter/site-templates/browser-manager.ts.hbs +233 -0
- package/dist/emitter/site-templates/config.ts.hbs +28 -0
- package/dist/emitter/site-templates/dockerfile.hbs +31 -0
- package/dist/emitter/site-templates/env.example.hbs +19 -0
- package/dist/emitter/site-templates/package.json.hbs +26 -0
- package/dist/emitter/site-templates/server-main-http.ts.hbs +108 -0
- package/dist/emitter/site-templates/server-main.ts.hbs +23 -0
- package/dist/emitter/site-templates/tool-handler-action.ts.hbs +86 -0
- package/dist/emitter/site-templates/tool-handler-form.ts.hbs +116 -0
- package/dist/emitter/site-templates/tool-handler-lifecycle.ts.hbs +146 -0
- package/dist/emitter/site-templates/tool-index.ts.hbs +11 -0
- package/dist/emitter/template-loader.d.ts +2 -0
- package/dist/emitter/template-loader.d.ts.map +1 -0
- package/dist/emitter/template-loader.js +28 -0
- package/dist/emitter/template-loader.js.map +1 -0
- package/dist/emitter/templates/auth-provider.ts.hbs +57 -0
- package/dist/emitter/templates/config.ts.hbs +110 -0
- package/dist/emitter/templates/discovery.ts.hbs +320 -0
- package/dist/emitter/templates/dockerfile.hbs +34 -0
- package/dist/emitter/templates/env.example.hbs +52 -0
- package/dist/emitter/templates/gitignore.hbs +5 -0
- package/dist/emitter/templates/http-executor.ts.hbs +159 -0
- package/dist/emitter/templates/oauth.ts.hbs +188 -0
- package/dist/emitter/templates/package.json.hbs +25 -0
- package/dist/emitter/templates/prompts.ts.hbs +22 -0
- package/dist/emitter/templates/readme.md.hbs +156 -0
- package/dist/emitter/templates/resources.ts.hbs +63 -0
- package/dist/emitter/templates/response-filter.ts.hbs +128 -0
- package/dist/emitter/templates/server-main-http.ts.hbs +442 -0
- package/dist/emitter/templates/server-main.ts.hbs +40 -0
- package/dist/emitter/templates/task-handlers.ts.hbs +189 -0
- package/dist/emitter/templates/task-manager.ts.hbs +139 -0
- package/dist/emitter/templates/task-sse.ts.hbs +105 -0
- package/dist/emitter/templates/tool-handler.ts.hbs +142 -0
- package/dist/emitter/templates/tool-index.ts.hbs +42 -0
- package/dist/emitter/templates/tool-test.ts.hbs +57 -0
- package/dist/emitter/templates/trace.ts.hbs +79 -0
- package/dist/emitter/templates/tsconfig.json.hbs +16 -0
- package/dist/emitter/templates/types.ts.hbs +5 -0
- package/dist/emitter/worker-template-loader.d.ts +6 -0
- package/dist/emitter/worker-template-loader.d.ts.map +1 -0
- package/dist/emitter/worker-template-loader.js +34 -0
- package/dist/emitter/worker-template-loader.js.map +1 -0
- package/dist/emitter/worker-templates/config.ts.hbs +93 -0
- package/dist/emitter/worker-templates/dev-vars.example.hbs +27 -0
- package/dist/emitter/worker-templates/gitignore.hbs +6 -0
- package/dist/emitter/worker-templates/package.json.hbs +24 -0
- package/dist/emitter/worker-templates/readme.md.hbs +73 -0
- package/dist/emitter/worker-templates/server.test.ts.hbs +20 -0
- package/dist/emitter/worker-templates/tool-handler.ts.hbs +103 -0
- package/dist/emitter/worker-templates/tool-index.ts.hbs +28 -0
- package/dist/emitter/worker-templates/tsconfig.json.hbs +17 -0
- package/dist/emitter/worker-templates/worker.ts.hbs +286 -0
- package/dist/emitter/worker-templates/wrangler.toml.hbs +19 -0
- package/dist/generator/spec-generator.d.ts +7 -0
- package/dist/generator/spec-generator.d.ts.map +1 -0
- package/dist/generator/spec-generator.js +51 -0
- package/dist/generator/spec-generator.js.map +1 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +101 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/har-filter.d.ts +9 -0
- package/dist/parser/har-filter.d.ts.map +1 -0
- package/dist/parser/har-filter.js +72 -0
- package/dist/parser/har-filter.js.map +1 -0
- package/dist/parser/har-loader.d.ts +3 -0
- package/dist/parser/har-loader.d.ts.map +1 -0
- package/dist/parser/har-loader.js +15 -0
- package/dist/parser/har-loader.js.map +1 -0
- package/dist/parser/har-normalizer.d.ts +21 -0
- package/dist/parser/har-normalizer.d.ts.map +1 -0
- package/dist/parser/har-normalizer.js +79 -0
- package/dist/parser/har-normalizer.js.map +1 -0
- package/dist/parser/index.d.ts +11 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +7 -0
- package/dist/parser/index.js.map +1 -0
- package/dist/parser/openapi-loader.d.ts +7 -0
- package/dist/parser/openapi-loader.d.ts.map +1 -0
- package/dist/parser/openapi-loader.js +309 -0
- package/dist/parser/openapi-loader.js.map +1 -0
- package/dist/parser/operation-extractor.d.ts +14 -0
- package/dist/parser/operation-extractor.d.ts.map +1 -0
- package/dist/parser/operation-extractor.js +156 -0
- package/dist/parser/operation-extractor.js.map +1 -0
- package/dist/parser/overlay-loader.d.ts +11 -0
- package/dist/parser/overlay-loader.d.ts.map +1 -0
- package/dist/parser/overlay-loader.js +185 -0
- package/dist/parser/overlay-loader.js.map +1 -0
- package/dist/parser/postman-loader.d.ts +10 -0
- package/dist/parser/postman-loader.d.ts.map +1 -0
- package/dist/parser/postman-loader.js +107 -0
- package/dist/parser/postman-loader.js.map +1 -0
- package/dist/parser/schema-converter.d.ts +13 -0
- package/dist/parser/schema-converter.d.ts.map +1 -0
- package/dist/parser/schema-converter.js +118 -0
- package/dist/parser/schema-converter.js.map +1 -0
- package/dist/plugins/adapter.d.ts +41 -0
- package/dist/plugins/adapter.d.ts.map +1 -0
- package/dist/plugins/adapter.js +16 -0
- package/dist/plugins/adapter.js.map +1 -0
- package/dist/plugins/loader.d.ts +26 -0
- package/dist/plugins/loader.d.ts.map +1 -0
- package/dist/plugins/loader.js +59 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/pricing.d.ts +56 -0
- package/dist/pricing.d.ts.map +1 -0
- package/dist/pricing.js +134 -0
- package/dist/pricing.js.map +1 -0
- package/dist/providers/index.d.ts +16 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +57 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/recorder/browser-recorder.d.ts +23 -0
- package/dist/recorder/browser-recorder.d.ts.map +1 -0
- package/dist/recorder/browser-recorder.js +206 -0
- package/dist/recorder/browser-recorder.js.map +1 -0
- package/dist/rescan/diff-engine.d.ts +6 -0
- package/dist/rescan/diff-engine.d.ts.map +1 -0
- package/dist/rescan/diff-engine.js +313 -0
- package/dist/rescan/diff-engine.js.map +1 -0
- package/dist/rescan/index.d.ts +4 -0
- package/dist/rescan/index.d.ts.map +1 -0
- package/dist/rescan/index.js +3 -0
- package/dist/rescan/index.js.map +1 -0
- package/dist/rescan/rescan-runner.d.ts +43 -0
- package/dist/rescan/rescan-runner.d.ts.map +1 -0
- package/dist/rescan/rescan-runner.js +70 -0
- package/dist/rescan/rescan-runner.js.map +1 -0
- package/dist/rescan/rescan-scheduler.d.ts +42 -0
- package/dist/rescan/rescan-scheduler.d.ts.map +1 -0
- package/dist/rescan/rescan-scheduler.js +180 -0
- package/dist/rescan/rescan-scheduler.js.map +1 -0
- package/dist/site-transformer/browser-tools.d.ts +11 -0
- package/dist/site-transformer/browser-tools.d.ts.map +1 -0
- package/dist/site-transformer/browser-tools.js +60 -0
- package/dist/site-transformer/browser-tools.js.map +1 -0
- package/dist/site-transformer/index.d.ts +3 -0
- package/dist/site-transformer/index.d.ts.map +1 -0
- package/dist/site-transformer/index.js +3 -0
- package/dist/site-transformer/index.js.map +1 -0
- package/dist/site-transformer/selector-healer.d.ts +9 -0
- package/dist/site-transformer/selector-healer.d.ts.map +1 -0
- package/dist/site-transformer/selector-healer.js +107 -0
- package/dist/site-transformer/selector-healer.js.map +1 -0
- package/dist/site-transformer/tool-generator.d.ts +14 -0
- package/dist/site-transformer/tool-generator.d.ts.map +1 -0
- package/dist/site-transformer/tool-generator.js +246 -0
- package/dist/site-transformer/tool-generator.js.map +1 -0
- package/dist/transformer/auth-detector.d.ts +29 -0
- package/dist/transformer/auth-detector.d.ts.map +1 -0
- package/dist/transformer/auth-detector.js +104 -0
- package/dist/transformer/auth-detector.js.map +1 -0
- package/dist/transformer/catalog-builder.d.ts +19 -0
- package/dist/transformer/catalog-builder.d.ts.map +1 -0
- package/dist/transformer/catalog-builder.js +57 -0
- package/dist/transformer/catalog-builder.js.map +1 -0
- package/dist/transformer/client-compat.d.ts +7 -0
- package/dist/transformer/client-compat.d.ts.map +1 -0
- package/dist/transformer/client-compat.js +45 -0
- package/dist/transformer/client-compat.js.map +1 -0
- package/dist/transformer/har-clusterer.d.ts +10 -0
- package/dist/transformer/har-clusterer.d.ts.map +1 -0
- package/dist/transformer/har-clusterer.js +28 -0
- package/dist/transformer/har-clusterer.js.map +1 -0
- package/dist/transformer/har-dedup.d.ts +11 -0
- package/dist/transformer/har-dedup.d.ts.map +1 -0
- package/dist/transformer/har-dedup.js +82 -0
- package/dist/transformer/har-dedup.js.map +1 -0
- package/dist/transformer/har-schema-inferrer.d.ts +16 -0
- package/dist/transformer/har-schema-inferrer.d.ts.map +1 -0
- package/dist/transformer/har-schema-inferrer.js +91 -0
- package/dist/transformer/har-schema-inferrer.js.map +1 -0
- package/dist/transformer/har-to-operations.d.ts +14 -0
- package/dist/transformer/har-to-operations.d.ts.map +1 -0
- package/dist/transformer/har-to-operations.js +193 -0
- package/dist/transformer/har-to-operations.js.map +1 -0
- package/dist/transformer/index.d.ts +9 -0
- package/dist/transformer/index.d.ts.map +1 -0
- package/dist/transformer/index.js +7 -0
- package/dist/transformer/index.js.map +1 -0
- package/dist/transformer/llm-namer.d.ts +7 -0
- package/dist/transformer/llm-namer.d.ts.map +1 -0
- package/dist/transformer/llm-namer.js +60 -0
- package/dist/transformer/llm-namer.js.map +1 -0
- package/dist/transformer/naming.d.ts +5 -0
- package/dist/transformer/naming.d.ts.map +1 -0
- package/dist/transformer/naming.js +31 -0
- package/dist/transformer/naming.js.map +1 -0
- package/dist/transformer/operation-filter.d.ts +14 -0
- package/dist/transformer/operation-filter.d.ts.map +1 -0
- package/dist/transformer/operation-filter.js +53 -0
- package/dist/transformer/operation-filter.js.map +1 -0
- package/dist/transformer/resource-builder.d.ts +13 -0
- package/dist/transformer/resource-builder.d.ts.map +1 -0
- package/dist/transformer/resource-builder.js +87 -0
- package/dist/transformer/resource-builder.js.map +1 -0
- package/dist/transformer/schema-merger.d.ts +15 -0
- package/dist/transformer/schema-merger.d.ts.map +1 -0
- package/dist/transformer/schema-merger.js +66 -0
- package/dist/transformer/schema-merger.js.map +1 -0
- package/dist/transformer/stainless-config.d.ts +75 -0
- package/dist/transformer/stainless-config.d.ts.map +1 -0
- package/dist/transformer/stainless-config.js +59 -0
- package/dist/transformer/stainless-config.js.map +1 -0
- package/dist/transformer/stainless-translator.d.ts +61 -0
- package/dist/transformer/stainless-translator.d.ts.map +1 -0
- package/dist/transformer/stainless-translator.js +375 -0
- package/dist/transformer/stainless-translator.js.map +1 -0
- package/dist/transformer/tool-builder.d.ts +4 -0
- package/dist/transformer/tool-builder.d.ts.map +1 -0
- package/dist/transformer/tool-builder.js +119 -0
- package/dist/transformer/tool-builder.js.map +1 -0
- package/dist/types/index.d.ts +140 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/site.d.ts +285 -0
- package/dist/types/site.d.ts.map +1 -0
- package/dist/types/site.js +9 -0
- package/dist/types/site.js.map +1 -0
- package/dist/utils/fail.d.ts +49 -0
- package/dist/utils/fail.d.ts.map +1 -0
- package/dist/utils/fail.js +223 -0
- package/dist/utils/fail.js.map +1 -0
- package/dist/utils/fs.d.ts +6 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +29 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/interactive.d.ts +7 -0
- package/dist/utils/interactive.d.ts.map +1 -0
- package/dist/utils/interactive.js +31 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +3 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/sanitize.d.ts +29 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +45 -0
- package/dist/utils/sanitize.js.map +1 -0
- package/dist/utils/watcher.d.ts +12 -0
- package/dist/utils/watcher.d.ts.map +1 -0
- package/dist/utils/watcher.js +37 -0
- package/dist/utils/watcher.js.map +1 -0
- package/package.json +45 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 token management.
|
|
3
|
+
* Supports Authorization Code + PKCE and Client Credentials flows.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import crypto from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
export interface OAuthConfig {
|
|
9
|
+
clientId: string;
|
|
10
|
+
clientSecret?: string;
|
|
11
|
+
authorizationUrl: string;
|
|
12
|
+
tokenUrl: string;
|
|
13
|
+
scopes: string[];
|
|
14
|
+
redirectUri: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface TokenResponse {
|
|
18
|
+
access_token: string;
|
|
19
|
+
token_type: string;
|
|
20
|
+
expires_in?: number;
|
|
21
|
+
refresh_token?: string;
|
|
22
|
+
scope?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let cachedToken: { accessToken: string; expiresAt: number; refreshToken?: string } | undefined;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get a valid access token, refreshing if needed.
|
|
29
|
+
*/
|
|
30
|
+
export async function getAccessToken(config: OAuthConfig): Promise<string> {
|
|
31
|
+
if (cachedToken && Date.now() < cachedToken.expiresAt - 60_000) {
|
|
32
|
+
return cachedToken.accessToken;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Try refresh first
|
|
36
|
+
if (cachedToken?.refreshToken) {
|
|
37
|
+
try {
|
|
38
|
+
const token = await refreshToken(config, cachedToken.refreshToken);
|
|
39
|
+
cacheToken(token);
|
|
40
|
+
return token.access_token;
|
|
41
|
+
} catch {
|
|
42
|
+
// Refresh failed — fall through to re-authenticate
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Client Credentials flow (machine-to-machine)
|
|
47
|
+
if (config.clientSecret && !process.env.OAUTH2_TOKEN) {
|
|
48
|
+
const token = await clientCredentialsGrant(config);
|
|
49
|
+
cacheToken(token);
|
|
50
|
+
return token.access_token;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Pre-obtained token from env
|
|
54
|
+
if (process.env.OAUTH2_TOKEN) {
|
|
55
|
+
return process.env.OAUTH2_TOKEN;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
throw new Error(
|
|
59
|
+
'No OAuth token available. Set OAUTH2_TOKEN or configure OAUTH2_CLIENT_ID + OAUTH2_CLIENT_SECRET for client credentials flow.',
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Client Credentials grant (RFC 6749 Section 4.4).
|
|
65
|
+
*/
|
|
66
|
+
async function clientCredentialsGrant(config: OAuthConfig): Promise<TokenResponse> {
|
|
67
|
+
const params = new URLSearchParams({
|
|
68
|
+
grant_type: 'client_credentials',
|
|
69
|
+
client_id: config.clientId,
|
|
70
|
+
...(config.clientSecret ? { client_secret: config.clientSecret } : {}),
|
|
71
|
+
...(config.scopes.length > 0 ? { scope: config.scopes.join(' ') } : {}),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const response = await fetch(config.tokenUrl, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
77
|
+
body: params.toString(),
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const body = await response.text();
|
|
82
|
+
throw new Error(`OAuth client credentials failed (${response.status}): ${body}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return response.json() as Promise<TokenResponse>;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Refresh an access token using a refresh token.
|
|
90
|
+
*/
|
|
91
|
+
async function refreshToken(config: OAuthConfig, refreshTokenValue: string): Promise<TokenResponse> {
|
|
92
|
+
const params = new URLSearchParams({
|
|
93
|
+
grant_type: 'refresh_token',
|
|
94
|
+
client_id: config.clientId,
|
|
95
|
+
refresh_token: refreshTokenValue,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const response = await fetch(config.tokenUrl, {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
101
|
+
body: params.toString(),
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
throw new Error(`Token refresh failed: ${response.status}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return response.json() as Promise<TokenResponse>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Generate PKCE code verifier and challenge for Authorization Code flow.
|
|
113
|
+
*/
|
|
114
|
+
export function generatePkce(): { codeVerifier: string; codeChallenge: string } {
|
|
115
|
+
const codeVerifier = crypto.randomBytes(32).toString('base64url');
|
|
116
|
+
const codeChallenge = crypto
|
|
117
|
+
.createHash('sha256')
|
|
118
|
+
.update(codeVerifier)
|
|
119
|
+
.digest('base64url');
|
|
120
|
+
return { codeVerifier, codeChallenge };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Build the authorization URL for the Authorization Code + PKCE flow.
|
|
125
|
+
*/
|
|
126
|
+
export function buildAuthorizationUrl(config: OAuthConfig, state: string, codeChallenge: string): string {
|
|
127
|
+
const params = new URLSearchParams({
|
|
128
|
+
response_type: 'code',
|
|
129
|
+
client_id: config.clientId,
|
|
130
|
+
redirect_uri: config.redirectUri,
|
|
131
|
+
state,
|
|
132
|
+
code_challenge: codeChallenge,
|
|
133
|
+
code_challenge_method: 'S256',
|
|
134
|
+
...(config.scopes.length > 0 ? { scope: config.scopes.join(' ') } : {}),
|
|
135
|
+
});
|
|
136
|
+
return `${config.authorizationUrl}?${params.toString()}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Exchange an authorization code for tokens (PKCE flow).
|
|
141
|
+
*/
|
|
142
|
+
export async function exchangeCode(config: OAuthConfig, code: string, codeVerifier: string): Promise<TokenResponse> {
|
|
143
|
+
const params = new URLSearchParams({
|
|
144
|
+
grant_type: 'authorization_code',
|
|
145
|
+
client_id: config.clientId,
|
|
146
|
+
code,
|
|
147
|
+
redirect_uri: config.redirectUri,
|
|
148
|
+
code_verifier: codeVerifier,
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const response = await fetch(config.tokenUrl, {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
154
|
+
body: params.toString(),
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
const body = await response.text();
|
|
159
|
+
throw new Error(`OAuth code exchange failed (${response.status}): ${body}`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const token = await response.json() as TokenResponse;
|
|
163
|
+
cacheToken(token);
|
|
164
|
+
return token;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function cacheToken(token: TokenResponse): void {
|
|
168
|
+
cachedToken = {
|
|
169
|
+
accessToken: token.access_token,
|
|
170
|
+
expiresAt: Date.now() + (token.expires_in ?? 3600) * 1000,
|
|
171
|
+
refreshToken: token.refresh_token,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* OAuth Authorization Server Metadata (RFC 8414).
|
|
177
|
+
*/
|
|
178
|
+
export function getAuthServerMetadata(config: OAuthConfig, issuer: string): Record<string, unknown> {
|
|
179
|
+
return {
|
|
180
|
+
issuer,
|
|
181
|
+
authorization_endpoint: config.authorizationUrl,
|
|
182
|
+
token_endpoint: config.tokenUrl,
|
|
183
|
+
response_types_supported: ['code'],
|
|
184
|
+
grant_types_supported: ['authorization_code', 'client_credentials', 'refresh_token'],
|
|
185
|
+
code_challenge_methods_supported: ['S256'],
|
|
186
|
+
scopes_supported: config.scopes,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{serverName}}",
|
|
3
|
+
"version": "{{serverVersion}}",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": true,
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"start": "node dist/index.js",
|
|
9
|
+
"dev": "tsx src/index.ts",
|
|
10
|
+
"test": "vitest run"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
14
|
+
"zod": "^3.24.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^22.0.0",
|
|
18
|
+
"typescript": "^5.8.0",
|
|
19
|
+
"tsx": "^4.19.0",
|
|
20
|
+
"vitest": "^3.1.0"
|
|
21
|
+
},
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=20.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
|
|
3
|
+
export function registerAllPrompts(server: McpServer): void {
|
|
4
|
+
{{#each prompts}}
|
|
5
|
+
server.prompt(
|
|
6
|
+
'{{name}}',
|
|
7
|
+
`{{{description}}}`,
|
|
8
|
+
() => ({
|
|
9
|
+
messages: [
|
|
10
|
+
{
|
|
11
|
+
role: 'user' as const,
|
|
12
|
+
content: {
|
|
13
|
+
type: 'text' as const,
|
|
14
|
+
text: `{{{template}}}`,
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
}),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
{{/each}}
|
|
22
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# {{serverName}}
|
|
2
|
+
|
|
3
|
+
MCP server generated by [mcpmake](https://github.com/starkyru/mcpmake).
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install
|
|
9
|
+
cp .env.example .env # fill in your credentials
|
|
10
|
+
npm run build
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Running
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# stdio transport (for Claude Desktop)
|
|
17
|
+
npm start
|
|
18
|
+
|
|
19
|
+
# Development mode (auto-reload)
|
|
20
|
+
npm run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
{{#if (eq transport "http")}}
|
|
24
|
+
## HTTP Transport
|
|
25
|
+
|
|
26
|
+
Run the server over HTTP instead of stdio:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
TRANSPORT=http PORT=3000 npm start
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The MCP endpoint is available at `http://localhost:3000/mcp`.
|
|
33
|
+
|
|
34
|
+
| Endpoint | Description |
|
|
35
|
+
|----------|-------------|
|
|
36
|
+
| `POST /mcp` | MCP protocol (JSON-RPC) |
|
|
37
|
+
| `GET /health` | Health check (returns 200 OK) |
|
|
38
|
+
| `GET /ready` | Readiness check (503 during startup/shutdown) |
|
|
39
|
+
{{#if hasOAuth}}
|
|
40
|
+
| `GET /.well-known/oauth-authorization-server` | OAuth Authorization Server Metadata (RFC 8414) |
|
|
41
|
+
{{/if}}
|
|
42
|
+
|
|
43
|
+
### Session mode (MCP 2026-07-28)
|
|
44
|
+
|
|
45
|
+
The server is **stateless by default** (no `Mcp-Session-Id`) so any request can
|
|
46
|
+
hit any instance. Set `MCP_STATEFUL=true` to keep per-session mode if a client
|
|
47
|
+
needs resumable streams. A `server/discover` preflight is supported, and the
|
|
48
|
+
`Mcp-Method`/`Mcp-Name` routing headers are accepted (and logged). See
|
|
49
|
+
[the migration notes](https://github.com/starkyru/mcpmake/blob/main/docs/mcp-2026-07-28-migration.md).
|
|
50
|
+
|
|
51
|
+
### Distributed tracing (W3C Trace Context)
|
|
52
|
+
|
|
53
|
+
An inbound [`traceparent`](https://www.w3.org/TR/trace-context/) is continued
|
|
54
|
+
(or a fresh trace started) per request and stamped onto every upstream API call,
|
|
55
|
+
so one trace spans client → MCP server → API. `tracestate` is propagated
|
|
56
|
+
verbatim. Standardized in MCP 2026-07-28.
|
|
57
|
+
|
|
58
|
+
### Docker Deployment
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
docker build -t {{serverName}} .
|
|
62
|
+
docker run --rm -p 3000:3000 --read-only --cap-drop=ALL --security-opt=no-new-privileges --env-file .env {{serverName}}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The container runs as a non-root user and is compatible with `--read-only` filesystems.
|
|
66
|
+
|
|
67
|
+
{{/if}}
|
|
68
|
+
## Configuration
|
|
69
|
+
|
|
70
|
+
Copy `.env.example` to `.env` and fill in:
|
|
71
|
+
|
|
72
|
+
| Variable | Description | Required |
|
|
73
|
+
|----------|-------------|----------|
|
|
74
|
+
| `BASE_URL` | API base URL | Yes |
|
|
75
|
+
{{#each authEnvVars}}
|
|
76
|
+
| `{{name}}` | {{description}} | {{#if required}}Yes{{else}}No{{/if}} |
|
|
77
|
+
{{/each}}
|
|
78
|
+
| `MAX_RETRIES` | Max retry attempts (default: 3) | No |
|
|
79
|
+
| `REQUEST_INTERVAL_MS` | Min ms between requests (default: 100) | No |
|
|
80
|
+
| `MCP_ENVIRONMENTS` | JSON map of environment name → base URL (e.g. `{"production":"…","sandbox":"…"}`) | No |
|
|
81
|
+
| `API_ENVIRONMENT` | Selects a named environment from `MCP_ENVIRONMENTS` (falls back to `BASE_URL`) | No |
|
|
82
|
+
| `MCP_TOOLS` | Comma-separated allowlist of tool names to register | No |
|
|
83
|
+
| `MCP_EXCLUDE_TOOLS` | Comma-separated denylist of tool names to skip | No |
|
|
84
|
+
| `MCP_DEFAULT_HEADERS` | JSON object of headers added to every upstream request | No |
|
|
85
|
+
| `MCP_IDEMPOTENCY_AUTO` | `true` to auto-generate an `Idempotency-Key` for mutating requests | No |
|
|
86
|
+
|
|
87
|
+
### Named environments
|
|
88
|
+
|
|
89
|
+
Select an upstream environment by name at runtime instead of hard-coding a URL:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
MCP_ENVIRONMENTS='{"production":"https://api.example.com","sandbox":"https://sandbox.example.com"}' \
|
|
93
|
+
API_ENVIRONMENT=sandbox npm start
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
When `API_ENVIRONMENT` is unset or names an unknown environment, the server uses `BASE_URL`.
|
|
97
|
+
|
|
98
|
+
### Runtime tool filtering
|
|
99
|
+
|
|
100
|
+
Narrow which tools are exposed without regenerating the server:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
MCP_TOOLS=list_pets,show_pet_by_id npm start # allowlist
|
|
104
|
+
MCP_EXCLUDE_TOOLS=delete_pet npm start # denylist
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Per-call arguments
|
|
108
|
+
|
|
109
|
+
Every tool accepts two optional arguments (omit them for the original behavior):
|
|
110
|
+
|
|
111
|
+
- `jq_filter` — a jq-style selector (e.g. `.data`, `.items[0].id`, `.items[]`) applied to the JSON response before it is returned, to trim large payloads.
|
|
112
|
+
- `idempotency_key` — sent as the `Idempotency-Key` header on mutating requests for safe retries.
|
|
113
|
+
|
|
114
|
+
## Tools
|
|
115
|
+
|
|
116
|
+
This server exposes {{tools.length}} tools:
|
|
117
|
+
{{#if dynamicDiscovery}}
|
|
118
|
+
|
|
119
|
+
> **Dynamic discovery is enabled.** Instead of registering all {{tools.length}} tools
|
|
120
|
+
> upfront (high token cost), this server exposes meta-tools — `list_tools`,
|
|
121
|
+
> `search_tools`, `get_tool_schema`, and `execute_tool` — so the agent loads only
|
|
122
|
+
> the schemas it needs.
|
|
123
|
+
{{/if}}
|
|
124
|
+
{{#if recommendDiscovery}}
|
|
125
|
+
|
|
126
|
+
> **Tip — large API ({{tools.length}} tools).** Registering this many tools upfront
|
|
127
|
+
> costs the agent a lot of context tokens, the most common reason big MCP servers
|
|
128
|
+
> underperform. Re-run your generate command with `--dynamic-discovery` to expose
|
|
129
|
+
> meta-tools (`list_tools` / `search_tools` / `get_tool_schema` / `execute_tool`)
|
|
130
|
+
> and load tool schemas on demand.
|
|
131
|
+
{{/if}}
|
|
132
|
+
|
|
133
|
+
{{#each tools}}
|
|
134
|
+
### `{{name}}`
|
|
135
|
+
|
|
136
|
+
{{description}}
|
|
137
|
+
|
|
138
|
+
{{/each}}
|
|
139
|
+
|
|
140
|
+
## Claude Desktop Integration
|
|
141
|
+
|
|
142
|
+
Add to your Claude Desktop config (`~/.claude/claude_desktop_config.json`):
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"mcpServers": {
|
|
147
|
+
"{{serverName}}": {
|
|
148
|
+
"command": "node",
|
|
149
|
+
"args": ["{{serverName}}/dist/index.js"],
|
|
150
|
+
"env": {
|
|
151
|
+
"BASE_URL": "YOUR_BASE_URL"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { executeRequest } from './http.js';
|
|
3
|
+
import type { AppConfig } from './config.js';
|
|
4
|
+
|
|
5
|
+
export function registerAllResources(server: McpServer, config: AppConfig): void {
|
|
6
|
+
{{#each resources}}
|
|
7
|
+
{{#if isTemplate}}
|
|
8
|
+
server.resource(
|
|
9
|
+
'{{name}}',
|
|
10
|
+
'{{uri}}',
|
|
11
|
+
{
|
|
12
|
+
description: `{{{description}}}`,
|
|
13
|
+
mimeType: 'application/json',
|
|
14
|
+
},
|
|
15
|
+
async (uri) => {
|
|
16
|
+
{{{urlBody}}}
|
|
17
|
+
const response = await executeRequest({
|
|
18
|
+
method: 'get',
|
|
19
|
+
url,
|
|
20
|
+
headers: {},
|
|
21
|
+
config,
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
contents: [
|
|
25
|
+
{
|
|
26
|
+
uri: uri.href,
|
|
27
|
+
mimeType: 'application/json',
|
|
28
|
+
text: JSON.stringify(response.data, null, 2),
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
);
|
|
34
|
+
{{else}}
|
|
35
|
+
server.resource(
|
|
36
|
+
'{{name}}',
|
|
37
|
+
'{{uri}}',
|
|
38
|
+
{
|
|
39
|
+
description: `{{{description}}}`,
|
|
40
|
+
mimeType: 'application/json',
|
|
41
|
+
},
|
|
42
|
+
async () => {
|
|
43
|
+
const response = await executeRequest({
|
|
44
|
+
method: 'get',
|
|
45
|
+
url: `${config.baseUrl}{{path}}`,
|
|
46
|
+
headers: {},
|
|
47
|
+
config,
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
contents: [
|
|
51
|
+
{
|
|
52
|
+
uri: '{{uri}}',
|
|
53
|
+
mimeType: 'application/json',
|
|
54
|
+
text: JSON.stringify(response.data, null, 2),
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
};
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
{{/if}}
|
|
61
|
+
|
|
62
|
+
{{/each}}
|
|
63
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal, dependency-free jq-style response filter.
|
|
3
|
+
*
|
|
4
|
+
* Stainless exposes a per-call `jq_filter` argument that trims the JSON response
|
|
5
|
+
* before it is returned to the agent (saving context tokens). A full jq engine is
|
|
6
|
+
* a heavy dependency, so this implements a safe, commonly-used subset:
|
|
7
|
+
*
|
|
8
|
+
* . identity (whole document)
|
|
9
|
+
* .a.b.c nested object access
|
|
10
|
+
* .a.b? optional access (never throws on a missing key)
|
|
11
|
+
* .items[0] array index (negative indexes count from the end)
|
|
12
|
+
* .items[] iterate an array, applying the rest of the path to each item
|
|
13
|
+
* .a | .b pipe (each stage receives the previous stage's output)
|
|
14
|
+
*
|
|
15
|
+
* Anything outside this subset (or any error while evaluating) makes the filter
|
|
16
|
+
* fall back to returning the unfiltered value, so a bad filter never breaks a
|
|
17
|
+
* tool call. This is intentionally conservative: it only ever *selects* data,
|
|
18
|
+
* it never executes arbitrary expressions.
|
|
19
|
+
*/
|
|
20
|
+
export function applyJqFilter(data: unknown, filter: string): unknown {
|
|
21
|
+
const trimmed = filter.trim();
|
|
22
|
+
if (!trimmed || trimmed === '.') return data;
|
|
23
|
+
|
|
24
|
+
// A pipe runs each stage in sequence; later stages refine the earlier output.
|
|
25
|
+
const stages = trimmed.split('|').map((s) => s.trim());
|
|
26
|
+
let current: unknown = data;
|
|
27
|
+
for (const stage of stages) {
|
|
28
|
+
current = applyStage(current, stage);
|
|
29
|
+
}
|
|
30
|
+
return current;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function applyStage(data: unknown, stage: string): unknown {
|
|
34
|
+
if (!stage || stage === '.') return data;
|
|
35
|
+
|
|
36
|
+
// Tokenize a leading-dot path into object keys and array selectors.
|
|
37
|
+
// e.g. ".a.b[0].c[]" → [{key:"a"}, {key:"b"}, {index:0}, {key:"c"}, {iterate:true}]
|
|
38
|
+
const tokens = tokenizePath(stage);
|
|
39
|
+
if (!tokens) return data; // unsupported syntax → leave the value untouched
|
|
40
|
+
|
|
41
|
+
let current: unknown = data;
|
|
42
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
43
|
+
const token = tokens[i];
|
|
44
|
+
if (current == null) return undefined;
|
|
45
|
+
|
|
46
|
+
if (token.iterate) {
|
|
47
|
+
if (!Array.isArray(current)) return undefined;
|
|
48
|
+
const rest = tokens.slice(i + 1);
|
|
49
|
+
return current.map((item) => walk(item, rest));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (token.index !== undefined) {
|
|
53
|
+
if (!Array.isArray(current)) return undefined;
|
|
54
|
+
const idx = token.index < 0 ? current.length + token.index : token.index;
|
|
55
|
+
current = current[idx];
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof current !== 'object' || Array.isArray(current)) return undefined;
|
|
60
|
+
current = (current as Record<string, unknown>)[token.key as string];
|
|
61
|
+
}
|
|
62
|
+
return current;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface PathToken {
|
|
66
|
+
key?: string;
|
|
67
|
+
index?: number;
|
|
68
|
+
iterate?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function walk(data: unknown, tokens: PathToken[]): unknown {
|
|
72
|
+
let current: unknown = data;
|
|
73
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
74
|
+
const token = tokens[i];
|
|
75
|
+
if (current == null) return undefined;
|
|
76
|
+
if (token.iterate) {
|
|
77
|
+
if (!Array.isArray(current)) return undefined;
|
|
78
|
+
const rest = tokens.slice(i + 1);
|
|
79
|
+
return current.map((item) => walk(item, rest));
|
|
80
|
+
}
|
|
81
|
+
if (token.index !== undefined) {
|
|
82
|
+
if (!Array.isArray(current)) return undefined;
|
|
83
|
+
const idx = token.index < 0 ? current.length + token.index : token.index;
|
|
84
|
+
current = current[idx];
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (typeof current !== 'object' || Array.isArray(current)) return undefined;
|
|
88
|
+
current = (current as Record<string, unknown>)[token.key as string];
|
|
89
|
+
}
|
|
90
|
+
return current;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function tokenizePath(path: string): PathToken[] | null {
|
|
94
|
+
const tokens: PathToken[] = [];
|
|
95
|
+
let i = 0;
|
|
96
|
+
const n = path.length;
|
|
97
|
+
while (i < n) {
|
|
98
|
+
const ch = path[i];
|
|
99
|
+
if (ch === '.') {
|
|
100
|
+
i++;
|
|
101
|
+
// Read an object key (identifier-ish chars). A bare "." (e.g. before "[")
|
|
102
|
+
// is allowed and contributes no key token.
|
|
103
|
+
let key = '';
|
|
104
|
+
while (i < n && /[A-Za-z0-9_$-]/.test(path[i])) {
|
|
105
|
+
key += path[i];
|
|
106
|
+
i++;
|
|
107
|
+
}
|
|
108
|
+
// Tolerate the jq optional-access marker "?".
|
|
109
|
+
if (i < n && path[i] === '?') i++;
|
|
110
|
+
if (key) tokens.push({ key });
|
|
111
|
+
} else if (ch === '[') {
|
|
112
|
+
const close = path.indexOf(']', i);
|
|
113
|
+
if (close === -1) return null;
|
|
114
|
+
const inner = path.slice(i + 1, close).trim();
|
|
115
|
+
if (inner === '') {
|
|
116
|
+
tokens.push({ iterate: true });
|
|
117
|
+
} else if (/^-?\d+$/.test(inner)) {
|
|
118
|
+
tokens.push({ index: parseInt(inner, 10) });
|
|
119
|
+
} else {
|
|
120
|
+
return null; // unsupported selector (filters, slices, string keys)
|
|
121
|
+
}
|
|
122
|
+
i = close + 1;
|
|
123
|
+
} else {
|
|
124
|
+
return null; // unexpected character → unsupported
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return tokens;
|
|
128
|
+
}
|