@brightbeamai/chap-coordinator-mcp 0.2.13 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -153,8 +153,8 @@ var SCHEMAS = {
153
153
  task_id: TASK_ID,
154
154
  state: {
155
155
  type: "string",
156
- enum: ["in_progress", "review_requested", "declined", "paused", "cancelled", "completed"],
157
- description: "The state to move the task to. Only the transitions in SPECIFICATION.md 8.1 are legal from the task's current state; others are refused with -32602. A task marked review_required cannot be moved to 'completed' here: submit the output with chap.task.complete, which opens the review."
156
+ enum: ["in_progress", "declined", "cancelled", "completed"],
157
+ description: "The state to move the task to. Only the transitions in SPECIFICATION.md 8.1 are legal from the task's current state; others are refused with -32602. A review opens with chap.review.request, or with chap.task.complete on a task that requires one, so 'review_requested' is not a target here. A task marked review_required cannot be moved to 'completed' here: submit the output with chap.task.complete, which opens the review."
158
158
  },
159
159
  progress_note: { type: "string", description: "Short note on what changed, kept in the task's history." }
160
160
  },
@@ -190,7 +190,12 @@ var SCHEMAS = {
190
190
  properties: {
191
191
  method: { type: "string", description: "Return only entries for this CHAP method, e.g. 'decide.override'. Matched in full, without the 'chap.' tool-name prefix." },
192
192
  from: { type: "string", description: "Return only entries whose actor is this participant URI." },
193
- task_id: { type: "string", description: "Return only entries about this task." }
193
+ task_id: { type: "string", description: "Return only entries about this task." },
194
+ outcome: {
195
+ type: "string",
196
+ enum: ["accepted", "refused"],
197
+ description: "Return only accepted calls, or only the refused attempts the coordinator recorded. Omit for both."
198
+ }
194
199
  }
195
200
  }
196
201
  },
@@ -310,8 +315,9 @@ var SCHEMAS = {
310
315
  description: "The successor task to open for the escalation target.",
311
316
  properties: {
312
317
  kind: { type: "string", description: "Task kind for the successor. Defaults to the original's kind." },
313
- assignee: { ...PARTICIPANT_URI, description: "Who the escalation goes to. Must be a workspace member." },
314
- input: { type: "object", additionalProperties: true, description: "Input payload for the successor. It defaults to an empty object rather than the original's input, so anything the new assignee needs has to be restated here." }
318
+ assignee: { ...PARTICIPANT_URI, description: "Who the escalation goes to. Must be a workspace member who is not paused: a paused assignee is refused with -32063." },
319
+ input: { type: "object", additionalProperties: true, description: "Input payload for the successor. It defaults to an empty object rather than the original's input, so anything the new assignee needs has to be restated here." },
320
+ mode: { type: "string", enum: ["shadow", "trial", "production"], description: "Mode for the successor, defaulting to the original's. A mode above the workspace's current ceiling is refused with -32040, so after the ceiling is lowered a task is escalated by naming a mode within it." }
315
321
  },
316
322
  required: ["assignee"]
317
323
  }
@@ -502,7 +508,7 @@ var SCHEMAS = {
502
508
  type: "string",
503
509
  enum: ["task", "participant", "workspace"],
504
510
  default: "task",
505
- description: "What the resume applies to. 'task' returns a paused task to 'in_progress'; a task that is not paused is refused with -32061. 'participant' allows that member to be assigned tasks again. 'workspace' returns the workspace to active."
511
+ description: "What the resume applies to. 'task' returns a paused task to the state it held at the pause; a task that is not paused is refused with -32061. 'participant' allows that member to be assigned tasks again. 'workspace' returns the workspace to active."
506
512
  },
507
513
  task_id: TASK_ID,
508
514
  participant_uri: { ...PARTICIPANT_URI, description: "Whose work to resume, when scope is 'participant'. Must be a workspace member." }
