@jam-mcp/server 1.5.0 → 1.6.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/README.md CHANGED
@@ -74,10 +74,10 @@ auth login Store Jira credentials in this user's OS secret store
74
74
  runtime Show or change which JAM build this machine runs
75
75
  ```
76
76
 
77
- Written out, that is `npx --yes @jam-mcp/launcher@1.5.0 doctor`, or just `jam
77
+ Written out, that is `npx --yes @jam-mcp/launcher@1.6.0 doctor`, or just `jam
78
78
  doctor` if you took the launcher's optional global install. Starting from
79
79
  nothing — no install, no runtime chosen yet — use
80
- `npx --yes @jam-mcp/bootstrap@1.5.0 init` instead.
80
+ `npx --yes @jam-mcp/bootstrap@1.6.0 init` instead.
81
81
 
82
82
  Credentials come from the process environment or this user's OS secret store —
83
83
  never from a repository file — and never appear in logs, telemetry, or tool
@@ -13,7 +13,7 @@ import { LAUNCHER_PACKAGE_SPEC } from "@jam-mcp/launcher";
13
13
  export { LAUNCHER_PACKAGE_SPEC };
14
14
  export declare const JAM_MCP_ENTRY: {
15
15
  readonly command: "npx";
16
- readonly args: readonly ["--yes", "@jam-mcp/launcher@1.5.0", "serve"];
16
+ readonly args: readonly ["--yes", "@jam-mcp/launcher@1.6.0", "serve"];
17
17
  };
