@pikku/inspector 0.7.7 → 0.8.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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @pikku/inspector
2
2
 
3
+ ## 0.8.0
4
+
5
+ ### Major Features
6
+
7
+ - **Model Context Protocol (MCP) Analysis**: Added comprehensive MCP endpoint analysis
8
+ - **Queue Worker Analysis**: Added queue analysis
9
+ - **Enhanced Service Analysis**: Added service destructuring analysis for better code generation and type safety
10
+
3
11
  ## 0.7.7
4
12
 
5
13
  ### Patch Changes
@@ -1,6 +1,7 @@
1
1
  import * as ts from 'typescript';
2
2
  import { getPropertyValue } from './get-property-value.js';
3
3
  import { pathToRegexp } from 'path-to-regexp';
4
+ import { PikkuEventTypes } from '@pikku/core';
4
5
  import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
6
  /**
6
7
  * Safely get the “initializer” expression of a property-like AST node:
@@ -291,7 +292,7 @@ export function addChannel(node, checker, state, filters) {
291
292
  const docs = getPropertyValue(obj, 'docs');
292
293
  const tags = getPropertyValue(obj, 'tags');
293
294
  const query = getPropertyValue(obj, 'query');
294
- if (!matchesFilters(filters, { tags }, { type: 'channel', name }))
295
+ if (!matchesFilters(filters, { tags }, { type: PikkuEventTypes.channel, name }))
295
296
  return;
296
297
  const connect = getPropertyAssignmentInitializer(obj, 'onConnect', false, checker);
297
298
  const disconnect = getPropertyAssignmentInitializer(obj, 'onDisconnect', false, checker);
@@ -1,6 +1,7 @@
1
1
  import * as ts from 'typescript';
2
2
  import { getPropertyValue } from './get-property-value.js';
3
3
  import { pathToRegexp } from 'path-to-regexp';
4
+ import { PikkuEventTypes } from '@pikku/core';
4
5
  import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
6
  /**
6
7
  * Populate metaInputTypes for a given route based on method, input type,
@@ -44,7 +45,7 @@ export const addHTTPRoute = (node, checker, state, filters) => {
44
45
  const docs = getPropertyValue(obj, 'docs') || undefined;
45
46
  const tags = getPropertyValue(obj, 'tags') || undefined;
46
47
  const query = getPropertyValue(obj, 'query') || [];
47
- if (!matchesFilters(filters, { tags }, { type: 'http', name: route })) {
48
+ if (!matchesFilters(filters, { tags }, { type: PikkuEventTypes.http, name: route })) {
48
49
  return;
49
50
  }
50
51
  // --- find the referenced function ---
@@ -0,0 +1,3 @@
1
+ import * as ts from 'typescript';
2
+ import { InspectorFilters, InspectorState } from './types.js';
3
+ export declare const addMCPPrompt: (node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters) => void;
@@ -0,0 +1,60 @@
1
+ import * as ts from 'typescript';
2
+ import { getPropertyValue } from './get-property-value.js';
3
+ import { PikkuEventTypes } from '@pikku/core';
4
+ import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
+ export const addMCPPrompt = (node, checker, state, filters) => {
6
+ if (!ts.isCallExpression(node)) {
7
+ return;
8
+ }
9
+ const args = node.arguments;
10
+ const firstArg = args[0];
11
+ const expression = node.expression;
12
+ // Check if the call is to addMCPPrompt
13
+ if (!ts.isIdentifier(expression) || expression.text !== 'addMCPPrompt') {
14
+ return;
15
+ }
16
+ if (!firstArg) {
17
+ return;
18
+ }
19
+ if (ts.isObjectLiteralExpression(firstArg)) {
20
+ const obj = firstArg;
21
+ const nameValue = getPropertyValue(obj, 'name');
22
+ const descriptionValue = getPropertyValue(obj, 'description');
23
+ const tags = getPropertyValue(obj, 'tags') || undefined;
24
+ const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
25
+ if (!funcInitializer) {
26
+ console.error(`• No valid 'func' property for MCP prompt '${nameValue}'.`);
27
+ return;
28
+ }
29
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
30
+ if (!nameValue) {
31
+ console.error(`• MCP prompt is missing the required 'name' property.`);
32
+ return;
33
+ }
34
+ if (!descriptionValue) {
35
+ console.error(`• MCP prompt '${nameValue}' is missing a description.`);
36
+ return;
37
+ }
38
+ if (!matchesFilters(filters, { tags }, { type: PikkuEventTypes.mcp, name: nameValue })) {
39
+ return;
40
+ }
41
+ // lookup existing function metadata
42
+ const fnMeta = state.functions.meta[pikkuFuncName];
43
+ if (!fnMeta) {
44
+ console.error(`• No function metadata found for '${pikkuFuncName}'.`);
45
+ return;
46
+ }
47
+ const inputSchema = fnMeta.inputs?.[0] || null;
48
+ const outputSchema = fnMeta.outputs?.[0] || null;
49
+ state.mcpEndpoints.files.add(node.getSourceFile().fileName);
50
+ state.mcpEndpoints.promptsMeta[nameValue] = {
51
+ pikkuFuncName,
52
+ name: nameValue,
53
+ description: descriptionValue,
54
+ tags,
55
+ inputSchema,
56
+ outputSchema,
57
+ arguments: [], // Will be populated by CLI during serialization
58
+ };
59
+ }
60
+ };
@@ -0,0 +1,3 @@
1
+ import * as ts from 'typescript';
2
+ import { InspectorFilters, InspectorState } from './types.js';
3
+ export declare const addMCPResource: (node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters) => void;
@@ -0,0 +1,67 @@
1
+ import * as ts from 'typescript';
2
+ import { getPropertyValue } from './get-property-value.js';
3
+ import { PikkuEventTypes } from '@pikku/core';
4
+ import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
+ export const addMCPResource = (node, checker, state, filters) => {
6
+ if (!ts.isCallExpression(node)) {
7
+ return;
8
+ }
9
+ const args = node.arguments;
10
+ const firstArg = args[0];
11
+ const expression = node.expression;
12
+ // Check if the call is to addMCPResource
13
+ if (!ts.isIdentifier(expression) || expression.text !== 'addMCPResource') {
14
+ return;
15
+ }
16
+ if (!firstArg) {
17
+ return;
18
+ }
19
+ if (ts.isObjectLiteralExpression(firstArg)) {
20
+ const obj = firstArg;
21
+ const uriValue = getPropertyValue(obj, 'uri');
22
+ const titleValue = getPropertyValue(obj, 'title');
23
+ const descriptionValue = getPropertyValue(obj, 'description');
24
+ const streamingValue = getPropertyValue(obj, 'streaming');
25
+ const tags = getPropertyValue(obj, 'tags') || undefined;
26
+ const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
27
+ if (!funcInitializer) {
28
+ console.error(`• No valid 'func' property for MCP resource '${uriValue}'.`);
29
+ return;
30
+ }
31
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
32
+ if (!uriValue) {
33
+ console.error(`• MCP resource is missing the required 'uri' property.`);
34
+ return;
35
+ }
36
+ if (!titleValue) {
37
+ console.error(`• MCP resource '${uriValue}' is missing the required 'title' property.`);
38
+ return;
39
+ }
40
+ if (!descriptionValue) {
41
+ console.error(`• MCP resource '${uriValue}' is missing a description.`);
42
+ return;
43
+ }
44
+ if (!matchesFilters(filters, { tags }, { type: PikkuEventTypes.mcp, name: uriValue })) {
45
+ return;
46
+ }
47
+ // lookup existing function metadata
48
+ const fnMeta = state.functions.meta[pikkuFuncName];
49
+ if (!fnMeta) {
50
+ console.error(`• No function metadata found for '${pikkuFuncName}'.`);
51
+ return;
52
+ }
53
+ const inputSchema = fnMeta.inputs?.[0] || null;
54
+ const outputSchema = fnMeta.outputs?.[0] || null;
55
+ state.mcpEndpoints.files.add(node.getSourceFile().fileName);
56
+ state.mcpEndpoints.resourcesMeta[uriValue] = {
57
+ pikkuFuncName,
58
+ uri: uriValue,
59
+ title: titleValue,
60
+ description: descriptionValue,
61
+ ...(streamingValue !== null && { streaming: streamingValue }),
62
+ tags,
63
+ inputSchema,
64
+ outputSchema,
65
+ };
66
+ }
67
+ };
@@ -0,0 +1,3 @@
1
+ import * as ts from 'typescript';
2
+ import { InspectorFilters, InspectorState } from './types.js';
3
+ export declare const addMCPTool: (node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters) => void;
@@ -0,0 +1,63 @@
1
+ import * as ts from 'typescript';
2
+ import { getPropertyValue } from './get-property-value.js';
3
+ import { PikkuEventTypes } from '@pikku/core';
4
+ import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
+ export const addMCPTool = (node, checker, state, filters) => {
6
+ if (!ts.isCallExpression(node)) {
7
+ return;
8
+ }
9
+ const args = node.arguments;
10
+ const firstArg = args[0];
11
+ const expression = node.expression;
12
+ // Check if the call is to addMCPTool
13
+ if (!ts.isIdentifier(expression) || expression.text !== 'addMCPTool') {
14
+ return;
15
+ }
16
+ if (!firstArg) {
17
+ return;
18
+ }
19
+ if (ts.isObjectLiteralExpression(firstArg)) {
20
+ const obj = firstArg;
21
+ const nameValue = getPropertyValue(obj, 'name');
22
+ const titleValue = getPropertyValue(obj, 'title');
23
+ const descriptionValue = getPropertyValue(obj, 'description');
24
+ const streamingValue = getPropertyValue(obj, 'streaming');
25
+ const tags = getPropertyValue(obj, 'tags') || undefined;
26
+ const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
27
+ if (!funcInitializer) {
28
+ console.error(`• No valid 'func' property for MCP tool '${nameValue}'.`);
29
+ return;
30
+ }
31
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
32
+ if (!nameValue) {
33
+ console.error(`• MCP tool is missing the required 'name' property.`);
34
+ return;
35
+ }
36
+ if (!descriptionValue) {
37
+ console.error(`• MCP tool '${nameValue}' is missing a description.`);
38
+ return;
39
+ }
40
+ if (!matchesFilters(filters, { tags }, { type: PikkuEventTypes.mcp, name: nameValue })) {
41
+ return;
42
+ }
43
+ // lookup existing function metadata
44
+ const fnMeta = state.functions.meta[pikkuFuncName];
45
+ if (!fnMeta) {
46
+ console.error(`• No function metadata found for '${pikkuFuncName}'.`);
47
+ return;
48
+ }
49
+ const inputSchema = fnMeta.inputs?.[0] || null;
50
+ const outputSchema = fnMeta.outputs?.[0] || null;
51
+ state.mcpEndpoints.files.add(node.getSourceFile().fileName);
52
+ state.mcpEndpoints.toolsMeta[nameValue] = {
53
+ pikkuFuncName,
54
+ name: nameValue,
55
+ title: titleValue || undefined,
56
+ description: descriptionValue,
57
+ ...(streamingValue !== null && { streaming: streamingValue }),
58
+ tags,
59
+ inputSchema,
60
+ outputSchema,
61
+ };
62
+ }
63
+ };
@@ -0,0 +1,3 @@
1
+ import * as ts from 'typescript';
2
+ import { InspectorFilters, InspectorState } from './types.js';
3
+ export declare const addQueueWorker: (node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters) => void;
@@ -0,0 +1,47 @@
1
+ import * as ts from 'typescript';
2
+ import { getPropertyValue } from './get-property-value.js';
3
+ import { PikkuEventTypes } from '@pikku/core';
4
+ import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
+ export const addQueueWorker = (node, checker, state, filters) => {
6
+ if (!ts.isCallExpression(node)) {
7
+ return;
8
+ }
9
+ const args = node.arguments;
10
+ const firstArg = args[0];
11
+ const expression = node.expression;
12
+ // Check if the call is to addQueueWorker
13
+ if (!ts.isIdentifier(expression) || expression.text !== 'addQueueWorker') {
14
+ return;
15
+ }
16
+ if (!firstArg) {
17
+ return;
18
+ }
19
+ if (ts.isObjectLiteralExpression(firstArg)) {
20
+ const obj = firstArg;
21
+ const queueName = getPropertyValue(obj, 'queueName');
22
+ const docs = getPropertyValue(obj, 'docs') || undefined;
23
+ const tags = getPropertyValue(obj, 'tags') || undefined;
24
+ // --- find the referenced function ---
25
+ const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
26
+ if (!funcInitializer) {
27
+ console.error(`• No valid 'func' property for queue processor '${queueName}'.`);
28
+ return;
29
+ }
30
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
31
+ if (!queueName) {
32
+ console.error(`• No 'queueName' provided for queue processor function '${pikkuFuncName}'.`);
33
+ return;
34
+ }
35
+ if (!matchesFilters(filters, { tags }, { type: PikkuEventTypes.queue, name: queueName })) {
36
+ console.info(`• Skipping queue processor '${pikkuFuncName}' for queue '${queueName}' due to filter mismatch.`);
37
+ return;
38
+ }
39
+ state.queueWorkers.files.add(node.getSourceFile().fileName);
40
+ state.queueWorkers.meta[queueName] = {
41
+ pikkuFuncName,
42
+ queueName,
43
+ docs,
44
+ tags,
45
+ };
46
+ }
47
+ };
@@ -1,6 +1,7 @@
1
1
  import * as ts from 'typescript';
2
2
  import { getPropertyValue } from './get-property-value.js';
3
- import { extractFunctionName, matchesFilters } from './utils.js';
3
+ import { PikkuEventTypes } from '@pikku/core';
4
+ import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
4
5
  export const addSchedule = (node, checker, state, filters) => {
5
6
  if (!ts.isCallExpression(node)) {
6
7
  return;
@@ -21,19 +22,16 @@ export const addSchedule = (node, checker, state, filters) => {
21
22
  const scheduleValue = getPropertyValue(obj, 'schedule');
22
23
  const docs = getPropertyValue(obj, 'docs') || undefined;
23
24
  const tags = getPropertyValue(obj, 'tags') || undefined;
24
- // --- find the referenced function ---
25
- const funcProp = obj.properties.find((p) => ts.isPropertyAssignment(p) &&
26
- ts.isIdentifier(p.name) &&
27
- p.name.text === 'func');
28
- if (!funcProp || !ts.isIdentifier(funcProp.initializer)) {
25
+ const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
26
+ if (!funcInitializer) {
29
27
  console.error(`• No valid 'func' property for scheduled task '${nameValue}'.`);
30
28
  return;
31
29
  }
32
- const pikkuFuncName = extractFunctionName(funcProp.initializer, checker).pikkuFuncName;
30
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker).pikkuFuncName;
33
31
  if (!nameValue || !scheduleValue) {
34
32
  return;
35
33
  }
36
- if (!matchesFilters(filters, { tags }, { type: 'schedule', name: nameValue })) {
34
+ if (!matchesFilters(filters, { tags }, { type: PikkuEventTypes.scheduled, name: nameValue })) {
37
35
  return;
38
36
  }
39
37
  state.scheduledTasks.files.add(node.getSourceFile().fileName);
package/dist/inspector.js CHANGED
@@ -36,9 +36,19 @@ export const inspect = (routeFiles, filters) => {
36
36
  meta: {},
37
37
  files: new Set(),
38
38
  },
39
+ queueWorkers: {
40
+ meta: {},
41
+ files: new Set(),
42
+ },
39
43
  rpc: {
40
44
  meta: {},
41
45
  },
46
+ mcpEndpoints: {
47
+ resourcesMeta: {},
48
+ toolsMeta: {},
49
+ promptsMeta: {},
50
+ files: new Set(),
51
+ },
42
52
  };
43
53
  // First sweep: add all functions
44
54
  for (const sourceFile of sourceFiles) {
package/dist/types.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { ChannelsMeta } from '@pikku/core/channel';
2
2
  import { HTTPRoutesMeta } from '@pikku/core/http';
3
3
  import { ScheduledTasksMeta } from '@pikku/core/scheduler';
4
+ import { queueWorkersMeta } from '@pikku/core/queue';
5
+ import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core';
4
6
  import { TypesMap } from './types-map.js';
5
7
  import { FunctionsMeta } from '@pikku/core';
6
- import { RPCMeta } from '../../core/src/rpc/rpc-types.js';
8
+ import { RPCMeta } from '@pikku/core/rpc';
7
9
  export type PathToNameAndType = Map<string, {
8
10
  variable: string;
9
11
  type: string | null;
@@ -48,7 +50,17 @@ export interface InspectorState {
48
50
  meta: ScheduledTasksMeta;
49
51
  files: Set<string>;
50
52
  };
53
+ queueWorkers: {
54
+ meta: queueWorkersMeta;
55
+ files: Set<string>;
56
+ };
51
57
  rpc: {
52
58
  meta: Record<string, RPCMeta>;
53
59
  };
60
+ mcpEndpoints: {
61
+ resourcesMeta: MCPResourceMeta;
62
+ toolsMeta: MCPToolMeta;
63
+ promptsMeta: MCPPromptMeta;
64
+ files: Set<string>;
65
+ };
54
66
  }
package/dist/utils.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import * as ts from 'typescript';
2
2
  import { InspectorFilters } from './types.js';
3
+ import { PikkuEventTypes } from '@pikku/core';
3
4
  type ExtractedFunctionName = {
4
5
  pikkuFuncName: string;
5
6
  name: string;
@@ -27,7 +28,7 @@ export declare function getPropertyAssignmentInitializer(obj: ts.ObjectLiteralEx
27
28
  export declare const matchesFilters: (filters: InspectorFilters, params: {
28
29
  tags?: string[];
29
30
  }, meta: {
30
- type: "schedule" | "http" | "channel";
31
+ type: PikkuEventTypes;
31
32
  name: string;
32
33
  }) => boolean;
33
34
  export {};
package/dist/visit.js CHANGED
@@ -3,6 +3,10 @@ import { addFileWithFactory } from './add-file-with-factory.js';
3
3
  import { addFileExtendsCoreType } from './add-file-extends-core-type.js';
4
4
  import { addHTTPRoute } from './add-http-route.js';
5
5
  import { addSchedule } from './add-schedule.js';
6
+ import { addQueueWorker } from './add-queue-worker.js';
7
+ import { addMCPResource } from './add-mcp-resource.js';
8
+ import { addMCPTool } from './add-mcp-tool.js';
9
+ import { addMCPPrompt } from './add-mcp-prompt.js';
6
10
  import { addFunctions } from './add-functions.js';
7
11
  import { addChannel } from './add-channel.js';
8
12
  export const visitSetup = (checker, node, state, filters) => {
@@ -18,6 +22,10 @@ export const visitSetup = (checker, node, state, filters) => {
18
22
  export const visitRoutes = (checker, node, state, filters) => {
19
23
  addHTTPRoute(node, checker, state, filters);
20
24
  addSchedule(node, checker, state, filters);
25
+ addQueueWorker(node, checker, state, filters);
21
26
  addChannel(node, checker, state, filters);
27
+ addMCPResource(node, checker, state, filters);
28
+ addMCPTool(node, checker, state, filters);
29
+ addMCPPrompt(node, checker, state, filters);
22
30
  ts.forEachChild(node, (child) => visitRoutes(checker, child, state, filters));
23
31
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/inspector",
3
- "version": "0.7.7",
3
+ "version": "0.8.0",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,7 +17,7 @@
17
17
  "test:coverage": "bash run-tests.sh --coverage"
18
18
  },
19
19
  "dependencies": {
20
- "@pikku/core": "^0.7.8",
20
+ "@pikku/core": "^0.8.0",
21
21
  "path-to-regexp": "^8.2.0",
22
22
  "typescript": "^5.6"
23
23
  },
@@ -1,7 +1,7 @@
1
1
  import * as ts from 'typescript'
2
2
  import { getPropertyValue } from './get-property-value.js'
3
3
  import { pathToRegexp } from 'path-to-regexp'
4
- import { APIDocs } from '@pikku/core'
4
+ import { APIDocs, PikkuEventTypes } from '@pikku/core'
5
5
  import {
6
6
  extractFunctionName,
7
7
  getPropertyAssignmentInitializer,
@@ -391,7 +391,10 @@ export function addChannel(
391
391
  const tags = getPropertyValue(obj, 'tags') as string[] | undefined
392
392
  const query = getPropertyValue(obj, 'query') as string[] | []
393
393
 
394
- if (!matchesFilters(filters, { tags }, { type: 'channel', name })) return
394
+ if (
395
+ !matchesFilters(filters, { tags }, { type: PikkuEventTypes.channel, name })
396
+ )
397
+ return
395
398
 
396
399
  const connect = getPropertyAssignmentInitializer(
397
400
  obj,
@@ -2,7 +2,7 @@ import * as ts from 'typescript'
2
2
  import { getPropertyValue } from './get-property-value.js'
3
3
  import { pathToRegexp } from 'path-to-regexp'
4
4
  import { HTTPMethod } from '@pikku/core/http'
5
- import { APIDocs } from '@pikku/core'
5
+ import { APIDocs, PikkuEventTypes } from '@pikku/core'
6
6
  import {
7
7
  extractFunctionName,
8
8
  getPropertyAssignmentInitializer,
@@ -69,7 +69,13 @@ export const addHTTPRoute = (
69
69
  const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
70
70
  const query = (getPropertyValue(obj, 'query') as string[]) || []
71
71
 
72
- if (!matchesFilters(filters, { tags }, { type: 'http', name: route })) {
72
+ if (
73
+ !matchesFilters(
74
+ filters,
75
+ { tags },
76
+ { type: PikkuEventTypes.http, name: route }
77
+ )
78
+ ) {
73
79
  return
74
80
  }
75
81
 
@@ -0,0 +1,100 @@
1
+ import * as ts from 'typescript'
2
+ import { getPropertyValue } from './get-property-value.js'
3
+ import { PikkuEventTypes } from '@pikku/core'
4
+ import { InspectorFilters, InspectorState } from './types.js'
5
+ import {
6
+ extractFunctionName,
7
+ getPropertyAssignmentInitializer,
8
+ matchesFilters,
9
+ } from './utils.js'
10
+
11
+ export const addMCPPrompt = (
12
+ node: ts.Node,
13
+ checker: ts.TypeChecker,
14
+ state: InspectorState,
15
+ filters: InspectorFilters
16
+ ) => {
17
+ if (!ts.isCallExpression(node)) {
18
+ return
19
+ }
20
+
21
+ const args = node.arguments
22
+ const firstArg = args[0]
23
+ const expression = node.expression
24
+
25
+ // Check if the call is to addMCPPrompt
26
+ if (!ts.isIdentifier(expression) || expression.text !== 'addMCPPrompt') {
27
+ return
28
+ }
29
+
30
+ if (!firstArg) {
31
+ return
32
+ }
33
+
34
+ if (ts.isObjectLiteralExpression(firstArg)) {
35
+ const obj = firstArg
36
+
37
+ const nameValue = getPropertyValue(obj, 'name') as string | null
38
+ const descriptionValue = getPropertyValue(obj, 'description') as
39
+ | string
40
+ | null
41
+ const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
42
+
43
+ const funcInitializer = getPropertyAssignmentInitializer(
44
+ obj,
45
+ 'func',
46
+ true,
47
+ checker
48
+ )
49
+ if (!funcInitializer) {
50
+ console.error(`• No valid 'func' property for MCP prompt '${nameValue}'.`)
51
+ return
52
+ }
53
+
54
+ const pikkuFuncName = extractFunctionName(
55
+ funcInitializer,
56
+ checker
57
+ ).pikkuFuncName
58
+
59
+ if (!nameValue) {
60
+ console.error(`• MCP prompt is missing the required 'name' property.`)
61
+ return
62
+ }
63
+
64
+ if (!descriptionValue) {
65
+ console.error(`• MCP prompt '${nameValue}' is missing a description.`)
66
+ return
67
+ }
68
+
69
+ if (
70
+ !matchesFilters(
71
+ filters,
72
+ { tags },
73
+ { type: PikkuEventTypes.mcp, name: nameValue }
74
+ )
75
+ ) {
76
+ return
77
+ }
78
+
79
+ // lookup existing function metadata
80
+ const fnMeta = state.functions.meta[pikkuFuncName]
81
+ if (!fnMeta) {
82
+ console.error(`• No function metadata found for '${pikkuFuncName}'.`)
83
+ return
84
+ }
85
+ const inputSchema = fnMeta.inputs?.[0] || null
86
+ const outputSchema = fnMeta.outputs?.[0] || null
87
+
88
+ state.mcpEndpoints.files.add(node.getSourceFile().fileName)
89
+
90
+ state.mcpEndpoints.promptsMeta[nameValue] = {
91
+ pikkuFuncName,
92
+ name: nameValue,
93
+ description: descriptionValue,
94
+ tags,
95
+ inputSchema,
96
+ outputSchema,
97
+ arguments: [], // Will be populated by CLI during serialization
98
+ }
99
+ }
100
+ }