@pikku/inspector 0.9.6-next.0 → 0.10.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/CHANGELOG.md +6 -0
- package/dist/add/add-channel.d.ts +5 -1
- package/dist/add/add-channel.js +51 -32
- package/dist/add/add-cli.d.ts +4 -0
- package/dist/add/add-cli.js +128 -23
- package/dist/add/add-file-extends-core-type.js +3 -2
- package/dist/add/add-file-with-factory.d.ts +2 -2
- package/dist/add/add-file-with-factory.js +34 -1
- package/dist/add/add-functions.js +52 -5
- package/dist/add/add-http-route.js +19 -12
- package/dist/add/add-mcp-prompt.js +20 -13
- package/dist/add/add-mcp-resource.js +24 -14
- package/dist/add/add-mcp-tool.js +23 -13
- package/dist/add/add-middleware.js +51 -12
- package/dist/add/add-permission.d.ts +1 -2
- package/dist/add/add-permission.js +275 -19
- package/dist/add/add-queue-worker.js +10 -12
- package/dist/add/add-schedule.js +9 -10
- package/dist/error-codes.d.ts +35 -0
- package/dist/error-codes.js +40 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/inspector.js +20 -1
- package/dist/types.d.ts +31 -3
- package/dist/utils/ensure-function-metadata.d.ts +6 -0
- package/dist/utils/ensure-function-metadata.js +18 -0
- package/dist/utils/extract-function-name.d.ts +2 -2
- package/dist/utils/extract-function-name.js +13 -8
- package/dist/utils/filter-inspector-state.d.ts +6 -0
- package/dist/utils/filter-inspector-state.js +382 -0
- package/dist/utils/filter-utils.d.ts +10 -0
- package/dist/utils/filter-utils.js +66 -2
- package/dist/utils/find-root-dir.d.ts +23 -0
- package/dist/utils/find-root-dir.js +55 -0
- package/dist/utils/get-files-and-methods.d.ts +2 -1
- package/dist/utils/get-files-and-methods.js +2 -1
- package/dist/utils/get-property-value.d.ts +9 -0
- package/dist/utils/get-property-value.js +20 -0
- package/dist/utils/middleware.d.ts +1 -1
- package/dist/utils/middleware.js +7 -7
- package/dist/utils/permissions.d.ts +43 -0
- package/dist/utils/permissions.js +178 -0
- package/dist/utils/post-process.d.ts +16 -0
- package/dist/utils/post-process.js +132 -0
- package/dist/utils/serialize-inspector-state.d.ts +179 -0
- package/dist/utils/serialize-inspector-state.js +170 -0
- package/dist/visit.js +3 -2
- package/package.json +4 -4
- package/src/add/add-channel.ts +92 -40
- package/src/add/add-cli.ts +188 -29
- package/src/add/add-file-extends-core-type.ts +5 -2
- package/src/add/add-file-with-factory.ts +45 -2
- package/src/add/add-functions.ts +60 -5
- package/src/add/add-http-route.ts +46 -21
- package/src/add/add-mcp-prompt.ts +42 -21
- package/src/add/add-mcp-prompt.ts.tmp +0 -0
- package/src/add/add-mcp-resource.ts +50 -24
- package/src/add/add-mcp-resource.ts.tmp +0 -0
- package/src/add/add-mcp-tool.ts +48 -21
- package/src/add/add-middleware.ts +74 -15
- package/src/add/add-permission.ts +364 -22
- package/src/add/add-queue-worker.ts +22 -25
- package/src/add/add-schedule.ts +19 -20
- package/src/error-codes.ts +43 -0
- package/src/index.ts +7 -0
- package/src/inspector.ts +22 -1
- package/src/types.ts +38 -3
- package/src/utils/ensure-function-metadata.ts +24 -0
- package/src/utils/extract-function-name.ts +20 -8
- package/src/utils/filter-inspector-state.test.ts +1433 -0
- package/src/utils/filter-inspector-state.ts +526 -0
- package/src/utils/filter-utils.test.ts +350 -1
- package/src/utils/filter-utils.ts +82 -2
- package/src/utils/find-root-dir.ts +68 -0
- package/src/utils/get-files-and-methods.ts +8 -0
- package/src/utils/get-property-value.ts +27 -0
- package/src/utils/middleware.ts +14 -7
- package/src/utils/permissions.test.ts +327 -0
- package/src/utils/permissions.ts +262 -0
- package/src/utils/post-process.ts +178 -0
- package/src/utils/serialize-inspector-state.ts +375 -0
- package/src/utils/test-data/inspector-state.json +1680 -0
- package/src/visit.ts +4 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -16,7 +16,7 @@ const nullifyTypes = (type) => {
|
|
|
16
16
|
}
|
|
17
17
|
return type;
|
|
18
18
|
};
|
|
19
|
-
const resolveTypeImports = (type, resolvedTypes, isCustom) => {
|
|
19
|
+
const resolveTypeImports = (type, resolvedTypes, isCustom, checker) => {
|
|
20
20
|
const types = [];
|
|
21
21
|
const visitType = (currentType) => {
|
|
22
22
|
const symbol = currentType.aliasSymbol || currentType.getSymbol();
|
|
@@ -27,9 +27,12 @@ const resolveTypeImports = (type, resolvedTypes, isCustom) => {
|
|
|
27
27
|
const sourceFile = declaration.getSourceFile();
|
|
28
28
|
const path = sourceFile.fileName;
|
|
29
29
|
// Skip built-in utility types or TypeScript lib types
|
|
30
|
+
// Skip enum members (but not the enum type itself)
|
|
31
|
+
const isEnumMember = declaration && ts.isEnumMember(declaration);
|
|
30
32
|
if (!path.includes('node_modules/typescript') &&
|
|
31
33
|
symbol.getName() !== '__type' &&
|
|
32
|
-
!isPrimitiveType(currentType)
|
|
34
|
+
!isPrimitiveType(currentType) &&
|
|
35
|
+
!isEnumMember) {
|
|
33
36
|
const originalName = symbol.getName();
|
|
34
37
|
// Check if the type is already in the map
|
|
35
38
|
let uniqueName = resolvedTypes.exists(originalName, path);
|
|
@@ -76,6 +79,26 @@ const resolveTypeImports = (type, resolvedTypes, isCustom) => {
|
|
|
76
79
|
const typeRef = currentType;
|
|
77
80
|
typeRef.typeArguments?.forEach(visitType);
|
|
78
81
|
}
|
|
82
|
+
// Handle anonymous object types with enum properties (e.g., { userType: UserType })
|
|
83
|
+
// Only traverse into enum property types to avoid over-importing other named types
|
|
84
|
+
if (currentType.flags & ts.TypeFlags.Object) {
|
|
85
|
+
const objectType = currentType;
|
|
86
|
+
const typeSymbol = objectType.getSymbol();
|
|
87
|
+
// Only traverse properties for anonymous object types (no symbol or __type symbol)
|
|
88
|
+
// Skip named types, interfaces, and enums to avoid over-importing
|
|
89
|
+
const isAnonymousObject = !typeSymbol || typeSymbol.getName() === '__type';
|
|
90
|
+
if (isAnonymousObject) {
|
|
91
|
+
const properties = objectType.getProperties();
|
|
92
|
+
for (const prop of properties) {
|
|
93
|
+
if (prop.valueDeclaration) {
|
|
94
|
+
const propType = checker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
95
|
+
if (propType) {
|
|
96
|
+
visitType(propType);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
79
102
|
};
|
|
80
103
|
visitType(type);
|
|
81
104
|
return types;
|
|
@@ -125,7 +148,7 @@ const getNamesAndTypes = (checker, typesMap, direction, funcName, type) => {
|
|
|
125
148
|
const aliasName = funcName.charAt(0).toUpperCase() + funcName.slice(1) + direction;
|
|
126
149
|
// record the alias in your TypesMap
|
|
127
150
|
const references = rawTypes
|
|
128
|
-
.map((t) => resolveTypeImports(t, typesMap, true))
|
|
151
|
+
.map((t) => resolveTypeImports(t, typesMap, true, checker))
|
|
129
152
|
.flat();
|
|
130
153
|
typesMap.addCustomType(aliasName, aliasType, references);
|
|
131
154
|
return {
|
|
@@ -144,7 +167,7 @@ const getNamesAndTypes = (checker, typesMap, direction, funcName, type) => {
|
|
|
144
167
|
return name;
|
|
145
168
|
}
|
|
146
169
|
// non-primitive: import/alias it inline
|
|
147
|
-
return resolveTypeImports(t, typesMap, false);
|
|
170
|
+
return resolveTypeImports(t, typesMap, false, checker);
|
|
148
171
|
})
|
|
149
172
|
.flat();
|
|
150
173
|
return {
|
|
@@ -207,7 +230,7 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
207
230
|
}
|
|
208
231
|
if (args.length === 0)
|
|
209
232
|
return;
|
|
210
|
-
const { pikkuFuncName, name, explicitName, exportedName } = extractFunctionName(node, checker);
|
|
233
|
+
const { pikkuFuncName, name, explicitName, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
211
234
|
let tags;
|
|
212
235
|
let expose;
|
|
213
236
|
let docs;
|
|
@@ -226,6 +249,17 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
226
249
|
if (!fnProp ||
|
|
227
250
|
(!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))) {
|
|
228
251
|
logger.error(`• No valid 'func' property found for ${pikkuFuncName}.`);
|
|
252
|
+
// Create stub metadata to prevent "function not found" errors in wirings
|
|
253
|
+
state.functions.meta[pikkuFuncName] = {
|
|
254
|
+
pikkuFuncName,
|
|
255
|
+
name,
|
|
256
|
+
services: { optimized: false, services: [] },
|
|
257
|
+
inputSchemaName: null,
|
|
258
|
+
outputSchemaName: null,
|
|
259
|
+
inputs: [],
|
|
260
|
+
outputs: [],
|
|
261
|
+
middleware: undefined,
|
|
262
|
+
};
|
|
229
263
|
return;
|
|
230
264
|
}
|
|
231
265
|
handlerNode = fnProp;
|
|
@@ -233,6 +267,17 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
233
267
|
if (!ts.isArrowFunction(handlerNode) &&
|
|
234
268
|
!ts.isFunctionExpression(handlerNode)) {
|
|
235
269
|
logger.error(`• Handler for ${name} is not a function.`);
|
|
270
|
+
// Create stub metadata to prevent "function not found" errors in wirings
|
|
271
|
+
state.functions.meta[pikkuFuncName] = {
|
|
272
|
+
pikkuFuncName,
|
|
273
|
+
name,
|
|
274
|
+
services: { optimized: false, services: [] },
|
|
275
|
+
inputSchemaName: null,
|
|
276
|
+
outputSchemaName: null,
|
|
277
|
+
inputs: [],
|
|
278
|
+
outputs: [],
|
|
279
|
+
middleware: undefined,
|
|
280
|
+
};
|
|
236
281
|
return;
|
|
237
282
|
}
|
|
238
283
|
const services = {
|
|
@@ -324,6 +369,8 @@ export const addFunctions = (logger, node, checker, state) => {
|
|
|
324
369
|
path: node.getSourceFile().fileName,
|
|
325
370
|
exportedName,
|
|
326
371
|
});
|
|
372
|
+
// Track exposed RPC function for service aggregation
|
|
373
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName);
|
|
327
374
|
}
|
|
328
375
|
// We add it to internal meta to allow autocomplete for everything
|
|
329
376
|
state.rpc.internalMeta[name] = pikkuFuncName;
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
2
|
+
import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
|
|
3
3
|
import { pathToRegexp } from 'path-to-regexp';
|
|
4
|
-
import { PikkuWiringTypes } from '@pikku/core';
|
|
5
4
|
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
6
5
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
7
|
-
import { matchesFilters } from '../utils/filter-utils.js';
|
|
8
6
|
import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js';
|
|
7
|
+
import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js';
|
|
8
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
9
|
+
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
|
|
10
|
+
import { ErrorCode } from '../error-codes.js';
|
|
9
11
|
/**
|
|
10
12
|
* Populate metaInputTypes for a given route based on method, input type,
|
|
11
13
|
* query and params. Returns undefined (we only mutate metaTypes).
|
|
@@ -46,23 +48,21 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
|
|
|
46
48
|
const params = keys.filter((k) => k.type === 'param').map((k) => k.name);
|
|
47
49
|
const method = getPropertyValue(obj, 'method')?.toLowerCase() || 'get';
|
|
48
50
|
const docs = getPropertyValue(obj, 'docs') || undefined;
|
|
49
|
-
const tags =
|
|
51
|
+
const tags = getPropertyTags(obj, 'HTTP route', route, logger);
|
|
50
52
|
const query = getPropertyValue(obj, 'query') || [];
|
|
51
|
-
|
|
52
|
-
if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.http, name: route, filePath }, logger)) {
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
// --- find the referenced function ---
|
|
53
|
+
// --- find the referenced function name first for filtering ---
|
|
56
54
|
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
57
55
|
if (!funcInitializer) {
|
|
58
|
-
|
|
56
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for route '${route}'.`);
|
|
59
57
|
return;
|
|
60
58
|
}
|
|
61
|
-
const funcName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
|
|
59
|
+
const funcName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
|
|
60
|
+
// Ensure function metadata exists (creates stub for inline functions)
|
|
61
|
+
ensureFunctionMetadata(state, funcName, route);
|
|
62
62
|
// lookup existing function metadata
|
|
63
63
|
const fnMeta = state.functions.meta[funcName];
|
|
64
64
|
if (!fnMeta) {
|
|
65
|
-
|
|
65
|
+
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${funcName}'.`);
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
68
|
const input = fnMeta.inputs?.[0] || null;
|
|
@@ -70,6 +70,12 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
|
|
|
70
70
|
const inputTypes = getInputTypes(state.http.metaInputTypes, method, input, query, params);
|
|
71
71
|
// --- resolve middleware ---
|
|
72
72
|
const middleware = resolveHTTPMiddlewareFromObject(state, route, obj, tags, checker);
|
|
73
|
+
// --- resolve permissions ---
|
|
74
|
+
const permissions = resolveHTTPPermissionsFromObject(state, route, obj, tags, checker);
|
|
75
|
+
// --- track used functions/middleware/permissions for service aggregation ---
|
|
76
|
+
state.serviceAggregation.usedFunctions.add(funcName);
|
|
77
|
+
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
78
|
+
extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
|
|
73
79
|
// --- record route ---
|
|
74
80
|
state.http.files.add(node.getSourceFile().fileName);
|
|
75
81
|
state.http.meta[method][route] = {
|
|
@@ -82,5 +88,6 @@ export const addHTTPRoute = (logger, node, checker, state, options) => {
|
|
|
82
88
|
docs,
|
|
83
89
|
tags,
|
|
84
90
|
middleware,
|
|
91
|
+
permissions,
|
|
85
92
|
};
|
|
86
93
|
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
-
import {
|
|
2
|
+
import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
|
|
3
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
4
|
+
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
|
|
4
5
|
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
5
6
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
6
|
-
import { matchesFilters } from '../utils/filter-utils.js';
|
|
7
7
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
8
|
+
import { resolvePermissions } from '../utils/permissions.js';
|
|
9
|
+
import { ErrorCode } from '../error-codes.js';
|
|
8
10
|
export const addMCPPrompt = (logger, node, checker, state, options) => {
|
|
9
11
|
if (!ts.isCallExpression(node)) {
|
|
10
12
|
return;
|
|
@@ -23,35 +25,39 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
|
|
|
23
25
|
const obj = firstArg;
|
|
24
26
|
const nameValue = getPropertyValue(obj, 'name');
|
|
25
27
|
const descriptionValue = getPropertyValue(obj, 'description');
|
|
26
|
-
const tags =
|
|
28
|
+
const tags = getPropertyTags(obj, 'MCP prompt', nameValue, logger);
|
|
27
29
|
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
28
30
|
if (!funcInitializer) {
|
|
29
|
-
|
|
31
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for MCP prompt '${nameValue}'.`);
|
|
30
32
|
return;
|
|
31
33
|
}
|
|
32
|
-
const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
|
|
34
|
+
const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
|
|
35
|
+
// Ensure function metadata exists (creates stub for inline functions)
|
|
36
|
+
ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined);
|
|
33
37
|
if (!nameValue) {
|
|
34
|
-
|
|
38
|
+
logger.critical(ErrorCode.MISSING_NAME, "MCP prompt is missing the required 'name' property.");
|
|
35
39
|
return;
|
|
36
40
|
}
|
|
37
41
|
if (!descriptionValue) {
|
|
38
|
-
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
const filePath = node.getSourceFile().fileName;
|
|
42
|
-
if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.mcp, name: nameValue, filePath }, logger)) {
|
|
42
|
+
logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP prompt '${nameValue}' is missing a description.`);
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
// lookup existing function metadata
|
|
46
46
|
const fnMeta = state.functions.meta[pikkuFuncName];
|
|
47
47
|
if (!fnMeta) {
|
|
48
|
-
|
|
48
|
+
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncName}'.`);
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
51
|
const inputSchema = fnMeta.inputs?.[0] || null;
|
|
52
52
|
const outputSchema = fnMeta.outputs?.[0] || null;
|
|
53
53
|
// --- resolve middleware ---
|
|
54
54
|
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
55
|
+
// --- resolve permissions ---
|
|
56
|
+
const permissions = resolvePermissions(state, obj, tags, checker);
|
|
57
|
+
// --- track used functions/middleware/permissions for service aggregation ---
|
|
58
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName);
|
|
59
|
+
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
60
|
+
extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
|
|
55
61
|
state.mcpEndpoints.files.add(node.getSourceFile().fileName);
|
|
56
62
|
state.mcpEndpoints.promptsMeta[nameValue] = {
|
|
57
63
|
pikkuFuncName,
|
|
@@ -62,6 +68,7 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
|
|
|
62
68
|
outputSchema,
|
|
63
69
|
arguments: [], // Will be populated by CLI during serialization
|
|
64
70
|
middleware,
|
|
71
|
+
permissions,
|
|
65
72
|
};
|
|
66
73
|
}
|
|
67
74
|
};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
-
import {
|
|
2
|
+
import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
|
|
3
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
4
|
+
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
|
|
4
5
|
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
5
6
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
6
|
-
import { matchesFilters } from '../utils/filter-utils.js';
|
|
7
7
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
8
|
+
import { resolvePermissions } from '../utils/permissions.js';
|
|
9
|
+
import { ErrorCode } from '../error-codes.js';
|
|
8
10
|
export const addMCPResource = (logger, node, checker, state, options) => {
|
|
9
11
|
if (!ts.isCallExpression(node)) {
|
|
10
12
|
return;
|
|
@@ -25,39 +27,46 @@ export const addMCPResource = (logger, node, checker, state, options) => {
|
|
|
25
27
|
const titleValue = getPropertyValue(obj, 'title');
|
|
26
28
|
const descriptionValue = getPropertyValue(obj, 'description');
|
|
27
29
|
const streamingValue = getPropertyValue(obj, 'streaming');
|
|
28
|
-
const tags =
|
|
30
|
+
const tags = getPropertyTags(obj, 'MCP resource', uriValue, logger);
|
|
31
|
+
if (streamingValue === true) {
|
|
32
|
+
logger.warn(`MCP resource '${uriValue}' has streaming enabled, but streaming is not yet supported.`);
|
|
33
|
+
}
|
|
29
34
|
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
30
35
|
if (!funcInitializer) {
|
|
31
|
-
|
|
36
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for MCP resource '${uriValue}'.`);
|
|
32
37
|
return;
|
|
33
38
|
}
|
|
34
|
-
const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
|
|
39
|
+
const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
|
|
40
|
+
// Ensure function metadata exists (creates stub for inline functions)
|
|
41
|
+
ensureFunctionMetadata(state, pikkuFuncName, uriValue || undefined);
|
|
35
42
|
if (!uriValue) {
|
|
36
|
-
|
|
43
|
+
logger.critical(ErrorCode.MISSING_URI, "MCP resource is missing the required 'uri' property.");
|
|
37
44
|
return;
|
|
38
45
|
}
|
|
39
46
|
if (!titleValue) {
|
|
40
|
-
|
|
47
|
+
logger.critical(ErrorCode.MISSING_TITLE, `MCP resource '${uriValue}' is missing the required 'title' property.`);
|
|
41
48
|
return;
|
|
42
49
|
}
|
|
43
50
|
if (!descriptionValue) {
|
|
44
|
-
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const filePath = node.getSourceFile().fileName;
|
|
48
|
-
if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.mcp, name: uriValue, filePath }, logger)) {
|
|
51
|
+
logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP resource '${uriValue}' is missing a description.`);
|
|
49
52
|
return;
|
|
50
53
|
}
|
|
51
54
|
// lookup existing function metadata
|
|
52
55
|
const fnMeta = state.functions.meta[pikkuFuncName];
|
|
53
56
|
if (!fnMeta) {
|
|
54
|
-
|
|
57
|
+
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncName}'.`);
|
|
55
58
|
return;
|
|
56
59
|
}
|
|
57
60
|
const inputSchema = fnMeta.inputs?.[0] || null;
|
|
58
61
|
const outputSchema = fnMeta.outputs?.[0] || null;
|
|
59
62
|
// --- resolve middleware ---
|
|
60
63
|
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
64
|
+
// --- resolve permissions ---
|
|
65
|
+
const permissions = resolvePermissions(state, obj, tags, checker);
|
|
66
|
+
// --- track used functions/middleware/permissions for service aggregation ---
|
|
67
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName);
|
|
68
|
+
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
69
|
+
extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
|
|
61
70
|
state.mcpEndpoints.files.add(node.getSourceFile().fileName);
|
|
62
71
|
state.mcpEndpoints.resourcesMeta[uriValue] = {
|
|
63
72
|
pikkuFuncName,
|
|
@@ -69,6 +78,7 @@ export const addMCPResource = (logger, node, checker, state, options) => {
|
|
|
69
78
|
inputSchema,
|
|
70
79
|
outputSchema,
|
|
71
80
|
middleware,
|
|
81
|
+
permissions,
|
|
72
82
|
};
|
|
73
83
|
}
|
|
74
84
|
};
|
package/dist/add/add-mcp-tool.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
-
import {
|
|
2
|
+
import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
|
|
3
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
4
|
+
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
|
|
4
5
|
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
5
6
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
6
|
-
import { matchesFilters } from '../utils/filter-utils.js';
|
|
7
7
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
8
|
+
import { resolvePermissions } from '../utils/permissions.js';
|
|
9
|
+
import { ErrorCode } from '../error-codes.js';
|
|
8
10
|
export const addMCPTool = (logger, node, checker, state, options) => {
|
|
9
11
|
if (!ts.isCallExpression(node)) {
|
|
10
12
|
return;
|
|
@@ -25,35 +27,42 @@ export const addMCPTool = (logger, node, checker, state, options) => {
|
|
|
25
27
|
const titleValue = getPropertyValue(obj, 'title');
|
|
26
28
|
const descriptionValue = getPropertyValue(obj, 'description');
|
|
27
29
|
const streamingValue = getPropertyValue(obj, 'streaming');
|
|
28
|
-
const tags =
|
|
30
|
+
const tags = getPropertyTags(obj, 'MCP tool', nameValue, logger);
|
|
31
|
+
if (streamingValue === true) {
|
|
32
|
+
logger.warn(`MCP tool '${nameValue}' has streaming enabled, but streaming is not yet supported.`);
|
|
33
|
+
}
|
|
29
34
|
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
30
35
|
if (!funcInitializer) {
|
|
31
|
-
|
|
36
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for MCP tool '${nameValue}'.`);
|
|
32
37
|
return;
|
|
33
38
|
}
|
|
34
|
-
const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
|
|
39
|
+
const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
|
|
40
|
+
// Ensure function metadata exists (creates stub for inline functions)
|
|
41
|
+
ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined);
|
|
35
42
|
if (!nameValue) {
|
|
36
|
-
|
|
43
|
+
logger.critical(ErrorCode.MISSING_NAME, "MCP tool is missing the required 'name' property.");
|
|
37
44
|
return;
|
|
38
45
|
}
|
|
39
46
|
if (!descriptionValue) {
|
|
40
|
-
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const filePath = node.getSourceFile().fileName;
|
|
44
|
-
if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.mcp, name: nameValue, filePath }, logger)) {
|
|
47
|
+
logger.critical(ErrorCode.MISSING_DESCRIPTION, `MCP tool '${nameValue}' is missing a description.`);
|
|
45
48
|
return;
|
|
46
49
|
}
|
|
47
50
|
// lookup existing function metadata
|
|
48
51
|
const fnMeta = state.functions.meta[pikkuFuncName];
|
|
49
52
|
if (!fnMeta) {
|
|
50
|
-
|
|
53
|
+
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncName}'.`);
|
|
51
54
|
return;
|
|
52
55
|
}
|
|
53
56
|
const inputSchema = fnMeta.inputs?.[0] || null;
|
|
54
57
|
const outputSchema = fnMeta.outputs?.[0] || null;
|
|
55
58
|
// --- resolve middleware ---
|
|
56
59
|
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
60
|
+
// --- resolve permissions ---
|
|
61
|
+
const permissions = resolvePermissions(state, obj, tags, checker);
|
|
62
|
+
// --- track used functions/middleware/permissions for service aggregation ---
|
|
63
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncName);
|
|
64
|
+
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
65
|
+
extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
|
|
57
66
|
state.mcpEndpoints.files.add(node.getSourceFile().fileName);
|
|
58
67
|
state.mcpEndpoints.toolsMeta[nameValue] = {
|
|
59
68
|
pikkuFuncName,
|
|
@@ -65,6 +74,7 @@ export const addMCPTool = (logger, node, checker, state, options) => {
|
|
|
65
74
|
inputSchema,
|
|
66
75
|
outputSchema,
|
|
67
76
|
middleware,
|
|
77
|
+
permissions,
|
|
68
78
|
};
|
|
69
79
|
}
|
|
70
80
|
};
|
|
@@ -2,6 +2,8 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { extractFunctionName, isNamedExport, } from '../utils/extract-function-name.js';
|
|
3
3
|
import { extractServicesFromFunction } from '../utils/extract-services.js';
|
|
4
4
|
import { extractMiddlewarePikkuNames } from '../utils/middleware.js';
|
|
5
|
+
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
6
|
+
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
5
7
|
/**
|
|
6
8
|
* Inspect pikkuMiddleware calls, addMiddleware calls, and addHTTPMiddleware calls
|
|
7
9
|
*/
|
|
@@ -15,23 +17,46 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
15
17
|
}
|
|
16
18
|
// Handle pikkuMiddleware(...) - individual middleware function definition
|
|
17
19
|
if (expression.text === 'pikkuMiddleware') {
|
|
18
|
-
const
|
|
19
|
-
if (!
|
|
20
|
+
const arg = args[0];
|
|
21
|
+
if (!arg)
|
|
20
22
|
return;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
let actualHandler;
|
|
24
|
+
let name;
|
|
25
|
+
let description;
|
|
26
|
+
// Check if using object syntax: pikkuMiddleware({ func: ..., name: '...', description: '...' })
|
|
27
|
+
if (ts.isObjectLiteralExpression(arg)) {
|
|
28
|
+
// Extract name and description metadata
|
|
29
|
+
const nameValue = getPropertyValue(arg, 'name');
|
|
30
|
+
const descValue = getPropertyValue(arg, 'description');
|
|
31
|
+
name = typeof nameValue === 'string' ? nameValue : undefined;
|
|
32
|
+
description = typeof descValue === 'string' ? descValue : undefined;
|
|
33
|
+
// Extract the func property
|
|
34
|
+
const fnProp = getPropertyAssignmentInitializer(arg, 'func', true, checker);
|
|
35
|
+
if (!fnProp ||
|
|
36
|
+
(!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))) {
|
|
37
|
+
logger.error(`• pikkuMiddleware object missing required 'func' property.`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
actualHandler = fnProp;
|
|
41
|
+
}
|
|
42
|
+
else if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
|
|
43
|
+
actualHandler = arg;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
23
46
|
logger.error(`• Handler for pikkuMiddleware is not a function.`);
|
|
24
47
|
return;
|
|
25
48
|
}
|
|
26
|
-
const services = extractServicesFromFunction(
|
|
27
|
-
const { pikkuFuncName, exportedName } = extractFunctionName(node, checker);
|
|
49
|
+
const services = extractServicesFromFunction(actualHandler);
|
|
50
|
+
const { pikkuFuncName, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
28
51
|
state.middleware.meta[pikkuFuncName] = {
|
|
29
52
|
services,
|
|
30
53
|
sourceFile: node.getSourceFile().fileName,
|
|
31
54
|
position: node.getStart(),
|
|
32
55
|
exportedName,
|
|
56
|
+
name,
|
|
57
|
+
description,
|
|
33
58
|
};
|
|
34
|
-
logger.debug(`• Found middleware with services: ${services.services.join(', ')}`);
|
|
59
|
+
logger.debug(`• Found middleware with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`);
|
|
35
60
|
return;
|
|
36
61
|
}
|
|
37
62
|
// Handle pikkuMiddlewareFactory(...) - middleware factory function
|
|
@@ -46,6 +71,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
46
71
|
}
|
|
47
72
|
// Extract services by looking inside the factory function body
|
|
48
73
|
// The factory should return pikkuMiddleware(...), so we need to find that call
|
|
74
|
+
// If no wrapper is found, extract from the factory's returned function directly
|
|
49
75
|
let services = { optimized: false, services: [] };
|
|
50
76
|
const findPikkuMiddlewareCall = (node) => {
|
|
51
77
|
if (ts.isCallExpression(node)) {
|
|
@@ -64,7 +90,20 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
64
90
|
services = extractServicesFromFunction(middlewareHandler);
|
|
65
91
|
}
|
|
66
92
|
}
|
|
67
|
-
|
|
93
|
+
else {
|
|
94
|
+
// No pikkuMiddleware wrapper found - extract from factory's return value directly
|
|
95
|
+
// Factory pattern: (config) => (services, interaction, next) => { ... }
|
|
96
|
+
if (ts.isArrowFunction(factoryNode) ||
|
|
97
|
+
ts.isFunctionExpression(factoryNode)) {
|
|
98
|
+
const factoryBody = factoryNode.body;
|
|
99
|
+
// Check if the body is an arrow function (direct return)
|
|
100
|
+
if (ts.isArrowFunction(factoryBody) ||
|
|
101
|
+
ts.isFunctionExpression(factoryBody)) {
|
|
102
|
+
services = extractServicesFromFunction(factoryBody);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const { pikkuFuncName, exportedName } = extractFunctionName(node, checker, state.rootDir);
|
|
68
107
|
state.middleware.meta[pikkuFuncName] = {
|
|
69
108
|
services,
|
|
70
109
|
sourceFile: node.getSourceFile().fileName,
|
|
@@ -99,7 +138,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
99
138
|
return;
|
|
100
139
|
}
|
|
101
140
|
// Extract middleware pikkuFuncNames from array
|
|
102
|
-
const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker);
|
|
141
|
+
const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker, state.rootDir);
|
|
103
142
|
if (middlewareNames.length === 0) {
|
|
104
143
|
logger.warn(`• addMiddleware('${tag}', ...) has empty middleware array`);
|
|
105
144
|
return;
|
|
@@ -139,7 +178,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
139
178
|
}
|
|
140
179
|
// If not a factory, get export name from the call expression itself
|
|
141
180
|
if (!isFactory) {
|
|
142
|
-
const extracted = extractFunctionName(node, checker);
|
|
181
|
+
const extracted = extractFunctionName(node, checker, state.rootDir);
|
|
143
182
|
exportedName = extracted.exportedName;
|
|
144
183
|
}
|
|
145
184
|
// Log warning if not using factory pattern
|
|
@@ -186,7 +225,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
186
225
|
return;
|
|
187
226
|
}
|
|
188
227
|
// Extract middleware pikkuFuncNames from array
|
|
189
|
-
const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker);
|
|
228
|
+
const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker, state.rootDir);
|
|
190
229
|
if (middlewareNames.length === 0) {
|
|
191
230
|
logger.warn(`• addHTTPMiddleware('${pattern}', ...) has empty middleware array`);
|
|
192
231
|
return;
|
|
@@ -225,7 +264,7 @@ export const addMiddleware = (logger, node, checker, state) => {
|
|
|
225
264
|
}
|
|
226
265
|
// If not a factory, get export name from the call expression itself
|
|
227
266
|
if (!isFactory) {
|
|
228
|
-
const extracted = extractFunctionName(node, checker);
|
|
267
|
+
const extracted = extractFunctionName(node, checker, state.rootDir);
|
|
229
268
|
exportedName = extracted.exportedName;
|
|
230
269
|
}
|
|
231
270
|
// Log warning if not using factory pattern
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { AddWiring } from '../types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Inspect pikkuPermission calls and
|
|
4
|
-
* for tree shaking optimization.
|
|
3
|
+
* Inspect pikkuPermission calls, addPermission calls, and addHTTPPermission calls
|
|
5
4
|
*/
|
|
6
5
|
export declare const addPermission: AddWiring;
|