ralph-hero-mcp-server 2.5.24 → 2.5.42

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.
@@ -166,6 +166,27 @@ export function createGitHubClient(clientConfig, debugLogger) {
166
166
  cache.set(cacheKey, login, 60 * 60 * 1000); // Cache for 1 hour
167
167
  return login;
168
168
  },
169
+ async restPost(path, body, useProjectToken = true) {
170
+ const token = useProjectToken
171
+ ? (clientConfig.projectToken ?? clientConfig.token)
172
+ : clientConfig.token;
173
+ const url = `https://api.github.com${path}`;
174
+ const response = await fetch(url, {
175
+ method: "POST",
176
+ headers: {
177
+ Authorization: `token ${token}`,
178
+ Accept: "application/vnd.github+json",
179
+ "X-GitHub-Api-Version": "2022-11-28",
180
+ "Content-Type": "application/json",
181
+ },
182
+ body: JSON.stringify(body),
183
+ });
184
+ if (!response.ok) {
185
+ const text = await response.text().catch(() => "");
186
+ throw new Error(`GitHub REST API error ${response.status} for ${path}: ${text}`);
187
+ }
188
+ return response.json();
189
+ },
169
190
  };
170
191
  }
171
192
  //# sourceMappingURL=github-client.js.map
package/dist/index.js CHANGED
@@ -22,6 +22,7 @@ import { registerProjectManagementTools } from "./tools/project-management-tools
22
22
  import { registerHygieneTools } from "./tools/hygiene-tools.js";
23
23
  import { registerDebugTools } from "./tools/debug-tools.js";
24
24
  import { registerDecomposeTools } from "./tools/decompose-tools.js";
25
+ import { registerViewTools } from "./tools/view-tools.js";
25
26
  /**
26
27
  * Initialize the GitHub client from environment variables.
27
28
  */
