github-pro-mcp 0.0.2 → 0.0.4

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 (43) hide show
  1. package/LICENSE +76 -0
  2. package/README.md +196 -69
  3. package/THIRD-PARTY-NOTICES.md +635 -0
  4. package/dist-package/index.js +28661 -0
  5. package/package.json +23 -17
  6. package/dist/client.d.ts +0 -28
  7. package/dist/client.js +0 -85
  8. package/dist/index.d.ts +0 -2
  9. package/dist/index.js +0 -70
  10. package/dist/tools/issue_fields.d.ts +0 -4
  11. package/dist/tools/issue_fields.js +0 -108
  12. package/dist/tools/issues.d.ts +0 -12
  13. package/dist/tools/issues.js +0 -634
  14. package/dist/tools/labels.d.ts +0 -3
  15. package/dist/tools/labels.js +0 -87
  16. package/dist/tools/organizations.d.ts +0 -4
  17. package/dist/tools/organizations.js +0 -89
  18. package/dist/tools/projects.d.ts +0 -4
  19. package/dist/tools/projects.js +0 -440
  20. package/dist/tools/pull_requests.d.ts +0 -4
  21. package/dist/tools/pull_requests.js +0 -127
  22. package/dist/tools/repos.d.ts +0 -9
  23. package/dist/tools/repos.js +0 -251
  24. package/dist/tools/server.d.ts +0 -14
  25. package/dist/tools/server.js +0 -37
  26. package/dist/types/github-api.d.ts +0 -63
  27. package/dist/types/github-api.js +0 -1
  28. package/dist/utils/circuitBreaker.d.ts +0 -11
  29. package/dist/utils/circuitBreaker.js +0 -125
  30. package/dist/utils/errors.d.ts +0 -17
  31. package/dist/utils/errors.js +0 -107
  32. package/dist/utils/logger.d.ts +0 -14
  33. package/dist/utils/logger.js +0 -40
  34. package/dist/utils/metrics.d.ts +0 -11
  35. package/dist/utils/metrics.js +0 -70
  36. package/dist/utils/rateLimit.d.ts +0 -21
  37. package/dist/utils/rateLimit.js +0 -40
  38. package/dist/utils/retry.d.ts +0 -9
  39. package/dist/utils/retry.js +0 -76
  40. package/dist/utils/types.d.ts +0 -33
  41. package/dist/utils/types.js +0 -33
  42. package/dist/utils/validation.d.ts +0 -13
  43. package/dist/utils/validation.js +0 -88
