@pikku/core 0.8.0 → 0.8.1

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 (39) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/events/channel/channel-handler.js +2 -0
  3. package/dist/events/mcp/index.d.ts +1 -0
  4. package/dist/events/mcp/index.js +1 -0
  5. package/dist/events/mcp/mcp-endpoint-registry.d.ts +48 -0
  6. package/dist/events/mcp/mcp-endpoint-registry.js +122 -0
  7. package/dist/events/queue/index.d.ts +1 -1
  8. package/dist/events/queue/index.js +1 -1
  9. package/dist/events/queue/register-queue-helper.d.ts +1 -1
  10. package/dist/events/queue/register-queue-helper.js +1 -1
  11. package/dist/events/rpc/rpc-runner.d.ts +1 -4
  12. package/dist/function/function-runner.d.ts +3 -2
  13. package/dist/function/function-runner.js +10 -42
  14. package/dist/index.d.ts +5 -1
  15. package/dist/index.js +5 -1
  16. package/dist/services/index.d.ts +2 -2
  17. package/dist/services/index.js +2 -2
  18. package/dist/services/jwt-service.d.ts +1 -1
  19. package/dist/types/core.types.d.ts +1 -10
  20. package/dist/types/core.types.js +1 -1
  21. package/lcov.info +1092 -397
  22. package/package.json +2 -1
  23. package/src/events/channel/channel-handler.ts +4 -0
  24. package/src/events/http/http-runner.test.ts +2 -3
  25. package/src/events/mcp/index.ts +1 -0
  26. package/src/events/mcp/mcp-endpoint-registry.test.d.ts +1 -0
  27. package/src/events/mcp/mcp-endpoint-registry.test.ts +389 -0
  28. package/src/events/mcp/mcp-endpoint-registry.ts +165 -0
  29. package/src/events/queue/index.ts +1 -1
  30. package/src/events/queue/register-queue-helper.ts +1 -1
  31. package/src/function/function-runner.ts +21 -66
  32. package/src/index.ts +5 -1
  33. package/src/middleware/auth-apikey.ts +1 -1
  34. package/src/middleware/auth-bearer.ts +1 -1
  35. package/src/middleware/auth-cookie.ts +1 -1
  36. package/src/services/index.ts +2 -2
  37. package/src/services/jwt-service.ts +1 -1
  38. package/src/types/core.types.ts +1 -10
  39. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  ## 0.8.0
2
2
 
3
+ ## 0.8.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 3261090: refactor: moving mcp endpoints into core
8
+ - 7c592b8: feat: support for required services and improved service configuration
9
+
10
+ This release includes several enhancements to service management and configuration:
11
+
12
+ - Added support for required services configuration
13
+ - Improved service discovery and registration
14
+ - Added typed RPC clients for service communication
15
+ - Updated middleware to run per function
16
+
17
+ - 30a082f: refactor: moving service implementations out of pikku/core since they aren't all edge compatible
18
+
3
19
  ### Major Features
4
20
 
5
21
  - **Model Context Protocol (MCP) Support**: Added MCP implementation with resources, tools, and prompts
@@ -46,6 +46,7 @@ export const processMessageHandlers = (services, session, channelConfig, channel
46
46
  }
47
47
  const { pikkuFuncName } = getRouteMeta(channelConfig.name, routingProperty, routerValue);
48
48
  const permissions = typeof onMessage === 'function' ? {} : onMessage.permissions;