18
18
  /**
19
19
  * Recognise wiring from before the launcher existed: a hard-coded `node` path
@@ -0,0 +1,66 @@
1
+ import { type HostId, type HostRunner, type HostState } from "../bootstrap/host-mcp.js";
2
+ export type LifecycleOptions = {
3
+ json?: boolean;
4
+ /** Injected by tests. Nothing here may reach a real host CLI or npm unasked. */
5
+ run?: HostRunner;
6
+ hosts?: () => HostState[];
7
+ };
8
+ export type RefreshHostPlan = {
9
+ id: HostId;
10
+ from?: string;
11
+ bare: boolean;
12
+ action: "repin" | "none";
13
+ };
14
+ export type RefreshPlan = {
15
+ /** The version this build is. `refresh` never moves off it - that is `update`. */
16
+ version: string;
17
+ hosts: RefreshHostPlan[];
18
+ steps: readonly string[];
19
+ };
20
+ /**
21
+ * What `refresh` would converge. Pure: it runs nothing and writes nothing.
22
+ *
23
+ * The scope is deliberately small. A registration that points at a different
24
+ * launcher than this build is what goes stale here. The project binding, the
25
+ * credentials, `~/.jam/config.yaml` and every byte of Jira data are outside
26
+ * it - re-deciding those is `setup`, and calling it "refresh" is how a person
27
+ * loses a binding they never asked to change.
28
+ */
29
+ export declare function planRefresh(hosts: readonly HostState[], version?: string): RefreshPlan;
30
+ export declare function refreshLine(plan: RefreshPlan): string;
31
+ /**
32
+ * `jam refresh` - keep the version, make the registration match it again.
33
+ *
34
+ * Nothing is removed before the replacement is known to work, which is the
35
+ * same order `update` uses and for the same reason: a failed refresh leaves
36
+ * the machine running what it was running.
37
+ */
38
+ export declare function jamRefreshCommand(command: string | undefined, options?: LifecycleOptions): Promise<number>;
39
+ export type UninstallPlan = {
40
+ /** Registrations that would be removed. */
41
+ hosts: HostId[];
42
+ /** Whether a global launcher install would be removed with it. */
43
+ runtime: string | null;
44
+ preserve: string[];
45
+ };
46
+ /**
47
+ * `jam uninstall` - remove what JAM installed, keep what is the person's.
48
+ *
49
+ * Removed: the MCP registrations JAM wrote, and the global launcher when one
50
+ * is installed. Kept: `~/.jam` in full - the project bindings, the runtime
51
+ * choice and the credentials in the OS secret store. There is no purge here
52
+ * on purpose: nothing yet needs a command that destroys those, and an
53
+ * irreversible one that nobody asked for is worse than a missing one.
54
+ */
55
+ export declare function planUninstall(hosts: readonly HostState[], installed: string | null): UninstallPlan;
56
+ export declare function jamUninstallCommand(command: string | undefined, options?: LifecycleOptions): Promise<number>;
57
+ /**
58
+ * `jam status` - the first place a person asks what is going on.
59
+ *
60
+ * This is `doctor`'s user-facing role under the name both products use. The
61
+ * judgement is not reimplemented: the same health gate and the same per-axis
62
+ * verdicts answer here, so the two commands can never disagree.
63
+ */
64
+ export declare function jamStatusCommand(options?: {
65
+ json?: boolean;
66
+ }): Promise<number>;
@@ -0,0 +1,233 @@
1
+ import { LAUNCHER_PACKAGE, SERVER_VERSION } from "@jam-mcp/launcher";
2
+ import { spawnSync } from "node:child_process";
3
+ import { doctorJsonCommand } from "./agent-api.js";
4
+ import { doctor } from "./doctor.js";
5
+ import { detectHosts, hostRegistration, hostUnregistration, } from "../bootstrap/host-mcp.js";
6
+ /**
7
+ * The lifecycle words JAM shares with ASC: `refresh` and `uninstall`.
8
+ *
9
+ * The two products answer to the same vocabulary because a person should not
10
+ * have to remember which one uses which verb:
11
+ *
12
+ * setup make it usable for the first time
13
+ * status what is configured, what works, what is blocked
14
+ * update move to a newer published release
15
+ * refresh keep the version, re-converge what this build registered
16
+ * uninstall remove the product; the person's state stays
17
+ * runtime which build this machine actually runs
18
+ *
19
+ * What is different is ownership, and that stays different. JAM is the Jira
20
+ * access layer: it has no execution mode, no approval path and no session
21
+ * model. A Jira write still travels ASC's decision path and lands through
22
+ * JAM's own write plan/apply - this file does not change that.
23
+ */
24
+ /** A runner with room for npm. The host runner's 20s is not enough for an install. */
25
+ const defaultRunner = ({ command, args }) => {
26
+ const result = spawnSync(command, args, {
27
+ encoding: "utf8",
28
+ timeout: 180_000,
29
+ shell: process.platform === "win32",
30
+ });
31
+ if (result.error)
32
+ return { status: null, failed: true, stdout: "" };
33
+ return { status: result.status, failed: false, stdout: result.stdout ?? "" };
34
+ };
35
+ /**
36
+ * What `refresh` would converge. Pure: it runs nothing and writes nothing.
37
+ *
38
+ * The scope is deliberately small. A registration that points at a different
39
+ * launcher than this build is what goes stale here. The project binding, the
40
+ * credentials, `~/.jam/config.yaml` and every byte of Jira data are outside
41
+ * it - re-deciding those is `setup`, and calling it "refresh" is how a person
42
+ * loses a binding they never asked to change.
43
+ */
44
+ export function planRefresh(hosts, version = SERVER_VERSION) {
45
+ const registered = hosts.filter((host) => host.cliAvailable && host.hasJamEntry);
46
+ const plans = registered.map((host) => ({
47
+ id: host.id,
48
+ ...(host.entryVersion ? { from: host.entryVersion } : {}),
49
+ bare: host.entryBare === true,
50
+ // A bare entry runs the global executable, so its line is already whatever
51
+ // that executable is. Only a pinned line can point somewhere else.
52
+ action: host.entryBare === true || host.entryVersion === version ? "none" : "repin",
53
+ }));
54
+ const moving = plans.filter((host) => host.action === "repin");
55
+ return {
56
+ version,
57
+ hosts: plans,
58
+ steps: moving.length === 0 ? [] : ["switch-registration", "verify"],
59
+ };
60
+ }
61
+ export function refreshLine(plan) {
62
+ const moving = plan.hosts.filter((host) => host.action === "repin");
63
+ if (plan.hosts.length === 0)
64
+ return "No host has a JAM registration - `jam setup` is what adds one.";
65
+ return moving.length === 0
66
+ ? `Registration is current - JAM ${plan.version}. Version unchanged.`
67
+ : `Would re-register: ${moving.map((host) => `${host.id} runs ${host.from ?? "?"}`).join(", ")}`;
68
+ }
69
+ /**
70
+ * `jam refresh` - keep the version, make the registration match it again.
71
+ *
72
+ * Nothing is removed before the replacement is known to work, which is the
73
+ * same order `update` uses and for the same reason: a failed refresh leaves
74
+ * the machine running what it was running.
75
+ */
76
+ export async function jamRefreshCommand(command, options = {}) {
77
+ if (command !== undefined && command !== "check" && command !== "plan") {
78
+ process.stderr.write(`Unknown refresh command: ${command}\nUsage: jam refresh [check|plan] [--json]\n`);
79
+ return 1;
80
+ }
81
+ const run = options.run ?? defaultRunner;
82
+ const hosts = (options.hosts ?? (() => detectHosts(run)))();
83
+ const plan = planRefresh(hosts);
84
+ if (command === "check" || command === "plan") {
85
+ if (options.json)
86
+ process.stdout.write(`${JSON.stringify({ package: LAUNCHER_PACKAGE, ...plan }, null, 2)}\n`);
87
+ else
88
+ process.stdout.write(`${refreshLine(plan)}\n`);
89
+ return 0;
90
+ }
91
+ if (plan.steps.length === 0) {
92
+ if (options.json)
93
+ process.stdout.write(`${JSON.stringify({ package: LAUNCHER_PACKAGE, ...plan, changed: [] }, null, 2)}\n`);
94
+ else
95
+ process.stdout.write(`${refreshLine(plan)}\n`);
96
+ return 0;
97
+ }
98
+ const changed = [];
99
+ for (const host of plan.hosts.filter((h) => h.action === "repin")) {
100
+ // `mcp add` over an existing entry changes nothing on Claude Code - it
101
+ // answers "already exists". The removal is what makes the re-pin land.
102
+ const remove = hostUnregistration(host.id);
103
+ if (remove)
104
+ run(remove);
105
+ const register = hostRegistration(host.id, { version: plan.version });
106
+ if (!register)
107
+ continue;
108
+ const result = run(register);
109
+ if (result.failed || result.status !== 0) {
110
+ process.stderr.write(`refresh failed on ${host.id} - re-register with \`jam setup --agent\`.\n`);
111
+ return 1;
112
+ }
113
+ changed.push(host.id);
114
+ }
115
+ // Read it back. A registration JAM could not verify is never reported as done.
116
+ const after = (options.hosts ?? (() => detectHosts(run)))();
117
+ const stale = after.filter((host) => changed.includes(host.id) && host.entryBare !== true && host.entryVersion !== plan.version);
118
+ if (stale.length > 0) {
119
+ process.stderr.write(`health: ${stale.map((host) => `${host.id} still runs ${host.entryVersion ?? "?"}`).join(", ")}\n`);
120
+ return 1;
121
+ }
122
+ if (options.json) {
123
+ process.stdout.write(`${JSON.stringify({ package: LAUNCHER_PACKAGE, ...plan, changed }, null, 2)}\n`);
124
+ }
125
+ else {
126
+ for (const id of changed)
127
+ process.stdout.write(`registered: ${id} -> ${plan.version}\n`);
128
+ process.stdout.write(`JAM ${plan.version} is registered. Version unchanged.\n`);
129
+ }
130
+ return 0;
131
+ }
132
+ /**
133
+ * `jam uninstall` - remove what JAM installed, keep what is the person's.
134
+ *
135
+ * Removed: the MCP registrations JAM wrote, and the global launcher when one
136
+ * is installed. Kept: `~/.jam` in full - the project bindings, the runtime
137
+ * choice and the credentials in the OS secret store. There is no purge here
138
+ * on purpose: nothing yet needs a command that destroys those, and an
139
+ * irreversible one that nobody asked for is worse than a missing one.
140
+ */
141
+ export function planUninstall(hosts, installed) {
142
+ return {
143
+ hosts: hosts.filter((host) => host.cliAvailable && host.hasJamEntry).map((host) => host.id),
144
+ runtime: installed,
145
+ preserve: [
146
+ "~/.jam/projects.yaml - which project each checkout is bound to",
147
+ "~/.jam/config.yaml - the runtime this machine chose",
148
+ "Jira credentials in the OS secret store",
149
+ "every .jira-agent/project.yaml a repository carries",
150
+ ],
151
+ };
152
+ }
153
+ export async function jamUninstallCommand(command, options = {}) {
154
+ if (command !== undefined && command !== "plan") {
155
+ process.stderr.write(`Unknown uninstall command: ${command}\nUsage: jam uninstall [plan] [--json]\n`);
156
+ return 1;
157
+ }
158
+ const run = options.run ?? defaultRunner;
159
+ const hosts = (options.hosts ?? (() => detectHosts(run)))();
160
+ const installed = globalLauncher(run);
161
+ const plan = planUninstall(hosts, installed);
162
+ if (command === "plan") {
163
+ if (options.json)
164
+ process.stdout.write(`${JSON.stringify(plan, null, 2)}\n`);
165
+ else {
166
+ process.stdout.write(plan.hosts.length === 0
167
+ ? "No host registration to remove.\n"
168
+ : `Would remove the JAM registration from: ${plan.hosts.join(", ")}\n`);
169
+ if (plan.runtime)
170
+ process.stdout.write(`Would remove ${LAUNCHER_PACKAGE}@${plan.runtime}\n`);
171
+ for (const kept of plan.preserve)
172
+ process.stdout.write(`Would keep: ${kept}\n`);
173
+ }
174
+ return 0;
175
+ }
176
+ let worst = 0;
177
+ for (const id of plan.hosts) {
178
+ const remove = hostUnregistration(id);
179
+ if (!remove)
180
+ continue;
181
+ const result = run(remove);
182
+ if (result.failed || result.status !== 0) {
183
+ process.stderr.write(`could not remove the registration from ${id}\n`);
184
+ worst = 1;
185
+ continue;
186
+ }
187
+ process.stdout.write(`removed: ${id} registration\n`);
188
+ }
189
+ if (plan.runtime) {
190
+ const removal = run({ command: "npm", args: ["uninstall", "-g", LAUNCHER_PACKAGE] });
191
+ if (removal.failed || removal.status !== 0) {
192
+ process.stderr.write(`could not remove ${LAUNCHER_PACKAGE} - run: npm uninstall -g ${LAUNCHER_PACKAGE}\n`);
193
+ worst = 1;
194
+ }
195
+ else {
196
+ process.stdout.write(`removed: ${LAUNCHER_PACKAGE}@${plan.runtime}\n`);
197
+ }
198
+ }
199
+ process.stdout.write("\nYour state stays:\n");
200
+ for (const kept of plan.preserve)
201
+ process.stdout.write(` ${kept}\n`);
202
+ return worst;
203
+ }
204
+ /** The globally installed launcher version, or null when there is none. */
205
+ function globalLauncher(run) {
206
+ const result = run({ command: "npm", args: ["ls", "-g", "--depth=0", "--json", LAUNCHER_PACKAGE] });
207
+ if (result.failed)
208
+ return null;
209
+ try {
210
+ const parsed = JSON.parse(result.stdout);
211
+ return parsed.dependencies?.[LAUNCHER_PACKAGE]?.version ?? null;
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
217
+ /**
218
+ * `jam status` - the first place a person asks what is going on.
219
+ *
220
+ * This is `doctor`'s user-facing role under the name both products use. The
221
+ * judgement is not reimplemented: the same health gate and the same per-axis
222
+ * verdicts answer here, so the two commands can never disagree.
223
+ */
224
+ export async function jamStatusCommand(options = {}) {
225
+ if (options.json)
226
+ return doctorJsonCommand();
227
+ process.stdout.write(`jam ${SERVER_VERSION}\n`);
228
+ const code = await doctor();
229
+ process.stdout.write(code === 0
230
+ ? "\nNext: nothing - reading Jira is ready. `jam update` when a newer release is out.\n"
231
+ : "\nNext: `jam setup` binds this project and stores what is missing. `jam refresh` only re-registers.\n");
232
+ return code;
233
+ }
@@ -3,5 +3,5 @@
3
3
  * points (notably @jam-mcp/bootstrap) can forward to exactly these commands
4
4
  * instead of reimplementing them.
5
5
  */
6
- export declare const USAGE = "jam - Jira Agent MCP\n\nUsage:\n jam serve Run the MCP server over stdio (default; this is what Claude Code / Codex launch)\n jam doctor Diagnose config, credentials and Jira connectivity\n jam setup [--project KEY] [--shared] [--migrate] [--non-interactive]\n Wire up this project and run doctor. Binds it to you\n alone, writing nothing to the repository; --shared\n adopts JAM for the team (project.yaml, .mcp.json)\n jam update Move this machine's registration to the published release\n jam update check What is registered, what is published (changes nothing)\n jam runtime Show which JAM build this machine runs\n jam runtime use package | development <path>\n Change it (writes ~/.jam/config.yaml only, never a project)\n jam auth login Store Jira credentials in this user's OS secret store\n jam auth logout Remove them again\n\nFor coding agents and scripts (stdout is JSON only, never prompts):\n jam setup --agent One shot: detect, plan, apply what is safe, verify\n jam setup plan --json Report what setup would change, changing nothing\n jam setup apply --non-interactive --json\n Execute the plan\n jam doctor --json Health check as structured output\n jam auth status --json Whether Jira credentials are configured (never their value)\n jam jira search <jql> [--scope preview|complete]\n jam jira context <KEY> [KEY...]\n jam jira full <KEY> [KEY...]\n Read Jira from the shell - the same reads the MCP\n tools do, for a session that cannot see them yet\n\nEnvironment:\n JIRA_BASE_URL https://your-site.atlassian.net\n JIRA_EMAIL Atlassian account email\n JIRA_API_TOKEN Atlassian API token\n JAM_PROJECT_KEY Jira project key, used by `jam setup`/`jam serve` when no\n .jira-agent/project.yaml exists yet\n\nCredentials and JAM_PROJECT_KEY are read from the current shell's environment\nfirst, then (on Windows) from the User environment - so a value set with\n`setx` works without opening a new terminal.\n";
6
+ export declare const USAGE = "jam - Jira Agent MCP\n\nLifecycle (the same words ASC uses)\n jam setup [--project KEY] [--shared] [--migrate] [--non-interactive]\n Wire up this project and verify it. Binds it to you\n alone, writing nothing to the repository; --shared\n adopts JAM for the team (project.yaml, .mcp.json)\n jam status What is configured, what works, what is blocked\n jam update Move this machine's registration to the published release\n jam refresh Keep the version; re-register what this build owns\n jam uninstall Remove JAM's registrations; your bindings and credentials stay\n jam runtime Show which JAM build this machine runs\n jam runtime use package | development <path>\n Change it (writes ~/.jam/config.yaml only, never a project)\n\nJira\n jam jira search <jql> [--scope preview|complete]\n jam jira context <KEY> [KEY...]\n jam jira full <KEY> [KEY...]\n Read Jira from the shell - the same reads the MCP\n tools do, for a session that cannot see them yet\n\nAuthentication\n jam auth status [--json] Whether Jira credentials are configured (never their value)\n jam auth login Store them in this user's OS secret store\n jam auth logout Remove them again\n\nFor coding agents and scripts (stdout is JSON only, never prompts):\n jam setup --agent One shot: detect, plan, apply what is safe, verify\n jam setup plan --json Report what setup would change, changing nothing\n jam setup apply --non-interactive --json\n Execute the plan\n jam status --json Health check as structured output\n jam update check|plan [--json]\n jam refresh check|plan [--json]\n jam uninstall plan [--json]\n\nHost runtime\n jam serve Run the MCP server over stdio - this is what Claude\n Code and Codex launch. Not a command a person types.\n\nEnvironment:\n JIRA_BASE_URL https://your-site.atlassian.net\n JIRA_EMAIL Atlassian account email\n JIRA_API_TOKEN Atlassian API token\n JAM_PROJECT_KEY Jira project key, used by `jam setup`/`jam serve` when no\n .jira-agent/project.yaml exists yet\n\nCredentials and JAM_PROJECT_KEY are read from the current shell's environment\nfirst, then (on Windows) from the User environment - so a value set with\n`setx` works without opening a new terminal.\n";
7
7
  export declare function runJamCommand(argv: string[]): Promise<number>;
package/dist/cli-entry.js CHANGED
@@ -4,6 +4,7 @@ import { showRuntime, useRuntime } from "./cli/runtime.js";
4
4
  import { serve } from "./cli/serve.js";
5
5
  import { setup } from "./cli/setup.js";
6
6
  import { jamUpdateCommand } from "./cli/update.js";
7
+ import { jamRefreshCommand, jamStatusCommand, jamUninstallCommand } from "./cli/lifecycle.js";
7
8
  import { runSetupWizard } from "./cli/setup-wizard.js";
8
9
  import { reportPromptError, Ui } from "./cli/ui.js";
9
10
  import { authStatusCommand, doctorJsonCommand, setupAgentCommand, setupApplyCommand, setupPlanCommand, } from "./cli/agent-api.js";
@@ -15,34 +16,45 @@ import { runJiraRead } from "./cli/jira-read.js";
15
16
  */
16
17
  export const USAGE = `jam - Jira Agent MCP
17
18
 
18
- Usage:
19
- jam serve Run the MCP server over stdio (default; this is what Claude Code / Codex launch)
20
- jam doctor Diagnose config, credentials and Jira connectivity
19
+ Lifecycle (the same words ASC uses)
21
20
  jam setup [--project KEY] [--shared] [--migrate] [--non-interactive]
22
- Wire up this project and run doctor. Binds it to you
21
+ Wire up this project and verify it. Binds it to you
23
22
  alone, writing nothing to the repository; --shared
24
23
  adopts JAM for the team (project.yaml, .mcp.json)
24
+ jam status What is configured, what works, what is blocked
25
25
  jam update Move this machine's registration to the published release
26
- jam update check What is registered, what is published (changes nothing)
26
+ jam refresh Keep the version; re-register what this build owns
27
+ jam uninstall Remove JAM's registrations; your bindings and credentials stay
27
28
  jam runtime Show which JAM build this machine runs
28
29
  jam runtime use package | development <path>
29
30
  Change it (writes ~/.jam/config.yaml only, never a project)
30
- jam auth login Store Jira credentials in this user's OS secret store
31
- jam auth logout Remove them again
32
31
 
33
- For coding agents and scripts (stdout is JSON only, never prompts):
34
- jam setup --agent One shot: detect, plan, apply what is safe, verify
35
- jam setup plan --json Report what setup would change, changing nothing
36
- jam setup apply --non-interactive --json
37
- Execute the plan
38
- jam doctor --json Health check as structured output
39
- jam auth status --json Whether Jira credentials are configured (never their value)
32
+ Jira
40
33
  jam jira search <jql> [--scope preview|complete]
41
34
  jam jira context <KEY> [KEY...]
42
35
  jam jira full <KEY> [KEY...]
43
36
  Read Jira from the shell - the same reads the MCP
44
37
  tools do, for a session that cannot see them yet
45
38
 
39
+ Authentication
40
+ jam auth status [--json] Whether Jira credentials are configured (never their value)
41
+ jam auth login Store them in this user's OS secret store
42
+ jam auth logout Remove them again
43
+
44
+ For coding agents and scripts (stdout is JSON only, never prompts):
45
+ jam setup --agent One shot: detect, plan, apply what is safe, verify
46
+ jam setup plan --json Report what setup would change, changing nothing
47
+ jam setup apply --non-interactive --json
48
+ Execute the plan
49
+ jam status --json Health check as structured output
50
+ jam update check|plan [--json]
51
+ jam refresh check|plan [--json]
52
+ jam uninstall plan [--json]
53
+
54
+ Host runtime
55
+ jam serve Run the MCP server over stdio - this is what Claude
56
+ Code and Codex launch. Not a command a person types.
57
+
46
58
  Environment:
47
59
  JIRA_BASE_URL https://your-site.atlassian.net
48
60
  JIRA_EMAIL Atlassian account email
@@ -80,7 +92,12 @@ export async function runJamCommand(argv) {
80
92
  switch (command ?? "serve") {
81
93
  case "serve":
82
94
  return serve();
95
+ case "status":
96
+ return jamStatusCommand({ json: rest.includes("--json") });
97
+ // The old name for the same question. It keeps working for two minor
98
+ // releases; `status` is the word both products answer to.
83
99
  case "doctor":
100
+ process.stderr.write("Deprecated. Use `jam status`.\n");
84
101
  return rest.includes("--json") ? doctorJsonCommand() : doctor();
85
102
  case "setup": {
86
103
  const explicitKey = findFlagValue(rest, "--project");
@@ -108,6 +125,16 @@ export async function runJamCommand(argv) {
108
125
  return jamUpdateCommand(rest[0] === "--json" ? undefined : rest[0], {
109
126
  json: rest.includes("--json"),
110
127
  });
128
+ case "refresh":
129
+ // Not `update`: the version does not move here. Only the registration
130
+ // this build owns is brought back to it.
131
+ return jamRefreshCommand(rest[0]?.startsWith("--") ? undefined : rest[0], {
132
+ json: rest.includes("--json"),
133
+ });
134
+ case "uninstall":
135
+ return jamUninstallCommand(rest[0]?.startsWith("--") ? undefined : rest[0], {
136
+ json: rest.includes("--json"),
137
+ });
111
138
  case "runtime": {
112
139
  const json = rest.includes("--json");
113
140
  if (rest[0] === "use")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jam-mcp/server",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "JAM (Jira Agent MCP) - agent-facing Jira access layer: MCP server, setup core, and CLI",
5
5
  "keywords": [
6
6
  "jira",
@@ -41,7 +41,7 @@
41
41
  "test:watch": "vitest"
42
42
  },
43
43
  "dependencies": {
44
- "@jam-mcp/launcher": "1.5.0",
44
+ "@jam-mcp/launcher": "1.6.0",
45
45
  "@modelcontextprotocol/sdk": "^1.30.0",
46
46
  "yaml": "^2.9.0",
47
47
  "zod": "^4.4.3"