@@ -301,6 +302,8 @@ async function main() {
301
302
  registerHygieneTools(server, client, fieldCache);
302
303
  // Decompose feature tool (cross-repo decomposition via .ralph-repos.yml)
303
304
  registerDecomposeTools(server, client, fieldCache);
305
+ // View management tools (REST API view creation)
306
+ registerViewTools(server, client, fieldCache);
304
307
  // Debug tools (only when RALPH_DEBUG=true)
305
308
  if (process.env.RALPH_DEBUG === 'true') {
306
309
  registerDebugTools(server, client);
@@ -386,6 +386,52 @@ async function fetchProject(client, owner, number) {
386
386
  }
387
387
  return null;
388
388
  }
389
+ const VIEWS_QUERY_USER = `
390
+ query($login: String!, $number: Int!) {
391
+ user(login: $login) {
392
+ projectV2(number: $number) {
393
+ views(first: 50) {
394
+ nodes { id name number layout filter }
395
+ }
396
+ }
397
+ }
398
+ }
399
+ `;
400
+ const VIEWS_QUERY_ORG = `
401
+ query($login: String!, $number: Int!) {
402
+ organization(login: $login) {
403
+ projectV2(number: $number) {
404
+ views(first: 50) {
405
+ nodes { id name number layout filter }
406
+ }
407
+ }
408
+ }
409
+ }
410
+ `;
411
+ /**
412
+ * Fetch project views via GraphQL with user→org fallback.
413
+ * Returns views AND the resolved ownerType so callers can construct
414
+ * the correct REST API path without a separate round-trip.
415
+ */
416
+ export async function fetchProjectViews(client, owner, projectNumber) {
417
+ // Try user first
418
+ try {
419
+ const result = await client.projectQuery(VIEWS_QUERY_USER, { login: owner, number: projectNumber });
420
+ const nodes = result.user?.projectV2?.views?.nodes;
421
+ if (nodes)
422
+ return { views: nodes, ownerType: "users" };
423
+ }
424
+ catch {
425
+ // fall through to org
426
+ }
427
+ // Try org
428
+ const result = await client.projectQuery(VIEWS_QUERY_ORG, { login: owner, number: projectNumber });
429
+ const nodes = result.organization?.projectV2?.views?.nodes;
430
+ if (!nodes) {
431
+ throw new Error(`Project #${projectNumber} not found for owner "${owner}"`);
432
+ }
433
+ return { views: nodes, ownerType: "orgs" };
434
+ }
389
435
  async function createSingleSelectField(client, projectId, fieldName, options) {
390
436
  const result = await client.projectMutate(`mutation($projectId: ID!, $name: String!, $dataType: ProjectV2CustomFieldType!, $singleSelectOptions: [ProjectV2SingleSelectFieldOptionInput!]) {
391
437
  createProjectV2Field(input: {
@@ -0,0 +1,102 @@
1
+ /**
2
+ * MCP tools for GitHub Projects V2 view management.
3
+ *
4
+ * Copies views from a source project (read via GraphQL) to a target
5
+ * project using the REST API POST endpoint.
6
+ */
7
+ import { z } from "zod";
8
+ import { toolError, toolSuccess } from "../types.js";
9
+ import { fetchProjectViews } from "./project-tools.js";
10
+ /**
11
+ * Convert GraphQL layout enum to REST API layout value.
12
+ * All three variants are handled exhaustively — TypeScript will error
13
+ * at build time if a new layout variant is added and not handled here.
14
+ */
15
+ export function toRestLayout(layout) {
16
+ switch (layout) {
17
+ case "TABLE_LAYOUT":
18
+ return "table";
19
+ case "BOARD_LAYOUT":
20
+ return "board";
21
+ case "ROADMAP_LAYOUT":
22
+ return "roadmap";
23
+ }
24
+ }
25
+ export function registerViewTools(server, client, _fieldCache) {
26
+ server.tool("ralph_hero__create_views", "Copy views from a source GitHub Project V2 to a target project using the REST API. Reads view names, layouts, and filter strings from the source project via GraphQL, then creates matching views in the target. Note: sort/group configuration is not available via API and must be set manually after creation.", {
27
+ owner: z
28
+ .string()
29
+ .optional()
30
+ .describe("GitHub owner (user or org). Defaults to RALPH_GH_OWNER env var"),
31
+ sourceProjectNumber: z.coerce
32
+ .number()
33
+ .describe("Project number to copy views FROM"),
34
+ targetProjectNumber: z.coerce
35
+ .number()
36
+ .describe("Project number to copy views INTO"),
37
+ }, async (args) => {
38
+ const owner = args.owner ?? client.config.projectOwner ?? client.config.owner;
39
+ if (!owner) {
40
+ return toolError("owner is required — set RALPH_GH_OWNER or pass owner param");
41
+ }
42
+ // Read views from source project; ownerType drives REST path selection
43
+ let sourceViews;
44
+ let ownerType;
45
+ try {
46
+ const result = await fetchProjectViews(client, owner, args.sourceProjectNumber);
47
+ sourceViews = result.views;
48
+ ownerType = result.ownerType;
49
+ }
50
+ catch (err) {
51
+ return toolError(`Failed to read views from project #${args.sourceProjectNumber}: ${err instanceof Error ? err.message : String(err)}`);
52
+ }
53
+ if (sourceViews.length === 0) {
54
+ return toolSuccess({
55
+ created: [],
56
+ failed: [],
57
+ count: 0,
58
+ sourceProject: args.sourceProjectNumber,
59
+ targetProject: args.targetProjectNumber,
60
+ message: "Source project has no views",
61
+ });
62
+ }
63
+ // REST path uses owner login (not numeric ID).
64
+ // filter is a plain top-level string matching the GraphQL field value.
65
+ const basePath = ownerType === "users"
66
+ ? `/users/${owner}/projectsV2/${args.targetProjectNumber}/views`
67
+ : `/orgs/${owner}/projectsV2/${args.targetProjectNumber}/views`;
68
+ const created = [];
69
+ const failed = [];
70
+ for (const view of sourceViews) {
71
+ const body = {
72
+ name: view.name,
73
+ layout: toRestLayout(view.layout),
74
+ };
75
+ if (view.filter) {
76
+ body.filter = view.filter;
77
+ }
78
+ try {
79
+ const createdView = await client.restPost(basePath, body);
80
+ created.push({
81
+ name: createdView.name,
82
+ layout: createdView.layout,
83
+ id: createdView.id,
84
+ });
85
+ }
86
+ catch (err) {
87
+ failed.push({
88
+ name: view.name,
89
+ error: err instanceof Error ? err.message : String(err),
90
+ });
91
+ }
92
+ }
93
+ return toolSuccess({
94
+ created,
95
+ failed,
96
+ count: created.length,
97
+ sourceProject: args.sourceProjectNumber,
98
+ targetProject: args.targetProjectNumber,
99
+ });
100
+ });
101
+ }
102
+ //# sourceMappingURL=view-tools.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ralph-hero-mcp-server",
3
- "version": "2.5.24",
3
+ "version": "2.5.42",
4
4
  "description": "MCP server for GitHub Projects V2 - Ralph workflow automation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",