@@ -1,107 +0,0 @@
1
- import { RequestError } from "@octokit/request-error";
2
- import { categorizeError, incrementMetric } from "./metrics.js";
3
- /**
4
- * Convert any GitHub API error (REST or GraphQL) into a structured
5
- * MCP error response with actionable guidance.
6
- *
7
- * Automatically increments the metrics counter for the error category.
8
- */
9
- export function handleGitHubError(err) {
10
- incrementMetric(categorizeError(err));
11
- const gqlErrors = err?.errors;
12
- if (gqlErrors && Array.isArray(gqlErrors) && gqlErrors.length > 0) {
13
- const gqlErr = gqlErrors[0];
14
- let hint = "";
15
- const msg = gqlErr.message ?? "";
16
- const type = gqlErr.type ?? "";
17
- if (type === "FORBIDDEN" || msg.includes("not accessible")) {
18
- hint = `\n• Your token lacks the required permissions. For Projects, ensure it has the 'project' scope (classic PAT) or 'Projects' permission (fine-grained PAT).`;
19
- }
20
- else if (type === "NOT_FOUND") {
21
- hint = `\n• The resource was not found. Check the owner/repo name or item ID.`;
22
- }
23
- return {
24
- content: [{ type: "text", text: `GraphQL error: ${msg}\n` +
25
- `• Type: ${type || "unknown"}\n` +
26
- `• Path: ${(gqlErr.path ?? []).join(".")}${hint}`
27
- }],
28
- isError: true,
29
- };
30
- }
31
- if (err instanceof RequestError) {
32
- const status = err.status;
33
- const resource = err.response?.url ?? "unknown";
34
- switch (status) {
35
- case 401:
36
- return {
37
- content: [{ type: "text", text: `Authentication failed (401). GitHub returned: "${err.message}".\n` +
38
- `Your GITHUB_PERSONAL_ACCESS_TOKEN may be expired, revoked, or invalid.\n` +
39
- `• Generate a new token at: https://github.com/settings/tokens\n` +
40
- `• Ensure the token has the required scopes (repo, read:org, etc.)\n` +
41
- `• Set it as the GITHUB_PERSONAL_ACCESS_TOKEN environment variable.`
42
- }],
43
- isError: true,
44
- };
45
- case 403: {
46
- const scopes = err.response?.headers?.["x-accepted-oauth-scopes"];
47
- return {
48
- content: [{ type: "text", text: `Permission denied (403). ${err.message}.\n` +
49
- `• Required OAuth scopes: ${scopes ?? "unknown"}\n` +
50
- `• For Projects v2, ensure the token has the 'project' scope (classic PAT) or 'Projects' permission (fine-grained PAT).\n` +
51
- `• Update your token at: https://github.com/settings/tokens\n` +
52
- `• If this is an organization resource, ensure SSO is authorized.`
53
- }],
54
- isError: true,
55
- };
56
- }
57
- case 404:
58
- return {
59
- content: [{ type: "text", text: `Resource not found (404) at ${resource}.\n` +
60
- `• The resource may not exist, or your token may lack access.\n` +
61
- `• For private repos, verify the token has the 'repo' scope.\n` +
62
- `• Check if the owner/repo name is spelled correctly.`
63
- }],
64
- isError: true,
65
- };
66
- case 429: {
67
- const resetEpoch = err.response?.headers?.["x-ratelimit-reset"];
68
- const resetTime = resetEpoch
69
- ? new Date(Number(resetEpoch) * 1000).toISOString()
70
- : "unknown";
71
- return {
72
- content: [{ type: "text", text: `Rate limit exceeded (429). Resets at ${resetTime}.\n` +
73
- `• Limit: ${err.response?.headers?.["x-ratelimit-limit"] ?? "?"} requests/hr\n` +
74
- `• Wait until the reset time before retrying.`
75
- }],
76
- isError: true,
77
- };
78
- }
79
- default:
80
- return {
81
- content: [{ type: "text", text: `GitHub API error (${status}): ${err.message}`
82
- }],
83
- isError: true,
84
- };
85
- }
86
- }
87
- if (err instanceof Error) {
88
- return {
89
- content: [{ type: "text", text: `Unexpected error: ${err.message}` }],
90
- isError: true,
91
- };
92
- }
93
- return {
94
- content: [{ type: "text", text: `Unknown error: ${String(err)}` }],
95
- isError: true,
96
- };
97
- }
98
- export function formatZodErrors(err) {
99
- const issues = err?.issues;
100
- if (!issues || !Array.isArray(issues))
101
- return null;
102
- const parts = issues.map((issue) => {
103
- const field = issue.path.length > 0 ? `"${issue.path.join(".")}"` : "input";
104
- return `• ${field}: ${issue.message}`;
105
- });
106
- return `Validation error:\n${parts.join("\n")}`;
107
- }
@@ -1,14 +0,0 @@
1
- type LogLevel = "error" | "warn" | "info" | "debug";
2
- type LogSink = (level: LogLevel, message: string) => void;
3
- export interface Logger {
4
- error: (message: string) => void;
5
- warn: (message: string) => void;
6
- info: (message: string) => void;
7
- debug: (message: string) => void;
8
- }
9
- export declare function createLogger(component: string): Logger;
10
- export declare function configureLogger(opts: {
11
- sink?: LogSink;
12
- minLevel?: LogLevel;
13
- }): void;
14
- export {};
@@ -1,40 +0,0 @@
1
- const LEVEL_ORDER = {
2
- error: 0,
3
- warn: 1,
4
- info: 2,
5
- debug: 3,
6
- };
7
- function resolveMinLevel() {
8
- const env = process.env.GITHUB_LOG_LEVEL?.toLowerCase();
9
- if (env === "debug")
10
- return "debug";
11
- if (env === "info")
12
- return "info";
13
- if (env === "warn")
14
- return "warn";
15
- return "error";
16
- }
17
- function defaultSink(level, message) {
18
- process.stderr.write(`[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}\n`);
19
- }
20
- let globalSink = defaultSink;
21
- let globalMinLevel = resolveMinLevel();
22
- export function createLogger(component) {
23
- const log = (level, message) => {
24
- if (LEVEL_ORDER[level] <= LEVEL_ORDER[globalMinLevel]) {
25
- globalSink(level, `[${component}] ${message}`);
26
- }
27
- };
28
- return {
29
- error: (msg) => log("error", msg),
30
- warn: (msg) => log("warn", msg),
31
- info: (msg) => log("info", msg),
32
- debug: (msg) => log("debug", msg),
33
- };
34
- }
35
- export function configureLogger(opts) {
36
- if (opts.sink)
37
- globalSink = opts.sink;
38
- if (opts.minLevel)
39
- globalMinLevel = opts.minLevel;
40
- }
@@ -1,11 +0,0 @@
1
- type ErrorCategory = "auth_401" | "permission_403" | "not_found_404" | "rate_limit_429" | "server_5xx" | "network" | "validation" | "circuit_breaker" | "unknown";
2
- interface MetricSnapshot {
3
- startedAt: number;
4
- counters: Record<ErrorCategory, number>;
5
- total: number;
6
- }
7
- export declare function incrementMetric(category: ErrorCategory): void;
8
- export declare function categorizeError(error: unknown): ErrorCategory;
9
- export declare function getMetricsSnapshot(): MetricSnapshot;
10
- export declare function getMetricsSummary(): string;
11
- export {};
@@ -1,70 +0,0 @@
1
- import { createLogger } from "./logger.js";
2
- const log = createLogger("metrics");
3
- const counters = {
4
- auth_401: 0,
5
- permission_403: 0,
6
- not_found_404: 0,
7
- rate_limit_429: 0,
8
- server_5xx: 0,
9
- network: 0,
10
- validation: 0,
11
- circuit_breaker: 0,
12
- unknown: 0,
13
- };
14
- const startedAt = Date.now();
15
- export function incrementMetric(category) {
16
- counters[category]++;
17
- log.debug(`Metric ${category} = ${counters[category]}`);
18
- }
19
- export function categorizeError(error) {
20
- if (error instanceof Error && error.name === "CircuitBreakerError") {
21
- return "circuit_breaker";
22
- }
23
- const status = error?.status;
24
- if (status === 401)
25
- return "auth_401";
26
- if (status === 403)
27
- return "permission_403";
28
- if (status === 404)
29
- return "not_found_404";
30
- if (status === 429)
31
- return "rate_limit_429";
32
- if (typeof status === "number" && status >= 500)
33
- return "server_5xx";
34
- const code = error?.code;
35
- if (code && ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "EPIPE"].includes(code)) {
36
- return "network";
37
- }
38
- const message = error instanceof Error ? error.message.toLowerCase() : "";
39
- if (message.includes("validation") || message.includes("invalid") || message.includes("zod")) {
40
- return "validation";
41
- }
42
- return "unknown";
43
- }
44
- export function getMetricsSnapshot() {
45
- let total = 0;
46
- for (const v of Object.values(counters))
47
- total += v;
48
- return {
49
- startedAt,
50
- counters: { ...counters },
51
- total,
52
- };
53
- }
54
- export function getMetricsSummary() {
55
- const snap = getMetricsSnapshot();
56
- const uptime = Math.round((Date.now() - snap.startedAt) / 1000);
57
- const lines = [
58
- `Metrics (uptime: ${uptime}s, total errors: ${snap.total})`,
59
- ` 401 (auth): ${snap.counters.auth_401}`,
60
- ` 403 (permission): ${snap.counters.permission_403}`,
61
- ` 404 (not found): ${snap.counters.not_found_404}`,
62
- ` 429 (rate limit): ${snap.counters.rate_limit_429}`,
63
- ` 5xx (server): ${snap.counters.server_5xx}`,
64
- ` network: ${snap.counters.network}`,
65
- ` validation: ${snap.counters.validation}`,
66
- ` circuit_breaker: ${snap.counters.circuit_breaker}`,
67
- ` unknown: ${snap.counters.unknown}`,
68
- ];
69
- return lines.join("\n");
70
- }
@@ -1,21 +0,0 @@
1
- interface RateLimitState {
2
- core: {
3
- limit: number;
4
- remaining: number;
5
- reset: number;
6
- };
7
- search: {
8
- limit: number;
9
- remaining: number;
10
- reset: number;
11
- };
12
- graphql: {
13
- limit: number;
14
- remaining: number;
15
- reset: number;
16
- };
17
- }
18
- export declare function updateRateLimits(headers: Record<string, string | undefined>): void;
19
- export declare function getRateLimitState(): RateLimitState;
20
- export declare function getRateLimitSummary(): string;
21
- export {};
@@ -1,40 +0,0 @@
1
- const state = {
2
- core: { limit: 5000, remaining: 5000, reset: 0 },
3
- search: { limit: 30, remaining: 30, reset: 0 },
4
- graphql: { limit: 5000, remaining: 5000, reset: 0 },
5
- };
6
- export function updateRateLimits(headers) {
7
- const limit = parseInt(headers["x-ratelimit-limit"] ?? "5000", 10);
8
- const remaining = parseInt(headers["x-ratelimit-remaining"] ?? String(limit), 10);
9
- const reset = parseInt(headers["x-ratelimit-reset"] ?? "0", 10);
10
- const resource = headers["x-ratelimit-resource"] ?? "core";
11
- if (resource === "search") {
12
- state.search = { limit, remaining, reset };
13
- }
14
- else if (resource === "graphql") {
15
- state.graphql = { limit, remaining, reset };
16
- }
17
- else {
18
- state.core = { limit, remaining, reset };
19
- }
20
- }
21
- export function getRateLimitState() {
22
- return {
23
- core: { ...state.core },
24
- search: { ...state.search },
25
- graphql: { ...state.graphql },
26
- };
27
- }
28
- export function getRateLimitSummary() {
29
- const s = getRateLimitState();
30
- const formatCategory = (label, r) => {
31
- const pct = r.limit > 0 ? Math.round((r.remaining / r.limit) * 100) : 100;
32
- const resetStr = r.reset > 0 ? ` (resets ${new Date(r.reset * 1000).toISOString()})` : "";
33
- return `${label}: ${r.remaining}/${r.limit} (${pct}%)${resetStr}`;
34
- };
35
- return [
36
- formatCategory("Core", s.core),
37
- formatCategory("Search", s.search),
38
- formatCategory("GraphQL", s.graphql),
39
- ].join("\n");
40
- }
@@ -1,9 +0,0 @@
1
- export interface RetryConfig {
2
- maxAttempts: number;
3
- initialDelayMs: number;
4
- backoffFactor: number;
5
- maxDelayMs: number;
6
- retryableStatusCodes: number[];
7
- retryableErrors: string[];
8
- }
9
- export declare function withRetry<T>(fn: () => Promise<T>, label: string, overrides?: Partial<RetryConfig>): Promise<T>;
@@ -1,76 +0,0 @@
1
- import { createLogger } from "./logger.js";
2
- const log = createLogger("retry");
3
- function defaultConfig() {
4
- return {
5
- maxAttempts: parseInt(process.env.GITHUB_RETRY_MAX_ATTEMPTS ?? "3", 10),
6
- initialDelayMs: parseInt(process.env.GITHUB_RETRY_BACKOFF_MS ?? "1000", 10),
7
- backoffFactor: parseFloat(process.env.GITHUB_RETRY_BACKOFF_FACTOR ?? "2"),
8
- maxDelayMs: 30000,
9
- retryableStatusCodes: [429, 500, 502, 503, 504],
10
- retryableErrors: [
11
- "ETIMEDOUT",
12
- "ECONNRESET",
13
- "ECONNREFUSED",
14
- "ENOTFOUND",
15
- "EPIPE",
16
- ],
17
- };
18
- }
19
- function delay(ms) {
20
- return new Promise((resolve) => setTimeout(resolve, ms));
21
- }
22
- function isRetryableError(error, config) {
23
- if (error instanceof Error) {
24
- const code = error.code;
25
- if (code && config.retryableErrors.includes(code))
26
- return true;
27
- const status = error.status;
28
- if (typeof status === "number" && config.retryableStatusCodes.includes(status))
29
- return true;
30
- const message = error.message.toLowerCase();
31
- if (message.includes("socket hang up") ||
32
- message.includes("request aborted") ||
33
- message.includes("network error") ||
34
- message.includes("rate limit")) {
35
- return true;
36
- }
37
- }
38
- return false;
39
- }
40
- function calcDelay(attempt, config) {
41
- const computed = config.initialDelayMs * Math.pow(config.backoffFactor, attempt - 1);
42
- const jitter = Math.random() * 0.3 * computed;
43
- return Math.min(computed + jitter, config.maxDelayMs);
44
- }
45
- export async function withRetry(fn, label, overrides) {
46
- const config = { ...defaultConfig(), ...overrides };
47
- for (let attempt = 1; attempt <= config.maxAttempts; attempt++) {
48
- try {
49
- return await fn();
50
- }
51
- catch (error) {
52
- if (attempt === config.maxAttempts || !isRetryableError(error, config)) {
53
- throw error;
54
- }
55
- const waitMs = calcDelay(attempt, config);
56
- log.warn(`Retry attempt ${attempt}/${config.maxAttempts} for "${label}" ` +
57
- `after ${Math.round(waitMs)}ms: ${extractErrorSummary(error)}`);
58
- await delay(waitMs);
59
- }
60
- }
61
- throw new Error(`Unexpected: retry loop exhausted for "${label}"`);
62
- }
63
- function extractErrorSummary(error) {
64
- if (error instanceof Error) {
65
- const status = error.status;
66
- const code = error.code;
67
- const parts = [];
68
- if (typeof status === "number")
69
- parts.push(`${status}`);
70
- if (code)
71
- parts.push(code);
72
- parts.push(error.message.split("\n")[0]);
73
- return parts.join(" ");
74
- }
75
- return String(error);
76
- }
@@ -1,33 +0,0 @@
1
- import * as z from "zod/v4";
2
- export declare const OwnerRepoSchema: z.ZodObject<{
3
- owner: z.ZodString;
4
- repo: z.ZodString;
5
- }, z.core.$strip>;
6
- export declare const PaginationSchema: z.ZodObject<{
7
- page: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
8
- per_page: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
9
- }, z.core.$strip>;
10
- export declare const IssueNumberSchema: z.ZodNumber;
11
- export declare const PullNumberSchema: z.ZodNumber;
12
- export declare const IncludeFlagsSchema: z.ZodObject<{
13
- include_comments: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
14
- include_labels: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
15
- include_fields: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
16
- include_sub_issues: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
17
- }, z.core.$strip>;
18
- export declare const PrIncludeFlagsSchema: z.ZodObject<{
19
- include_commits: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
20
- include_reviews: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
21
- }, z.core.$strip>;
22
- interface RepoShape {
23
- full_name: string;
24
- description: string | null;
25
- private: boolean;
26
- language: string | null;
27
- default_branch: string;
28
- updated_at: string;
29
- stars: number;
30
- forks: number;
31
- }
32
- export declare function formatRepo(r: Record<string, unknown>): RepoShape;
33
- export {};
@@ -1,33 +0,0 @@
1
- import * as z from "zod/v4";
2
- export const OwnerRepoSchema = z.object({
3
- owner: z.string().min(1).describe("Repository owner (user or organization)"),
4
- repo: z.string().min(1).describe("Repository name"),
5
- });
6
- export const PaginationSchema = z.object({
7
- page: z.number().int().min(1).optional().default(1).describe("Page number (starts at 1)"),
8
- per_page: z.number().int().min(1).max(100).optional().default(30).describe("Results per page (max 100)"),
9
- });
10
- export const IssueNumberSchema = z.number().int().positive().describe("Issue number");
11
- export const PullNumberSchema = z.number().int().positive().describe("Pull request number");
12
- export const IncludeFlagsSchema = z.object({
13
- include_comments: z.boolean().optional().default(false).describe("Include issue comments"),
14
- include_labels: z.boolean().optional().default(false).describe("Include labels"),
15
- include_fields: z.boolean().optional().default(false).describe("Include issue field values (Priority, Effort, dates)"),
16
- include_sub_issues: z.boolean().optional().default(false).describe("Include sub-issues"),
17
- });
18
- export const PrIncludeFlagsSchema = z.object({
19
- include_commits: z.boolean().optional().default(false).describe("Include commit list"),
20
- include_reviews: z.boolean().optional().default(false).describe("Include review list"),
21
- });
22
- export function formatRepo(r) {
23
- return {
24
- full_name: String(r.full_name ?? ""),
25
- description: r.description ?? null,
26
- private: Boolean(r.private),
27
- language: r.language ?? null,
28
- default_branch: String(r.default_branch ?? ""),
29
- updated_at: String(r.updated_at ?? ""),
30
- stars: Number(r.stargazers_count ?? 0),
31
- forks: Number(r.forks_count ?? 0),
32
- };
33
- }
@@ -1,13 +0,0 @@
1
- interface ErrorResponse {
2
- content: [{
3
- type: "text";
4
- text: string;
5
- }];
6
- isError: true;
7
- [x: string]: unknown;
8
- }
9
- export declare function validatePinnedOwnerRepo(owner?: string, repo?: string): ErrorResponse | null;
10
- export declare function requireOrg(owner: string): Promise<ErrorResponse | null>;
11
- export declare function requireUser(owner: string): Promise<ErrorResponse | null>;
12
- export declare function requireIssueOpen(owner: string, repo: string, issue_number: number): Promise<ErrorResponse | null>;
13
- export {};
@@ -1,88 +0,0 @@
1
- import { createClient } from "../client.js";
2
- export function validatePinnedOwnerRepo(owner, repo) {
3
- const pinnedOwner = process.env.GITHUB_OWNER;
4
- const pinnedRepo = process.env.GITHUB_REPO;
5
- const errors = [];
6
- if (pinnedOwner && owner && pinnedOwner.toLowerCase() !== owner.toLowerCase()) {
7
- errors.push(`FATAL: This MCP server is pinned to a specific owner. ` +
8
- `The provided owner "${owner}" is not permitted. This operation cannot proceed.`);
9
- }
10
- if (pinnedRepo && repo && pinnedRepo.toLowerCase() !== repo.toLowerCase()) {
11
- errors.push(`FATAL: This MCP server is pinned to a specific repository. ` +
12
- `The provided repo "${repo}" is not permitted. This operation cannot proceed.`);
13
- }
14
- if (errors.length > 0) {
15
- return {
16
- content: [{ type: "text", text: errors.join("\n") }],
17
- isError: true,
18
- };
19
- }
20
- return null;
21
- }
22
- const ownerTypeCache = new Map();
23
- const OWNER_TYPE_CACHE_TTL_MS = parseInt(process.env.GITHUB_OWNER_TYPE_CACHE_MS ?? "300000", 10);
24
- function getCachedOwnerType(owner) {
25
- const entry = ownerTypeCache.get(owner.toLowerCase());
26
- if (!entry)
27
- return null;
28
- if (Date.now() > entry.expiresAt) {
29
- ownerTypeCache.delete(owner.toLowerCase());
30
- return null;
31
- }
32
- return entry.type;
33
- }
34
- function setCachedOwnerType(owner, type) {
35
- ownerTypeCache.set(owner.toLowerCase(), {
36
- type,
37
- expiresAt: Date.now() + OWNER_TYPE_CACHE_TTL_MS,
38
- });
39
- }
40
- async function resolveOwnerType(owner) {
41
- const cached = getCachedOwnerType(owner);
42
- if (cached)
43
- return cached;
44
- const octokit = createClient();
45
- const { data: user } = await octokit.rest.users.getByUsername({ username: owner });
46
- const type = user.type;
47
- setCachedOwnerType(owner, type);
48
- return type;
49
- }
50
- export async function requireOrg(owner) {
51
- const type = await resolveOwnerType(owner);
52
- if (type !== "Organization") {
53
- return {
54
- content: [{ type: "text", text: `"${owner}" is a user account, not an organization. ` +
55
- `This tool only works for organizations. ` +
56
- `Tip: Use owner_list_organization to find organizations for a user.`
57
- }],
58
- isError: true,
59
- };
60
- }
61
- return null;
62
- }
63
- export async function requireUser(owner) {
64
- const type = await resolveOwnerType(owner);
65
- if (type !== "User") {
66
- return {
67
- content: [{ type: "text", text: `"${owner}" is an organization, not a user account. ` +
68
- `This tool lists organizations that a user belongs to; provide a username instead. ` +
69
- `Tip: To find user accounts, use organization_search with a name query.`
70
- }],
71
- isError: true,
72
- };
73
- }
74
- return null;
75
- }
76
- export async function requireIssueOpen(owner, repo, issue_number) {
77
- const octokit = createClient();
78
- const { data } = await octokit.rest.issues.get({ owner, repo, issue_number });
79
- if (data.state === "closed") {
80
- return {
81
- content: [{ type: "text", text: `Issue #${issue_number} in ${owner}/${repo} is closed. ` +
82
- `Reopen it first before modifying the title or body.`
83
- }],
84
- isError: true,
85
- };
86
- }
87
- return null;
88
- }