github-pro-mcp 0.0.1 → 0.0.3
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/LICENSE +76 -0
- package/README.md +167 -66
- package/THIRD-PARTY-NOTICES.md +635 -0
- package/dist-package/index.js +28601 -0
- package/package.json +33 -17
- package/dist/client.d.ts +0 -27
- package/dist/client.js +0 -80
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -52
- package/dist/tools/issue_fields.d.ts +0 -4
- package/dist/tools/issue_fields.js +0 -104
- package/dist/tools/issues.d.ts +0 -12
- package/dist/tools/issues.js +0 -607
- package/dist/tools/labels.d.ts +0 -3
- package/dist/tools/labels.js +0 -87
- package/dist/tools/organizations.d.ts +0 -4
- package/dist/tools/organizations.js +0 -89
- package/dist/tools/projects.d.ts +0 -4
- package/dist/tools/projects.js +0 -474
- package/dist/tools/pull_requests.d.ts +0 -4
- package/dist/tools/pull_requests.js +0 -127
- package/dist/tools/repos.d.ts +0 -8
- package/dist/tools/repos.js +0 -217
- package/dist/types/github-api.d.ts +0 -63
- package/dist/types/github-api.js +0 -1
- package/dist/utils/circuitBreaker.d.ts +0 -11
- package/dist/utils/circuitBreaker.js +0 -125
- package/dist/utils/errors.d.ts +0 -17
- package/dist/utils/errors.js +0 -107
- package/dist/utils/logger.d.ts +0 -14
- package/dist/utils/logger.js +0 -40
- package/dist/utils/metrics.d.ts +0 -11
- package/dist/utils/metrics.js +0 -70
- package/dist/utils/rateLimit.d.ts +0 -21
- package/dist/utils/rateLimit.js +0 -40
- package/dist/utils/retry.d.ts +0 -9
- package/dist/utils/retry.js +0 -76
- package/dist/utils/types.d.ts +0 -33
- package/dist/utils/types.js +0 -33
- package/dist/utils/validation.d.ts +0 -12
- package/dist/utils/validation.js +0 -75
package/dist/utils/logger.d.ts
DELETED
|
@@ -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 {};
|
package/dist/utils/logger.js
DELETED
|
@@ -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
|
-
}
|
package/dist/utils/metrics.d.ts
DELETED
|
@@ -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 {};
|
package/dist/utils/metrics.js
DELETED
|
@@ -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 {};
|
package/dist/utils/rateLimit.js
DELETED
|
@@ -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
|
-
}
|
package/dist/utils/retry.d.ts
DELETED
|
@@ -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>;
|
package/dist/utils/retry.js
DELETED
|
@@ -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
|
-
}
|
package/dist/utils/types.d.ts
DELETED
|
@@ -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 {};
|
package/dist/utils/types.js
DELETED
|
@@ -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,12 +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 {};
|
package/dist/utils/validation.js
DELETED
|
@@ -1,75 +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
|
-
}
|