@pikku/inspector 0.12.2 → 0.12.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/add/add-ai-agent.js +4 -0
  3. package/dist/add/add-approval-description.d.ts +5 -0
  4. package/dist/add/add-approval-description.js +52 -0
  5. package/dist/add/add-channel.js +42 -4
  6. package/dist/add/add-cli.js +73 -13
  7. package/dist/add/add-file-with-factory.js +1 -0
  8. package/dist/add/add-functions.js +22 -3
  9. package/dist/add/add-gateway.js +5 -0
  10. package/dist/add/add-http-route.js +5 -0
  11. package/dist/add/add-mcp-prompt.js +5 -0
  12. package/dist/add/add-mcp-resource.js +5 -0
  13. package/dist/add/add-queue-worker.js +5 -0
  14. package/dist/add/add-schedule.js +5 -0
  15. package/dist/add/add-wire-addon.js +7 -0
  16. package/dist/index.d.ts +1 -0
  17. package/dist/index.js +1 -0
  18. package/dist/inspector.js +7 -0
  19. package/dist/types.d.ts +10 -0
  20. package/dist/utils/load-addon-functions-meta.d.ts +12 -0
  21. package/dist/utils/load-addon-functions-meta.js +76 -0
  22. package/dist/utils/post-process.js +26 -0
  23. package/dist/utils/resolve-function-meta.d.ts +11 -0
  24. package/dist/utils/resolve-function-meta.js +17 -0
  25. package/dist/utils/serialize-inspector-state.d.ts +2 -0
  26. package/dist/utils/serialize-inspector-state.js +4 -0
  27. package/dist/utils/serialize-mcp-json.js +13 -7
  28. package/dist/visit.js +2 -0
  29. package/package.json +2 -2
  30. package/src/add/add-ai-agent.ts +6 -0
  31. package/src/add/add-approval-description.ts +76 -0
  32. package/src/add/add-channel.ts +44 -4
  33. package/src/add/add-cli.ts +108 -21
  34. package/src/add/add-file-with-factory.ts +1 -0
  35. package/src/add/add-functions.ts +28 -3
  36. package/src/add/add-gateway.ts +6 -0
  37. package/src/add/add-http-route.ts +6 -0
  38. package/src/add/add-mcp-prompt.ts +6 -0
  39. package/src/add/add-mcp-resource.ts +6 -0
  40. package/src/add/add-queue-worker.ts +6 -0
  41. package/src/add/add-schedule.ts +6 -0
  42. package/src/add/add-wire-addon.ts +8 -0
  43. package/src/index.ts +1 -0
  44. package/src/inspector.ts +12 -0
  45. package/src/types.ts +11 -0
  46. package/src/utils/load-addon-functions-meta.ts +94 -0
  47. package/src/utils/post-process.ts +25 -0
  48. package/src/utils/resolve-function-meta.ts +25 -0
  49. package/src/utils/serialize-inspector-state.ts +6 -0
  50. package/src/utils/serialize-mcp-json.ts +12 -7
  51. package/src/visit.ts +2 -0
  52. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## 0.12.0
2
2
 
3
+ ## 0.12.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 508a796: Fix MCP server not exposing addon tools: resolve namespaced function IDs in MCP runner, load addon schemas after schema generation, and use resolveFunctionMeta for MCP JSON serialization
8
+ - 387b2ee: Add approval description inspection, track packageName on wire metadata, and resolve addon package names in channel/RPC wirings
9
+ - Updated dependencies [387b2ee]
10
+ - Updated dependencies [32ed003]
11
+ - Updated dependencies [7d369f3]
12
+ - Updated dependencies [508a796]
13
+ - Updated dependencies [ffe83af]
14
+ - Updated dependencies [c7ff141]
15
+ - @pikku/core@0.12.3
16
+
3
17
  ## 0.12.2
4
18
 
5
19
  ### Patch Changes
