@praxis-ai/praxis 0.1.2 → 0.1.4

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.
@@ -25,10 +25,10 @@ import { assemblePromptPack } from "../executionEngine/promptPack/promptAssemble
25
25
  import { assemblePromptContextMaterials, promptPackMaterialsForManifest } from "../runtimeImplementation/runtime.execEngine/promptContextAssembly.js";
26
26
  import { createObservationMaterial } from "../executionEngine/coreLogic/observationIntegrator.js";
27
27
  import { createMemoryPlane, memoryPlane } from "../memory_managementPool/index.js";
28
- import { buildMcpServerProfilesFromManifest, createMcpApplicationStateView, mcp, planMcpHarnessExposure } from "../runtimeImplementation/runtime.mcpPlane/index.js";
28
+ import { buildMcpServerProfilesFromManifest, createMcpApplicationStateView, createFileMcpPlusProfileStore, createFileMcpPlusSkillStore, createInMemoryMcpPlusOverlayStore, createInMemoryMcpPlusProfileStore, createInMemoryMcpPlusSkillStore, mcp, planMcpHarnessExposure } from "../runtimeImplementation/runtime.mcpPlane/index.js";
29
29
  export { PromptPack, PraxisAgent, PraxisAgentArchetype, STATE_PLANE_STANDARD_CONTROLS, append, compileAgent, endpoint, harness, inspectAgentManifest, loop, mainLoop, markdown, markdownFile, model, modelFleet, overwrite, policy, prepend, replaceLastLines, sandbox, session, statePlane, storage, tool, toolPolicies, tools, validateAgentManifest, type AgentCompileErrorCode, type AgentCompileResult, type AgentIdentity, type AgentManifest, type AgentManifestInspection, type AgentManifestValidationResult, type BaseToolPolicyDecision, type BaseToolPolicyMatrixSpec, type BaseToolPolicyProfile, type BaseToolPolicyRisk, type BaseToolPolicyRule, type HarnessSpec, type FrameworkCoreContractSpec, type LoopSpec, type MainLoopSpec, type ModelEndpointSpec, type ModelFleetSpec, type ModelSpec, type PolicySpec, type PraxisAgentClass, type PraxisAgentInput, type PromptMaterialSource, type PromptPackSpec, type PromptPatchSpec, type SandboxSpec, type SandboxIsolationLevel, type SandboxMountPolicy, type SandboxNetworkRuntimePolicy, type SandboxPlatformSupport, type SandboxPlatformSupportStatus, type SandboxProcessPolicy, type SandboxProviderFamily, type SessionSpec, type StatePlaneSpec, type StorageSpec, type ToolSpec, type ToolPolicyCustomInput, } from "../runtimeImplementation/runtimeAgentManifest.js";
30
- export { buildMcpServerProfilesFromManifest, createMcpApplicationStateView, mcp, planMcpHarnessExposure, };
31
- export type { McpApplicationServerView, McpApplicationServerInput, McpApplicationStateView, McpHarnessExposurePlan, McpHarnessModuleSpec, McpHarnessServerMode, McpHarnessServerSpec, McpPlusApplicationServerInput, McpTransportSpec, } from "../runtimeImplementation/runtime.mcpPlane/index.js";
30
+ export { buildMcpServerProfilesFromManifest, createMcpApplicationStateView, createFileMcpPlusProfileStore, createFileMcpPlusSkillStore, createInMemoryMcpPlusOverlayStore, createInMemoryMcpPlusProfileStore, createInMemoryMcpPlusSkillStore, mcp, planMcpHarnessExposure, };
31
+ export type { McpApplicationServerView, McpApplicationServerInput, McpApplicationStateView, McpHarnessExposurePlan, McpHarnessModuleSpec, McpHarnessServerMode, McpHarnessServerSpec, McpPlusLearnedProfile, McpPlusApplicationServerInput, McpPlusOverlayStore, McpPlusOverlayStoreKey, McpPlusProfileProposal, McpPlusProfileStore, McpPlusProfileStoreKey, McpPlusRuntimeOverlay, McpPlusSkillNote, McpPlusSkillStore, McpTransportSpec, } from "../runtimeImplementation/runtime.mcpPlane/index.js";
32
32
  export { createMemoryPlane, memoryPlane, };
33
33
  export type { MemoryArtifactRef, MemoryIndexStatus, MemoryLayout, MemoryPlane, MemoryPlaneOptions, MemoryPolicyRisk, MemoryProfile, MemoryPromptGuide, MemoryReindexResult, MemoryRiskMetadata, MemoryScope, MemorySearchGuide, MemorySearchRequest, MemorySourceType, } from "../memory_managementPool/index.js";
34
34
  export { capabilities, capability, createProvisionPlan, dependencies, dependencyAuthoring, provisionRuntimeDescriptor, type CapabilityFallbackSpec, type CapabilityInput, type CapabilityKind, type CapabilityPolicySpec, type CapabilityReadiness, type CapabilitySpec, type CodeIntelligenceCapabilityInput, type ProvisionPlan, type SandboxCapabilityInput, } from "../runtimeImplementation/runtime.provisionPlane/index.js";
@@ -31,9 +31,9 @@ import { assemblePromptContextMaterials, promptPackMaterialsForManifest, } from
31
31
  import { createObservationMaterial, } from "../executionEngine/coreLogic/observationIntegrator.js";
32
32
  import { runtimeAuth, } from "../runtimeImplementation/runtime.authPlane/index.js";
33
33
  import { createMemoryPlane, memoryPlane, } from "../memory_managementPool/index.js";
34
- import { buildMcpServerProfilesFromManifest, createMcpApplicationStateView, mcp, planMcpHarnessExposure, } from "../runtimeImplementation/runtime.mcpPlane/index.js";
34
+ import { buildMcpServerProfilesFromManifest, createMcpApplicationStateView, createFileMcpPlusProfileStore, createFileMcpPlusSkillStore, createInMemoryMcpPlusOverlayStore, createInMemoryMcpPlusProfileStore, createInMemoryMcpPlusSkillStore, mcp, planMcpHarnessExposure, } from "../runtimeImplementation/runtime.mcpPlane/index.js";
35
35
  export { PromptPack, PraxisAgent, PraxisAgentArchetype, STATE_PLANE_STANDARD_CONTROLS, append, compileAgent, endpoint, harness, inspectAgentManifest, loop, mainLoop, markdown, markdownFile, model, modelFleet, overwrite, policy, prepend, replaceLastLines, sandbox, session, statePlane, storage, tool, toolPolicies, tools, validateAgentManifest, } from "../runtimeImplementation/runtimeAgentManifest.js";
36
- export { buildMcpServerProfilesFromManifest, createMcpApplicationStateView, mcp, planMcpHarnessExposure, };
36
+ export { buildMcpServerProfilesFromManifest, createMcpApplicationStateView, createFileMcpPlusProfileStore, createFileMcpPlusSkillStore, createInMemoryMcpPlusOverlayStore, createInMemoryMcpPlusProfileStore, createInMemoryMcpPlusSkillStore, mcp, planMcpHarnessExposure, };
37
37
  export { createMemoryPlane, memoryPlane, };
38
38
  export { capabilities, capability, createProvisionPlan, dependencies, dependencyAuthoring, provisionRuntimeDescriptor, } from "../runtimeImplementation/runtime.provisionPlane/index.js";
39
39
  export { component, createRuntimeComponentRegistry, lookupRuntimeComponent, officialRuntimeComponents, } from "../runtimeImplementation/runtime.componentPlane/index.js";
@@ -1,5 +1,5 @@
1
1
  import { type AgentManifest, type BaseToolExecutorPort, type AgentToolCallProgressEvent, type RuntimeApprovalResolver, type RuntimeAgentReviewResolver, type RuntimeAuthResolver, type RuntimeAuthResolverRequest, type SandboxExecutionProviderPort, type CompactExecutor, type PreCompactGovernanceExecutor, type PraxisProjectRuntime } from "../agentCore/index.js";
2
- import { type McpApplicationServerInput, type McpHarnessModuleSpec, type McpPlusApplicationServerInput } from "../runtimeImplementation/runtime.mcpPlane/index.js";
2
+ import { type McpApplicationServerInput, type McpHarnessModuleSpec, type McpPlusApplicationServerInput, type McpPlusOverlayStore, type McpPlusProfileStore, type McpPlusSkillStore } from "../runtimeImplementation/runtime.mcpPlane/index.js";
3
3
  import { type OpenAIV1ResponsesProviderCaller, type OpenAIV1ResponsesResult } from "../modelAdapter/actualInvocationLayer/openai/v1_responses.js";
4
4
  import type { OpenAiV1ChatCompletionsProviderCaller } from "../modelAdapter/actualInvocationLayer/openai/v1_chat_completions.js";
5
5
  import type { AnthropicV1MessagesProviderCaller } from "../modelAdapter/actualInvocationLayer/anthropic/v1_messages.js";