@@ -551,8 +557,10 @@ var SCHEMAS = {
551
557
  description: "The replacement task.",
552
558
  properties: {
553
559
  kind: { type: "string", description: "Task kind for the successor. Required." },
554
- assignee: { ...PARTICIPANT_URI, description: "Who takes the replacement on. Defaults to the superseded task's assignee, and must be a workspace member." },
555
- input: { type: "object", additionalProperties: true, description: "Input payload for the successor. Defaults to an empty object." }
560
+ assignee: { ...PARTICIPANT_URI, description: "Who takes the replacement on. Defaults to the superseded task's assignee, and must be a workspace member who is not paused." },
561
+ input: { type: "object", additionalProperties: true, description: "Input payload for the successor. Defaults to an empty object." },
562
+ mode: { type: "string", enum: ["shadow", "trial", "production"], description: "Mode for the successor, defaulting to the superseded task's. A mode above the workspace's current ceiling is refused with -32040." },
563
+ review_required: { type: "boolean", description: "Whether completing the successor needs a reviewer decision. A successor of a task that requires review requires it whatever is passed here, and under modes/1.0 so does a trial-mode successor." }
556
564
  },
557
565
  required: ["kind"]
558
566
  },
@@ -583,7 +591,7 @@ var SCHEMAS = {
583
591
  workspace: WORKSPACE_ID,
584
592
  from: PARTICIPANT_URI,
585
593
  task_id: TASK_ID,
586
- candidates: { type: "array", items: PARTICIPANT_URI, description: "Candidate assignees. An empty list is refused with -32513. Candidates that are not workspace members are dropped, and if none remain the call is refused with -32510. The default policy selects the first remaining candidate; an operator-supplied routing policy may select on any basis." }
594
+ candidates: { type: "array", items: PARTICIPANT_URI, description: "Candidate assignees. An empty list is refused with -32513. The default policy takes the first candidate that is a workspace member and is not paused, and refuses the call with -32510 when there is none. An operator-supplied routing policy may select on any basis; a choice that is not a member is refused with -32510, and one who is paused with -32063." }
587
595
  },
588
596
  required: ["workspace", "from", "task_id", "candidates"]
589
597
  },