@@ -167,6 +167,7 @@ export const addAIAgent = (logger, node, checker, state, options) => {
167
167
  const maxStepsValue = getPropertyValue(obj, 'maxSteps');
168
168
  const temperatureValue = getPropertyValue(obj, 'temperature');
169
169
  const toolChoiceValue = getPropertyValue(obj, 'toolChoice');
170
+ const dynamicWorkflowsValue = getPropertyValue(obj, 'dynamicWorkflows');
170
171
  const toolsValue = resolveToolReferences(obj, checker, nameValue || '', logger);
171
172
  if (toolsValue) {
172
173
  for (const toolName of toolsValue) {
@@ -301,6 +302,9 @@ export const addAIAgent = (logger, node, checker, state, options) => {
301
302
  }),
302
303
  ...(toolsValue !== null && { tools: toolsValue }),
303
304
  ...(agentsValue !== null && { agents: agentsValue }),
305
+ ...(dynamicWorkflowsValue !== null && {
306
+ dynamicWorkflows: dynamicWorkflowsValue,
307
+ }),
304
308
  tags,
305
309
  inputSchema,
306
310
  outputSchema,
@@ -0,0 +1,5 @@
1
+ import type { AddWiring } from '../types.js';
2
+ /**
3
+ * Inspect pikkuApprovalDescription() calls and extract metadata
4
+ */
5
+ export declare const addApprovalDescription: AddWiring;
@@ -0,0 +1,52 @@
1
+ import * as ts from 'typescript';
2
+ import { extractFunctionName } from '../utils/extract-function-name.js';
3
+ import { extractServicesFromFunction, extractUsedWires, } from '../utils/extract-services.js';
4
+ /**
5
+ * Inspect pikkuApprovalDescription() calls and extract metadata
6
+ */
7
+ export const addApprovalDescription = (logger, node, checker, state) => {
8
+ if (!ts.isCallExpression(node))
9
+ return;
10
+ const { expression, arguments: args } = node;
11
+ if (!ts.isIdentifier(expression))
12
+ return;
13
+ if (expression.text !== 'pikkuApprovalDescription')
14
+ return;
15
+ const arg = args[0];
16
+ if (!arg)
17
+ return;
18
+ let actualHandler;
19
+ if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
20
+ actualHandler = arg;
21
+ }
22
+ else {
23
+ logger.error(`• Handler for pikkuApprovalDescription is not a function.`);
24
+ return;
25
+ }
26
+ const services = extractServicesFromFunction(actualHandler);
27
+ const wires = extractUsedWires(actualHandler, 1);
28
+ let { pikkuFuncId, exportedName } = extractFunctionName(node, checker, state.rootDir);
29
+ if (pikkuFuncId.startsWith('__temp_')) {
30
+ if (ts.isVariableDeclaration(node.parent) &&
31
+ ts.isIdentifier(node.parent.name)) {
32
+ pikkuFuncId = node.parent.name.text;
33
+ }
34
+ else if (ts.isPropertyAssignment(node.parent) &&
35
+ ts.isIdentifier(node.parent.name)) {
36
+ pikkuFuncId = node.parent.name.text;
37
+ }
38
+ else {
39
+ logger.error(`• pikkuApprovalDescription() must be assigned to a variable or object property. ` +
40
+ `Extract it to a const: const myApproval = pikkuApprovalDescription(...)`);
41
+ return;
42
+ }
43
+ }
44
+ state.functions.approvalDescriptions[pikkuFuncId] = {
45
+ services,
46
+ wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
47
+ sourceFile: node.getSourceFile().fileName,
48
+ position: node.getStart(),
49
+ exportedName,
50
+ };
51
+ logger.debug(`• Found approval description '${pikkuFuncId}' with services: ${services.services.join(', ')}`);
52
+ };
@@ -7,6 +7,8 @@ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
7
  import { resolveMiddleware, resolveChannelMiddleware, } from '../utils/middleware.js';
8
8
  import { extractWireNames } from '../utils/post-process.js';
9
9
  import { resolveIdentifier } from '../utils/resolve-identifier.js';
10
+ import { resolveFunctionMeta } from '../utils/resolve-function-meta.js';
11
+ import { resolveAddonName } from '../utils/resolve-addon-package.js';
10
12
  import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js';
11
13
  /**
12
14
  * Safely get the "initializer" expression of a property-like AST node:
@@ -63,6 +65,13 @@ function getHandlerNameFromExpression(expr, checker, rootDir) {
63
65
  }
64
66
  // Handle call expressions
65
67
  if (ts.isCallExpression(expr)) {
68
+ // Handle addon('namespace:funcName') calls
69
+ if (ts.isIdentifier(expr.expression) && expr.expression.text === 'addon') {
70
+ const [firstArg] = expr.arguments;
71
+ if (firstArg && ts.isStringLiteral(firstArg)) {
72
+ return firstArg.text;
73
+ }
74
+ }
66
75
  const { pikkuFuncId } = extractFunctionName(expr, checker, rootDir);
67
76
  return pikkuFuncId;
68
77
  }
@@ -315,9 +324,9 @@ export function addMessagesRoutes(logger, obj, state, checker) {
315
324
  logger.error(`Could not resolve handler for message route '${routeKey}'`);
316
325
  continue;
317
326
  }
318
- const fnMeta = state.functions.meta[handlerName];
327
+ const fnMeta = resolveFunctionMeta(state, handlerName);
319
328
  if (!fnMeta) {
320
- logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for handler '${handlerName}'`);
329
+ logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for channel handler '${handlerName}' on route '${routeKey}'. If this is an inline function, it must be exported for the inspector to discover it.`);
321
330
  continue;
322
331
  }
323
332
  // Resolve middleware and permissions for this route
@@ -328,8 +337,15 @@ export function addMessagesRoutes(logger, obj, state, checker) {
328
337
  const routeMiddleware = ts.isObjectLiteralExpression(init)
329
338
  ? resolveMiddleware(state, init, routeTags, checker)
330
339
  : undefined;
340
+ // Resolve package name for addon functions (e.g. 'swaggerPetstore:addPet')
341
+ const colonIdx = handlerName.indexOf(':');
342
+ const addonNs = colonIdx !== -1 ? handlerName.substring(0, colonIdx) : null;
343
+ const packageName = addonNs
344
+ ? state.rpc.wireAddonDeclarations.get(addonNs)?.package
345
+ : undefined;
331
346
  result[channelKey][routeKey] = {
332
347
  pikkuFuncId: handlerName,
348
+ packageName,
333
349
  middleware: routeMiddleware,
334
350
  };
335
351
  }
@@ -382,8 +398,12 @@ export const addChannel = (logger, node, checker, state, options) => {
382
398
  logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for onMessage handler '${msgFuncId}'`);
383
399
  return;
384
400
  }
401
+ const msgPackageName = ts.isIdentifier(onMsgProp)
402
+ ? resolveAddonName(onMsgProp, checker, state.rpc.wireAddonDeclarations)
403
+ : null;
385
404
  message = {
386
405
  pikkuFuncId: msgFuncId,
406
+ ...(msgPackageName && { packageName: msgPackageName }),
387
407
  };
388
408
  }
389
409
  // nested message-routes
@@ -394,19 +414,27 @@ export const addChannel = (logger, node, checker, state, options) => {
394
414
  // --- track used functions/middleware for service aggregation ---
395
415
  // Track connect/disconnect/message handlers
396
416
  let connectFuncId;
417
+ let connectPackageName = null;
397
418
  if (connect) {
398
419
  const extracted = extractFunctionName(connect, checker, state.rootDir);
399
420
  connectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
400
421
  ? makeContextBasedId('channel', name, 'connect')
401
422
  : extracted.pikkuFuncId;
423
+ connectPackageName = ts.isIdentifier(connect)
424
+ ? resolveAddonName(connect, checker, state.rpc.wireAddonDeclarations)
425
+ : null;
402
426
  state.serviceAggregation.usedFunctions.add(connectFuncId);
403
427
  }
404
428
  let disconnectFuncId;
429
+ let disconnectPackageName = null;
405
430
  if (disconnect) {
406
431
  const extracted = extractFunctionName(disconnect, checker, state.rootDir);
407
432
  disconnectFuncId = extracted.pikkuFuncId.startsWith('__temp_')
408
433
  ? makeContextBasedId('channel', name, 'disconnect')
409
434
  : extracted.pikkuFuncId;
435
+ disconnectPackageName = ts.isIdentifier(disconnect)
436
+ ? resolveAddonName(disconnect, checker, state.rpc.wireAddonDeclarations)
437
+ : null;
410
438
  state.serviceAggregation.usedFunctions.add(disconnectFuncId);
411
439
  }
412
440
  if (message) {
@@ -436,8 +464,18 @@ export const addChannel = (logger, node, checker, state, options) => {
436
464
  input: null,
437
465
  params: params.length ? params : undefined,
438
466
  query: query?.length ? query : undefined,
439
- connect: connectFuncId ? { pikkuFuncId: connectFuncId } : null,
440
- disconnect: disconnectFuncId ? { pikkuFuncId: disconnectFuncId } : null,
467
+ connect: connectFuncId
468
+ ? {
469
+ pikkuFuncId: connectFuncId,
470
+ ...(connectPackageName && { packageName: connectPackageName }),
471
+ }
472
+ : null,
473
+ disconnect: disconnectFuncId
474
+ ? {
475
+ pikkuFuncId: disconnectFuncId,
476
+ ...(disconnectPackageName && { packageName: disconnectPackageName }),
477
+ }
478
+ : null,
441
479
  message,
442
480
  messageWirings,
443
481
  binary: binary === undefined ? undefined : binary,
@@ -1,9 +1,11 @@
1
1
  import ts from 'typescript';
2
2
  import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
3
3
  import { resolveMiddleware } from '../utils/middleware.js';
4
+ import { resolveFunctionMeta } from '../utils/resolve-function-meta.js';
4
5
  import { extractWireNames } from '../utils/post-process.js';
5
6
  import { getPropertyValue } from '../utils/get-property-value.js';
6
7
  import { resolveIdentifier } from '../utils/resolve-identifier.js';
8
+ import { resolveAddonName } from '../utils/resolve-addon-package.js';
7
9
  import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js';
8
10
  // Track if we've warned about missing Config type to avoid duplicate warnings
9
11
  const configTypeWarningShown = new Set();
@@ -202,11 +204,38 @@ function processCommand(logger, inspectorState, options, name, node, sourceFile,
202
204
  continue;
203
205
  const propName = prop.name.text;
204
206
  if (propName === 'func') {
205
- pikkuFuncId = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
206
- if (pikkuFuncId.startsWith('__temp_')) {
207
- pikkuFuncId = makeContextBasedId('cli', programName, ...fullPath);
207
+ if (ts.isCallExpression(prop.initializer) &&
208
+ ts.isIdentifier(prop.initializer.expression) &&
209
+ prop.initializer.expression.text === 'addon') {
210
+ const [firstArg] = prop.initializer.arguments;
211
+ if (!firstArg || !ts.isStringLiteral(firstArg)) {
212
+ throw new Error(`addon() call requires a string literal argument in the form "namespace:funcName"`);
213
+ }
214
+ pikkuFuncId = firstArg.text;
215
+ const addonNamespace = pikkuFuncId.split(':')[0];
216
+ if (!addonNamespace || !pikkuFuncId.includes(':')) {
217
+ throw new Error(`Malformed addon function ID "${pikkuFuncId}": expected "namespace:funcName" format`);
218
+ }
219
+ if (!inspectorState.rpc.wireAddonDeclarations.has(addonNamespace)) {
220
+ throw new Error(`Unknown addon namespace "${addonNamespace}" in "${pikkuFuncId}": no matching wireAddonDeclarations entry found`);
221
+ }
222
+ meta.pikkuFuncId = pikkuFuncId;
223
+ meta.packageName =
224
+ inspectorState.rpc.wireAddonDeclarations.get(addonNamespace).package;
225
+ }
226
+ else {
227
+ pikkuFuncId = extractFunctionName(prop.initializer, typeChecker, inspectorState.rootDir).pikkuFuncId;
228
+ if (pikkuFuncId.startsWith('__temp_')) {
229
+ pikkuFuncId = makeContextBasedId('cli', programName, ...fullPath);
230
+ }
231
+ meta.pikkuFuncId = pikkuFuncId;
232
+ const cliPackageName = ts.isIdentifier(prop.initializer)
233
+ ? resolveAddonName(prop.initializer, typeChecker, inspectorState.rpc.wireAddonDeclarations)
234
+ : null;
235
+ if (cliPackageName) {
236
+ meta.packageName = cliPackageName;
237
+ }
208
238
  }
209
- meta.pikkuFuncId = pikkuFuncId;
210
239
  }
211
240
  else if (propName === 'options' &&
212
241
  ts.isObjectLiteralExpression(prop.initializer)) {
@@ -357,17 +386,20 @@ function processOptions(logger, node, typeChecker, inspectorState, inspectorOpti
357
386
  }
358
387
  }
359
388
  // Extract enum values from the function input type if available
360
- // Get the input type if we have a pikkuFuncId
361
- let inputTypes;
362
- if (pikkuFuncId) {
363
- inputTypes = inspectorState.typesLookup.get(pikkuFuncId);
364
- }
365
389
  let derivedChoices = null;
366
- if (inputTypes && inputTypes.length > 0) {
367
- derivedChoices = extractEnumFromPropertyType(inputTypes[0], optionName, typeChecker);
390
+ if (pikkuFuncId) {
391
+ // 1. Try TypeScript types first (most precise — handles unions, TS enums)
392
+ const inputTypes = inspectorState.typesLookup.get(pikkuFuncId);
393
+ if (inputTypes && inputTypes.length > 0) {
394
+ derivedChoices = extractEnumFromPropertyType(inputTypes[0], optionName, typeChecker);
395
+ }
396
+ // 2. Fallback: try JSON schema (works for addon functions)
397
+ if (!derivedChoices) {
398
+ derivedChoices = extractEnumFromJsonSchema(inspectorState, pikkuFuncId, optionName);
399
+ }
368
400
  }
369
- else {
370
- // Fallback: try to extract from Config type
401
+ // 3. Last resort: try Config type
402
+ if (!derivedChoices) {
371
403
  derivedChoices = extractEnumFromConfigType(logger, optionName, typeChecker, inspectorState, inspectorOptions);
372
404
  }
373
405
  // Validate and set choices
@@ -488,6 +520,34 @@ function extractEnumFromConfigType(logger, propertyName, typeChecker, inspectorS
488
520
  // Extract enum from the property
489
521
  return extractEnumFromPropertyType(configType, propertyName, typeChecker);
490
522
  }
523
+ /**
524
+ * Extracts enum values from the function's JSON schema.
525
+ * Works for addon functions whose schemas are generated from OpenAPI/Zod.
526
+ */
527
+ function extractEnumFromJsonSchema(inspectorState, pikkuFuncId, propertyName) {
528
+ const fnMeta = resolveFunctionMeta(inspectorState, pikkuFuncId);
529
+ if (!fnMeta?.inputSchemaName)
530
+ return null;
531
+ const schema = inspectorState.schemas[fnMeta.inputSchemaName];
532
+ if (!schema?.properties?.[propertyName])
533
+ return null;
534
+ const prop = schema.properties[propertyName];
535
+ // Direct enum on property
536
+ if (prop.enum && Array.isArray(prop.enum)) {
537
+ const strings = prop.enum.filter((v) => typeof v === 'string');
538
+ if (strings.length > 0)
539
+ return strings;
540
+ }
541
+ // Array with enum items (e.g. z.array(z.enum([...])))
542
+ if (prop.type === 'array' &&
543
+ prop.items?.enum &&
544
+ Array.isArray(prop.items.enum)) {
545
+ const strings = prop.items.enum.filter((v) => typeof v === 'string');
546
+ if (strings.length > 0)
547
+ return strings;
548
+ }
549
+ return null;
550
+ }
491
551
  /**
492
552
  * Gets the property name from a property assignment
493
553
  */
@@ -7,6 +7,7 @@ const wrapperFunctionMap = {
7
7
  pikkuServices: 'CreateSingletonServices',
8
8
  pikkuAddonServices: 'CreateSingletonServices',
9
9
  pikkuWireServices: 'CreateWireServices',
10
+ pikkuAddonWireServices: 'CreateWireServices',
10
11
  };
11
12
  export const addFileWithFactory = (node, checker, methods = new Map(), expectedTypeName, state) => {
12
13
  if (ts.isVariableDeclaration(node)) {
@@ -245,7 +245,8 @@ export const addFunctions = (logger, node, checker, state, options) => {
245
245
  let remote;
246
246
  let mcp;
247
247
  let readonly_;
248
- let requiresApproval;
248
+ let approvalRequired;
249
+ let approvalDescription;
249
250
  let version;
250
251
  let objectNode;
251
252
  let nodeDisplayName = null;
@@ -311,7 +312,24 @@ export const addFunctions = (logger, node, checker, state, options) => {
311
312
  remote = getPropertyValue(firstArg, 'remote');
312
313
  mcp = getPropertyValue(firstArg, 'mcp');
313
314
  readonly_ = getPropertyValue(firstArg, 'readonly');
314
- requiresApproval = getPropertyValue(firstArg, 'requiresApproval');
315
+ approvalRequired = getPropertyValue(firstArg, 'approvalRequired');
316
+ // Extract approvalDescription identifier reference
317
+ for (const prop of firstArg.properties) {
318
+ if (ts.isPropertyAssignment(prop) &&
319
+ ts.isIdentifier(prop.name) &&
320
+ prop.name.text === 'approvalDescription' &&
321
+ ts.isIdentifier(prop.initializer)) {
322
+ const { pikkuFuncId: descId } = extractFunctionName(prop.initializer, checker, state.rootDir);
323
+ if (descId && !descId.startsWith('__temp_')) {
324
+ approvalDescription = descId;
325
+ }
326
+ else {
327
+ // Try resolving the identifier directly
328
+ approvalDescription = prop.initializer.text;
329
+ }
330
+ break;
331
+ }
332
+ }
315
333
  const versionRaw = getPropertyValue(firstArg, 'version');
316
334
  if (versionRaw !== null && versionRaw !== undefined) {
317
335
  const parsed = Number(versionRaw);
@@ -565,7 +583,8 @@ export const addFunctions = (logger, node, checker, state, options) => {
565
583
  remote: remote || undefined,
566
584
  mcp: mcpEnabled || undefined,
567
585
  readonly: readonly_ || undefined,
568
- requiresApproval: requiresApproval || undefined,
586
+ approvalRequired: approvalRequired || undefined,
587
+ approvalDescription: approvalDescription || undefined,
569
588
  version,
570
589
  title,
571
590
  tags: tags || undefined,
@@ -4,6 +4,7 @@ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-funct
4
4
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
5
  import { resolveMiddleware } from '../utils/middleware.js';
6
6
  import { extractWireNames } from '../utils/post-process.js';
7
+ import { resolveAddonName } from '../utils/resolve-addon-package.js';
7
8
  import { ErrorCode } from '../error-codes.js';
8
9
  export const addGateway = (logger, node, checker, state, _options) => {
9
10
  if (!ts.isCallExpression(node)) {
@@ -35,6 +36,9 @@ export const addGateway = (logger, node, checker, state, _options) => {
35
36
  if (pikkuFuncId.startsWith('__temp_') && nameValue) {
36
37
  pikkuFuncId = makeContextBasedId('gateway', nameValue);
37
38
  }
39
+ const packageName = ts.isIdentifier(funcInitializer)
40
+ ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
41
+ : null;
38
42
  if (!nameValue || !typeValue) {
39
43
  return;
40
44
  }
@@ -44,6 +48,7 @@ export const addGateway = (logger, node, checker, state, _options) => {
44
48
  state.gateways.files.add(node.getSourceFile().fileName);
45
49
  state.gateways.meta[nameValue] = {
46
50
  pikkuFuncId,
51
+ ...(packageName && { packageName }),
47
52
  name: nameValue,
48
53
  type: typeValue,
49
54
  route: routeValue,
@@ -10,6 +10,7 @@ import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
10
10
  import { ErrorCode } from '../error-codes.js';
11
11
  import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js';
12
12
  import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
13
+ import { resolveAddonName } from '../utils/resolve-addon-package.js';
13
14
  /**
14
15
  * Extract header schema reference from headers property
15
16
  */
@@ -112,6 +113,9 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
112
113
  if (funcName.startsWith('__temp_')) {
113
114
  funcName = makeContextBasedId('http', method, fullRoute);
114
115
  }
116
+ const packageName = ts.isIdentifier(funcInitializer)
117
+ ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
118
+ : null;
115
119
  ensureFunctionMetadata(state, funcName, fullRoute, funcInitializer, checker, extracted.isHelper);
116
120
  // Lookup existing function metadata
117
121
  const fnMeta = state.functions.meta[funcName];
@@ -168,6 +172,7 @@ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, bas
168
172
  state.http.files.add(sourceFile.fileName);
169
173
  state.http.meta[method][fullRoute] = {
170
174
  pikkuFuncId: funcName,
175
+ ...(packageName && { packageName }),
171
176
  route: fullRoute,
172
177
  method: method,
173
178
  params: params.length > 0 ? params : undefined,
@@ -6,6 +6,7 @@ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-funct
6
6
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
7
  import { resolveMiddleware } from '../utils/middleware.js';
8
8
  import { resolvePermissions } from '../utils/permissions.js';
9
+ import { resolveAddonName } from '../utils/resolve-addon-package.js';
9
10
  import { ErrorCode } from '../error-codes.js';
10
11
  export const addMCPPrompt = (logger, node, checker, state, options) => {
11
12
  if (!ts.isCallExpression(node)) {
@@ -37,6 +38,9 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
37
38
  if (pikkuFuncId.startsWith('__temp_') && nameValue) {
38
39
  pikkuFuncId = makeContextBasedId('mcp', 'prompt', nameValue);
39
40
  }
41
+ const packageName = ts.isIdentifier(funcInitializer)
42
+ ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
43
+ : null;
40
44
  ensureFunctionMetadata(state, pikkuFuncId, nameValue || undefined, funcInitializer, checker, extracted.isHelper);
41
45
  if (!nameValue) {
42
46
  logger.critical(ErrorCode.MISSING_NAME, "MCP prompt is missing the required 'name' property.");
@@ -65,6 +69,7 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
65
69
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
66
70
  state.mcpEndpoints.promptsMeta[nameValue] = {
67
71
  pikkuFuncId,
72
+ ...(packageName && { packageName }),
68
73
  name: nameValue,
69
74
  description,
70
75
  summary,
@@ -6,6 +6,7 @@ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-funct
6
6
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
7
  import { resolveMiddleware } from '../utils/middleware.js';
8
8
  import { resolvePermissions } from '../utils/permissions.js';
9
+ import { resolveAddonName } from '../utils/resolve-addon-package.js';
9
10
  import { ErrorCode } from '../error-codes.js';
10
11
  export const addMCPResource = (logger, node, checker, state, options) => {
11
12
  if (!ts.isCallExpression(node)) {
@@ -42,6 +43,9 @@ export const addMCPResource = (logger, node, checker, state, options) => {
42
43
  if (pikkuFuncId.startsWith('__temp_') && uriValue) {
43
44
  pikkuFuncId = makeContextBasedId('mcp', 'resource', uriValue);
44
45
  }
46
+ const packageName = ts.isIdentifier(funcInitializer)
47
+ ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
48
+ : null;
45
49
  ensureFunctionMetadata(state, pikkuFuncId, uriValue || undefined, funcInitializer, checker, extracted.isHelper);
46
50
  if (!uriValue) {
47
51
  logger.critical(ErrorCode.MISSING_URI, "MCP resource is missing the required 'uri' property.");
@@ -74,6 +78,7 @@ export const addMCPResource = (logger, node, checker, state, options) => {
74
78
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
75
79
  state.mcpEndpoints.resourcesMeta[uriValue] = {
76
80
  pikkuFuncId,
81
+ ...(packageName && { packageName }),
77
82
  uri: uriValue,
78
83
  title: titleValue,
79
84
  description,
@@ -4,6 +4,7 @@ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-funct
4
4
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
5
  import { resolveMiddleware } from '../utils/middleware.js';
6
6
  import { extractWireNames } from '../utils/post-process.js';
7
+ import { resolveAddonName } from '../utils/resolve-addon-package.js';
7
8
  import { ErrorCode } from '../error-codes.js';
8
9
  export const addQueueWorker = (logger, node, checker, state) => {
9
10
  if (!ts.isCallExpression(node)) {
@@ -36,6 +37,9 @@ export const addQueueWorker = (logger, node, checker, state) => {
36
37
  if (pikkuFuncId.startsWith('__temp_') && name) {
37
38
  pikkuFuncId = makeContextBasedId('queue', name);
38
39
  }
40
+ const packageName = ts.isIdentifier(funcInitializer)
41
+ ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
42
+ : null;
39
43
  if (!name) {
40
44
  logger.critical(ErrorCode.MISSING_QUEUE_NAME, `No 'name' provided for queue processor function '${pikkuFuncId}'.`);
41
45
  return;
@@ -48,6 +52,7 @@ export const addQueueWorker = (logger, node, checker, state) => {
48
52
  state.queueWorkers.files.add(node.getSourceFile().fileName);
49
53
  state.queueWorkers.meta[name] = {
50
54
  pikkuFuncId,
55
+ ...(packageName && { packageName }),
51
56
  name,
52
57
  summary,
53
58
  description,
@@ -4,6 +4,7 @@ import { extractFunctionName, makeContextBasedId, } from '../utils/extract-funct
4
4
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
5
5
  import { resolveMiddleware } from '../utils/middleware.js';
6
6
  import { extractWireNames } from '../utils/post-process.js';
7
+ import { resolveAddonName } from '../utils/resolve-addon-package.js';
7
8
  import { ErrorCode } from '../error-codes.js';
8
9
  export const addSchedule = (logger, node, checker, state, options) => {
9
10
  if (!ts.isCallExpression(node)) {
@@ -36,6 +37,9 @@ export const addSchedule = (logger, node, checker, state, options) => {
36
37
  if (pikkuFuncId.startsWith('__temp_') && nameValue) {
37
38
  pikkuFuncId = makeContextBasedId('scheduler', nameValue);
38
39
  }
40
+ const packageName = ts.isIdentifier(funcInitializer)
41
+ ? resolveAddonName(funcInitializer, checker, state.rpc.wireAddonDeclarations)
42
+ : null;
39
43
  if (!nameValue || !scheduleValue) {
40
44
  return;
41
45
  }
@@ -47,6 +51,7 @@ export const addSchedule = (logger, node, checker, state, options) => {
47
51
  state.scheduledTasks.files.add(node.getSourceFile().fileName);
48
52
  state.scheduledTasks.meta[nameValue] = {
49
53
  pikkuFuncId,
54
+ ...(packageName && { packageName }),
50
55
  name: nameValue,
51
56
  schedule: scheduleValue,
52
57
  summary,
@@ -32,6 +32,7 @@ export function addWireAddon(node, state, logger) {
32
32
  let name;
33
33
  let pkg;
34
34
  let rpcEndpoint;
35
+ let mcp;
35
36
  let secretOverrides;
36
37
  let variableOverrides;
37
38
  for (const prop of firstArg.properties) {
@@ -47,6 +48,11 @@ export function addWireAddon(node, state, logger) {
47
48
  else if (key === 'rpcEndpoint' && ts.isStringLiteral(prop.initializer)) {
48
49
  rpcEndpoint = prop.initializer.text;
49
50
  }
51
+ else if (key === 'mcp' &&
52
+ (prop.initializer.kind === ts.SyntaxKind.TrueKeyword ||
53
+ prop.initializer.kind === ts.SyntaxKind.FalseKeyword)) {
54
+ mcp = prop.initializer.kind === ts.SyntaxKind.TrueKeyword;
55
+ }
50
56
  else if (key === 'secretOverrides' &&
51
57
  ts.isObjectLiteralExpression(prop.initializer)) {
52
58
  secretOverrides = parseStringRecord(prop.initializer);
@@ -62,6 +68,7 @@ export function addWireAddon(node, state, logger) {
62
68
  state.rpc.wireAddonDeclarations.set(name, {
63
69
  package: pkg,
64
70
  rpcEndpoint,
71
+ mcp,
65
72
  secretOverrides,
66
73
  variableOverrides,
67
74
  });
package/dist/index.d.ts CHANGED
@@ -13,4 +13,5 @@ export { serializeMCPJson } from './utils/serialize-mcp-json.js';
13
13
  export type { OpenAPISpecInfo } from './utils/serialize-openapi-json.js';
14
14
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
15
15
  export { getFilesAndMethods } from './utils/get-files-and-methods.js';
16
+ export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
16
17
  export type { SerializedWorkflowGraph, SerializedWorkflowGraphs, } from './utils/workflow/graph/index.js';
package/dist/index.js CHANGED
@@ -7,3 +7,4 @@ export { createEmptyManifest, serializeManifest, } from './utils/contract-hashes
7
7
  export { serializeMCPJson } from './utils/serialize-mcp-json.js';
8
8
  export { deserializeDslWorkflow, deserializeGraphWorkflow, deserializeAllDslWorkflows, } from './utils/workflow/dsl/index.js';
9
9
  export { getFilesAndMethods } from './utils/get-files-and-methods.js';
10
+ export { resolveFunctionMeta } from './utils/resolve-function-meta.js';
package/dist/inspector.js CHANGED
@@ -11,6 +11,7 @@ import { resolveLatestVersions } from './utils/resolve-versions.js';
11
11
  import { finalizeWorkflows } from './utils/workflow/graph/finalize-workflows.js';
12
12
  import { finalizeWorkflowHelperTypes, finalizeWorkflowWires, } from './utils/workflow/graph/finalize-workflow-wires.js';
13
13
  import { generateAllSchemas } from './utils/schema-generator.js';
14
+ import { loadAddonFunctionsMeta, loadAddonSchemas, } from './utils/load-addon-functions-meta.js';
14
15
  import { computeContractHashes, extractContractsFromMeta, updateManifest, createEmptyManifest, validateContracts, } from './utils/contract-hashes.js';
15
16
  /**
16
17
  * Creates an initial/empty inspector state with all required properties initialized
@@ -37,6 +38,7 @@ export function getInitialInspectorState(rootDir) {
37
38
  typesMap: new TypesMap(),
38
39
  meta: {},
39
40
  files: new Map(),
41
+ approvalDescriptions: {},
40
42
  },
41
43
  http: {
42
44
  metaInputTypes: new Map(),
@@ -171,6 +173,7 @@ export function getInitialInspectorState(rootDir) {
171
173
  requiredSchemas: new Set(),
172
174
  openAPISpec: null,
173
175
  diagnostics: [],
176
+ addonFunctions: {},
174
177
  };
175
178
  }
176
179
  export const inspect = async (logger, routeFiles, options = {}) => {
@@ -205,6 +208,8 @@ export const inspect = async (logger, routeFiles, options = {}) => {
205
208
  ts.forEachChild(sourceFile, (child) => visitSetup(logger, checker, child, state, options));
206
209
  }
207
210
  logger.debug(`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(2)}ms`);
211
+ // Load addon function metadata so wirings can reference addon functions
212
+ await loadAddonFunctionsMeta(logger, state);
208
213
  if (!options.setupOnly) {
209
214
  // Second sweep: add all transports
210
215
  const startRoutes = performance.now();
@@ -218,6 +223,8 @@ export const inspect = async (logger, routeFiles, options = {}) => {
218
223
  computeContractHashes(state.schemas, state.functions.typesMap, state.functions.meta);
219
224
  computeRequiredSchemas(state, options);
220
225
  }
226
+ // Re-load addon schemas (generateAllSchemas replaces state.schemas)
227
+ await loadAddonSchemas(logger, state);
221
228
  state.manifest.initial = options.manifest ?? null;
222
229
  const contracts = extractContractsFromMeta(state.functions.meta);
223
230
  const baseManifest = state.manifest.initial ?? createEmptyManifest();