@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 +2 -2
- package/dist/bootstrap/mcp-config-merger.d.ts +1 -1
- package/dist/cli/lifecycle.d.ts +66 -0
- package/dist/cli/lifecycle.js +233 -0
- package/dist/cli-entry.d.ts +1 -1
- package/dist/cli-entry.js +41 -14
- package/package.json +2 -2
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.
|
|
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.
|
|
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.
|
|
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
|
+
}
|
package/dist/cli-entry.d.ts
CHANGED
|
@@ -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\
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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"
|