49
+ const middleware = typeof onMessage === 'function' ? [] : onMessage.middleware;
49
50
  return await runPikkuFunc(pikkuFuncName, {
50
51
  getAllServices: () => ({
51
52
  ...services,
@@ -54,6 +55,7 @@ export const processMessageHandlers = (services, session, channelConfig, channel
54
55
  data,
55
56
  session,
56
57
  permissions,
58
+ middleware,
57
59
  });
58
60
  };
59
61
  const onMessage = async (rawData) => {
@@ -1,2 +1,3 @@
1
1
  export * from './mcp.types.js';
2
2
  export * from './mcp-runner.js';
3
+ export * from './mcp-endpoint-registry.js';
@@ -1,2 +1,3 @@
1
1
  export * from './mcp.types.js';
2
2
  export * from './mcp-runner.js';
3
+ export * from './mcp-endpoint-registry.js';
@@ -0,0 +1,48 @@
1
+ import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core';
2
+ export interface MCPToolEndpoint {
3
+ name: string;
4
+ title: string;
5
+ description: string;
6
+ inputSchema?: any;
7
+ outputSchema?: any;
8
+ enabled: boolean;
9
+ }
10
+ export interface MCPResourceEndpoint {
11
+ uri: string;
12
+ title: string;
13
+ description: string;
14
+ inputSchema?: any;
15
+ mimeType?: string;
16
+ enabled: boolean;
17
+ }
18
+ export interface MCPPromptEndpoint {
19
+ name: string;
20
+ description: string;
21
+ inputSchema?: any;
22
+ enabled: boolean;
23
+ }
24
+ export declare class MCPEndpointRegistry {
25
+ private tools;
26
+ private resources;
27
+ private prompts;
28
+ private resourcesMeta;
29
+ private toolsMeta;
30
+ private promptsMeta;
31
+ loadFromMCPJsonFile(mcpJsonPath: string): Promise<void>;
32
+ loadFromMCPJson(mcpData: any): Promise<void>;
33
+ setResourcesMeta(meta: MCPResourceMeta): void;
34
+ setToolsMeta(meta: MCPToolMeta): void;
35
+ setPromptsMeta(meta: MCPPromptMeta): void;
36
+ getTools(): MCPToolEndpoint[];
37
+ getResources(): MCPResourceEndpoint[];
38
+ getPrompts(): MCPPromptEndpoint[];
39
+ getTool(name: string): MCPToolEndpoint | undefined;
40
+ getResource(name: string): MCPResourceEndpoint | undefined;
41
+ getPrompt(name: string): MCPPromptEndpoint | undefined;
42
+ hasResource(name: string): boolean;
43
+ hasTool(name: string): boolean;
44
+ hasPrompt(name: string): boolean;
45
+ enableTools(tools: Record<any, boolean>): boolean;
46
+ enablePrompts(prompts: Record<any, boolean>): boolean;
47
+ enableResources(resources: Record<any, boolean>): boolean;
48
+ }
@@ -0,0 +1,122 @@
1
+ import { readFile } from 'fs/promises';
2
+ export class MCPEndpointRegistry {
3
+ tools = new Map();
4
+ resources = new Map();
5
+ prompts = new Map();
6
+ resourcesMeta = {};
7
+ toolsMeta = {};
8
+ promptsMeta = {};
9
+ async loadFromMCPJsonFile(mcpJsonPath) {
10
+ try {
11
+ const mcpJsonContent = await readFile(mcpJsonPath, 'utf-8');
12
+ await this.loadFromMCPJson(JSON.parse(mcpJsonContent));
13
+ }
14
+ catch (error) {
15
+ throw new Error(`Failed to load MCP JSON from ${mcpJsonPath}: ${error}`);
16
+ }
17
+ }
18
+ async loadFromMCPJson(mcpData) {
19
+ if (mcpData.tools && Array.isArray(mcpData.tools)) {
20
+ for (const tool of mcpData.tools) {
21
+ this.tools.set(tool.name, {
22
+ name: tool.name,
23
+ title: tool.title,
24
+ description: tool.description,
25
+ inputSchema: tool.parameters,
26
+ outputSchema: tool.returns,
27
+ enabled: tool.enabled !== undefined ? tool.enabled : true,
28
+ });
29
+ }
30
+ }
31
+ if (mcpData.resources && Array.isArray(mcpData.resources)) {
32
+ for (const resource of mcpData.resources) {
33
+ this.resources.set(resource.name, {
34
+ title: resource.name,
35
+ uri: resource.uri,
36
+ description: resource.description,
37
+ inputSchema: resource.parameters,
38
+ enabled: resource.enabled !== undefined ? resource.enabled : true,
39
+ });
40
+ }
41
+ }
42
+ if (mcpData.prompts && Array.isArray(mcpData.prompts)) {
43
+ for (const prompt of mcpData.prompts) {
44
+ this.prompts.set(prompt.name, {
45
+ name: prompt.name,
46
+ description: prompt.description,
47
+ inputSchema: prompt.arguments,
48
+ enabled: prompt.enabled !== undefined ? prompt.enabled : true,
49
+ });
50
+ }
51
+ }
52
+ }
53
+ setResourcesMeta(meta) {
54
+ this.resourcesMeta = meta;
55
+ }
56
+ setToolsMeta(meta) {
57
+ this.toolsMeta = meta;
58
+ }
59
+ setPromptsMeta(meta) {
60
+ this.promptsMeta = meta;
61
+ }
62
+ getTools() {
63
+ return Array.from(this.tools.values()).filter((tool) => tool.enabled);
64
+ }
65
+ getResources() {
66
+ return Array.from(this.resources.values());
67
+ }
68
+ getPrompts() {
69
+ return Array.from(this.prompts.values());
70
+ }
71
+ getTool(name) {
72
+ return this.tools.get(name);
73
+ }
74
+ getResource(name) {
75
+ return this.resources.get(name);
76
+ }
77
+ getPrompt(name) {
78
+ return this.prompts.get(name);
79
+ }
80
+ hasResource(name) {
81
+ return name in this.resourcesMeta;
82
+ }
83
+ hasTool(name) {
84
+ return name in this.toolsMeta;
85
+ }
86
+ hasPrompt(name) {
87
+ return name in this.promptsMeta;
88
+ }
89
+ enableTools(tools) {
90
+ let changed = false;
91
+ for (const [name, enabled] of Object.entries(tools)) {
92
+ const tool = this.tools.get(name);
93
+ if (tool && tool.enabled !== enabled) {
94
+ tool.enabled = enabled;
95
+ changed = true;
96
+ }
97
+ }
98
+ return changed;
99
+ }
100
+ enablePrompts(prompts) {
101
+ let changed = false;
102
+ for (const [name, enabled] of Object.entries(prompts)) {
103
+ const prompt = this.prompts.get(name);
104
+ if (prompt && prompt.enabled !== enabled) {
105
+ prompt.enabled = enabled;
106
+ changed = true;
107
+ }
108
+ }
109
+ return changed;
110
+ }
111
+ enableResources(resources) {
112
+ let changed = false;
113
+ for (const [name, enabled] of Object.entries(resources)) {
114
+ const resource = this.resources.get(name);
115
+ if (resource && resource.enabled !== enabled) {
116
+ resource.enabled = enabled;
117
+ changed = true;
118
+ }
119
+ }
120
+ return changed;
121
+ }
122
+ }
@@ -2,5 +2,5 @@ export type * from './queue.types.js';
2
2
  export { addQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, } from './queue-runner.js';
3
3
  export { validateWorkerConfig } from './validate-worker-config.js';
4
4
  export type { QueueConfigMapping } from './validate-worker-config.js';
5
- export { registerqueueWorkers } from './register-queue-helper.js';
5
+ export { registerQueueWorkers } from './register-queue-helper.js';
6
6
  export type { QueueRegistrationCallback } from './register-queue-helper.js';
@@ -3,4 +3,4 @@ export { addQueueWorker, runQueueJob, getQueueWorkers, removeQueueWorker, } from
3
3
  // Configuration validation
4
4
  export { validateWorkerConfig } from './validate-worker-config.js';
5
5
  // Queue registration helper
6
- export { registerqueueWorkers } from './register-queue-helper.js';
6
+ export { registerQueueWorkers } from './register-queue-helper.js';
@@ -14,4 +14,4 @@ export type QueueRegistrationCallback<T = any> = (queueName: string, processor:
14
14
  * @param logger - Optional logger for info/error messages
15
15
  * @returns Record of validation results by queue name
16
16
  */
17
- export declare function registerqueueWorkers<T = any>(configMappings: QueueConfigMapping, logger: Logger, registerCallback: QueueRegistrationCallback<T>): Promise<Record<string, ConfigValidationResult[]>>;
17
+ export declare function registerQueueWorkers<T = any>(configMappings: QueueConfigMapping, logger: Logger, registerCallback: QueueRegistrationCallback<T>): Promise<Record<string, ConfigValidationResult[]>>;
@@ -9,7 +9,7 @@ import { validateWorkerConfig, } from './validate-worker-config.js';
9
9
  * @param logger - Optional logger for info/error messages
10
10
  * @returns Record of validation results by queue name
11
11
  */
12
- export async function registerqueueWorkers(configMappings, logger, registerCallback) {
12
+ export async function registerQueueWorkers(configMappings, logger, registerCallback) {
13
13
  const configValidation = {};
14
14
  const queueWorkers = getQueueWorkers();
15
15
  for (const [queueName, processor] of queueWorkers) {
@@ -6,16 +6,13 @@ export declare class PikkuRPCService {
6
6
  private config?;
7
7
  initialize(config: RPCServiceConfig): void;
8
8
  injectRPCService(coreServices: CoreServices, depth?: number): {
9
- jwt?: import("../../index.js").JWTService;
10
9
  schema?: import("../../index.js").SchemaService;
11
10
  config: {
12
11
  logLevel?: import("../../index.js").LogLevel;
13
12
  secrets?: {};
14
13
  };
15
14
  logger: import("../../index.js").Logger;
16
- variables: import("../../index.js").VariablesService;
17
- eventHub?: import("../channel/eventhub-service.js").EventHubService<unknown>;
18
- secrets?: import("../../index.js").SecretService;
15
+ variables: import("../../services/variables-service.js").VariablesService;
19
16
  http?: import("../http/http.types.js").PikkuHTTP;
20
17
  mcp?: import("../mcp/mcp.types.js").PikkuMCP;
21
18
  rpc?: import("./rpc-types.js").PikkuRPC;
@@ -1,11 +1,12 @@
1
- import { CoreServices, CoreUserSession } from '../types/core.types.js';
1
+ import { CoreServices, CoreUserSession, PikkuFunctionMiddleware } from '../types/core.types.js';
2
2
  import { CorePermissionGroup, CorePikkuFunctionConfig } from './functions.types.js';
3
3
  export declare const addFunction: (funcName: string, funcConfig: CorePikkuFunctionConfig<any, any>) => void;
4
4
  export declare const runPikkuFuncDirectly: <In, Out>(funcName: string, allServices: CoreServices, data: In, session?: CoreUserSession) => Promise<Out>;
5
- export declare const runPikkuFunc: <In = any, Out = any>(funcName: string, { getAllServices, data, session, permissions: transportPermissions, coerceDataFromSchema, }: {
5
+ export declare const runPikkuFunc: <In = any, Out = any>(funcName: string, { getAllServices, data, session, permissions: transportPermissions, middleware: transportMiddleware, coerceDataFromSchema, }: {
6
6
  getAllServices: () => Promise<CoreServices> | CoreServices;
7
7
  data: In;
8
8
  session?: CoreUserSession;
9
9
  permissions?: CorePermissionGroup;
10
+ middleware?: PikkuFunctionMiddleware[];
10
11
  coerceDataFromSchema?: boolean;
11
12
  }) => Promise<Out>;
@@ -3,42 +3,6 @@ import { runMiddleware } from '../middleware-runner.js';
3
3
  import { verifyPermissions } from '../permissions.js';
4
4
  import { pikkuState } from '../pikku-state.js';
5
5
  import { coerceTopLevelDataFromSchema, validateSchema } from '../schema.js';
6
- // Permission merging function
7
- function mergePermissions(logger, funcName, funcPermissions, transportPermissions) {
8
- if (!funcPermissions && !transportPermissions) {
9
- return undefined;
10
- }
11
- if (!funcPermissions) {
12
- return transportPermissions;
13
- }
14
- if (!transportPermissions) {
15
- return funcPermissions;
16
- }
17
- // Start with a copy of function permissions
18
- const mergedPermissions = { ...funcPermissions };
19
- // Merge in transport permissions
20
- for (const [key, transportValue] of Object.entries(transportPermissions)) {
21
- if (key in mergedPermissions) {
22
- // For permission arrays, concatenate and deduplicate values
23
- if (Array.isArray(mergedPermissions[key]) &&
24
- Array.isArray(transportValue)) {
25
- mergedPermissions[key] = [
26
- ...new Set([...mergedPermissions[key], ...transportValue]),
27
- ];
28
- }
29
- // For other types, warn about conflict and use the more restrictive (transport-level) value
30
- else {
31
- logger.warn(`Permission conflict on key "${key}" for function "${funcName}". Using transport-level permission.`);
32
- mergedPermissions[key] = transportValue;
33
- }
34
- }
35
- else {
36
- // If key doesn't exist in function permissions, add it
37
- mergedPermissions[key] = transportValue;
38
- }
39
- }
40
- return mergedPermissions;
41
- }
42
6
  export const addFunction = (funcName, funcConfig) => {
43
7
  pikkuState('function', 'functions').set(funcName, funcConfig);
44
8
  };
@@ -49,7 +13,7 @@ export const runPikkuFuncDirectly = async (funcName, allServices, data, session)
49
13
  }
50
14
  return (await funcConfig.func(allServices, data, session));
51
15
  };
52
- export const runPikkuFunc = async (funcName, { getAllServices, data, session, permissions: transportPermissions, coerceDataFromSchema, }) => {
16
+ export const runPikkuFunc = async (funcName, { getAllServices, data, session, permissions: transportPermissions, middleware: transportMiddleware, coerceDataFromSchema, }) => {
53
17
  const funcConfig = pikkuState('function', 'functions').get(funcName);
54
18
  if (!funcConfig) {
55
19
  throw new Error(`Function not found: ${funcName}`);
@@ -71,18 +35,22 @@ export const runPikkuFunc = async (funcName, { getAllServices, data, session, pe
71
35
  coerceTopLevelDataFromSchema(schemaName, data);
72
36
  }
73
37
  }
74
- // Execute permission checks
75
- const mergedPermissions = mergePermissions(allServices.logger, funcName, funcConfig.permissions, transportPermissions);
76
- const permissioned = await verifyPermissions(mergedPermissions, allServices, data, session);
38
+ let permissioned = true;
39
+ if (funcConfig.permissions) {
40
+ permissioned = await verifyPermissions(funcConfig.permissions, allServices, data, session);
41
+ }
42
+ if (!permissioned && transportPermissions) {
43
+ permissioned = await verifyPermissions(transportPermissions, allServices, data, session);
44
+ }
77
45
  if (permissioned === false) {
78
46
  throw new ForbiddenError('Permission denied');
79
47
  }
80
- if (funcConfig.middleware) {
48
+ if (transportMiddleware || funcConfig.middleware) {
81
49
  return (await runMiddleware(allServices, {
82
50
  http: allServices.http,
83
51
  mcp: allServices.mcp,
84
52
  rpc: allServices.rpc,
85
- }, funcConfig.middleware, async () => await funcConfig.func(allServices, data, session)));
53
+ }, [...(transportMiddleware || []), ...(funcConfig.middleware || [])], async () => await funcConfig.func(allServices, data, session)));
86
54
  }
87
55
  return (await funcConfig.func(allServices, data, session));
88
56
  };
package/dist/index.d.ts CHANGED
@@ -5,7 +5,11 @@ export * from './types/core.types.js';
5
5
  export * from './function/index.js';
6
6
  export * from './pikku-request.js';
7
7
  export * from './pikku-response.js';
8
- export * from './services/index.js';
8
+ export * from './services/logger.js';
9
+ export * from './services/schema-service.js';
10
+ export * from './services/jwt-service.js';
11
+ export * from './services/secret-service.js';
12
+ export * from './services/user-session-service.js';
9
13
  export * from './events/http/index.js';
10
14
  export * from './events/channel/index.js';
11
15
  export * from './events/scheduler/index.js';
package/dist/index.js CHANGED
@@ -5,7 +5,11 @@ export * from './types/core.types.js';
5
5
  export * from './function/index.js';
6
6
  export * from './pikku-request.js';
7
7
  export * from './pikku-response.js';
8
- export * from './services/index.js';
8
+ export * from './services/logger.js';
9
+ export * from './services/schema-service.js';
10
+ export * from './services/jwt-service.js';
11
+ export * from './services/secret-service.js';
12
+ export * from './services/user-session-service.js';
9
13
  export * from './events/http/index.js';
10
14
  export * from './events/channel/index.js';
11
15
  export * from './events/scheduler/index.js';
@@ -4,11 +4,11 @@
4
4
  export * from './content-service.js';
5
5
  export * from './jwt-service.js';
6
6
  export * from './logger.js';
7
- export * from './logger-console.js';
8
7
  export * from './secret-service.js';
9
8
  export * from './variables-service.js';
10
9
  export * from './schema-service.js';
11
10
  export * from './user-session-service.js';
12
- export * from './local-content.js';
13
11
  export * from './local-secrets.js';
14
12
  export * from './local-variables.js';
13
+ export * from './logger-console.js';
14
+ export * from './local-content.js';
@@ -5,12 +5,12 @@
5
5
  export * from './content-service.js';
6
6
  export * from './jwt-service.js';
7
7
  export * from './logger.js';
8
- export * from './logger-console.js';
9
8
  export * from './secret-service.js';
10
9
  export * from './variables-service.js';
11
10
  export * from './schema-service.js';
12
11
  export * from './user-session-service.js';
13
- export * from './local-content.js';
14
12
  // Local implementations
15
13
  export * from './local-secrets.js';
16
14
  export * from './local-variables.js';
15
+ export * from './logger-console.js';
16
+ export * from './local-content.js';
@@ -1,4 +1,4 @@
1
- import { RelativeTimeInput } from '../time-utils.js';
1
+ import type { RelativeTimeInput } from '../time-utils.js';
2
2
  /**
3
3
  * Interface for handling JSON Web Tokens (JWT).
4
4
  */
@@ -1,17 +1,14 @@
1
1
  import type { Logger, LogLevel } from '../services/logger.js';
2
2
  import { VariablesService } from '../services/variables-service.js';
3
- import { EventHubService } from '../events/channel/eventhub-service.js';
4
3
  import { SchemaService } from '../services/schema-service.js';
5
4
  import { PikkuHTTP } from '../events/http/http.types.js';
6
5
  import { UserSessionService } from '../services/user-session-service.js';
7
- import { JWTService } from '../services/jwt-service.js';
8
- import { SecretService } from '../services/secret-service.js';
9
6
  import { PikkuChannel } from '../events/channel/channel.types.js';
10
7
  import { PikkuRPC } from '../events/rpc/rpc-types.js';
11
8
  import { PikkuMCP } from '../events/mcp/mcp.types.js';
12
9
  export declare enum PikkuEventTypes {
13
10
  http = "http",
14
- scheduled = "scheduled",
11
+ scheduler = "scheduler",
15
12
  channel = "channel",
16
13
  rpc = "rpc",
17
14
  queue = "queue",
@@ -72,8 +69,6 @@ export interface CoreUserSession {
72
69
  * Interface for core singleton services provided by Pikku.
73
70
  */
74
71
  export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
75
- /** JWT Service */
76
- jwt?: JWTService;
77
72
  /** The schema library used to validate data */
78
73
  schema?: SchemaService;
79
74
  /** The core configuration for the application. */
@@ -82,10 +77,6 @@ export interface CoreSingletonServices<Config extends CoreConfig = CoreConfig> {
82
77
  logger: Logger;
83
78
  /** The variable service to be used */
84
79
  variables: VariablesService;
85
- /** The subscription service that is passed to streams */
86
- eventHub?: EventHubService<unknown>;
87
- /** SecretServce */
88
- secrets?: SecretService;
89
80
  }
90
81
  /**
91
82
  * Represents different forms of interaction within Pikku and the outside world.
@@ -1,7 +1,7 @@
1
1
  export var PikkuEventTypes;
2
2
  (function (PikkuEventTypes) {
3
3
  PikkuEventTypes["http"] = "http";
4
- PikkuEventTypes["scheduled"] = "scheduled";
4
+ PikkuEventTypes["scheduler"] = "scheduler";
5
5
  PikkuEventTypes["channel"] = "channel";
6
6
  PikkuEventTypes["rpc"] = "rpc";
7
7
  PikkuEventTypes["queue"] = "queue";