@jr2/orchestrator 0.6.0 → 0.7.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/console/components/app.ts +55 -15
- package/console/components/bottom-drawer.ts +259 -0
- package/console/components/drawer.ts +14 -6
- package/console/components/fleet.ts +36 -13
- package/console/components/machine-pane.ts +61 -5
- package/console/components/nav.ts +54 -8
- package/console/conversation.ts +161 -0
- package/console/login.ts +123 -0
- package/console/main.ts +600 -60
- package/console/store.ts +611 -64
- package/console/style.css +231 -0
- package/docs/agents-block.md +2 -2
- package/docs/concepts/agents-and-turns.md +86 -9
- package/docs/concepts/architecture.md +41 -28
- package/docs/concepts/callers-and-hooks.md +205 -0
- package/docs/concepts/charts.md +7 -5
- package/docs/concepts/composition.md +10 -1
- package/docs/concepts/converge.md +4 -2
- package/docs/concepts/durable-runs.md +43 -16
- package/docs/concepts/effects.md +50 -22
- package/docs/concepts/gates.md +95 -25
- package/docs/concepts/machines-and-workflows.md +5 -4
- package/docs/concepts/pools-and-sources.md +187 -11
- package/docs/concepts/secrets-and-trust.md +73 -33
- package/docs/concepts/the-menu.md +11 -0
- package/docs/concepts/workspaces.md +6 -5
- package/docs/get-started/a-model-decides.md +14 -13
- package/docs/get-started/an-agent-in-a-sandbox.md +43 -34
- package/docs/get-started/connect-a-model.md +1 -1
- package/docs/get-started/create-an-instance.md +17 -14
- package/docs/guides/add-tools-to-the-sandbox.md +55 -11
- package/docs/guides/answer-a-gate-from-a-forge.md +304 -0
- package/docs/guides/answer-a-gate.md +74 -37
- package/docs/guides/bind-open-parts.md +24 -8
- package/docs/guides/call-an-mcp-tool-from-a-machine.md +572 -0
- package/docs/guides/cancel-a-run.md +26 -16
- package/docs/guides/fix-a-failed-converge.md +18 -6
- package/docs/guides/give-a-caller-a-scope.md +203 -0
- package/docs/guides/give-an-agent-mcp-tools.md +422 -0
- package/docs/guides/iterate-on-a-machine-on-kind.md +10 -9
- package/docs/guides/let-an-agent-write-a-chart.md +9 -6
- package/docs/guides/look-inside-a-sandbox.md +101 -31
- package/docs/guides/open-the-console.md +63 -14
- package/docs/guides/read-a-conversation.md +225 -0
- package/docs/guides/run-a-chart-by-name.md +17 -16
- package/docs/guides/run-one-worker-per-item.md +15 -9
- package/docs/guides/set-an-agents-access-and-dials.md +2 -0
- package/docs/guides/start-a-run-for-each-alert.md +290 -0
- package/docs/guides/start-a-run-from-a-run.md +18 -16
- package/docs/guides/start-a-run.md +24 -18
- package/docs/guides/steer-an-agent.md +332 -0
- package/docs/guides/use-a-hosted-provider.md +1 -1
- package/docs/guides/watch-a-run.md +127 -121
- package/docs/guides/write-a-hook-kind.md +238 -0
- package/docs/guides/write-a-tool-as-an-mcp-server.md +351 -0
- package/docs/guides/write-a-workflow.md +17 -7
- package/docs/llms.txt +81 -15
- package/docs/reference/authoring-surface/agent.md +1 -1
- package/docs/reference/authoring-surface/answer.md +1 -1
- package/docs/reference/authoring-surface/arg.md +1 -1
- package/docs/reference/authoring-surface/basic.md +1 -1
- package/docs/reference/authoring-surface/bearer.md +1 -1
- package/docs/reference/authoring-surface/branchname.md +1 -1
- package/docs/reference/authoring-surface/carry.md +23 -0
- package/docs/reference/authoring-surface/chartschema.md +1 -1
- package/docs/reference/authoring-surface/chartschemafor.md +1 -1
- package/docs/reference/authoring-surface/command.md +1 -1
- package/docs/reference/authoring-surface/customize.md +4 -1
- package/docs/reference/authoring-surface/defineconfig.md +1 -1
- package/docs/reference/authoring-surface/defineevent.md +1 -1
- package/docs/reference/authoring-surface/dispatch.md +5 -4
- package/docs/reference/authoring-surface/fail.md +1 -1
- package/docs/reference/authoring-surface/file.md +1 -1
- package/docs/reference/authoring-surface/frompick.md +1 -1
- package/docs/reference/authoring-surface/gate.md +118 -0
- package/docs/reference/authoring-surface/group-agents.md +15 -0
- package/docs/reference/authoring-surface/group-charts.md +16 -0
- package/docs/reference/authoring-surface/group-composition.md +25 -0
- package/docs/reference/authoring-surface/group-config.md +17 -0
- package/docs/reference/authoring-surface/group-effects.md +48 -0
- package/docs/reference/authoring-surface/group-failure.md +13 -0
- package/docs/reference/authoring-surface/group-gates.md +16 -0
- package/docs/reference/authoring-surface/group-machine-and-events.md +15 -0
- package/docs/reference/authoring-surface/group-steering.md +12 -0
- package/docs/reference/authoring-surface/group-types-these-signatures-name.md +137 -0
- package/docs/reference/authoring-surface/group-workspaces-and-repos.md +15 -0
- package/docs/reference/authoring-surface/http.md +1 -1
- package/docs/reference/authoring-surface/isfailure.md +1 -1
- package/docs/reference/authoring-surface/isready.md +1 -1
- package/docs/reference/authoring-surface/jr2setup.md +13 -3
- package/docs/reference/authoring-surface/mcp.md +107 -0
- package/docs/reference/authoring-surface/open.md +5 -3
- package/docs/reference/authoring-surface/openbody.md +1 -1
- package/docs/reference/authoring-surface/paramsof.md +1 -1
- package/docs/reference/authoring-surface/pool.md +77 -9
- package/docs/reference/authoring-surface/push.md +1 -1
- package/docs/reference/authoring-surface/readyof.md +1 -1
- package/docs/reference/authoring-surface/reread.md +19 -0
- package/docs/reference/authoring-surface/secret.md +1 -1
- package/docs/reference/authoring-surface/sentby.md +1 -1
- package/docs/reference/authoring-surface/skip.md +22 -0
- package/docs/reference/authoring-surface/source.md +56 -1
- package/docs/reference/authoring-surface/steer.md +93 -0
- package/docs/reference/authoring-surface/text.md +1 -1
- package/docs/reference/authoring-surface/type-agentdefinition.md +14 -1
- package/docs/reference/authoring-surface/type-agentmcpserver.md +54 -0
- package/docs/reference/authoring-surface/type-anyframepath.md +1 -1
- package/docs/reference/authoring-surface/type-anyworktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-binding.md +1 -1
- package/docs/reference/authoring-surface/type-caller.md +40 -0
- package/docs/reference/authoring-surface/type-callerrecord.md +32 -0
- package/docs/reference/authoring-surface/type-carry.md +19 -0
- package/docs/reference/authoring-surface/type-chainlink.md +3 -3
- package/docs/reference/authoring-surface/type-commandoutput.md +1 -1
- package/docs/reference/authoring-surface/type-customize.md +16 -1
- package/docs/reference/authoring-surface/type-delivered.md +17 -0
- package/docs/reference/authoring-surface/type-deliveredevent.md +18 -0
- package/docs/reference/authoring-surface/type-dispatchdefinition.md +4 -3
- package/docs/reference/authoring-surface/type-dispatchoutput.md +1 -1
- package/docs/reference/authoring-surface/type-effectbinding.md +12 -2
- package/docs/reference/authoring-surface/type-framepath.md +1 -1
- package/docs/reference/authoring-surface/type-gatebinding.md +15 -0
- package/docs/reference/authoring-surface/type-gatehooks.md +12 -0
- package/docs/reference/authoring-surface/type-gatelook.md +37 -0
- package/docs/reference/authoring-surface/type-gateoutcome.md +32 -0
- package/docs/reference/authoring-surface/type-gatereply.md +19 -0
- package/docs/reference/authoring-surface/type-gateslot.md +21 -0
- package/docs/reference/authoring-surface/type-gateslotinput.md +13 -0
- package/docs/reference/authoring-surface/type-gateslotparts.md +18 -0
- package/docs/reference/authoring-surface/type-gateslots.md +12 -0
- package/docs/reference/authoring-surface/type-gatetypesat.md +13 -0
- package/docs/reference/authoring-surface/type-hostinjectedinput.md +1 -1
- package/docs/reference/authoring-surface/type-httpoutput.md +1 -1
- package/docs/reference/authoring-surface/type-jr2config.md +37 -2
- package/docs/reference/authoring-surface/type-lateroutcome.md +1 -1
- package/docs/reference/authoring-surface/type-lends.md +5 -1
- package/docs/reference/authoring-surface/type-lookoutput.md +14 -0
- package/docs/reference/authoring-surface/type-mcpcalloutput.md +78 -0
- package/docs/reference/authoring-surface/type-mcpnotreturned.md +42 -0
- package/docs/reference/authoring-surface/type-mcpoptions.md +47 -0
- package/docs/reference/authoring-surface/type-mcpoutput.md +50 -0
- package/docs/reference/authoring-surface/type-mcpreadoutput.md +53 -0
- package/docs/reference/authoring-surface/type-mcpremoteserver.md +50 -0
- package/docs/reference/authoring-surface/type-mcpserver.md +41 -0
- package/docs/reference/authoring-surface/type-mcpstdioserver.md +42 -0
- package/docs/reference/authoring-surface/type-namedsecret.md +4 -4
- package/docs/reference/authoring-surface/type-openbodydeclaration.md +1 -1
- package/docs/reference/authoring-surface/type-openbodyoutput.md +1 -1
- package/docs/reference/authoring-surface/type-poolnextinput.md +1 -1
- package/docs/reference/authoring-surface/type-poolspec.md +11 -6
- package/docs/reference/authoring-surface/type-pushoutput.md +1 -1
- package/docs/reference/authoring-surface/type-pushsourcespec.md +38 -0
- package/docs/reference/authoring-surface/type-readsourcespec.md +47 -0
- package/docs/reference/authoring-surface/type-reposlot.md +1 -1
- package/docs/reference/authoring-surface/type-reread.md +18 -0
- package/docs/reference/authoring-surface/type-runsconfig.md +41 -0
- package/docs/reference/authoring-surface/type-sender.md +26 -1
- package/docs/reference/authoring-surface/type-sendercallerfield.md +14 -0
- package/docs/reference/authoring-surface/type-size.md +1 -1
- package/docs/reference/authoring-surface/type-skip.md +19 -0
- package/docs/reference/authoring-surface/type-sourcehooks.md +23 -0
- package/docs/reference/authoring-surface/type-sourcein.md +25 -0
- package/docs/reference/authoring-surface/type-sourcereply.md +18 -0
- package/docs/reference/authoring-surface/type-sourcespec.md +16 -7
- package/docs/reference/authoring-surface/type-sourceverdict.md +49 -0
- package/docs/reference/authoring-surface/type-standingpoolmachine.md +12 -0
- package/docs/reference/authoring-surface/type-standingpoolspec.md +51 -0
- package/docs/reference/authoring-surface/type-steerargs.md +20 -0
- package/docs/reference/authoring-surface/type-thinkinglevel.md +1 -1
- package/docs/reference/authoring-surface/type-usercontainer.md +1 -1
- package/docs/reference/authoring-surface/type-workspaced.md +1 -1
- package/docs/reference/authoring-surface/type-worktreefound.md +3 -3
- package/docs/reference/authoring-surface/type-worktreeinput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoptions.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeoutput.md +1 -1
- package/docs/reference/authoring-surface/type-worktreepath.md +1 -1
- package/docs/reference/authoring-surface/type-worktreeready.md +1 -1
- package/docs/reference/authoring-surface/type-worktreerefused.md +4 -4
- package/docs/reference/authoring-surface/workspace.md +1 -1
- package/docs/reference/authoring-surface/worktree.md +1 -1
- package/docs/reference/authoring-surface.md +88 -167
- package/docs/reference/cli.md +413 -74
- package/docs/reference/config.md +235 -2
- package/docs/reference/decisions/0002-duplex-streaming-actor.md +71 -11
- package/docs/reference/decisions/0005-sandbox-pod-composition.md +14 -10
- package/docs/reference/decisions/0006-agent-control-surface.md +4 -4
- package/docs/reference/decisions/0007-durable-machine-state.md +1 -1
- package/docs/reference/decisions/0009-cli-and-instance-interface.md +150 -49
- package/docs/reference/decisions/0010-bdd-acceptance-tests.md +5 -3
- package/docs/reference/decisions/0011-workflow-defined-events.md +256 -12
- package/docs/reference/decisions/0012-workspace-wrapper-machine.md +9 -2
- package/docs/reference/decisions/0013-the-agent-reaches-its-machine-through-a-container-it-cannot-read.md +20 -14
- package/docs/reference/decisions/0014-observation-is-open-run-state-is-not.md +53 -27
- package/docs/reference/decisions/0015-authoring-surface-absorbs-the-mechanism.md +18 -14
- package/docs/reference/decisions/0017-pool-over-a-source-port.md +226 -12
- package/docs/reference/decisions/0018-instance-agents-are-definitions-jr2-assembles-the-harness.md +15 -7
- package/docs/reference/decisions/0019-one-converging-command-against-the-current-context.md +32 -18
- package/docs/reference/decisions/0021-workspace-continuity-is-a-lease-that-answers-back.md +14 -1
- package/docs/reference/decisions/0022-observation-is-a-level-triggered-feed.md +4 -4
- package/docs/reference/decisions/0023-the-harness-prints-the-conversation.md +116 -34
- package/docs/reference/decisions/0024-an-agents-turn-ends-with-the-state-that-asked-for-it.md +38 -37
- package/docs/reference/decisions/0025-cancel-ends-a-run-stop-parks-it.md +10 -5
- package/docs/reference/decisions/0027-the-harness-is-jr2s-own-server-flue-retires-the-wire-stays.md +7 -6
- package/docs/reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md +23 -7
- package/docs/reference/decisions/0029-a-menu-is-fixed-for-a-conversation-and-a-turn-is-told-its-allowed-picks.md +18 -10
- package/docs/reference/decisions/0030-a-snapshot-names-the-machine-it-was-written-under.md +3 -2
- package/docs/reference/decisions/0031-menu-only-agents-run-on-the-instance-harness.md +23 -18
- package/docs/reference/decisions/0032-the-console-unlocks-with-a-callers-credential.md +101 -0
- package/docs/reference/decisions/0033-a-machine-declares-the-input-that-starts-it.md +4 -3
- package/docs/reference/decisions/0034-the-console-renders-with-preact-and-the-server-erases-the-types.md +6 -6
- package/docs/reference/decisions/0037-an-instance-builds-its-sandbox-images-jr2-injects-the-harness.md +5 -4
- package/docs/reference/decisions/0049-a-machine-carries-its-parts-and-composes-by-invoke.md +19 -19
- package/docs/reference/decisions/0050-what-code-names-config-declares-what-the-cli-names-discovery-finds.md +6 -1
- package/docs/reference/decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md +55 -40
- package/docs/reference/decisions/0055-a-release-is-a-pushed-tag-and-the-publish-guard-is-the-credential.md +14 -13
- package/docs/reference/decisions/0057-a-turn-is-its-frame-its-dials-and-whether-it-continues.md +7 -0
- package/docs/reference/decisions/0058-a-harness-answers-the-orchestrator-alone.md +13 -7
- package/docs/reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md +38 -30
- package/docs/reference/decisions/0062-jr2-briefs-the-agent-on-its-seat-never-on-its-task.md +64 -17
- package/docs/reference/decisions/0063-the-orchestrator-watches-the-cluster.md +2 -1
- package/docs/reference/decisions/0064-a-workspace-waits-for-capacity.md +2 -2
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-kept-for-a-set-time.md +109 -0
- package/docs/reference/decisions/0066-a-model-tier-runs-a-real-model-and-a-change-to-what-it-reads-runs-the-tier.md +51 -23
- package/docs/reference/decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md +94 -54
- package/docs/reference/decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md +101 -47
- package/docs/reference/decisions/0069-the-orchestrator-is-a-kit-image-and-an-instance-publishes-its-assembly.md +32 -9
- package/docs/reference/decisions/0070-a-machine-lends-its-parts-to-a-chart.md +17 -13
- package/docs/reference/decisions/0071-a-run-starts-a-run-through-a-dispatch-effect.md +56 -30
- package/docs/reference/decisions/0072-the-docs-ship-inside-the-kit-and-an-instances-agents-md-indexes-them.md +35 -25
- package/docs/reference/decisions/0075-a-machine-cuts-a-worktree-and-the-workspace-only-clones.md +4 -3
- package/docs/reference/decisions/0076-an-agents-control-credential-lives-only-where-no-workspace-code-runs.md +37 -10
- package/docs/reference/decisions/0078-a-caller-comes-from-one-authenticator-seam-and-acts-within-its-scopes.md +285 -0
- package/docs/reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md +198 -0
- package/docs/reference/decisions.md +6 -4
- package/docs/reference/glossary.md +96 -42
- package/docs/reference/hooks.md +1077 -0
- package/docs/reference/http-api.md +976 -149
- package/docs/why.md +2 -1
- package/package.json +10 -3
- package/src/actor.ts +36 -10
- package/src/agent.ts +50 -0
- package/src/assembly-checks.ts +530 -4
- package/src/callers.ts +245 -0
- package/src/chart.ts +52 -7
- package/src/config.ts +550 -1
- package/src/conversations.ts +453 -0
- package/src/custodian.ts +8 -2
- package/src/customize.ts +143 -4
- package/src/dispatch-effect.ts +142 -26
- package/src/durability.ts +2 -0
- package/src/duration.ts +20 -0
- package/src/effect-checks.ts +2 -1
- package/src/effect.ts +60 -7
- package/src/effector-wire.ts +272 -27
- package/src/gate-brand.ts +16 -0
- package/src/gate.ts +634 -48
- package/src/harness-client.ts +10 -4
- package/src/held-secrets.ts +72 -23
- package/src/holds.ts +2 -2
- package/src/hook-kinds/discord.ts +92 -0
- package/src/hook-kinds/door.ts +107 -0
- package/src/hook-kinds/github.ts +89 -0
- package/src/hook-kinds/gitlab.ts +72 -0
- package/src/hook-kinds/hook.ts +7 -0
- package/src/hook-kinds/index.ts +465 -0
- package/src/hook-kinds/slack.ts +151 -0
- package/src/hook-kinds/webhook.ts +113 -0
- package/src/hooks.ts +506 -0
- package/src/http-effect.ts +76 -38
- package/src/http.ts +889 -94
- package/src/index.ts +16 -5
- package/src/instance.ts +196 -26
- package/src/internal.ts +22 -3
- package/src/machine-doc.ts +26 -1
- package/src/mcp-effect.ts +424 -0
- package/src/mount-digest.ts +41 -7
- package/src/names.ts +64 -2
- package/src/notices.ts +138 -10
- package/src/open-body.ts +25 -11
- package/src/open-effect.ts +17 -4
- package/src/open.ts +4 -2
- package/src/parts.ts +87 -9
- package/src/party.ts +2 -1
- package/src/pick.ts +2 -2
- package/src/pool.ts +1142 -163
- package/src/registration.ts +203 -13
- package/src/run-host.ts +1509 -171
- package/src/run-list.ts +191 -0
- package/src/sandbox-kube.ts +53 -5
- package/src/secret-placement.ts +239 -33
- package/src/server.ts +53 -4
- package/src/setup.ts +72 -32
- package/src/snapshot-store.ts +565 -48
- package/src/steer.ts +111 -0
- package/src/stub-harness.ts +6 -2
- package/src/tokens.ts +263 -22
- package/src/wire.ts +17 -0
- package/src/workspace.ts +50 -6
- package/src/worktree.ts +3 -3
- package/docs/reference/decisions/0032-the-console-unlocks-with-the-instance-token.md +0 -61
- package/docs/reference/decisions/0065-a-persist-survives-a-crash-and-history-is-a-week.md +0 -65
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Give an Agent MCP tools
|
|
3
|
+
description: Declare MCP servers in jr2.config.ts, and give an Agent the tools that it can call on each server.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Give an Agent MCP tools
|
|
7
|
+
|
|
8
|
+
Declare the MCP servers of your [Instance](../reference/glossary.md#instance) in `jr2.config.ts`, and give an
|
|
9
|
+
[Agent](../reference/glossary.md#agent) the tools that it can call on each server.
|
|
10
|
+
|
|
11
|
+
An MCP server gives tools for a service, such as a chat, a ticket tracker or a forge. The tools that an Agent gets from
|
|
12
|
+
the servers that its definition names are its [MCP tools](../reference/glossary.md#mcp-tools). The model calls them in
|
|
13
|
+
its [Turn](../reference/glossary.md#turn), as it calls its [Working tools](../reference/glossary.md#working-tools). A
|
|
14
|
+
call of an MCP tool is not a pick: the Turn goes on after it. When a call needs the approval of a person, the
|
|
15
|
+
[Machine](../reference/glossary.md#machine) makes it.
|
|
16
|
+
[Call an MCP tool from a Machine](call-an-mcp-tool-from-a-machine.md) tells how.
|
|
17
|
+
|
|
18
|
+
In this [Workflow](../reference/glossary.md#workflow), a Menu-only Agent reads the stand-up channel of two chat
|
|
19
|
+
workspaces, Acme and a partner. Then it posts one summary to the Acme workspace, and picks `posted`. Each workspace has
|
|
20
|
+
its own server entry and its own token. Save the file as `workflows/standup.ts` in your Instance:
|
|
21
|
+
|
|
22
|
+
**Anthropic**
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { assign } from "xstate";
|
|
26
|
+
import { z } from "zod";
|
|
27
|
+
import { agent, defineEvent, fail, jr2Setup } from "@jr2/orchestrator";
|
|
28
|
+
|
|
29
|
+
// The Agent's one pick. Each MCP tool call before it is not a pick, and the Turn goes on.
|
|
30
|
+
const posted = defineEvent({
|
|
31
|
+
name: "posted",
|
|
32
|
+
description: "Call this after you posted the summary. Give the link to your message.",
|
|
33
|
+
input: z.object({ link: z.string().describe("The link to the message that you posted.") }),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const door = z.object({ day: z.string().describe("The day of the stand-up, such as 2026-11-04.") });
|
|
37
|
+
|
|
38
|
+
type Input = z.infer<typeof door>;
|
|
39
|
+
type Ctx = Input & { link?: string; fault?: string };
|
|
40
|
+
|
|
41
|
+
export const machine = jr2Setup({
|
|
42
|
+
types: {} as { context: Ctx; input: Input; output: { link: string } },
|
|
43
|
+
events: [posted],
|
|
44
|
+
actors: {
|
|
45
|
+
reporter: agent({
|
|
46
|
+
model: "anthropic/claude-haiku-5-5",
|
|
47
|
+
instructions: "You read what two teams said at their stand-up, and you post one short summary for both teams.",
|
|
48
|
+
// No Sandbox and no Working tools. Its Turns run on the Instance Harness.
|
|
49
|
+
workspace: "none",
|
|
50
|
+
// The servers of the mcp block of jr2.config.ts, by name, each with the tools that the Agent can call.
|
|
51
|
+
mcp: [
|
|
52
|
+
{ server: "slack-acme", tools: ["read_channel", "post_message"] },
|
|
53
|
+
// Only read: the Agent cannot post to the partner's workspace.
|
|
54
|
+
{ server: "slack-partner", tools: ["read_channel"] },
|
|
55
|
+
],
|
|
56
|
+
}),
|
|
57
|
+
fail: fail(),
|
|
58
|
+
},
|
|
59
|
+
}).createMachine({
|
|
60
|
+
id: "standup",
|
|
61
|
+
input: door,
|
|
62
|
+
context: ({ input }) => ({ day: input.day }),
|
|
63
|
+
initial: "summarizing",
|
|
64
|
+
states: {
|
|
65
|
+
summarizing: {
|
|
66
|
+
invoke: {
|
|
67
|
+
src: "reporter",
|
|
68
|
+
input: ({ context }) => ({
|
|
69
|
+
prompt: `Read #standup in the Acme and the partner workspaces for ${context.day}. Post one summary to #standup of Acme.`,
|
|
70
|
+
}),
|
|
71
|
+
},
|
|
72
|
+
on: {
|
|
73
|
+
posted: { target: "done", actions: assign({ link: ({ event }) => event.link }) },
|
|
74
|
+
// A server that does not answer at the first Turn fails the Turn. The reason names the server.
|
|
75
|
+
"agent.fault": { target: "failed", actions: assign({ fault: ({ event }) => event.reason }) },
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
done: { type: "final" },
|
|
79
|
+
// Not a final state: the run ends `failed`, with the reason of the Turn.
|
|
80
|
+
failed: { invoke: { src: "fail", input: ({ context }) => ({ reason: context.fault ?? "the Turn failed" }) } },
|
|
81
|
+
},
|
|
82
|
+
output: ({ context }) => ({ link: context.link ?? "" }),
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Local**
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { assign } from "xstate";
|
|
90
|
+
import { z } from "zod";
|
|
91
|
+
import { agent, defineEvent, fail, jr2Setup } from "@jr2/orchestrator";
|
|
92
|
+
|
|
93
|
+
// The Agent's one pick. Each MCP tool call before it is not a pick, and the Turn goes on.
|
|
94
|
+
const posted = defineEvent({
|
|
95
|
+
name: "posted",
|
|
96
|
+
description: "Call this after you posted the summary. Give the link to your message.",
|
|
97
|
+
input: z.object({ link: z.string().describe("The link to the message that you posted.") }),
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const door = z.object({ day: z.string().describe("The day of the stand-up, such as 2026-11-04.") });
|
|
101
|
+
|
|
102
|
+
type Input = z.infer<typeof door>;
|
|
103
|
+
type Ctx = Input & { link?: string; fault?: string };
|
|
104
|
+
|
|
105
|
+
export const machine = jr2Setup({
|
|
106
|
+
types: {} as { context: Ctx; input: Input; output: { link: string } },
|
|
107
|
+
events: [posted],
|
|
108
|
+
actors: {
|
|
109
|
+
reporter: agent({
|
|
110
|
+
model: "local/qwen3.6-35b-a3b",
|
|
111
|
+
instructions: "You read what two teams said at their stand-up, and you post one short summary for both teams.",
|
|
112
|
+
// No Sandbox and no Working tools. Its Turns run on the Instance Harness.
|
|
113
|
+
workspace: "none",
|
|
114
|
+
// The servers of the mcp block of jr2.config.ts, by name, each with the tools that the Agent can call.
|
|
115
|
+
mcp: [
|
|
116
|
+
{ server: "slack-acme", tools: ["read_channel", "post_message"] },
|
|
117
|
+
// Only read: the Agent cannot post to the partner's workspace.
|
|
118
|
+
{ server: "slack-partner", tools: ["read_channel"] },
|
|
119
|
+
],
|
|
120
|
+
}),
|
|
121
|
+
fail: fail(),
|
|
122
|
+
},
|
|
123
|
+
}).createMachine({
|
|
124
|
+
id: "standup",
|
|
125
|
+
input: door,
|
|
126
|
+
context: ({ input }) => ({ day: input.day }),
|
|
127
|
+
initial: "summarizing",
|
|
128
|
+
states: {
|
|
129
|
+
summarizing: {
|
|
130
|
+
invoke: {
|
|
131
|
+
src: "reporter",
|
|
132
|
+
input: ({ context }) => ({
|
|
133
|
+
prompt: `Read #standup in the Acme and the partner workspaces for ${context.day}. Post one summary to #standup of Acme.`,
|
|
134
|
+
}),
|
|
135
|
+
},
|
|
136
|
+
on: {
|
|
137
|
+
posted: { target: "done", actions: assign({ link: ({ event }) => event.link }) },
|
|
138
|
+
// A server that does not answer at the first Turn fails the Turn. The reason names the server.
|
|
139
|
+
"agent.fault": { target: "failed", actions: assign({ fault: ({ event }) => event.reason }) },
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
done: { type: "final" },
|
|
143
|
+
// Not a final state: the run ends `failed`, with the reason of the Turn.
|
|
144
|
+
failed: { invoke: { src: "fail", input: ({ context }) => ({ reason: context.fault ?? "the Turn failed" }) } },
|
|
145
|
+
},
|
|
146
|
+
output: ({ context }) => ({ link: context.link ?? "" }),
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The Agent uses the model of the tab. Add the two servers and their tokens to `jr2.config.ts`, beside the fields and the
|
|
151
|
+
entries that are there:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { defineConfig } from "@jr2/orchestrator";
|
|
155
|
+
|
|
156
|
+
export default defineConfig({
|
|
157
|
+
// The two tokens. The values come from .env.
|
|
158
|
+
secrets: [
|
|
159
|
+
{ name: "SLACK_ACME_TOKEN", value: process.env.SLACK_ACME_TOKEN },
|
|
160
|
+
{ name: "SLACK_PARTNER_TOKEN", value: process.env.SLACK_PARTNER_TOKEN },
|
|
161
|
+
],
|
|
162
|
+
// Two servers of one service, each with its own token. The names are yours.
|
|
163
|
+
mcp: {
|
|
164
|
+
"slack-acme": { url: "https://mcp.slack.example/mcp", secret: "SLACK_ACME_TOKEN" },
|
|
165
|
+
"slack-partner": { url: "https://mcp.slack.example/mcp", secret: "SLACK_PARTNER_TOKEN", timeout: "90s" },
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`https://mcp.slack.example/mcp` is an example. Write the URL of the MCP endpoint of your service. Then put the two
|
|
171
|
+
tokens in `.env`:
|
|
172
|
+
|
|
173
|
+
```sh
|
|
174
|
+
SLACK_ACME_TOKEN=<acme token>
|
|
175
|
+
SLACK_PARTNER_TOKEN=<partner token>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`<acme token>` and `<partner token>` are the tokens that the service gave you for each workspace. Run
|
|
179
|
+
[`jr2 up`](../reference/cli.md#up). Then start a run:
|
|
180
|
+
|
|
181
|
+
```sh
|
|
182
|
+
jr2 run standup --input '{"day":"2026-11-04"}'
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Declare the servers
|
|
186
|
+
|
|
187
|
+
The [`mcp`](../reference/config.md#mcp) block of `jr2.config.ts` gives each server a name of yours. An Agent and an
|
|
188
|
+
[Effect](../reference/glossary.md#effect) name a server by this name, and never by its URL or its token. A name has
|
|
189
|
+
lowercase letters, digits and `-`, and it starts with a letter. `jr2` is not a server name, because the tools of the
|
|
190
|
+
[Menu](../reference/glossary.md#menu) have the names `mcp__jr2__<event>`.
|
|
191
|
+
|
|
192
|
+
A server is one of two kinds:
|
|
193
|
+
|
|
194
|
+
- **A remote server**, `{ url, secret?, timeout? }`. The Harness speaks to it over Streamable HTTP. `secret` is the name
|
|
195
|
+
of an entry of the [`secrets`](../reference/config.md#secrets) list: the bearer token of the server. With a `secret`,
|
|
196
|
+
the URL is `https://`. With no `secret`, it can be `http://`.
|
|
197
|
+
- **A stdio server**, `{ command, args?, timeout? }`. It is a program of the
|
|
198
|
+
[Sandbox Image](../reference/glossary.md#sandbox-image). See [A stdio server](#a-stdio-server).
|
|
199
|
+
|
|
200
|
+
`timeout` is the longest time of one call, such as `"90s"` or `"5m"`. The default is 60 seconds. After it, jr2 cancels
|
|
201
|
+
the call, and the model reads that the server did not answer. The server can still do the work of the call.
|
|
202
|
+
|
|
203
|
+
The token of a remote server is a [Held secret](../reference/glossary.md#held-secret). The Harness sends
|
|
204
|
+
`Authorization: Bearer <Stand-in>`, and the [Custodian](../reference/glossary.md#custodian) puts the token in place of
|
|
205
|
+
the [Stand-in](../reference/glossary.md#stand-in). The model and the
|
|
206
|
+
[Workspace code](../reference/glossary.md#workspace-code) never read the token. With no `hosts`, `headers` or `paths` on
|
|
207
|
+
the entry of the `secrets` list, jr2 binds the token to the host of the server's URL, in the header `authorization`.
|
|
208
|
+
When you set them, they must let the Stand-in through to the URL.
|
|
209
|
+
|
|
210
|
+
## Name the servers in the Agent
|
|
211
|
+
|
|
212
|
+
The `mcp` field of the definition is a list. Each entry names one server, and `tools` is the allowlist for a remote
|
|
213
|
+
server:
|
|
214
|
+
|
|
215
|
+
- `tools: ["read_channel", "post_message"]`: the Agent gets these tools of the server, and no other tool.
|
|
216
|
+
- `tools: "*"`: the Agent gets each tool of the server.
|
|
217
|
+
|
|
218
|
+
A stdio server takes no `tools`. The Agent gets each tool of the server.
|
|
219
|
+
|
|
220
|
+
The model reads each tool as `mcp__<server>__<tool>`, such as `mcp__slack-acme__post_message`. That name has only
|
|
221
|
+
letters, digits, `_` and `-`, and at most 128 characters. When `"*"` gives a tool whose name breaks this rule, the
|
|
222
|
+
Harness does not give the tool, and it writes one line in its log that names it.
|
|
223
|
+
|
|
224
|
+
An Agent can name a server only one time. A Menu-only Agent can name remote servers only. Its Turns run on the
|
|
225
|
+
[Instance Harness](../reference/glossary.md#instance-harness), where no Workspace code runs.
|
|
226
|
+
|
|
227
|
+
## When the allowlist is a guarantee
|
|
228
|
+
|
|
229
|
+
The allowlist guards the token of the server. For a remote server, it is a guarantee: the Harness gives the model only
|
|
230
|
+
those tools, and it runs only those tools. The Harness holds the Stand-in of the token in a file that no Workspace code
|
|
231
|
+
can read. Thus only a call of the Harness gets the token from the Custodian.
|
|
232
|
+
|
|
233
|
+
Workspace code in the Agent's [Sandbox](../reference/glossary.md#sandbox) can spend the token in three cases. In each
|
|
234
|
+
case, jr2 takes only `tools: "*"`:
|
|
235
|
+
|
|
236
|
+
- The [`workspace()`](../reference/authoring-surface/workspace.md) of the Sandbox lists the secret under `secrets`.
|
|
237
|
+
- A `command` Effect of that Workspace names the secret in its `env`.
|
|
238
|
+
- An `http` Effect of that Workspace sends a file of a Worktree with the secret.
|
|
239
|
+
|
|
240
|
+
In these cases, a file that the Agent wrote can call each tool of the server with the token. Thus a list would not be a
|
|
241
|
+
limit, and `jr2 up` refuses it. A Menu-only Agent keeps its list, because its Turns run on the Instance Harness.
|
|
242
|
+
|
|
243
|
+
The scopes of the token are the outer limit. The allowlist makes what the Agent can do with a token smaller, and it
|
|
244
|
+
never makes it larger. Give the server a token with the smallest scopes that the Agent needs. jr2 cannot see one token
|
|
245
|
+
under two secret names. When you do that, a list on one name does not limit what the other name can do.
|
|
246
|
+
|
|
247
|
+
## Two servers of one service
|
|
248
|
+
|
|
249
|
+
In the example, `slack-acme` and `slack-partner` have the same URL, and each has its own token. Each Agent that names
|
|
250
|
+
one of them gets its own tools: `mcp__slack-acme__read_channel` and `mcp__slack-partner__read_channel` are two tools.
|
|
251
|
+
|
|
252
|
+
The two tokens go to one host in one header. The Custodian chooses the token by the Stand-in that the request carries.
|
|
253
|
+
Thus each call goes out with the token of its own server.
|
|
254
|
+
|
|
255
|
+
## A stdio server
|
|
256
|
+
|
|
257
|
+
A stdio server is a program in the Sandbox Image. Name its `command` and its `args` in the `mcp` block. The Harness
|
|
258
|
+
starts the program for each conversation of an Agent that names it, at the first Turn. The program runs until the
|
|
259
|
+
conversation ends, or until the Harness stops. Two Agents never share one process. When the process stops, its call
|
|
260
|
+
fails, and the next call starts the program again.
|
|
261
|
+
|
|
262
|
+
The program is Workspace code. The Harness starts it through the spawner, inside the Landlock confinement of the
|
|
263
|
+
Sandbox, in the directory of the Turn. Its env has only the Stand-ins that the `workspace()` grants, so a stdio server
|
|
264
|
+
has no `secret` of its own. [Write a tool as an MCP server](write-a-tool-as-an-mcp-server.md) adds a stdio server to the
|
|
265
|
+
Sandbox Image.
|
|
266
|
+
|
|
267
|
+
## The tools of a conversation
|
|
268
|
+
|
|
269
|
+
`jr2 up` asks each remote server for its tools before it converges, from the check pod and through its Custodian. Each
|
|
270
|
+
name in an allowlist must be a tool of the server:
|
|
271
|
+
|
|
272
|
+
```text
|
|
273
|
+
…
|
|
274
|
+
mcp: listing the tools of "slack-acme" at <your MCP URL> on the Instance Harness, through a Custodian that holds SLACK_ACME_TOKEN, with its Stand-in
|
|
275
|
+
mcp: listing the tools of "slack-partner" at <your MCP URL> on the Instance Harness, through a Custodian that holds SLACK_PARTNER_TOKEN, with its Stand-in
|
|
276
|
+
provider: the check pod has up to 5 min to start, then up to 6 min for its model and 1 min for each of its 2 MCP servers
|
|
277
|
+
…
|
|
278
|
+
mcp: "slack-acme" lists 2 tool(s); every allowlisted name is there
|
|
279
|
+
mcp: "slack-partner" lists 2 tool(s); every allowlisted name is there
|
|
280
|
+
…
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
`<your MCP URL>` is the URL of your server. The check pod has the placement of the pods that run the Agent: here, the
|
|
284
|
+
Instance Harness. The number in `lists 2 tool(s)` is the number of tools of the server, and yours can be different.
|
|
285
|
+
`jr2 up` stops when a name is not there, and when the server refuses the token. It only warns when the server does not
|
|
286
|
+
answer. `--skip-provider-preflight` skips the check of the tools and the check of the models. `jr2 up` does not check a
|
|
287
|
+
stdio server.
|
|
288
|
+
|
|
289
|
+
The Harness reads the tools of each server at the first Turn of a conversation. It keeps the allowed tools, sorted by
|
|
290
|
+
name, and it records them with the conversation. Each later Turn of the conversation gets the same tools, also after a
|
|
291
|
+
park and after a restart of the Harness. A change in the tools block makes the model provider read the full conversation
|
|
292
|
+
again, so the Harness does not read the list again. When the server removes a tool, a call of it fails with the text
|
|
293
|
+
`<server> no longer has the tool <tool>`. A new conversation gets the new list.
|
|
294
|
+
|
|
295
|
+
The tools block has the Working tools first, then the MCP tools, then the Menu. The
|
|
296
|
+
[Briefing](../reference/glossary.md#briefing) of an Agent with MCP tools tells the model that a call of an MCP tool is
|
|
297
|
+
not a pick and does not end its Turn (see [The Briefing](../concepts/agents-and-turns.md#the-briefing)).
|
|
298
|
+
|
|
299
|
+
The printed conversation shows a call of an MCP tool as `[<server>/<tool>] <input>`, such as
|
|
300
|
+
`[slack-acme/post_message] {"channel":"C024","text":"…"}`. A pick prints by the name of its event, so the two never look
|
|
301
|
+
the same. The Harness does not print the result of an MCP tool. [Watch a run](watch-a-run.md) gives each line.
|
|
302
|
+
|
|
303
|
+
## What the model reads
|
|
304
|
+
|
|
305
|
+
- The text of the result. A result with `structuredContent` and no text gives the JSON as text.
|
|
306
|
+
- The images of the result, such as a screenshot. A model that reads no image gets a line of text in place of each
|
|
307
|
+
image. An image larger than about 5 MiB is one line of jr2:
|
|
308
|
+
`[jr2: the result has an image that is too large or not base64, which the model does not read]`.
|
|
309
|
+
- One line of jr2 for each audio or resource in the result:
|
|
310
|
+
`[jr2: the result has <kind>, which the model does not read]`.
|
|
311
|
+
- A result with `isError` as an error.
|
|
312
|
+
- At most 64 KiB of a result. At the cut, the model reads `[jr2 cut this result at 64 KiB]`.
|
|
313
|
+
- When a call passes the `timeout` of its server:
|
|
314
|
+
`<server> did not answer the call of <tool> in <seconds> s, so jr2 cancelled the call. The server can still do the work that the call asked for`
|
|
315
|
+
- When the server asks for input during a call:
|
|
316
|
+
`<server> asked for input during the call of <tool>, and no one can answer it: an Agent's MCP call cannot ask for input. The call failed`
|
|
317
|
+
- When a stdio server stops during a call: `<server> stopped while it ran <tool>. The next call starts it again`
|
|
318
|
+
|
|
319
|
+
When a server does not answer at the first Turn of a conversation, the Turn fails before the model reads anything. The
|
|
320
|
+
Machine gets [`agent.fault`](../reference/mechanism-events.md#agent-fault), and its `reason` is
|
|
321
|
+
`<server> did not answer: <reason>`. In the example, `agent.fault` goes to `failed`, and the run ends `failed` with that
|
|
322
|
+
reason. At a later call, the model reads the same text, and the Turn goes on.
|
|
323
|
+
|
|
324
|
+
A result comes from outside the Instance. It can hold text that tries to give the model orders, as a web page can. The
|
|
325
|
+
allowlist limits what the Agent can do with the token. The Machine limits what the run does.
|
|
326
|
+
|
|
327
|
+
## Bind the servers of a packaged Machine
|
|
328
|
+
|
|
329
|
+
A Machine from a package names the servers that its Agents need. When your `mcp` block has other names, bind the Agent
|
|
330
|
+
to your servers with [`customize()`](../reference/authoring-surface/customize.md), on the line that registers the
|
|
331
|
+
Machine. For example, `customize(standup, { agents: { reporter: { mcp: [{ server: "chat-main", tools: "*" }] } } })`
|
|
332
|
+
gives the `reporter` each tool of your server `chat-main`.
|
|
333
|
+
|
|
334
|
+
The list replaces the whole `mcp` list of the Agent. [Bind a shipped Machine's Open parts](bind-open-parts.md) tells how
|
|
335
|
+
to bind the parts of a Machine that you get from a package.
|
|
336
|
+
|
|
337
|
+
## Rules
|
|
338
|
+
|
|
339
|
+
When the config loads, it refuses:
|
|
340
|
+
|
|
341
|
+
- the name `jr2`:
|
|
342
|
+
`<file> at mcp.jr2: the server name jr2 is reserved — the Menu's tools are named mcp__jr2__<event>; give the server another name (ADR-0079)`
|
|
343
|
+
- a name with other characters:
|
|
344
|
+
`<file> at mcp.<name>: a server name is lowercase letters, digits and -, and starts with a letter ([a-z][a-z0-9-]*) — it is the <server> of the tool names mcp__<server>__<tool> (ADR-0079)`
|
|
345
|
+
- a `secret` that the `secrets` list does not have:
|
|
346
|
+
`<file> at mcp.<name>.secret: "<NAME>" is not an entry of secrets — add it: secrets: [{ name: "<NAME>", value: process.env.<NAME> }] (ADR-0079)`
|
|
347
|
+
- a `timeout` that is not a positive duration:
|
|
348
|
+
`<file> at mcp.<name>.timeout: a timeout is a positive duration, such as "90s", "5m" or "1h" — an entry is <name>: { url, secret?, timeout? } for a remote server, or <name>: { command, args?, timeout? } for a stdio server (ADR-0079)`
|
|
349
|
+
- an entry that is not one of the two kinds, such as one with a `url` and a `command`:
|
|
350
|
+
`<file> at mcp.<name>: Unrecognized key: "<key>" — an entry is <name>: { url, secret?, timeout? } for a remote server, or <name>: { command, args?, timeout? } for a stdio server (ADR-0079)`
|
|
351
|
+
|
|
352
|
+
`jr2 up` refuses a URL:
|
|
353
|
+
|
|
354
|
+
- that is not `http://` or `https://`, such as an empty URL from a variable that `.env` does not set:
|
|
355
|
+
`refusing: mcp.<name>.url "<url>" is not an http:// or https:// URL — an unset .env variable reads as empty; set it, such as url: process.env.<NAME>_URL (ADR-0079)`
|
|
356
|
+
- with a user name or a password:
|
|
357
|
+
`refusing: mcp.<name>.url carries userinfo — put the credential in secret, never in the url (ADR-0079)`
|
|
358
|
+
- with `http://`, for a server with a `secret`:
|
|
359
|
+
`refusing: mcp.<name>.url must be an https:// URL — the server has a secret, <NAME>, and the Custodian carries TLS only (ADR-0059, ADR-0079)`
|
|
360
|
+
|
|
361
|
+
`jr2 up` refuses a binding of a server's secret that does not let its Stand-in through to the URL. `<i>` is the place of
|
|
362
|
+
the entry in the `secrets` list:
|
|
363
|
+
|
|
364
|
+
- `secrets[<i>].headers leaves out "authorization", where the Harness sends the Stand-in of <NAME> to mcp.<name>.url — each call of that server would arrive with no credential. Add it, or drop headers to take that default (ADR-0059, ADR-0079)`
|
|
365
|
+
- `secrets[<i>].hosts leaves out <host>, the host of mcp.<name>.url, whose secret <NAME> is — each call of that server would arrive with the Stand-in and fail with 401. Add it, or drop hosts to take that default (ADR-0059, ADR-0079)`
|
|
366
|
+
- `secrets[<i>].paths does not cover <path>, the path of mcp.<name>.url, whose secret <NAME> is — each call of that server would be refused with 403. Add "<path>", or drop paths (ADR-0059, ADR-0079)`
|
|
367
|
+
|
|
368
|
+
`jr2 up` refuses an Agent's `mcp` list, and prints the line to write. Each refusal starts with
|
|
369
|
+
`refusing: workflow "<workflow>": Agent "<slot>"`, then:
|
|
370
|
+
|
|
371
|
+
- a server that the `mcp` block does not declare:
|
|
372
|
+
`names the MCP server "<server>", which the mcp block declares "<names>" — bind the Agent to a declared server where the Machine is registered: export const machine = customize(<import>, { agents: { <slot>: { mcp: [<entries>] } } }); or declare it in jr2.config.ts: mcp: { "<server>": { url: "https://<host>/mcp", secret: "<SECRET>" } } (ADR-0079)`
|
|
373
|
+
- a remote server with no `tools`:
|
|
374
|
+
`names the remote MCP server "<server>" with no tools — a remote server needs its allowlist: { server: "<server>", tools: ["<tool>"] }, or tools: "*" for every tool (ADR-0079)`
|
|
375
|
+
- an empty list:
|
|
376
|
+
`gives MCP server "<server>" an empty tools list, which offers no tool — name the tools, or drop the entry (ADR-0079)`
|
|
377
|
+
- a list for a stdio server:
|
|
378
|
+
`gives a tools list for the stdio MCP server "<server>" — a stdio server takes none: the Agent gets every tool, because the server runs as Workspace code, and a list would guard nothing. Write { server: "<server>" } (ADR-0079)`
|
|
379
|
+
- a stdio server on a Menu-only Agent:
|
|
380
|
+
`has workspace: "none" and names the stdio MCP server "<server>" — a stdio server runs as Workspace code, inside a Sandbox's confinement, and a "none" Agent's Turns run on the Instance Harness, which has none: name a remote server, or give the Agent a Workspace (ADR-0079, ADR-0031)`
|
|
381
|
+
- a tool whose name breaks the rule:
|
|
382
|
+
`lists the tool "<tool>" of MCP server "<server>": the model would read it as "mcp__<server>__<tool>", and a tool name is [A-Za-z0-9_-], at most 128 characters — a model provider refuses any other (ADR-0079)`
|
|
383
|
+
- a tool that the list names two times:
|
|
384
|
+
`lists the tool "<tool>" of MCP server "<server>" twice — a model provider refuses a tools block that has one name two times: list each tool once (ADR-0079)`
|
|
385
|
+
- a server that the list names two times: `names the MCP server "<server>" twice — one entry for each server (ADR-0079)`
|
|
386
|
+
- a list where Workspace code in the Sandbox can spend the secret. `<how>` is one of
|
|
387
|
+
`its workspace() lists it under secrets`, `the command Effect "<effect>" names it` and
|
|
388
|
+
`the http Effect "<effect>" sends a Worktree file with it`:
|
|
389
|
+
`gives a tools list for MCP server "<server>", and Workspace code in its Sandbox can spend that server's secret, <NAME>: <how>. A list would be a hint, not a limit — write { server: "<server>", tools: "*" }, or keep <NAME> out of that Workspace (ADR-0079, ADR-0028)`
|
|
390
|
+
|
|
391
|
+
For a child Machine, `(on the Machine composed as "<slot>")` comes after the Agent's slot. The publish door of the
|
|
392
|
+
Orchestrator runs the same check of the lists. Thus a publish that did not go through `jr2 up` is refused too, and the
|
|
393
|
+
Orchestrator keeps the Assembly that it has.
|
|
394
|
+
|
|
395
|
+
`jr2 up` checks the tools of each remote server from the check pod. It stops with an error for:
|
|
396
|
+
|
|
397
|
+
- a name in an allowlist that the server does not list:
|
|
398
|
+
`error: mcp: workflow "<workflow>": Agent "<slot>" allowlists the tool "<tool>", which "<server>" does not list — it lists: <tools> (ADR-0079)`
|
|
399
|
+
- a server that refuses the request with 401 or 403:
|
|
400
|
+
`error: mcp: "<server>" at <url> refused the request from a pod on <where> (HTTP <status>) — a wrong or expired value of <NAME>, or a binding of it (hosts, headers, paths) that does not let its Stand-in through to the url; every call of an Agent that names it would fail (ADR-0079)`
|
|
401
|
+
|
|
402
|
+
It warns about a server that does not answer:
|
|
403
|
+
`warning: mcp: "<server>" at <url> did not answer from a pod on <where> (<reason>) — its tool names are not checked; a Turn of an Agent that names it fails if it does not answer then (ADR-0079)`.
|
|
404
|
+
With `--skip-provider-preflight`, it warns:
|
|
405
|
+
`warning: provider: preflight skipped (--skip-provider-preflight) — these models were not checked: <models>; these MCP servers' tool lists were not checked: <servers>`.
|
|
406
|
+
|
|
407
|
+
## Read next
|
|
408
|
+
|
|
409
|
+
- [Agents and Turns](../concepts/agents-and-turns.md#the-mcp-tools): the MCP tools beside the Working tools and the
|
|
410
|
+
Menu.
|
|
411
|
+
- [Secrets and trust](../concepts/secrets-and-trust.md): who holds the token of a server.
|
|
412
|
+
- [Call an MCP tool from a Machine](call-an-mcp-tool-from-a-machine.md): a call that a person approves first.
|
|
413
|
+
- [Write a tool as an MCP server](write-a-tool-as-an-mcp-server.md): your own tool, in the Sandbox Image.
|
|
414
|
+
- Reference: [`mcp`](../reference/config.md#mcp),
|
|
415
|
+
[`AgentMcpServer`](../reference/authoring-surface/type-agentmcpserver.md) and
|
|
416
|
+
[`AgentDefinition`](../reference/authoring-surface/type-agentdefinition.md).
|
|
417
|
+
- [ADR-0079](../reference/decisions/0079-an-agent-holds-mcp-tools-a-machine-calls-them-as-an-effect-and-the-machine-is-the-approval.md):
|
|
418
|
+
an Agent holds MCP tools, a Machine calls them as an Effect, and the Machine is the approval.
|
|
419
|
+
- [ADR-0028](../reference/decisions/0028-what-an-agent-may-do-to-the-workspace-is-part-of-its-definition.md): the
|
|
420
|
+
allowlist is a guarantee about the credential.
|
|
421
|
+
- [ADR-0059](../reference/decisions/0059-a-harness-holds-stand-ins-and-the-custodian-holds-the-keys.md): the Custodian
|
|
422
|
+
holds the keys.
|
|
@@ -49,21 +49,21 @@ jr2 up — instance "my-instance" → context kind-jr2 / namespace my-instance
|
|
|
49
49
|
typecheck: tsc --noEmit (the instance's own compiler)
|
|
50
50
|
agents: (none) (carried by 1 workflow machine(s))
|
|
51
51
|
assembly: staging
|
|
52
|
-
assembly:
|
|
52
|
+
assembly: 0756fbb530cb (kit v<version>; 1 workflow(s): ping)
|
|
53
53
|
…
|
|
54
54
|
sandbox images: skipped (no registered Machine composes a Sandbox)
|
|
55
55
|
…
|
|
56
56
|
orchestrator: applying (image ghcr.io/snapwich/jr2-orchestrator:<version>)
|
|
57
57
|
orchestrator: waiting for rollout
|
|
58
58
|
orchestrator: verified — 1 pod(s) running ghcr.io/snapwich/jr2-orchestrator:<version>
|
|
59
|
-
instance effector: none (no Effect is placed there)
|
|
60
|
-
assembly: publishing
|
|
61
|
-
assembly:
|
|
62
|
-
|
|
59
|
+
instance effector: none (no Effect is placed there, and no Hook needs it)
|
|
60
|
+
assembly: publishing 0756fbb530cb (current is 50e6f81e3bfb)
|
|
61
|
+
assembly: 0756fbb530cb is current — workflows: ping
|
|
62
|
+
…
|
|
63
63
|
converged — `jr2 run <workflow>` when ready
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
`
|
|
66
|
+
`0756fbb530cb` is the hash of the new [Assembly](../reference/glossary.md#assembly), and `50e6f81e3bfb` is the hash of
|
|
67
67
|
the Assembly that was current. Your hashes can be different. `<version>` is the
|
|
68
68
|
[Kit version](../reference/glossary.md#kit-version). The hash changed, because the Workflow changed. Thus `jr2 up`
|
|
69
69
|
published the new Assembly to the [Orchestrator](../reference/glossary.md#orchestrator). The image of the Orchestrator
|
|
@@ -84,9 +84,9 @@ jr2 run ping --input '{"message":"hi"}'
|
|
|
84
84
|
|
|
85
85
|
```text
|
|
86
86
|
→ context kind-jr2 / namespace my-instance
|
|
87
|
-
run
|
|
87
|
+
run 8b6c68a2-2ab6-498b-9cc5-bb722d71140f (ping) — attached
|
|
88
88
|
→ done "done"
|
|
89
|
-
{"runId":"
|
|
89
|
+
{"runId":"8b6c68a2-2ab6-498b-9cc5-bb722d71140f","workflow":"ping","instanceId":"919c7165-d20e-472b-b889-1ba4209a488b","startedAt":"2026-11-06T19:17:06.158Z","status":"done","value":"done","context":{"message":"hi","reply":"pong, again: hi"},"children":[],"waiting":[],"held":[],"assembly":"0756fbb530cb","startedBy":{"kind":"caller","via":"token","name":"instance","onBehalfOf":"kubernetes-admin"}}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
92
|
Your run id and your instance id are different. The `reply` comes from the new Machine, and `assembly` is the hash of
|
|
@@ -112,8 +112,9 @@ A second `jr2 up` with no change builds nothing and publishes nothing:
|
|
|
112
112
|
|
|
113
113
|
```text
|
|
114
114
|
…
|
|
115
|
-
assembly:
|
|
115
|
+
assembly: 0756fbb530cb (current — publish skipped)
|
|
116
116
|
swept nothing — every labeled image is named by a live root
|
|
117
|
+
…
|
|
117
118
|
converged — `jr2 run <workflow>` when ready
|
|
118
119
|
```
|
|
119
120
|
|
|
@@ -158,15 +158,18 @@ jr2 run plan --input '{"task":"Write the release notes for the greet CLI, and as
|
|
|
158
158
|
Open the run in the [Console](../reference/glossary.md#console). After the planner picks `run_chart`, the Console draws
|
|
159
159
|
the Chart inside `running`. The Chart of each run is different, because the planner writes it.
|
|
160
160
|
|
|
161
|
-
When the door refuses a Chart, the answer to the pick holds the report. The
|
|
162
|
-
[
|
|
163
|
-
|
|
164
|
-
Agent, and the first `grep` keeps the lines of your run:
|
|
161
|
+
When the door refuses a Chart, the answer to the pick holds the report. The conversation of the planner shows each
|
|
162
|
+
answer. [`jr2 logs`](../reference/cli.md#logs) prints it. Replace `<run id>` with the run id, or its first 4 characters
|
|
163
|
+
or more:
|
|
165
164
|
|
|
166
165
|
```sh
|
|
167
|
-
|
|
166
|
+
jr2 logs <run id> planner | grep -F '[run_chart answer]'
|
|
168
167
|
```
|
|
169
168
|
|
|
169
|
+
[Read a conversation](read-a-conversation.md) tells more. The log of the Instance Harness has the same lines, for each
|
|
170
|
+
run of a Menu-only Agent:
|
|
171
|
+
`kubectl -n <namespace> logs statefulset/jr2-instance-harness -c harness | grep -F '[<run id>/'`, with the full run id.
|
|
172
|
+
|
|
170
173
|
For example, in one run with the model of each tab, the first Chart of the planner gave a prompt to the Gate. The door
|
|
171
174
|
refused it:
|
|
172
175
|
|
|
@@ -185,7 +188,7 @@ it. Then the Chart ends, and the run ends with the status `done`.
|
|
|
185
188
|
|
|
186
189
|
- [`openBody()`](../reference/authoring-surface/openbody.md) refuses a declaration that is not correct, with one line
|
|
187
190
|
for each problem. The refusal starts with `openBody: `, then each problem, separated by `; `. For example:
|
|
188
|
-
- `lends.<kind>: an Open body lends agents, effects, machines, guards, actions, delays, gate and fail only`
|
|
191
|
+
- `lends.<kind>: an Open body lends agents, effects, machines, gates, guards, actions, delays, gate and fail only`
|
|
189
192
|
- `allows.workspace: "<value>" is not a Workspace access (write, read, none)`
|
|
190
193
|
- `ceilings.<ceiling>: missing; an Open body states every ceiling`
|
|
191
194
|
- `ceilings.<ceiling>: <value> is not a positive integer`
|