@jam-mcp/server 1.1.0 → 1.2.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.
Files changed (42) hide show
  1. package/README.md +2 -2
  2. package/dist/adapters/credentials/windows-user-env.d.ts +3 -1
  3. package/dist/adapters/credentials/windows-user-env.js +20 -1
  4. package/dist/adapters/jira-cloud/jira-create-metadata.adapter.d.ts +27 -0
  5. package/dist/adapters/jira-cloud/jira-create-metadata.adapter.js +81 -0
  6. package/dist/adapters/jira-cloud/jira-read.adapter.d.ts +10 -1
  7. package/dist/adapters/jira-cloud/jira-read.adapter.js +17 -0
  8. package/dist/adapters/jira-cloud/jira-write.adapter.d.ts +13 -7
  9. package/dist/adapters/jira-cloud/jira-write.adapter.js +25 -20
  10. package/dist/application/apply-create-issue.d.ts +20 -0
  11. package/dist/application/apply-create-issue.js +187 -0
  12. package/dist/application/apply-write.js +18 -3
  13. package/dist/application/plan-create-issue.d.ts +44 -0
  14. package/dist/application/plan-create-issue.js +188 -0
  15. package/dist/application/plan-write.d.ts +10 -3
  16. package/dist/application/plan-write.js +54 -11
  17. package/dist/application/write-plan-store.d.ts +2 -2
  18. package/dist/application/write-plan-store.js +13 -1
  19. package/dist/bootstrap/mcp-config-merger.d.ts +1 -1
  20. package/dist/cli/auth.d.ts +6 -0
  21. package/dist/cli/auth.js +2 -1
  22. package/dist/deps.d.ts +11 -0
  23. package/dist/deps.js +6 -0
  24. package/dist/domain/adf.d.ts +35 -0
  25. package/dist/domain/adf.js +65 -0
  26. package/dist/domain/errors.d.ts +1 -1
  27. package/dist/domain/errors.js +9 -0
  28. package/dist/domain/write.d.ts +128 -15
  29. package/dist/domain/write.js +33 -1
  30. package/dist/mcp/create-server.d.ts +4 -0
  31. package/dist/mcp/create-server.js +5 -0
  32. package/dist/mcp/tools/jira-write-plan.tool.js +34 -6
  33. package/dist/policy/consistency-policy.d.ts +10 -4
  34. package/dist/policy/create-policy.d.ts +86 -0
  35. package/dist/policy/create-policy.js +182 -0
  36. package/dist/policy/write-policy.d.ts +10 -1
  37. package/dist/policy/write-policy.js +15 -1
  38. package/dist/ports/jira-create-metadata.port.d.ts +25 -0
  39. package/dist/ports/jira-create-metadata.port.js +1 -0
  40. package/dist/ports/jira-read.port.d.ts +23 -0
  41. package/dist/ports/jira-write.port.d.ts +9 -0
  42. package/package.json +2 -2
@@ -1,4 +1,4 @@
1
- import { type FieldUpdateInput, type JiraTransition, type WriteOperation } from "../domain/write.js";
1
+ import { type ExistingIssueOperation, type FieldUpdateInput, type JiraTransition, type WriteOperation } from "../domain/write.js";
2
2
  /**
3
3
  * What a write is allowed to be, decided before anything reaches Jira.
4
4
  *
@@ -32,6 +32,15 @@ export declare function projectKeyOf(issueKey: string): string | undefined;
32
32
  */
33
33
  export declare function assertWriteScope(issueKey: string, configuredProject: string): string;
34
34
  export declare function assertOperationAllowed(operation: string): WriteOperation;