@@ -720,52 +728,147 @@ var TOOL_DESCRIPTIONS = {
720
728
  // Core
721
729
  "chap.workspace.create": "Create a workspace: the container for the participants, tasks and audit log of one piece of collaborative work. Returns the workspace id, which every later call carries.",
722
730
  "chap.workspace.describe": "Report the current state of a workspace: its members, enabled profiles, audit length, and task and override counts.",
723
- "chap.workspace.set_profiles": "Replace the set of profiles enabled on a workspace. Enabling audit-scitt/1.0 on a workspace that already has entries leaves those entries outside the hash chain.",
731
+ "chap.workspace.set_profiles": "Replace the whole set of profiles enabled on a workspace. The list is not merged: any profile absent from it is dropped, so send the complete set you want. Enabling audit-scitt/1.0 on a workspace that already has entries starts the chain from that point and leaves the earlier entries permanently outside it, which chap.audit.verify_chain then reports as not_evaluated. Prefer setting profiles at chap.workspace.create, where no entries exist yet to strand.",
724
732
  "chap.participant.join": "Add a participant to a workspace. The type given, human, agent, service, group or workspace, decides whether they are eligible to review work that requires it.",
725
- "chap.participant.leave": "Remove a participant from a workspace. Entries they have already written stay in the audit log.",
726
- "chap.task.create": "Create a task: a unit of work assigned to one participant. Set review_required to make the task's completion depend on a reviewer decision rather than on the assignee.",
733
+ "chap.participant.leave": "Remove a participant from a workspace. Entries they have already written stay in the audit log and their past signatures still verify, so leaving is not a way to withdraw a decision. They can no longer be assigned tasks or act as a reviewer, and returning means a fresh chap.participant.join.",
734
+ "chap.task.create": "Create a task: a unit of work assigned to one participant. Set review_required to make completion depend on a reviewer decision rather than on the assignee. Returns a task_id that the rest of the lifecycle takes. Supplying idempotency_key is what makes a retry safe: a repeat carrying a key already seen returns the original task, while a repeat without one creates a second task. Assigning to a paused member is refused.",
727
735
  "chap.task.update": "Move a task to a new state. Only the transitions in the specification's lifecycle table are accepted, and a task that requires review cannot be completed here.",
728
736
  "chap.task.complete": "Submit a task's output. A task that requires review does not complete: the output is held as the artefact under review, the task moves to review_requested, and a reviewer decision completes it. Any other task completes immediately.",
729
- "chap.audit.read": "Read entries from a workspace's audit log, optionally within a sequence range and filtered by method, sender or task.",
737
+ "chap.audit.read": "Read entries from a workspace's audit log, optionally within a sequence range and filtered by method, sender, task or outcome. This is the query surface for everything the workspace has recorded, decisions and overrides included. An accepted call is under 'envelope'. A refused attempt the coordinator recorded, such as a decision by someone the review was not addressed to, is under 'request' with an 'outcome' giving the error code, and it did not take effect. Reads only: it records nothing, so it never appears in its own output. Entries come back as recorded, without aggregation, so grouping them, by tag for instance, is the reader's job.",
730
738
  // review/1.0
731
739
  "chap.review.request": "Open a review on a task and address it to one or more reviewers. They then call chap.decide.approve, chap.decide.reject, chap.decide.override or chap.abstain.declare. Repeating the request with the same artefact adds reviewers to the open review.",
732
- "chap.decide.approve": "Approve the artefact under review. The task completes once the review's rule is satisfied.",
733
- "chap.decide.reject": "Reject the artefact under review. The task is declined, or returns to in_progress if request_revision is set.",
740
+ "chap.decide.approve": "Approve the artefact under review as it stands. Only a reviewer the review was addressed to may decide, and anyone else is refused with -32011. The task completes once the review's rule is satisfied, so under all_approve or quorum:N an approval may leave the review open awaiting others. Use chap.decide.override instead to accept a corrected version.",
741
+ "chap.decide.reject": "Reject the artefact under review. The task is declined, or returns to in_progress when request_revision is set, which is the choice between ending the work and sending it back. Only a reviewer the review was addressed to may decide. Use chap.decide.override instead to correct the artefact and accept it rather than refuse it.",
734
742
  "chap.decide.override": "Correct the artefact under review with an RFC 6902 JSON Patch and accept the result. The patch, the rationale and any tags are recorded together, so the audit log holds what was changed and why, rather than only that the work was not accepted as written.",
735
- "chap.abstain.declare": "Stand aside from a review, giving a reason and a category. The task moves to abstained.",
736
- "chap.escalate.raise": "Hand a task upwards. The original moves to escalated and is linked to a new task opened for whoever takes it on. The successor starts with an empty input unless one is supplied.",
743
+ "chap.abstain.declare": "Stand aside from a review this participant cannot decide, for example on a conflict of interest or for want of context. Use it in place of chap.decide.reject, which judges the work; abstaining judges nothing and leaves the artefact intact. It requires an open review addressed to the caller and answers -32010 otherwise. The task moves to abstained, which is not terminal: chap.escalate.raise or a fresh chap.review.request can still carry it forward.",
744
+ "chap.escalate.raise": "Hand a task upwards, where the current holder can act but should not decide alone. The original moves to escalated and is linked to a new task for whoever takes it on. Use chap.handoff.propose to pass work sideways without that link, and chap.control.supersede to replace a task outright. The successor starts with an empty input unless one is supplied, so restate whatever the new assignee needs. A completed, cancelled or superseded task cannot be escalated. The successor takes the mode new_task.mode names, or else the original's, and requires review when the original did, or when it is a trial task under modes/1.0. A paused assignee is refused with -32063, and a mode above the workspace's current ceiling with -32040; after the ceiling is lowered, name a mode within it.",
737
745
  // whisper/1.0
738
746
  "chap.whisper.ask": "Put one question to one or more participants, with a deadline and a default. If the deadline passes unanswered the default applies, so a task is never blocked waiting on a reply.",
739
- "chap.whisper.answer": "Answer an open whisper. Where the question carried options, the answer must name one of them.",
747
+ "chap.whisper.answer": "Answer an open whisper. Where the question carried options, the answer must name one of their ids and any other value is refused with -32022. A whisper already answered is refused with -32020 and one past its deadline with -32021, in which case the default supplied at chap.whisper.ask has already been applied.",
740
748
  // deliberation/1.0
741
- "chap.deliberate.open": "Open a deliberation among several participants under a stated voting rule: any_one_approves, all_approve, quorum:N, weighted_vote:T or weighted_vote_with_veto:T.",
742
- "chap.deliberate.comment": "Record a comment on an open deliberation, so the reasoning is on the audit log alongside the votes.",
743
- "chap.deliberate.vote": "Cast a yea, nay or abstain in an open deliberation. Each participant votes once.",
744
- "chap.deliberate.close": "Close a deliberation and compute its outcome from the votes cast under its rule.",
749
+ "chap.deliberate.open": "Open a group decision among several participants under a stated voting rule: any_one_approves, all_approve, quorum:N, weighted_vote:T or weighted_vote_with_veto:T. Use it where several people must weigh in on one question; chap.review.request is the tool for judging one participant's output. Returns a deliberation_id that the comment, vote and close tools all take. The participant list, rule, weights and veto map are fixed at open, so the terms are settled before anyone votes. No outcome exists until chap.deliberate.close computes one.",
750
+ "chap.deliberate.comment": "Record a comment on an open deliberation, so the reasoning behind a vote survives alongside the tally. Comments carry no weight in the outcome and may be added by any participant at any point before chap.deliberate.close.",
751
+ "chap.deliberate.vote": "Cast a yea, nay or abstain in an open deliberation. Each participant votes once and a second attempt answers -32031, so a vote cannot be revised. An abstention is recorded but counts as neither side, which under all_approve or quorum:N withholds the approval those rules need. The tally is computed later, by chap.deliberate.close.",
752
+ "chap.deliberate.close": "Close a deliberation and compute its outcome from the votes cast under its rule. Closing is final: further comments and votes are refused with -32032. Calling it again on a closed deliberation returns the same outcome and changes nothing, so a retry is safe. Votes never cast are simply absent from the tally rather than counted as abstentions.",
745
753
  // handoff/1.0
746
754
  "chap.handoff.propose": "Propose handing one or more tasks to another participant or a group, with the context needed to pick them up. Every task must currently be assigned to the proposer.",
747
- "chap.handoff.accept": "Accept a proposed handoff. The accepted tasks are reassigned to the accepting participant.",
748
- "chap.handoff.decline": "Decline a proposed handoff, with a reason and optionally a suggestion of who should take it instead.",
755
+ "chap.handoff.accept": "Accept a proposed handoff. The accepted tasks are reassigned to the accepting participant, which is the point at which responsibility actually moves. Omitting accepted_task_ids accepts all of them. Accepting resolves the proposal for good: a later accept or decline on the same handoff answers -32051. A paused participant cannot accept and is refused with -32063.",
756
+ "chap.handoff.decline": "Decline a proposed handoff. The tasks stay with the proposer and nothing is reassigned, which is the difference from chap.handoff.accept. Declining resolves the proposal for good: a later accept or decline on the same handoff answers -32051, and trying again means a fresh proposal. The reason is recorded so the proposer can route the work elsewhere.",
749
757
  // control/1.0
750
758
  "chap.control.pause": "Pause work. Scoped to a task it moves that task to paused; to a participant it stops new tasks being assigned to them; to the workspace it refuses every method except describing, reading the audit log, joining, leaving and resuming.",
751
- "chap.control.resume": "Resume work paused at the same scope: a task returns to in_progress, a participant can be assigned tasks again, a workspace returns to active.",
759
+ "chap.control.resume": "Resume work paused at the same scope: a task returns to the state it held at the pause, a participant can be assigned tasks again, a workspace returns to active.",
752
760
  "chap.control.cancel": "Cancel a task. Cancelled is terminal, and a task that has already settled cannot be cancelled.",
753
- "chap.control.snapshot": "Capture the workspace state as an artefact and return its id, which chap.control.rollback takes as its target.",
761
+ "chap.control.snapshot": "Capture the workspace state as an artefact before a change you may want to undo. Returns a snapshot artefact id, which is the only thing chap.control.rollback accepts as a target; the label is for a human reading the log and cannot be used to select it. Taking a snapshot changes nothing. Note that a rollback restores only the mode ceiling and member roles, so a snapshot is a narrow safety net rather than a general undo.",
754
762
  "chap.control.rollback": "Restore workspace state from a snapshot. The mode ceiling and member roles are restored; the rollback is appended to the audit log rather than rewriting it.",
755
- "chap.control.supersede": "Replace a task with a successor in one call. The original moves to superseded and stays linked to its replacement.",
763
+ "chap.control.supersede": "Replace a task with a successor in one call. The original moves to superseded and stays linked to its replacement. The successor requires review when the original did, whatever its own review_required says, and a paused assignee is refused with -32063.",
756
764
  "chap.control.set_mode_ceiling": "Set the highest operating mode tasks in this workspace may request. A task above the ceiling is refused.",
757
765
  // routing/1.0
758
- "chap.task.route": "Choose an assignee for a task from a list of candidates and record a route_decision artefact naming the policy, the candidate chosen, and the alternatives it passed over.",
766
+ "chap.task.route": "Choose an assignee for a task from a list of candidates and reassign the task to the one selected. Use it where the choice itself belongs on the record: it writes a route_decision artefact naming the policy, the candidate chosen and the alternatives passed over, none of which is captured by setting assignee directly on chap.task.create. The default policy takes the first candidate that is a workspace member and is not paused. An operator-supplied policy may choose on any basis, and the call is refused with -32510 when its choice is not a member, or with -32063 when its choice is paused.",
759
767
  "chap.review.depth": "Decide how much review a task warrants, skip, spot_check or full, from its routing hints. Records a route_decision artefact giving the rule that produced the answer.",
760
- "chap.escalate.auto": "Evaluate a task's routing hints against the escalation policy and report whether it should be escalated, and to whom. Records a route_decision artefact.",
768
+ "chap.escalate.auto": "Evaluate a task's routing hints against the escalation policy and report whether it should be escalated, and to whom. It decides only: a route_decision artefact is recorded but the task does not move, so act on the answer with chap.escalate.raise. The default policy escalates on criticality critical, or high with confidence below 0.6.",
761
769
  // security-signed/1.0
762
770
  "chap.participant.rotate_key": "Retire a participant's signing key and register its replacement. The old key stays in the key history with a valid_until timestamp, so envelopes it signed still verify.",
763
771
  "chap.participant.revoke_key": "Revoke a signing key, for example after a device is lost. Signatures presented with it are refused from then on. Revoking another participant's key requires the admin role.",
764
772
  // audit-scitt/1.0
765
773
  "chap.audit.submit_to_scitt": "Build COSE_Sign1-shaped statements for a range of audit entries and submit them to the configured SCITT transparency service. Where none is configured the statements are returned for submission out of band.",
766
- "chap.audit.verify_receipt": "Verify a SCITT receipt through the configured verifier. Verification fails closed where no verifier is configured.",
774
+ "chap.audit.verify_receipt": "Check a receipt returned by an external SCITT transparency service, confirming that service logged the statement. This is the external half of verification; chap.audit.verify_chain checks the local prev-hash chain instead, and the two answer different questions. Verification is delegated to a hook the deployment supplies and fails closed with -32082 when none is configured, so a missing verifier can never read as a pass. Reads only: nothing is recorded.",
767
775
  "chap.audit.verify_chain": "Replay a workspace's prev-hash chain. Only status verified with ok true means the log was checked and is intact. Status not_evaluated with ok false means part of the log was never checked, so its integrity is unknown and must not be reported as verified; entries_unchecked says how much. An error means the chain is broken or absent."
768
776
  };