@@ -75,6 +75,13 @@ export type PraxisApplicationRuntimeOptions = {
75
75
  mcpServers?: readonly McpApplicationServerInput[];
76
76
  mcpPlusServers?: readonly McpPlusApplicationServerInput[];
77
77
  mcpModule?: McpHarnessModuleSpec;
78
+ mcpPlus?: {
79
+ projectId?: string;
80
+ profileStore?: McpPlusProfileStore;
81
+ overlayStore?: McpPlusOverlayStore;
82
+ skillStore?: McpPlusSkillStore;
83
+ reprofileConsecutiveIndexedCalls?: number;
84
+ };
78
85
  onApplicationToolEvent?: (event: PraxisApplicationEvent) => void | Promise<void>;
79
86
  initialConversations?: readonly PraxisApplicationInitialConversation[];
80
87
  foundationProject?: PraxisProjectRuntime;
@@ -2839,6 +2839,7 @@ export function createPraxisApplicationRuntime(options) {
2839
2839
  agentReviewResolver: options.agentReviewResolver,
2840
2840
  mcpServers: applicationMcpServerProfiles(applicationMcpModule),
2841
2841
  mcpModule: applicationMcpModule,
2842
+ mcpPlus: options.mcpPlus,
2842
2843
  storage: {
2843
2844
  cwd: childSession.workingDirectory,
2844
2845
  workspaceRoot: path.join(project.projectRoot, ".raxode"),
@@ -3466,6 +3467,7 @@ export function createPraxisApplicationRuntime(options) {
3466
3467
  agentReviewResolver: options.agentReviewResolver,
3467
3468
  mcpServers: applicationMcpServerProfiles(applicationMcpModule),
3468
3469
  mcpModule: applicationMcpModule,
3470
+ mcpPlus: options.mcpPlus,
3469
3471
  storage: {
3470
3472
  cwd: state.cwd,
3471
3473
  workspaceRoot: path.join(project.projectRoot, ".raxode"),
@@ -1,5 +1,6 @@
1
1
  export type { PraxisApplicationAttachment, PraxisApplicationAgentEntryView, PraxisApplicationAuxiliaryTaskInput, PraxisApplicationAuthProfileView, PraxisApplicationAuthState, PraxisApplicationCommand, PraxisApplicationCommandResult, PraxisApplicationContextTelemetry, PraxisApplicationEvent, PraxisApplicationEventKind, PraxisApplicationInputEnvelope, PraxisApplicationManifestView, PraxisApplicationModelState, PraxisApplicationPermissionProfile, PraxisApplicationReasoningEffort, PraxisApplicationRuntime, PraxisApplicationRuntimeMode, PraxisApplicationStatus, PraxisApplicationToolCatalogState, PraxisApplicationToolProfile, PraxisApplicationUsageTelemetry, PraxisApplicationViewModel, } from "./applicationContract.js";
2
2
  export { loadApplicationProject, type PraxisApplicationProject, type PraxisApplicationProjectDescriptor, type PraxisApplicationProjectResult, } from "./applicationProject.js";
3
3
  export { createApplicationProjectRuntime, createPraxisApplicationRuntime, type PraxisApplicationBaseToolIntegrationOptions, type CreateApplicationProjectRuntimeOptions, type PraxisApplicationInitialConversation, type PraxisApplicationInitialConversationMessage, type PraxisApplicationLiveProvider, type PraxisApplicationRuntimeOptions, } from "./applicationRuntime.js";
4
- export type { McpApplicationServerInput, McpPlusApplicationServerInput, } from "../runtimeImplementation/runtime.mcpPlane/index.js";
4
+ export type { McpApplicationServerInput, McpPlusLearnedProfile, McpPlusApplicationServerInput, McpPlusOverlayStore, McpPlusProfileProposal, McpPlusProfileStore, McpPlusRuntimeOverlay, McpPlusSkillNote, McpPlusSkillStore, } from "../runtimeImplementation/runtime.mcpPlane/index.js";
5
+ export { createFileMcpPlusProfileStore, createFileMcpPlusSkillStore, createInMemoryMcpPlusOverlayStore, createInMemoryMcpPlusProfileStore, createInMemoryMcpPlusSkillStore, } from "../runtimeImplementation/runtime.mcpPlane/index.js";
5
6
  export { createApplicationRestServer, createApplicationWebSocketServer, createLocalApplicationTransport, describeApplicationRestTransport, describeApplicationWebSocketTransport, type PraxisApplicationRestServer, type PraxisApplicationWebSocketServer, type PraxisApplicationProtocolMessage, type PraxisApplicationRestRoute, type PraxisApplicationTransportClient, type PraxisApplicationTransportDescriptor, type PraxisApplicationTransportKind, type PraxisApplicationWebSocketMessage, } from "./applicationTransport.js";
@@ -4,4 +4,5 @@
4
4
  */
5
5
  export { loadApplicationProject, } from "./applicationProject.js";
6
6
  export { createApplicationProjectRuntime, createPraxisApplicationRuntime, } from "./applicationRuntime.js";
7
+ export { createFileMcpPlusProfileStore, createFileMcpPlusSkillStore, createInMemoryMcpPlusOverlayStore, createInMemoryMcpPlusProfileStore, createInMemoryMcpPlusSkillStore, } from "../runtimeImplementation/runtime.mcpPlane/index.js";
7
8
  export { createApplicationRestServer, createApplicationWebSocketServer, createLocalApplicationTransport, describeApplicationRestTransport, describeApplicationWebSocketTransport, } from "./applicationTransport.js";
@@ -22,7 +22,7 @@ import { type RuntimeApprovalRecord, type RuntimeSessionSnapshot, type RuntimeSe
22
22
  import { type RaxStorageInitMode } from "./runtime.storagePlane/storagePlaneRuntime.js";
23
23
  import type { SandboxRemoteWorkerAdapter } from "./runtime.sandboxPlane/sandboxCommandRunner.js";
24
24
  import type { SandboxExecutionProviderPort } from "./runtime.sandboxPlane/sandboxPolicyMiddleware.js";
25
- import { type McpHarnessModuleSpec } from "./runtime.mcpPlane/index.js";
25
+ import { type McpPlusOverlayStore, type McpPlusProfileStore, type McpPlusSkillStore, type McpHarnessModuleSpec } from "./runtime.mcpPlane/index.js";
26
26
  import type { McpRuntimeServerProfile } from "./runtime.execEngine/mcpRuntimeAdapter.js";
27
27
  export type PraxisRuntimeKernelErrorCode = "MANIFEST_COMPILE_FAILED" | "TEXT_INPUT_REJECTED" | "PROMPT_PACK_FAILED" | "MAIN_LOOP_INTERRUPTED" | "MODEL_INVOCATION_FAILED" | "MODEL_DECISION_FAILED" | "TOOL_INVOCATION_FAILED" | "PROCEDURE_INVOCATION_FAILED" | "APPROVAL_REQUIRED" | "SANDBOX_UNAVAILABLE" | "STORAGE_RESOLUTION_FAILED" | "TEXT_OUTPUT_REJECTED";
28
28
  export type PraxisRuntimeKernelError = {
@@ -52,6 +52,13 @@ export type PraxisRuntimeKernelOptions = {
52
52
  baseToolAdapters?: Partial<BaseToolExecutorPort>;
53
53
  mcpServers?: readonly McpRuntimeServerProfile[];
54
54
  mcpModule?: McpHarnessModuleSpec;
55
+ mcpPlus?: {
56
+ projectId?: string;
57
+ profileStore?: McpPlusProfileStore;
58
+ overlayStore?: McpPlusOverlayStore;
59
+ skillStore?: McpPlusSkillStore;
60
+ reprofileConsecutiveIndexedCalls?: number;
61
+ };
55
62
  baseToolPolicy?: RuntimeBaseToolExecutorPolicy;
56
63
  baseToolResourceLimits?: RuntimeBaseToolExecutorResourceLimits;
57
64
  store?: RuntimeSessionStateEventStore;
@@ -42,7 +42,7 @@ import { approvalInterfaceEnvelope, } from "../interfaceAdapter/interfaceEnvelop
42
42
  import { createInMemorySessionStateEventStore, createSqliteSessionStateEventStore, } from "./runtimeSessionStateEventStore.js";
43
43
  import { applyRaxStorageInitPlan, createStoragePlaneRuntime, } from "./runtime.storagePlane/storagePlaneRuntime.js";
44
44
  import { prepareSandboxRuntime, } from "./runtime.sandboxPlane/sandboxRuntimeProvider.js";
45
- import { buildMcpServerProfilesFromManifest, mcp, planMcpHarnessExposure, } from "./runtime.mcpPlane/index.js";
45
+ import { buildMcpServerProfilesFromManifest, createInMemoryMcpPlusOverlayStore, createFileMcpPlusProfileStore, createFileMcpPlusSkillStore, learnedProfileFromProposal, mcp, mcpHarnessModuleFrom, planMcpHarnessExposure, } from "./runtime.mcpPlane/index.js";
46
46
  async function inferFilesystemActionForTool(input) {
47
47
  if (input.toolId !== "patch.apply")
48
48
  return undefined;
@@ -290,9 +290,7 @@ function defaultMcpServerId(toolId, args) {
290
290
  return undefined;
291
291
  return readString(args.serverId) ?? "local-mcp";
292
292
  }
293
- function withRuntimeHarnessTools(manifest, dynamicTools) {
294
- if (dynamicTools.length === 0)
295
- return manifest;
293
+ function withRuntimeHarnessToolLayer(manifest, dynamicTools, reason) {
296
294
  const byId = new Map();
297
295
  for (const tool of [...manifest.harness.tools, ...dynamicTools]) {
298
296
  byId.set(tool.toolId, tool);
@@ -305,6 +303,7 @@ function withRuntimeHarnessTools(manifest, dynamicTools) {
305
303
  metadata: {
306
304
  ...manifest.harness.metadata,
307
305
  runtimeMcpDynamicToolCount: dynamicTools.length,
306
+ runtimeMcpDynamicToolRefreshReason: reason,
308
307
  },
309
308
  },
310
309
  };
@@ -345,7 +344,246 @@ function normalizeMcpNativeToolDeclaration(tool) {
345
344
  const schema = isRecord(tool.inputSchema) ? tool.inputSchema : isRecord(tool.input_schema) ? tool.input_schema : {};
346
345
  return { name, description, inputSchema: schema };
347
346
  }
348
- async function discoverRuntimeMcpDynamicTools(manifest, executor) {
347
+ function runtimeMcpPlusProjectId(input) {
348
+ if (typeof input.explicit === "string" && input.explicit.trim().length > 0)
349
+ return input.explicit.trim();
350
+ return `project.${createHash("sha256").update(path.resolve(input.workspaceRoot)).digest("hex").slice(0, 16)}`;
351
+ }
352
+ function mcpPlusOverlayToExposureState(overlay) {
353
+ if (overlay === undefined)
354
+ return {};
355
+ return {
356
+ mode: overlay.mode,
357
+ activeTools: [
358
+ ...overlay.activeTools,
359
+ ...(overlay.pendingReprofile ? ["mcp_plus.reprofile"] : []),
360
+ ],
361
+ };
362
+ }
363
+ function readStringList(value) {
364
+ if (!Array.isArray(value))
365
+ return [];
366
+ return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
367
+ }
368
+ function proposalFromArgs(args, fallbackServerId) {
369
+ const rawToolCards = isRecord(args.toolCards) ? args.toolCards : undefined;
370
+ const toolCards = rawToolCards === undefined ? undefined : Object.fromEntries(Object.entries(rawToolCards).flatMap(([toolName, card]) => {
371
+ if (!isRecord(card))
372
+ return [];
373
+ return [[toolName, {
374
+ title: readString(card.title),
375
+ summary: readString(card.summary),
376
+ keywords: readStringList(card.keywords),
377
+ }]];
378
+ }));
379
+ const rawSkillChapters = Array.isArray(args.skillChapters) ? args.skillChapters : [];
380
+ const proposal = {
381
+ serverId: readString(args.serverId) ?? fallbackServerId,
382
+ pinnedTools: readStringList(args.pinnedTools),
383
+ warmTools: readStringList(args.warmTools),
384
+ indexedTools: readStringList(args.indexedTools),
385
+ alwaysIndexTools: readStringList(args.alwaysIndexTools),
386
+ toolCards,
387
+ skillChapters: rawSkillChapters.flatMap((chapter) => {
388
+ if (!isRecord(chapter))
389
+ return [];
390
+ const id = readString(chapter.id);
391
+ const title = readString(chapter.title);
392
+ const summary = readString(chapter.summary);
393
+ return id === undefined || title === undefined || summary === undefined ? [] : [{ id, title, summary }];
394
+ }),
395
+ rationale: readString(args.rationale),
396
+ };
397
+ if (Object.hasOwn(args, "modeHint")) {
398
+ return {
399
+ ...proposal,
400
+ modeHint: args.modeHint,
401
+ };
402
+ }
403
+ return proposal;
404
+ }
405
+ function mcpPlusServerSpec(manifest, serverId) {
406
+ return mcpHarnessModuleFrom(manifest.harness)?.servers.find((server) => server.serverId === serverId);
407
+ }
408
+ function createRuntimeMcpPlusController(input) {
409
+ let nativeInventory = {};
410
+ async function loadOverlay(serverId, now) {
411
+ return await input.overlayStore.load({ sessionId: input.sessionId, serverId }) ?? {
412
+ serverId,
413
+ sessionId: input.sessionId,
414
+ mode: "expanded",
415
+ activeTools: [],
416
+ counters: { consecutiveIndexedToolCalls: {} },
417
+ updatedAt: now,
418
+ };
419
+ }
420
+ return {
421
+ async setNativeInventory(inventory) {
422
+ nativeInventory = inventory;
423
+ },
424
+ async learnedProfilesByServerId(manifest) {
425
+ const module = mcpHarnessModuleFrom(manifest.harness);
426
+ const profiles = {};
427
+ for (const server of module?.servers ?? []) {
428
+ if (server.mode !== "mcp-plus" || server.manifest !== undefined)
429
+ continue;
430
+ profiles[server.serverId] = await input.profileStore.load({ projectId: input.projectId, serverId: server.serverId });
431
+ }
432
+ return profiles;
433
+ },
434
+ async exposureStateByServerId(manifest) {
435
+ const module = mcpHarnessModuleFrom(manifest.harness);
436
+ const states = {};
437
+ for (const server of module?.servers ?? []) {
438
+ if (server.mode !== "mcp-plus")
439
+ continue;
440
+ states[server.serverId] = mcpPlusOverlayToExposureState(await input.overlayStore.load({ sessionId: input.sessionId, serverId: server.serverId }));
441
+ }
442
+ return states;
443
+ },
444
+ async callControlTool(control) {
445
+ if (control.controlName === "mcp_plus.init" || control.controlName === "mcp_plus.reprofile") {
446
+ const proposal = proposalFromArgs(control.args, control.serverId);
447
+ const nativeTools = nativeInventory[proposal.serverId] ?? [];
448
+ const existing = await input.profileStore.load({ projectId: input.projectId, serverId: proposal.serverId });
449
+ const learned = learnedProfileFromProposal({
450
+ proposal,
451
+ nativeTools,
452
+ projectId: input.projectId,
453
+ now: control.now,
454
+ existing,
455
+ });
456
+ if (!learned.ok)
457
+ return { ok: false, error: learned.error };
458
+ await input.profileStore.save({ projectId: input.projectId, serverId: proposal.serverId }, learned.profile);
459
+ const overlay = await loadOverlay(proposal.serverId, control.now);
460
+ await input.overlayStore.save({ sessionId: input.sessionId, serverId: proposal.serverId }, {
461
+ ...overlay,
462
+ mode: "expanded",
463
+ activeTools: [],
464
+ pendingReprofile: false,
465
+ counters: { consecutiveIndexedToolCalls: {} },
466
+ updatedAt: control.now,
467
+ metadata: {
468
+ ...(overlay.metadata ?? {}),
469
+ lastProfileControl: control.controlName,
470
+ },
471
+ });
472
+ return {
473
+ ok: true,
474
+ output: {
475
+ accepted: true,
476
+ serverId: proposal.serverId,
477
+ projectId: input.projectId,
478
+ schemaVersion: learned.profile.schemaVersion,
479
+ controlName: control.controlName,
480
+ },
481
+ metadata: { serverId: proposal.serverId, projectId: input.projectId, controlName: control.controlName },
482
+ };
483
+ }
484
+ if (control.controlName === "mcp_plus.skill_read") {
485
+ const serverId = readString(control.args.serverId) ?? control.serverId;
486
+ const notes = await input.skillStore.read({ serverId, projectId: input.projectId }, {
487
+ id: readString(control.args.id),
488
+ chapter: readString(control.args.chapter),
489
+ });
490
+ return { ok: true, output: { serverId, projectId: input.projectId, notes } };
491
+ }
492
+ if (control.controlName === "mcp_plus.skill_write" || control.controlName === "mcp_plus.finish") {
493
+ const rawSkill = control.controlName === "mcp_plus.finish" && isRecord(control.args.skill)
494
+ ? control.args.skill
495
+ : control.args;
496
+ const serverId = readString(rawSkill.serverId) ?? readString(control.args.serverId) ?? control.serverId;
497
+ const chapter = readString(rawSkill.chapter);
498
+ const title = readString(rawSkill.title);
499
+ const summary = readString(rawSkill.summary);
500
+ if (chapter === undefined || title === undefined || summary === undefined) {
501
+ return {
502
+ ok: true,
503
+ output: {
504
+ accepted: false,
505
+ serverId,
506
+ projectId: input.projectId,
507
+ reason: "No complete skill note was provided.",
508
+ },
509
+ };
510
+ }
511
+ const note = await input.skillStore.write({ serverId, projectId: input.projectId }, {
512
+ chapter,
513
+ title,
514
+ summary,
515
+ whenToUse: readString(rawSkill.whenToUse),
516
+ do: readStringList(rawSkill.do),
517
+ why: readString(rawSkill.why),
518
+ avoid: readStringList(rawSkill.avoid),
519
+ pitfalls: readStringList(rawSkill.pitfalls),
520
+ });
521
+ return { ok: true, output: { accepted: true, serverId, projectId: input.projectId, note } };
522
+ }
523
+ if (control.controlName === "mcp_plus.expand") {
524
+ const serverId = readString(control.args.server) ?? readString(control.args.serverId) ?? control.serverId;
525
+ const request = readString(control.args.request)?.toLowerCase() ?? "";
526
+ const server = mcpPlusServerSpec(control.manifest, serverId);
527
+ const profile = await input.profileStore.load({ projectId: input.projectId, serverId });
528
+ const indexedTools = server?.manifest?.exposure?.indexedTools ?? profile?.exposure.indexedTools ?? [];
529
+ const activatedTools = request.length === 0
530
+ ? indexedTools
531
+ : indexedTools.filter((toolName) => toolName.toLowerCase().includes(request));
532
+ const overlay = await loadOverlay(serverId, control.now);
533
+ await input.overlayStore.save({ sessionId: input.sessionId, serverId }, {
534
+ ...overlay,
535
+ mode: "expanded",
536
+ activeTools: [...new Set([...overlay.activeTools, ...activatedTools])],
537
+ updatedAt: control.now,
538
+ });
539
+ return { ok: true, output: { serverId, activatedTools, mode: "expanded" } };
540
+ }
541
+ return { ok: false, error: { code: "MCP_PLUS_CONTROL_UNKNOWN", message: `Unknown MCP+ control tool: ${control.controlName}`, publicSafe: true } };
542
+ },
543
+ async recordToolCall(record) {
544
+ if (!record.ok)
545
+ return false;
546
+ const toolSpec = record.manifest.harness.tools.find((tool) => tool.toolId === record.toolId);
547
+ if (toolSpec?.metadata?.toolProviderKind !== "mcp-static")
548
+ return false;
549
+ const serverId = readString(toolSpec.metadata.serverId);
550
+ const nativeToolName = readString(toolSpec.metadata.nativeToolName);
551
+ if (serverId === undefined || nativeToolName === undefined)
552
+ return false;
553
+ const server = mcpPlusServerSpec(record.manifest, serverId);
554
+ if (server?.mode !== "mcp-plus")
555
+ return false;
556
+ const profile = server.manifest === undefined
557
+ ? await input.profileStore.load({ projectId: input.projectId, serverId })
558
+ : undefined;
559
+ const indexedTools = new Set(server.manifest?.exposure?.indexedTools ?? profile?.exposure.indexedTools ?? []);
560
+ const overlay = await loadOverlay(serverId, record.now);
561
+ const consecutiveIndexedToolCalls = { ...overlay.counters.consecutiveIndexedToolCalls };
562
+ let pendingReprofile = overlay.pendingReprofile;
563
+ if (indexedTools.has(nativeToolName)) {
564
+ consecutiveIndexedToolCalls[nativeToolName] = (consecutiveIndexedToolCalls[nativeToolName] ?? 0) + 1;
565
+ if (consecutiveIndexedToolCalls[nativeToolName] >= input.reprofileConsecutiveIndexedCalls) {
566
+ pendingReprofile = true;
567
+ }
568
+ }
569
+ else {
570
+ for (const toolName of Object.keys(consecutiveIndexedToolCalls)) {
571
+ consecutiveIndexedToolCalls[toolName] = 0;
572
+ }
573
+ }
574
+ await input.overlayStore.save({ sessionId: input.sessionId, serverId }, {
575
+ ...overlay,
576
+ mode: "expanded",
577
+ activeTools: [...new Set([...overlay.activeTools, nativeToolName])],
578
+ pendingReprofile,
579
+ counters: { consecutiveIndexedToolCalls },
580
+ updatedAt: record.now,
581
+ });
582
+ return true;
583
+ },
584
+ };
585
+ }
586
+ async function discoverRuntimeMcpDynamicTools(manifest, executor, mcpPlusRuntime) {
349
587
  const profiles = buildMcpServerProfilesFromManifest(manifest);
350
588
  if (profiles.length === 0 || executor.mcp?.listTools === undefined)
351
589
  return [];
@@ -359,7 +597,8 @@ async function discoverRuntimeMcpDynamicTools(manifest, executor) {
359
597
  .map(normalizeMcpNativeToolDeclaration)
360
598
  .filter((tool) => tool !== undefined);
361
599
  }
362
- return planMcpHarnessExposure(manifest, inventory).servers.flatMap((server) => [...server.dynamicToolSpecs]);
600
+ await mcpPlusRuntime?.setNativeInventory(inventory);
601
+ return planMcpHarnessExposure(manifest, inventory, await mcpPlusRuntime?.exposureStateByServerId(manifest) ?? {}, await mcpPlusRuntime?.learnedProfilesByServerId(manifest) ?? {}).servers.flatMap((server) => [...server.dynamicToolSpecs]);
363
602
  }
364
603
  function providerToolMappings(manifest) {
365
604
  return createProviderToolMappings(manifest.harness.tools);
@@ -2562,6 +2801,123 @@ async function executeBaseToolDecision(input) {
2562
2801
  const dynamicRuntimeArguments = isRecord(dynamicToolSpec?.metadata?.runtimeArguments)
2563
2802
  ? dynamicToolSpec.metadata.runtimeArguments
2564
2803
  : undefined;
2804
+ if (dynamicToolSpec?.metadata?.toolProviderKind === "mcp-plus-control") {
2805
+ const serverId = readString(dynamicToolSpec.metadata.serverId);
2806
+ const controlName = readString(dynamicToolSpec.metadata.controlName);
2807
+ if (serverId === undefined || controlName === undefined || input.mcpPlusRuntime === undefined) {
2808
+ const record = {
2809
+ callId: input.toolCallId,
2810
+ toolId: input.toolId,
2811
+ arguments: input.args,
2812
+ ok: false,
2813
+ error: {
2814
+ code: "MCP_PLUS_CONTROL_UNAVAILABLE",
2815
+ message: "MCP+ control tool is not available in this runtime.",
2816
+ publicSafe: true,
2817
+ },
2818
+ };
2819
+ const observation = createObservationMaterial({
2820
+ observationId: `${input.sessionId}:observation:${input.toolCallId}`,
2821
+ source: "baseTool",
2822
+ status: "failed",
2823
+ title: `MCP+ control ${controlName ?? input.toolId}`,
2824
+ summary: "MCP+ control tool is not available.",
2825
+ refs: [input.toolCallId, input.toolId],
2826
+ payload: record.error,
2827
+ metadata: metadataRecord({ toolCallId: input.toolCallId, toolId: input.toolId }),
2828
+ });
2829
+ return {
2830
+ record,
2831
+ observation,
2832
+ events: ["runtime.mcpPlus.control.unavailable"],
2833
+ governance: {
2834
+ kind: "runtime.execEngine.baseTool.governanceDecision",
2835
+ toolId: input.toolId,
2836
+ status: "deny",
2837
+ risk: "safe",
2838
+ policyProfile: input.manifest.toolPolicy.profile,
2839
+ policyMatrixId: input.manifest.toolPolicy.matrixId,
2840
+ approvalRequired: false,
2841
+ approvalReason: "MCP+ control tool is not available.",
2842
+ sandbox: {
2843
+ sandboxId: input.manifest.sandbox.sandboxId,
2844
+ profile: input.manifest.sandbox.profile,
2845
+ providerFamily: input.manifest.sandbox.providerFamily,
2846
+ isolationLevel: input.manifest.sandbox.isolationLevel,
2847
+ filesystem: input.manifest.sandbox.filesystem,
2848
+ network: input.manifest.sandbox.network,
2849
+ shell: input.manifest.sandbox.shell,
2850
+ hostObserved: input.manifest.sandbox.profile === "host-observed",
2851
+ dependencyRefs: input.manifest.sandbox.dependencyRefs ?? [],
2852
+ },
2853
+ resourceLimits: input.manifest.sandbox.resourceLimits,
2854
+ publicSafe: true,
2855
+ events: ["runtime.mcpPlus.control.unavailable"],
2856
+ metadata: { toolCallId: input.toolCallId },
2857
+ },
2858
+ };
2859
+ }
2860
+ const controlResult = await input.mcpPlusRuntime.callControlTool({
2861
+ manifest: input.manifest,
2862
+ serverId,
2863
+ controlName,
2864
+ args: input.args,
2865
+ now: input.now(),
2866
+ });
2867
+ const record = {
2868
+ callId: input.toolCallId,
2869
+ toolId: input.toolId,
2870
+ arguments: input.args,
2871
+ ok: controlResult.ok,
2872
+ ...(controlResult.ok ? { output: controlResult.output } : { error: controlResult.error }),
2873
+ };
2874
+ const controlFailureMessage = controlResult.ok ? undefined : controlResult.error?.message ?? "MCP+ control tool failed.";
2875
+ const observation = createObservationMaterial({
2876
+ observationId: `${input.sessionId}:observation:${input.toolCallId}`,
2877
+ source: "baseTool",
2878
+ status: controlResult.ok ? "completed" : "failed",
2879
+ title: `MCP+ control ${controlName}`,
2880
+ summary: controlResult.ok ? "MCP+ control tool completed." : controlFailureMessage ?? "MCP+ control tool failed.",
2881
+ refs: [input.toolCallId, input.toolId],
2882
+ payload: controlResult.ok ? controlResult.output : controlResult.error ?? { code: "MCP_PLUS_CONTROL_FAILED", message: controlFailureMessage, publicSafe: true },
2883
+ metadata: metadataRecord({
2884
+ toolCallId: input.toolCallId,
2885
+ toolId: input.toolId,
2886
+ serverId,
2887
+ controlName,
2888
+ observationStatus: controlResult.ok ? "completed" : "failed",
2889
+ }),
2890
+ });
2891
+ return {
2892
+ record,
2893
+ observation,
2894
+ events: [controlResult.ok ? "runtime.mcpPlus.control.completed" : "runtime.mcpPlus.control.failed"],
2895
+ governance: {
2896
+ kind: "runtime.execEngine.baseTool.governanceDecision",
2897
+ toolId: input.toolId,
2898
+ status: controlResult.ok ? "allow" : "deny",
2899
+ risk: "safe",
2900
+ policyProfile: input.manifest.toolPolicy.profile,
2901
+ policyMatrixId: input.manifest.toolPolicy.matrixId,
2902
+ approvalRequired: false,
2903
+ sandbox: {
2904
+ sandboxId: input.manifest.sandbox.sandboxId,
2905
+ profile: input.manifest.sandbox.profile,
2906
+ providerFamily: input.manifest.sandbox.providerFamily,
2907
+ isolationLevel: input.manifest.sandbox.isolationLevel,
2908
+ filesystem: input.manifest.sandbox.filesystem,
2909
+ network: input.manifest.sandbox.network,
2910
+ shell: input.manifest.sandbox.shell,
2911
+ hostObserved: input.manifest.sandbox.profile === "host-observed",
2912
+ dependencyRefs: input.manifest.sandbox.dependencyRefs ?? [],
2913
+ },
2914
+ resourceLimits: input.manifest.sandbox.resourceLimits,
2915
+ publicSafe: true,
2916
+ events: [controlResult.ok ? "runtime.mcpPlus.control.completed" : "runtime.mcpPlus.control.failed"],
2917
+ metadata: { serverId, controlName },
2918
+ },
2919
+ };
2920
+ }
2565
2921
  if (dynamicRuntimeToolId !== undefined && dynamicRuntimeToolId !== input.toolId) {
2566
2922
  const delegatedArgs = dynamicRuntimeToolId === "mcp.use"
2567
2923
  ? {
@@ -2576,6 +2932,7 @@ async function executeBaseToolDecision(input) {
2576
2932
  ...input,
2577
2933
  toolId: dynamicRuntimeToolId,
2578
2934
  args: delegatedArgs,
2935
+ mcpPlusRuntime: input.mcpPlusRuntime,
2579
2936
  });
2580
2937
  return {
2581
2938
  ...delegated,
@@ -3525,6 +3882,7 @@ export class PraxisRuntimeKernel {
3525
3882
  });
3526
3883
  const dryRun = options.dryRun !== false;
3527
3884
  manifest = withRuntimeMcpModule(manifest, options.mcpModule);
3885
+ const runtimeMcpBaseManifest = manifest;
3528
3886
  const defaultBaseToolPolicy = {
3529
3887
  workspaceRoot: toolWorkspaceRoot,
3530
3888
  allowedRoots: toolAllowedRoots,
@@ -3573,10 +3931,25 @@ export class PraxisRuntimeKernel {
3573
3931
  id: "praxis-runtime-kernel",
3574
3932
  sessionId,
3575
3933
  };
3576
- manifest = withRuntimeHarnessTools(manifest, await discoverRuntimeMcpDynamicTools(manifest, executor));
3934
+ const mcpPlusRuntime = createRuntimeMcpPlusController({
3935
+ sessionId,
3936
+ projectId: runtimeMcpPlusProjectId({
3937
+ explicit: options.mcpPlus?.projectId,
3938
+ workspaceRoot: toolWorkspaceRoot,
3939
+ }),
3940
+ profileStore: options.mcpPlus?.profileStore ?? createFileMcpPlusProfileStore(path.join(toolWorkspaceRoot, ".rax_workspace", "mcp-plus")),
3941
+ overlayStore: options.mcpPlus?.overlayStore ?? createInMemoryMcpPlusOverlayStore(),
3942
+ skillStore: options.mcpPlus?.skillStore ?? createFileMcpPlusSkillStore(path.join(toolWorkspaceRoot, ".rax_workspace", "mcp-plus")),
3943
+ reprofileConsecutiveIndexedCalls: options.mcpPlus?.reprofileConsecutiveIndexedCalls ?? 6,
3944
+ });
3577
3945
  const maxModelTurns = manifest.harness.loop.maxModelTurns ?? 2;
3578
3946
  const maxToolCalls = manifest.harness.loop.maxToolCalls ?? 4;
3579
- const toolMappings = providerToolMappings(manifest);
3947
+ let toolMappings = [];
3948
+ async function refreshRuntimeMcpTools(reason) {
3949
+ manifest = withRuntimeHarnessToolLayer(runtimeMcpBaseManifest, await discoverRuntimeMcpDynamicTools(runtimeMcpBaseManifest, executor, mcpPlusRuntime), reason);
3950
+ toolMappings = providerToolMappings(manifest);
3951
+ }
3952
+ await refreshRuntimeMcpTools("session.checkpoint.start");
3580
3953
  const providerFamily = providerToolSchemaFamilyForModel(manifest.model);
3581
3954
  let toolContextSelection = {
3582
3955
  families: normalizedSelection(options.toolContextSelection?.families),
@@ -4758,6 +5131,7 @@ export class PraxisRuntimeKernel {
4758
5131
  store,
4759
5132
  approvalResolver: options.approvalResolver,
4760
5133
  agentReviewResolver: options.agentReviewResolver,
5134
+ mcpPlusRuntime,
4761
5135
  preparedSandbox: sandboxPrepared.sandbox,
4762
5136
  dependencyRuntime: options.baseToolDependencyRuntime,
4763
5137
  now,
@@ -4770,6 +5144,16 @@ export class PraxisRuntimeKernel {
4770
5144
  });
4771
5145
  toolCalls.push(executed.record);
4772
5146
  observations.push(executed.observation);
5147
+ const mcpPlusToolCallUpdated = await mcpPlusRuntime.recordToolCall({
5148
+ manifest,
5149
+ toolId: executed.record.toolId,
5150
+ ok: executed.record.ok,
5151
+ now: now(),
5152
+ });
5153
+ if (executed.record.ok && (mcpPlusToolCallUpdated ||
5154
+ manifest.harness.tools.find((tool) => tool.toolId === executed.record.toolId)?.metadata?.toolProviderKind === "mcp-plus-control")) {
5155
+ await refreshRuntimeMcpTools("session.checkpoint.after_mcp_tool");
5156
+ }
4773
5157
  if (invalidatesFileReadCache(executed.record.toolId)) {
4774
5158
  sameTurnFileReadCache.clear();
4775
5159
  }
@@ -1,4 +1,4 @@
1
- import { type ExposureState, type McpCompatibleSurface, type McpPlusManifest, type NativeToolDeclaration } from "@praxis-ai/mcp-plus";
1
+ import { type ExposureMode, type ExposureState, type McpCompatibleSurface, type McpPlusManifest, type NativeToolDeclaration } from "@praxis-ai/mcp-plus";
2
2
  import type { ToolSpec } from "../runtimeAgentManifest.js";
3
3
  import type { McpRuntimeServerProfile } from "../runtime.execEngine/mcpRuntimeAdapter.js";
4
4
  export type McpHarnessServerMode = "native" | "mcp-plus";
@@ -74,6 +74,90 @@ export type McpExposurePlanServer = {
74
74
  export type McpHarnessExposurePlan = {
75
75
  servers: readonly McpExposurePlanServer[];
76
76
  };
77
+ export type McpPlusProfileProposal = {
78
+ serverId: string;
79
+ pinnedTools?: readonly string[];
80
+ warmTools?: readonly string[];
81
+ indexedTools?: readonly string[];
82
+ alwaysIndexTools?: readonly string[];
83
+ toolCards?: Readonly<Record<string, {
84
+ title?: string;
85
+ summary?: string;
86
+ keywords?: readonly string[];
87
+ }>>;
88
+ skillChapters?: readonly {
89
+ id: string;
90
+ title: string;
91
+ summary: string;
92
+ }[];
93
+ rationale?: string;
94
+ metadata?: Readonly<Record<string, unknown>>;
95
+ };
96
+ export type McpPlusLearnedProfile = {
97
+ schemaVersion: "mcp-plus.profile.v1";
98
+ serverId: string;
99
+ projectId: string;
100
+ exposure: NonNullable<McpPlusManifest["exposure"]>;
101
+ skills?: NonNullable<McpPlusManifest["skills"]>;
102
+ rationale?: string;
103
+ createdAt: string;
104
+ updatedAt: string;
105
+ metadata?: Readonly<Record<string, unknown>>;
106
+ };
107
+ export type McpPlusRuntimeOverlay = {
108
+ serverId: string;
109
+ sessionId: string;
110
+ mode: ExposureMode;
111
+ activeTools: readonly string[];
112
+ pendingReprofile?: boolean;
113
+ counters: {
114
+ consecutiveIndexedToolCalls: Readonly<Record<string, number>>;
115
+ };
116
+ updatedAt: string;
117
+ metadata?: Readonly<Record<string, unknown>>;
118
+ };
119
+ export type McpPlusProfileStoreKey = {
120
+ serverId: string;
121
+ projectId: string;
122
+ };
123
+ export type McpPlusOverlayStoreKey = {
124
+ serverId: string;
125
+ sessionId: string;
126
+ };
127
+ export type McpPlusProfileStore = {
128
+ load(key: McpPlusProfileStoreKey): Promise<McpPlusLearnedProfile | undefined>;
129
+ save(key: McpPlusProfileStoreKey, profile: McpPlusLearnedProfile): Promise<void>;
130
+ };
131
+ export type McpPlusOverlayStore = {
132
+ load(key: McpPlusOverlayStoreKey): Promise<McpPlusRuntimeOverlay | undefined>;
133
+ save(key: McpPlusOverlayStoreKey, overlay: McpPlusRuntimeOverlay): Promise<void>;
134
+ };
135
+ export type McpPlusSkillNote = {
136
+ id: string;
137
+ serverId: string;
138
+ projectId: string;
139
+ chapter: string;
140
+ title: string;
141
+ summary: string;
142
+ whenToUse?: string;
143
+ do?: readonly string[];
144
+ why?: string;
145
+ avoid?: readonly string[];
146
+ pitfalls?: readonly string[];
147
+ createdAt: string;
148
+ updatedAt: string;
149
+ metadata?: Readonly<Record<string, unknown>>;
150
+ };
151
+ export type McpPlusSkillStore = {
152
+ list(key: McpPlusProfileStoreKey): Promise<readonly McpPlusSkillNote[]>;
153
+ read(key: McpPlusProfileStoreKey, query: {
154
+ id?: string;
155
+ chapter?: string;
156
+ }): Promise<readonly McpPlusSkillNote[]>;
157
+ write(key: McpPlusProfileStoreKey, note: Omit<McpPlusSkillNote, "id" | "serverId" | "projectId" | "createdAt" | "updatedAt"> & {
158
+ id?: string;
159
+ }): Promise<McpPlusSkillNote>;
160
+ };
77
161
  export type McpPlusApplicationServerInput = McpTransportSpec & {
78
162
  serverId: string;
79
163
  manifest: McpPlusManifest;
@@ -106,9 +190,36 @@ export declare function buildMcpServerProfilesFromManifest(input: {
106
190
  };
107
191
  }): readonly McpRuntimeServerProfile[];
108
192
  export declare function createMcpApplicationStateView(module: McpHarnessModuleSpec | undefined): McpApplicationStateView;
193
+ export declare function createMcpPlusInitToolDeclaration(): NativeToolDeclaration;
194
+ export declare function createMcpPlusReprofileToolDeclaration(): NativeToolDeclaration;
195
+ export declare function createMcpPlusSkillReadToolDeclaration(): NativeToolDeclaration;
196
+ export declare function createMcpPlusSkillWriteToolDeclaration(): NativeToolDeclaration;
197
+ export declare function createMcpPlusFinishToolDeclaration(): NativeToolDeclaration;
198
+ export declare function createInMemoryMcpPlusProfileStore(initial?: readonly McpPlusLearnedProfile[]): McpPlusProfileStore;
199
+ export declare function createInMemoryMcpPlusOverlayStore(initial?: readonly McpPlusRuntimeOverlay[]): McpPlusOverlayStore;
200
+ export declare function createInMemoryMcpPlusSkillStore(initial?: readonly McpPlusSkillNote[]): McpPlusSkillStore;
201
+ export declare function createFileMcpPlusProfileStore(rootDir: string): McpPlusProfileStore;
202
+ export declare function createFileMcpPlusSkillStore(rootDir: string): McpPlusSkillStore;
203
+ export declare function learnedProfileFromProposal(input: {
204
+ proposal: McpPlusProfileProposal;
205
+ nativeTools: readonly NativeToolDeclaration[];
206
+ projectId: string;
207
+ now: string;
208
+ existing?: McpPlusLearnedProfile;
209
+ }): {
210
+ ok: true;
211
+ profile: McpPlusLearnedProfile;
212
+ } | {
213
+ ok: false;
214
+ error: {
215
+ code: string;
216
+ message: string;
217
+ publicSafe: true;
218
+ };
219
+ };
109
220
  export declare function planMcpHarnessExposure(manifest: {
110
221
  harness: {
111
222
  modules: Readonly<Record<string, unknown>>;
112
223
  };
113
- }, nativeToolInventoryByServerId: Readonly<Record<string, readonly NativeToolDeclaration[]>>, stateByServerId?: Readonly<Record<string, Partial<ExposureState>>>): McpHarnessExposurePlan;
224
+ }, nativeToolInventoryByServerId: Readonly<Record<string, readonly NativeToolDeclaration[]>>, stateByServerId?: Readonly<Record<string, Partial<ExposureState>>>, profileByServerId?: Readonly<Record<string, McpPlusLearnedProfile | undefined>>): McpHarnessExposurePlan;
114
225
  export {};
@@ -3,7 +3,9 @@
3
3
  * 核心目的:把标准 MCP server 声明、MCP+ exposure policy、runtime server profile、
4
4
  * dynamic harness tools 和 application view 串成一条 OAO 可编译、可检查、可挂载的合同。
5
5
  */
6
- import { compileMcpPlusManifest, lowerExposurePlanToMcpSurface, planExposure, } from "@praxis-ai/mcp-plus";
6
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
7
+ import path from "node:path";
8
+ import { compileMcpPlusManifest, createExpandToolDeclaration, lowerExposurePlanToMcpSurface, planExposure, } from "@praxis-ai/mcp-plus";
7
9
  export function mcpServer(serverId, input) {
8
10
  return {
9
11
  ...input,
@@ -102,6 +104,97 @@ export function createMcpApplicationStateView(module) {
102
104
  function toolIdPart(value) {
103
105
  return value.replace(/[^a-zA-Z0-9_-]+/gu, ".").replace(/^\.+|\.+$/gu, "") || "tool";
104
106
  }
107
+ function jsonObjectSchema(properties, required = []) {
108
+ return {
109
+ type: "object",
110
+ properties,
111
+ required: [...required],
112
+ additionalProperties: false,
113
+ };
114
+ }
115
+ export function createMcpPlusInitToolDeclaration() {
116
+ return {
117
+ name: "mcp_plus.init",
118
+ description: "Submit the first MCP+ profile proposal for this standard MCP server after inspecting its full MCP tools/list surface.",
119
+ inputSchema: profileProposalInputSchema("init"),
120
+ };
121
+ }
122
+ export function createMcpPlusReprofileToolDeclaration() {
123
+ return {
124
+ name: "mcp_plus.reprofile",
125
+ description: "Submit an updated MCP+ profile proposal for this standard MCP server when runtime usage shows the current profile is stale.",
126
+ inputSchema: profileProposalInputSchema("reprofile"),
127
+ };
128
+ }
129
+ export function createMcpPlusSkillReadToolDeclaration() {
130
+ return {
131
+ name: "mcp_plus.skill_read",
132
+ description: "Read server-bound MCP+ skill notes for the current project when a reusable workflow is relevant.",
133
+ inputSchema: jsonObjectSchema({
134
+ serverId: { type: "string" },
135
+ id: { type: "string" },
136
+ chapter: { type: "string" },
137
+ }, ["serverId"]),
138
+ };
139
+ }
140
+ export function createMcpPlusSkillWriteToolDeclaration() {
141
+ return {
142
+ name: "mcp_plus.skill_write",
143
+ description: "Write a concise server-bound MCP+ skill note for the current project after a reusable workflow, pitfall, or difficult MCP usage is discovered.",
144
+ inputSchema: jsonObjectSchema({
145
+ serverId: { type: "string" },
146
+ chapter: { type: "string" },
147
+ title: { type: "string" },
148
+ summary: { type: "string" },
149
+ whenToUse: { type: "string" },
150
+ do: { type: "array", items: { type: "string" } },
151
+ why: { type: "string" },
152
+ avoid: { type: "array", items: { type: "string" } },
153
+ pitfalls: { type: "array", items: { type: "string" } },
154
+ }, ["serverId", "chapter", "title", "summary"]),
155
+ };
156
+ }
157
+ export function createMcpPlusFinishToolDeclaration() {
158
+ return {
159
+ name: "mcp_plus.finish",
160
+ description: "Finish an MCP+ workflow and optionally submit a reusable skill note. Praxis records the note only when the model provides one.",
161
+ inputSchema: jsonObjectSchema({
162
+ serverId: { type: "string" },
163
+ outcome: { type: "string", enum: ["success", "failure", "partial"] },
164
+ skill: createMcpPlusSkillWriteToolDeclaration().inputSchema,
165
+ }, ["serverId", "outcome"]),
166
+ };
167
+ }
168
+ function profileProposalInputSchema(kind) {
169
+ return jsonObjectSchema({
170
+ serverId: { type: "string" },
171
+ pinnedTools: { type: "array", items: { type: "string" } },
172
+ warmTools: { type: "array", items: { type: "string" } },
173
+ indexedTools: { type: "array", items: { type: "string" } },
174
+ alwaysIndexTools: { type: "array", items: { type: "string" } },
175
+ toolCards: {
176
+ type: "object",
177
+ additionalProperties: {
178
+ type: "object",
179
+ properties: {
180
+ title: { type: "string" },
181
+ summary: { type: "string" },
182
+ keywords: { type: "array", items: { type: "string" } },
183
+ },
184
+ additionalProperties: false,
185
+ },
186
+ },
187
+ skillChapters: {
188
+ type: "array",
189
+ items: jsonObjectSchema({
190
+ id: { type: "string" },
191
+ title: { type: "string" },
192
+ summary: { type: "string" },
193
+ }, ["id", "title", "summary"]),
194
+ },
195
+ rationale: { type: "string", description: `${kind} rationale for the host runtime.` },
196
+ }, ["serverId"]);
197
+ }
105
198
  function dynamicToolSpecForNativeTool(serverId, tool) {
106
199
  return {
107
200
  toolId: `mcp.${toolIdPart(serverId)}.${toolIdPart(tool.name)}`,
@@ -119,16 +212,278 @@ function dynamicToolSpecForNativeTool(serverId, tool) {
119
212
  },
120
213
  };
121
214
  }
215
+ function dynamicToolSpecForMcpPlusControlTool(serverId, tool) {
216
+ return {
217
+ toolId: `mcp.${toolIdPart(serverId)}.${toolIdPart(tool.name)}`,
218
+ family: "mcp",
219
+ group: serverId,
220
+ description: tool.description,
221
+ inputSchema: tool.inputSchema,
222
+ metadata: {
223
+ toolProviderKind: "mcp-plus-control",
224
+ serverId,
225
+ controlName: tool.name,
226
+ source: "runtime.mcpPlane.mcpPlusControlTool",
227
+ },
228
+ };
229
+ }
230
+ function isMcpPlusControlToolName(name) {
231
+ return name.startsWith("mcp_plus.");
232
+ }
122
233
  function dynamicToolSpecsForSurface(serverId, surface) {
123
- return surface.tools
124
- .filter((tool) => tool.name !== "mcp_plus.expand")
125
- .map((tool) => dynamicToolSpecForNativeTool(serverId, tool));
234
+ return surface.tools.map((tool) => isMcpPlusControlToolName(tool.name)
235
+ ? dynamicToolSpecForMcpPlusControlTool(serverId, tool)
236
+ : dynamicToolSpecForNativeTool(serverId, tool));
237
+ }
238
+ export function createInMemoryMcpPlusProfileStore(initial = []) {
239
+ const profiles = new Map(initial.map((profile) => [`${profile.projectId}:${profile.serverId}`, profile]));
240
+ return {
241
+ async load(key) {
242
+ return profiles.get(`${key.projectId}:${key.serverId}`);
243
+ },
244
+ async save(key, profile) {
245
+ profiles.set(`${key.projectId}:${key.serverId}`, profile);
246
+ },
247
+ };
248
+ }
249
+ export function createInMemoryMcpPlusOverlayStore(initial = []) {
250
+ const overlays = new Map(initial.map((overlay) => [`${overlay.sessionId}:${overlay.serverId}`, overlay]));
251
+ return {
252
+ async load(key) {
253
+ return overlays.get(`${key.sessionId}:${key.serverId}`);
254
+ },
255
+ async save(key, overlay) {
256
+ overlays.set(`${key.sessionId}:${key.serverId}`, overlay);
257
+ },
258
+ };
259
+ }
260
+ export function createInMemoryMcpPlusSkillStore(initial = []) {
261
+ const notes = [...initial];
262
+ return {
263
+ async list(key) {
264
+ return notes.filter((note) => note.projectId === key.projectId && note.serverId === key.serverId);
265
+ },
266
+ async read(key, query) {
267
+ return notes.filter((note) => note.projectId === key.projectId &&
268
+ note.serverId === key.serverId &&
269
+ (query.id === undefined || note.id === query.id) &&
270
+ (query.chapter === undefined || note.chapter === query.chapter));
271
+ },
272
+ async write(key, note) {
273
+ const now = new Date().toISOString();
274
+ const noteId = note.id ?? `${key.serverId}:${note.chapter}:${toolIdPart(note.title).toLowerCase()}`;
275
+ const existingIndex = notes.findIndex((item) => item.projectId === key.projectId && item.serverId === key.serverId && item.id === noteId);
276
+ const written = {
277
+ ...note,
278
+ id: noteId,
279
+ serverId: key.serverId,
280
+ projectId: key.projectId,
281
+ createdAt: existingIndex === -1 ? now : notes[existingIndex]?.createdAt ?? now,
282
+ updatedAt: now,
283
+ };
284
+ if (existingIndex === -1)
285
+ notes.push(written);
286
+ else
287
+ notes[existingIndex] = written;
288
+ return written;
289
+ },
290
+ };
291
+ }
292
+ async function readJsonFile(filePath, fallback) {
293
+ try {
294
+ return JSON.parse(await readFile(filePath, "utf8"));
295
+ }
296
+ catch {
297
+ return fallback;
298
+ }
299
+ }
300
+ async function writeJsonFile(filePath, value) {
301
+ await mkdir(path.dirname(filePath), { recursive: true });
302
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, { mode: 0o600 });
126
303
  }
127
- export function planMcpHarnessExposure(manifest, nativeToolInventoryByServerId, stateByServerId = {}) {
304
+ function storeFilePart(value) {
305
+ return toolIdPart(value).replace(/\.+/gu, "-").toLowerCase() || "default";
306
+ }
307
+ export function createFileMcpPlusProfileStore(rootDir) {
308
+ function profilePath(key) {
309
+ return path.join(rootDir, "profiles", storeFilePart(key.projectId), `${storeFilePart(key.serverId)}.json`);
310
+ }
311
+ return {
312
+ async load(key) {
313
+ return await readJsonFile(profilePath(key), undefined);
314
+ },
315
+ async save(key, profile) {
316
+ await writeJsonFile(profilePath(key), profile);
317
+ },
318
+ };
319
+ }
320
+ export function createFileMcpPlusSkillStore(rootDir) {
321
+ function skillPath(key) {
322
+ return path.join(rootDir, "skills", storeFilePart(key.projectId), `${storeFilePart(key.serverId)}.json`);
323
+ }
324
+ return {
325
+ async list(key) {
326
+ return await readJsonFile(skillPath(key), []);
327
+ },
328
+ async read(key, query) {
329
+ const notes = await readJsonFile(skillPath(key), []);
330
+ return notes.filter((note) => (query.id === undefined || note.id === query.id) &&
331
+ (query.chapter === undefined || note.chapter === query.chapter));
332
+ },
333
+ async write(key, note) {
334
+ const notes = await readJsonFile(skillPath(key), []);
335
+ const now = new Date().toISOString();
336
+ const noteId = note.id ?? `${key.serverId}:${note.chapter}:${toolIdPart(note.title).toLowerCase()}`;
337
+ const existingIndex = notes.findIndex((item) => item.id === noteId);
338
+ const written = {
339
+ ...note,
340
+ id: noteId,
341
+ serverId: key.serverId,
342
+ projectId: key.projectId,
343
+ createdAt: existingIndex === -1 ? now : notes[existingIndex]?.createdAt ?? now,
344
+ updatedAt: now,
345
+ };
346
+ if (existingIndex === -1)
347
+ notes.push(written);
348
+ else
349
+ notes[existingIndex] = written;
350
+ await writeJsonFile(skillPath(key), notes);
351
+ return written;
352
+ },
353
+ };
354
+ }
355
+ function uniqueStrings(values) {
356
+ return [...new Set((values ?? []).filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()))];
357
+ }
358
+ function defaultToolCard(tool) {
359
+ return {
360
+ title: tool.name,
361
+ summary: tool.description || tool.name,
362
+ keywords: tool.name.split(/[^a-zA-Z0-9]+/u).filter(Boolean),
363
+ };
364
+ }
365
+ function learnedManifestFromProfile(profile, server) {
366
+ return {
367
+ server: {
368
+ id: server.serverId,
369
+ title: server.title ?? server.serverId,
370
+ summary: server.summary ?? server.manifest?.server.summary ?? "Learned MCP+ profile.",
371
+ },
372
+ exposure: profile.exposure,
373
+ skills: profile.skills,
374
+ };
375
+ }
376
+ function fallbackMcpPlusManifest(server, nativeTools) {
377
+ const smallServer = nativeTools.length <= 8;
378
+ return {
379
+ server: {
380
+ id: server.serverId,
381
+ title: server.title ?? server.serverId,
382
+ summary: server.summary ?? "Standard MCP server with Praxis MCP+ native exposure.",
383
+ },
384
+ exposure: smallServer
385
+ ? { pinnedTools: nativeTools.map((tool) => tool.name), indexedTools: [] }
386
+ : {
387
+ pinnedTools: nativeTools.slice(0, 3).map((tool) => tool.name),
388
+ indexedTools: nativeTools.slice(3).map((tool) => tool.name),
389
+ toolCards: Object.fromEntries(nativeTools.slice(3).map((tool) => [tool.name, defaultToolCard(tool)])),
390
+ },
391
+ };
392
+ }
393
+ function bootstrapSurface(server, nativeTools) {
394
+ return {
395
+ tools: [
396
+ ...nativeTools,
397
+ createMcpPlusInitToolDeclaration(),
398
+ createMcpPlusSkillReadToolDeclaration(),
399
+ createMcpPlusSkillWriteToolDeclaration(),
400
+ createMcpPlusFinishToolDeclaration(),
401
+ ],
402
+ sidecar: {
403
+ serverCard: {
404
+ id: server.serverId,
405
+ title: server.title ?? server.serverId,
406
+ summary: server.summary ?? "Standard MCP server awaiting MCP+ profile initialization.",
407
+ mode: "expanded",
408
+ },
409
+ toolIndex: [],
410
+ skillIndex: [],
411
+ },
412
+ };
413
+ }
414
+ export function learnedProfileFromProposal(input) {
415
+ if (Object.hasOwn(input.proposal, "modeHint")) {
416
+ return {
417
+ ok: false,
418
+ error: {
419
+ code: "MCP_PLUS_PROFILE_MODE_HINT_UNSUPPORTED",
420
+ message: "MCP+ profile proposals do not accept modeHint in v1; runtime overlays own exposure mode.",
421
+ publicSafe: true,
422
+ },
423
+ };
424
+ }
425
+ const nativeToolNames = new Set(input.nativeTools.map((tool) => tool.name));
426
+ const referenced = [
427
+ ...uniqueStrings(input.proposal.pinnedTools),
428
+ ...uniqueStrings(input.proposal.warmTools),
429
+ ...uniqueStrings(input.proposal.indexedTools),
430
+ ...uniqueStrings(input.proposal.alwaysIndexTools),
431
+ ...Object.keys(input.proposal.toolCards ?? {}),
432
+ ];
433
+ const unknown = referenced.filter((toolName) => !nativeToolNames.has(toolName));
434
+ if (unknown.length > 0) {
435
+ return {
436
+ ok: false,
437
+ error: {
438
+ code: "MCP_PLUS_PROFILE_UNKNOWN_TOOL",
439
+ message: `MCP+ profile proposal references unknown tool(s): ${unknown.join(", ")}`,
440
+ publicSafe: true,
441
+ },
442
+ };
443
+ }
444
+ const alwaysIndex = new Set(uniqueStrings(input.proposal.alwaysIndexTools));
445
+ const pinnedAlwaysIndex = uniqueStrings(input.proposal.pinnedTools).filter((toolName) => alwaysIndex.has(toolName));
446
+ if (pinnedAlwaysIndex.length > 0) {
447
+ return {
448
+ ok: false,
449
+ error: {
450
+ code: "MCP_PLUS_PROFILE_ALWAYS_INDEX_PINNED",
451
+ message: `alwaysIndexTools cannot be pinned: ${pinnedAlwaysIndex.join(", ")}`,
452
+ publicSafe: true,
453
+ },
454
+ };
455
+ }
456
+ const profile = {
457
+ schemaVersion: "mcp-plus.profile.v1",
458
+ serverId: input.proposal.serverId,
459
+ projectId: input.projectId,
460
+ exposure: {
461
+ pinnedTools: uniqueStrings(input.proposal.pinnedTools),
462
+ warmTools: uniqueStrings(input.proposal.warmTools),
463
+ indexedTools: uniqueStrings(input.proposal.indexedTools),
464
+ alwaysIndexTools: uniqueStrings(input.proposal.alwaysIndexTools),
465
+ toolCards: input.proposal.toolCards === undefined ? undefined : Object.fromEntries(Object.entries(input.proposal.toolCards).map(([toolName, card]) => [toolName, {
466
+ title: card.title,
467
+ summary: card.summary,
468
+ keywords: card.keywords === undefined ? undefined : [...card.keywords],
469
+ }])),
470
+ },
471
+ skills: input.proposal.skillChapters === undefined ? input.existing?.skills : {
472
+ ...(input.existing?.skills ?? {}),
473
+ chapters: input.proposal.skillChapters.map((chapter) => ({ ...chapter })),
474
+ },
475
+ rationale: input.proposal.rationale,
476
+ createdAt: input.existing?.createdAt ?? input.now,
477
+ updatedAt: input.now,
478
+ metadata: input.proposal.metadata,
479
+ };
480
+ return { ok: true, profile };
481
+ }
482
+ export function planMcpHarnessExposure(manifest, nativeToolInventoryByServerId, stateByServerId = {}, profileByServerId = {}) {
128
483
  const module = mcpHarnessModuleFrom(manifest.harness);
129
484
  const servers = (module?.servers ?? []).map((server) => {
130
485
  const nativeTools = [...(nativeToolInventoryByServerId[server.serverId] ?? [])];
131
- if (server.mode !== "mcp-plus" || server.manifest === undefined) {
486
+ if (server.mode !== "mcp-plus") {
132
487
  const surface = {
133
488
  tools: nativeTools,
134
489
  sidecar: {
@@ -149,18 +504,45 @@ export function planMcpHarnessExposure(manifest, nativeToolInventoryByServerId,
149
504
  dynamicToolSpecs: nativeTools.map((tool) => dynamicToolSpecForNativeTool(server.serverId, tool)),
150
505
  };
151
506
  }
152
- const graph = compileMcpPlusManifest(server.manifest, nativeTools);
507
+ const learnedProfile = profileByServerId[server.serverId];
508
+ if (server.manifest === undefined && learnedProfile === undefined) {
509
+ const surface = bootstrapSurface(server, nativeTools);
510
+ return {
511
+ serverId: server.serverId,
512
+ mode: "mcp-plus",
513
+ surface,
514
+ dynamicToolSpecs: dynamicToolSpecsForSurface(server.serverId, surface),
515
+ };
516
+ }
517
+ const effectiveManifest = server.manifest ?? (learnedProfile === undefined
518
+ ? fallbackMcpPlusManifest(server, nativeTools)
519
+ : learnedManifestFromProfile(learnedProfile, server));
520
+ const graph = compileMcpPlusManifest(effectiveManifest, nativeTools);
153
521
  const state = {
154
522
  serverId: server.serverId,
155
523
  mode: stateByServerId[server.serverId]?.mode ?? "expanded",
156
524
  activeTools: stateByServerId[server.serverId]?.activeTools ?? [],
157
525
  };
158
- const surface = lowerExposurePlanToMcpSurface(planExposure(graph, state));
526
+ const plan = planExposure(graph, state);
527
+ const surface = lowerExposurePlanToMcpSurface(plan);
528
+ const withNativeControls = {
529
+ tools: [
530
+ ...surface.tools.filter((tool) => tool.name !== "mcp_plus.expand"),
531
+ createExpandToolDeclaration(),
532
+ ...(stateByServerId[server.serverId]?.mode === "frozen" ? [] : [
533
+ createMcpPlusSkillReadToolDeclaration(),
534
+ createMcpPlusSkillWriteToolDeclaration(),
535
+ createMcpPlusFinishToolDeclaration(),
536
+ ]),
537
+ ...(stateByServerId[server.serverId]?.activeTools?.includes("mcp_plus.reprofile") ? [createMcpPlusReprofileToolDeclaration()] : []),
538
+ ],
539
+ sidecar: surface.sidecar,
540
+ };
159
541
  return {
160
542
  serverId: server.serverId,
161
543
  mode: "mcp-plus",
162
- surface,
163
- dynamicToolSpecs: dynamicToolSpecsForSurface(server.serverId, surface),
544
+ surface: withNativeControls,
545
+ dynamicToolSpecs: dynamicToolSpecsForSurface(server.serverId, withNativeControls),
164
546
  };
165
547
  });
166
548
  return { servers };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@praxis-ai/praxis",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Praxis agentCore architecture scaffold, tests, and engineering contracts.",
@@ -154,7 +154,7 @@
154
154
  "@lancedb/lancedb": "^0.27.2",
155
155
  "@modelcontextprotocol/sdk": "^1.29.0",
156
156
  "@openai/agents": "^0.7.2",
157
- "@praxis-ai/mcp-plus": "^1.0.0",
157
+ "@praxis-ai/mcp-plus": "1.0.1",
158
158
  "@praxis-ai/raxcell": "^0.1.5",
159
159
  "@types/react": "^18.3.28",
160
160
  "apache-arrow": "^18.1.0",
@@ -55,7 +55,7 @@
55
55
  "@lancedb/lancedb": "^0.27.2",
56
56
  "@modelcontextprotocol/sdk": "^1.29.0",
57
57
  "@openai/agents": "^0.11.4",
58
- "@praxis-ai/praxis": "0.1.2",
58
+ "@praxis-ai/praxis": "0.1.4",
59
59
  "@praxis-ai/raxcell": "^0.1.5",
60
60
  "apache-arrow": "^18.1.0",
61
61
  "class-variance-authority": "^0.7.1",