35
+ /**
36
+ * Narrow an already-allowed operation to one that acts on an existing issue.
37
+ *
38
+ * Reached only after routing has sent `issue.create` elsewhere, so this is a
39
+ * type-level guarantee rather than a second policy decision - but it is a
40
+ * guarantee worth having, because everything downstream reads an issue key
41
+ * that creation does not have.
42
+ */
43
+ export declare function assertExistingIssueOperation(operation: string): ExistingIssueOperation;
35
44
  /**
36
45
  * Reject anything outside the field whitelist before planning goes further.
37
46
  *
@@ -1,5 +1,5 @@
1
1
  import { JamError } from "../domain/errors.js";
2
- import { isWritableField, isWriteOperation, WRITABLE_FIELDS, WRITE_OPERATIONS, } from "../domain/write.js";
2
+ import { EXISTING_ISSUE_OPERATIONS, isExistingIssueOperation, isWritableField, isWriteOperation, WRITABLE_FIELDS, WRITE_OPERATIONS, } from "../domain/write.js";
3
3
  /**
4
4
  * What a write is allowed to be, decided before anything reaches Jira.
5
5
  *
@@ -54,6 +54,20 @@ export function assertOperationAllowed(operation) {
54
54
  }
55
55
  return operation;
56
56
  }
57
+ /**
58
+ * Narrow an already-allowed operation to one that acts on an existing issue.
59
+ *
60
+ * Reached only after routing has sent `issue.create` elsewhere, so this is a
61
+ * type-level guarantee rather than a second policy decision - but it is a
62
+ * guarantee worth having, because everything downstream reads an issue key
63
+ * that creation does not have.
64
+ */
65
+ export function assertExistingIssueOperation(operation) {
66
+ if (!isExistingIssueOperation(operation)) {
67
+ throw new JamError("JAM_WRITE_OPERATION_NOT_ALLOWED", `"${operation}" does not change an existing issue. Operations that do: ${EXISTING_ISSUE_OPERATIONS.join(", ")}.`, { operation, supported: [...EXISTING_ISSUE_OPERATIONS] });
68
+ }
69
+ return operation;
70
+ }
57
71
  /**
58
72
  * Reject anything outside the field whitelist before planning goes further.
59
73
  *
@@ -0,0 +1,25 @@
1
+ import type { CreateFieldMetadata, CreateIssueType } from "../domain/write.js";
2
+ /**
3
+ * What Jira will accept when creating an issue in one project, right now.
4
+ *
5
+ * Separate from `JiraWritePort` on purpose. Everything behind that port
6
+ * changes something; nothing behind this one does. Folding schema discovery
7
+ * into the write port would put a read on the far side of a boundary whose
8
+ * whole documented rule is "these calls mutate, and none of them retry" - and
9
+ * the rule is worth more than the one fewer interface.
10
+ *
11
+ * Kept out of `JiraReadPort` too, for the opposite reason: that port answers
12
+ * questions about issues, with completeness semantics attached to every
13
+ * result. This answers a question about a project's configuration, and
14
+ * `meta.complete` would mean nothing here.
15
+ *
16
+ * These calls do not retry. Their answers decide a mutation, and a retried
17
+ * answer is a possibly-stale one - the same argument that keeps
18
+ * `getTransitions` on the non-retrying side.
19
+ */
20
+ export interface JiraCreateMetadataPort {
21
+ /** Issue types this account may create in this project. */
22
+ getIssueTypes(projectKey: string): Promise<CreateIssueType[]>;
23
+ /** The create screen's fields for one issue type in one project. */
24
+ getCreateFields(projectKey: string, issueTypeId: string): Promise<CreateFieldMetadata[]>;
25
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -20,6 +20,15 @@ export type GetIssuesRequest = {
20
20
  keys: string[];
21
21
  fields: string[];
22
22
  };
23
+ export type GetIssueRequest = {
24
+ key: string;
25
+ fields: string[];
26
+ };
27
+ export type GetIssueResult = {
28
+ /** Absent when Jira has no such issue, or this account cannot see it. */
29
+ issue?: FullIssueContext;
30
+ responseBytes: number;
31
+ };
23
32
  export type GetIssuesResult = {
24
33
  issues: FullIssueContext[];
25
34
  /** Keys that were requested but not returned (missing or not permitted). */
@@ -59,6 +68,20 @@ export type ListProjectsResult = {
59
68
  };
60
69
  export interface JiraReadPort {
61
70
  searchPage(req: SearchPageRequest): Promise<SearchPageResult>;
71
+ /**
72
+ * One issue, read directly by key.
73
+ *
74
+ * Separate from `getIssues` because ConsistencyPolicy requires a direct
75
+ * issue GET for anything that decides or confirms a write, and `getIssues`
76
+ * is not one: it is a bulk endpoint that takes a list, and a bulk read is
77
+ * free to answer from a different path than a single-issue GET. The
78
+ * distinction only matters in one place - the write plane - which is exactly
79
+ * where being wrong about it is most expensive.
80
+ *
81
+ * Reads and writes both use it: the pre-write conflict check, the post-write
82
+ * confirmation, and the post-create confirmation.
83
+ */
84
+ getIssue(req: GetIssueRequest): Promise<GetIssueResult>;
62
85
  getIssues(req: GetIssuesRequest): Promise<GetIssuesResult>;
63
86
  getComments(req: GetCommentsRequest): Promise<GetCommentsResult>;
64
87
  /** Used by `jam doctor` to prove authentication works. */
@@ -16,6 +16,15 @@ import type { JiraTransition } from "../domain/write.js";
16
16
  * surface is not: only whitelisted operations reach it (see domain/write.ts).
17
17
  */
18
18
  export interface JiraWritePort {
19
+ /**
20
+ * Create one issue. The only call here that brings an issue into existence,
21
+ * and the one where a retry is most expensive: a duplicate update is a
22
+ * no-op, a duplicate create is a second issue on someone's board.
23
+ */
24
+ createIssue(fields: Record<string, unknown>): Promise<{
25
+ id: string;
26
+ key: string;
27
+ }>;
19
28
  updateIssue(key: string, fields: Record<string, unknown>): Promise<void>;
20
29
  addComment(key: string, body: string): Promise<{
21
30
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jam-mcp/server",
3
- "version": "1.1.0",
3
+ "version": "1.2.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.1.0",
44
+ "@jam-mcp/launcher": "1.2.0",
45
45
  "@modelcontextprotocol/sdk": "^1.30.0",
46
46
  "yaml": "^2.9.0",
47
47
  "zod": "^4.4.3"