777
+ var READ_ONLY = /* @__PURE__ */ new Set([
778
+ "chap.workspace.describe",
779
+ "chap.audit.read",
780
+ "chap.audit.verify_chain",
781
+ "chap.audit.verify_receipt",
782
+ // Submitting reads the chain and sends it onward. It changes nothing here,
783
+ // which is what readOnlyHint claims; openWorldHint carries the fact that it
784
+ // reaches a service outside this coordinator.
785
+ "chap.audit.submit_to_scitt"
786
+ ]);
787
+ var DESTRUCTIVE = /* @__PURE__ */ new Set([
788
+ "chap.workspace.set_profiles",
789
+ // replaces the profile set; can strand entries outside the chain
790
+ "chap.participant.leave",
791
+ // removes a member
792
+ "chap.participant.revoke_key",
793
+ // invalidates a key from then on
794
+ "chap.control.cancel",
795
+ // cancelled is terminal
796
+ "chap.control.supersede",
797
+ // superseded is terminal
798
+ "chap.control.rollback"
799
+ // overwrites the mode ceiling and member roles
800
+ ]);
801
+ var IDEMPOTENT = /* @__PURE__ */ new Set([
802
+ "chap.workspace.describe",
803
+ "chap.audit.read",
804
+ "chap.audit.verify_chain",
805
+ "chap.audit.verify_receipt",
806
+ "chap.participant.join",
807
+ // a re-join is additive and adds no duplicate member
808
+ "chap.participant.leave",
809
+ // leaving twice leaves the same absence
810
+ "chap.control.pause",
811
+ // pausing an already paused target changes nothing
812
+ "chap.control.set_mode_ceiling",
813
+ // setting the ceiling it already has changes nothing
814
+ "chap.deliberate.close"
815
+ // returns the same outcome, recomputing nothing
816
+ ]);
817
+ var OPEN_WORLD = /* @__PURE__ */ new Set([
818
+ "chap.audit.submit_to_scitt",
819
+ "chap.audit.verify_receipt"
820
+ ]);
821
+ var TITLES = {
822
+ "chap.workspace.create": "Create workspace",
823
+ "chap.workspace.describe": "Describe workspace",
824
+ "chap.workspace.set_profiles": "Replace workspace profiles",
825
+ "chap.participant.join": "Add participant",
826
+ "chap.participant.leave": "Remove participant",
827
+ "chap.task.create": "Create task",
828
+ "chap.task.update": "Change task state",
829
+ "chap.task.complete": "Submit task output",
830
+ "chap.audit.read": "Read audit log",
831
+ "chap.review.request": "Request review",
832
+ "chap.decide.approve": "Approve",
833
+ "chap.decide.reject": "Reject",
834
+ "chap.decide.override": "Override with a correction",
835
+ "chap.abstain.declare": "Abstain from review",
836
+ "chap.escalate.raise": "Escalate task",
837
+ "chap.whisper.ask": "Ask a whisper",
838
+ "chap.whisper.answer": "Answer a whisper",
839
+ "chap.deliberate.open": "Open deliberation",
840
+ "chap.deliberate.comment": "Comment on deliberation",
841
+ "chap.deliberate.vote": "Vote in deliberation",
842
+ "chap.deliberate.close": "Close deliberation",
843
+ "chap.handoff.propose": "Propose handoff",
844
+ "chap.handoff.accept": "Accept handoff",
845
+ "chap.handoff.decline": "Decline handoff",
846
+ "chap.control.pause": "Pause work",
847
+ "chap.control.resume": "Resume work",
848
+ "chap.control.cancel": "Cancel task",
849
+ "chap.control.snapshot": "Snapshot workspace",
850
+ "chap.control.rollback": "Roll back to snapshot",
851
+ "chap.control.supersede": "Supersede task",
852
+ "chap.control.set_mode_ceiling": "Set mode ceiling",
853
+ "chap.task.route": "Route task to an assignee",
854
+ "chap.review.depth": "Decide review depth",
855
+ "chap.escalate.auto": "Evaluate auto-escalation",
856
+ "chap.participant.rotate_key": "Rotate signing key",
857
+ "chap.participant.revoke_key": "Revoke signing key",
858
+ "chap.audit.submit_to_scitt": "Anchor audit log in SCITT",
859
+ "chap.audit.verify_receipt": "Verify SCITT receipt",
860
+ "chap.audit.verify_chain": "Verify audit chain"
861
+ };
862
+ var TOOL_ANNOTATIONS = Object.fromEntries(
863
+ Object.keys(TOOL_DESCRIPTIONS).map((name) => [name, {
864
+ title: TITLES[name] ?? name,
865
+ readOnlyHint: READ_ONLY.has(name),
866
+ // Meaningful only when readOnlyHint is false, per the specification.
867
+ destructiveHint: DESTRUCTIVE.has(name),
868
+ idempotentHint: IDEMPOTENT.has(name),
869
+ openWorldHint: OPEN_WORLD.has(name)
870
+ }])
871
+ );
769
872
 
