import { type EnvironmentId, type OrchestrationV2LinkOrigin, OrchestratorMcpEnvironmentTarget, OrchestratorMcpFailure, type ProviderInteractionMode, type RuntimeMode, type ThreadId, } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; import * as Option from "effect/Option"; import * as Predicate from "effect/Predicate"; import * as Ref from "effect/Ref"; import * as Struct from "effect/Struct"; import type * as Layer from "effect/Layer"; import type * as Scope from "effect/Scope"; import type { Tool, Toolkit } from "effect/ai"; import { DispatchModeLimit, type DispatchModeRefusal, } from "../orchestration-v2/DispatchModeLimit.ts"; import * as PeerForwarding from "../peer/PeerForwarding.ts"; import { assertNotLinked, assertSameLink, callerLinkOrigin } from "./linkOrigin.ts"; import * as McpInvocationContext from "./McpInvocationContext.ts"; import { resolveInteractionMode, resolveRuntimeMode } from "./delegatedTaskModes.ts"; import { assertFullAccess, assertLiveCaller, assertTargetWithinLimits, type Caller, loadCaller, readCaller, unavailable, } from "./threadAccess.ts"; /** Private constructors only enforce types; the token also blocks runtime construction. */ const builtHere: unique symbol = Symbol("t3/mcp/McpToolAccess/builtHere"); const refuseOutsideConstruction = (token: symbol) => { if (token !== builtHere) { throw new TypeError("Only McpToolAccess builds MCP tool declarations and handler layers."); } }; let declare:
(
handle: (params: P) => Effect.Effect,
remote: RemoteCheck | undefined,
) => Declaration<(params: P) => Effect.Effect>;
/**
* What a call bound for a linked environment checks here before it goes: the
* caller, not the thread or project it names, which live there and are
* checked there. `undefined` for tools that act as the calling thread itself.
*/
type RemoteCheck = (
params: P,
) => Effect.Effect (handle: (params: P) => Effect.Effect) =>
declare(
(params: P) => handle(params),
() => Effect.void,
);
export const readsAsCaller = (handle: (params: P) => Effect.Effect) =>
declare((params: P) => requireThreadCaller.pipe(Effect.flatMap(() => handle(params))), undefined);
export const actsAsCaller = (handle: (params: P) => Effect.Effect) =>
declare(
(params: P) =>
requireThreadCaller.pipe(
Effect.andThen(writingCaller),
Effect.flatMap(() => handle(params)),
),
undefined,
);
/**
* Changes something that belongs to no thread, such as a pending upload or a
* scheduled task. Work a linked environment started may not: none of it
* carries the link's origin, so none of it is that link's.
*/
export const writes = (
handle: (params: P) => Effect.Effect,
options?: {
/** Whether a linked caller may make it, as for uploads its own sends need. */
readonly linkedCallers?: "allowed";
},
) =>
declare(
(params: P) =>
orchestratingCaller.pipe(
Effect.tap((caller) =>
options?.linkedCallers === "allowed"
? Effect.void
: assertNotLinked(caller, "change this environment's scheduled tasks"),
),
Effect.flatMap(() => handle(params)),
),
() => orchestratingCaller,
);
/** Requires handler capability checks, not thread control; linked work is refused the same way. */
export const writesWithCapability = (handle: (params: P) => Effect.Effect) =>
declare(
(params: P) =>
writingCaller.pipe(
Effect.tap((caller) => assertNotLinked(caller, "start work here that is not its link's")),
Effect.flatMap(() => handle(params)),
),
() => writingCaller,
);
export const writesThreads = (
threads: (params: P) => ReadonlyArray (
modes: (params: P) => {
readonly runtimeMode?: RuntimeMode | undefined;
readonly interactionMode?: ProviderInteractionMode | undefined;
},
handle: (params: P, modes: StartedModes) => Effect.Effect,
linked: "stamped" | { readonly refused: string },
) => {
const started = Effect.fn("McpToolAccess.started")(function* (caller: Caller, params: P) {
const requested = modes(params);
return {
runtimeMode: yield* resolveRuntimeMode(caller.limits.runtimeMode, requested.runtimeMode),
interactionMode: yield* resolveInteractionMode(
caller.limits.interactionMode,
requested.interactionMode,
),
linkOrigin: callerLinkOrigin(caller),
} satisfies StartedModes;
});
return declare(
(params: P) =>
Effect.gen(function* () {
const caller = yield* writingCaller;
if (linked !== "stamped") yield* assertNotLinked(caller, linked.refused);
return yield* handle(params, yield* started(caller, params));
}),
(params: P) => Effect.flatMap(writingCaller, (caller) => started(caller, params)),
);
};
/** One tool with two access rules, picked per call, such as create versus update. */
export const either = (
useFirst: (params: P) => boolean,
first: Declaration<(params: P) => Effect.Effect>,
second: Declaration<(params: P) => Effect.Effect