770
873
  // src/envelope.ts
771
874
  import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
@@ -827,7 +930,7 @@ var ServerDiscoverRequestSchema = z.object({
827
930
  function makeChapMcpServer(coord, options = {}) {
828
931
  const serverInfo = {
829
932
  name: options.name ?? "chap",
830
- version: options.version ?? "0.2.13"
933
+ version: options.version ?? "0.3.1"
831
934
  };
832
935
  const server = new Server(
833
936
  serverInfo,
@@ -840,12 +943,20 @@ function makeChapMcpServer(coord, options = {}) {
840
943
  let counter = 0;
841
944
  const nextId = options.envelopeIdFactory ?? (() => `mcp-${++counter}`);
842
945
  const filter = options.toolFilter ?? (() => true);
843
- const enabledTools = TOOL_NAMES.filter((name) => filter(name) && methodForTool(name) !== null).map((name) => ({
844
- name,
845
- title: name,
846
- description: TOOL_DESCRIPTIONS[name] ?? `CHAP method ${methodForTool(name)}.`,
847
- inputSchema: SCHEMAS[name]
848
- }));
946
+ const enabledTools = TOOL_NAMES.filter((name) => filter(name) && methodForTool(name) !== null).map((name) => {
947
+ const ann = TOOL_ANNOTATIONS[name];
948
+ return {
949
+ name,
950
+ // The title was the tool name, which told a reader nothing they could
951
+ // not already see. The annotation carries a readable one.
952
+ title: ann?.title ?? name,
953
+ description: TOOL_DESCRIPTIONS[name] ?? `CHAP method ${methodForTool(name)}.`,
954
+ inputSchema: SCHEMAS[name],
955
+ // Behavioural hints. Without these a client has only the prose to go
956
+ // on when deciding whether a call is safe to make or to retry.
957
+ ...ann ? { annotations: ann } : {}
958
+ };
959
+ });
849
960
  server.setRequestHandler(ListToolsRequestSchema, async (request) => {
850
961
  const era = classifyEnvelope(request.params);
851
962
  if (era === "legacy") return { tools: enabledTools };
@@ -865,7 +976,7 @@ function makeChapMcpServer(coord, options = {}) {
865
976
  resultType: "complete",
866
977
  supportedVersions: SUPPORTED_PROTOCOL_VERSIONS,
867
978
  capabilities: { tools: {} },
868
- instructions: "CHAP Coordinator exposed as MCP tools. Every tool call is recorded on the CHAP audit log; governed methods enforce membership and review rules server-side.",
979
+ instructions: "CHAP Coordinator exposed as MCP tools. Every accepted call that changes state is recorded on the CHAP audit log, as is a member's refused attempt at a governed action. Governed methods enforce membership and review rules server-side.",
869
980
  ttlMs: LIST_TTL_MS,
870
981
  cacheScope: "public",
871
982
  _meta: { [META_SERVER_INFO]: serverInfo }
@@ -924,7 +1035,7 @@ function makeChapMcpServer(coord, options = {}) {
924
1035
  }
925
1036
 
926
1037
  // src/cli.ts
927
- var VERSION = "0.2.13";
1038
+ var VERSION = "0.3.1";
928
1039
  var DEFAULT_PROFILES = [
929
1040
  "core/1.0",
930
1041
  "review/1.0",