mcp-server-diff 2.1.0 โ†’ 2.1.5

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/src/git.ts DELETED
@@ -1,262 +0,0 @@
1
- /**
2
- * Git utilities for MCP server diff
3
- */
4
-
5
- import * as exec from "@actions/exec";
6
- import * as core from "@actions/core";
7
-
8
- export interface GitInfo {
9
- currentBranch: string;
10
- compareRef: string;
11
- }
12
-
13
- /**
14
- * Get current branch name
15
- */
16
- export async function getCurrentBranch(): Promise<string> {
17
- let output = "";
18
- try {
19
- await exec.exec("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
20
- silent: true,
21
- listeners: {
22
- stdout: (data) => {
23
- output += data.toString();
24
- },
25
- },
26
- });
27
- return output.trim() || "HEAD";
28
- } catch {
29
- return "HEAD";
30
- }
31
- }
32
-
33
- /**
34
- * Determine what ref to compare against
35
- * Priority: 1) Explicit compare_ref, 2) Auto-detect previous tag, 3) Merge-base with main
36
- */
37
- export async function determineCompareRef(
38
- explicitRef?: string,
39
- githubRef?: string
40
- ): Promise<string> {
41
- // If explicit ref provided, use it
42
- if (explicitRef) {
43
- core.info(`Using explicit compare ref: ${explicitRef}`);
44
- return explicitRef;
45
- }
46
-
47
- // Check if this is a tag push
48
- if (githubRef?.startsWith("refs/tags/")) {
49
- const currentTag = githubRef.replace("refs/tags/", "");
50
- core.info(`Detected tag push: ${currentTag}`);
51
-
52
- // Try to find previous tag
53
- const previousTag = await findPreviousTag(currentTag);
54
- if (previousTag && previousTag !== currentTag) {
55
- core.info(`Auto-detected previous tag: ${previousTag}`);
56
- return previousTag;
57
- }
58
-
59
- // Fall back to first commit
60
- const firstCommit = await getFirstCommit();
61
- core.warning("No previous tag found, comparing against initial commit");
62
- return firstCommit;
63
- }
64
-
65
- // Default: find merge-base with main
66
- const baseRef = await findMainBranch();
67
- const mergeBase = await getMergeBase(baseRef);
68
- core.info(`Using merge-base with ${baseRef}: ${mergeBase}`);
69
- return mergeBase;
70
- }
71
-
72
- /**
73
- * Find the previous tag (sorted by version)
74
- */
75
- async function findPreviousTag(currentTag: string): Promise<string | null> {
76
- let output = "";
77
- try {
78
- await exec.exec("git", ["tag", "--sort=-v:refname"], {
79
- silent: true,
80
- listeners: {
81
- stdout: (data) => {
82
- output += data.toString();
83
- },
84
- },
85
- });
86
-
87
- const tags = output.trim().split("\n");
88
- const currentIndex = tags.indexOf(currentTag);
89
- if (currentIndex >= 0 && currentIndex < tags.length - 1) {
90
- return tags[currentIndex + 1];
91
- }
92
- return null;
93
- } catch {
94
- return null;
95
- }
96
- }
97
-
98
- /**
99
- * Get the first commit in the repository
100
- */
101
- async function getFirstCommit(): Promise<string> {
102
- let output = "";
103
- await exec.exec("git", ["rev-list", "--max-parents=0", "HEAD"], {
104
- silent: true,
105
- listeners: {
106
- stdout: (data) => {
107
- output += data.toString();
108
- },
109
- },
110
- });
111
- return output.trim().split("\n")[0];
112
- }
113
-
114
- /**
115
- * Find the main branch (origin/main, main, or first commit)
116
- */
117
- async function findMainBranch(): Promise<string> {
118
- // Try origin/main
119
- try {
120
- await exec.exec("git", ["rev-parse", "--verify", "origin/main"], { silent: true });
121
- return "origin/main";
122
- } catch {
123
- // Try main
124
- try {
125
- await exec.exec("git", ["rev-parse", "--verify", "main"], { silent: true });
126
- return "main";
127
- } catch {
128
- // Fall back to first commit
129
- return await getFirstCommit();
130
- }
131
- }
132
- }
133
-
134
- /**
135
- * Get merge-base between HEAD and a ref
136
- */
137
- async function getMergeBase(ref: string): Promise<string> {
138
- let output = "";
139
- try {
140
- await exec.exec("git", ["merge-base", "HEAD", ref], {
141
- silent: true,
142
- listeners: {
143
- stdout: (data) => {
144
- output += data.toString();
145
- },
146
- },
147
- });
148
- return output.trim();
149
- } catch {
150
- return ref;
151
- }
152
- }
153
-
154
- /**
155
- * Create a worktree for the compare ref
156
- */
157
- export async function createWorktree(ref: string, path: string): Promise<boolean> {
158
- try {
159
- await exec.exec("git", ["worktree", "add", "--quiet", path, ref], { silent: true });
160
- return true;
161
- } catch {
162
- return false;
163
- }
164
- }
165
-
166
- /**
167
- * Remove a worktree
168
- */
169
- export async function removeWorktree(path: string): Promise<void> {
170
- try {
171
- await exec.exec("git", ["worktree", "remove", "--force", path], { silent: true });
172
- } catch {
173
- // Ignore errors
174
- }
175
- }
176
-
177
- /**
178
- * Checkout a ref (fallback if worktree fails)
179
- */
180
- export async function checkout(ref: string): Promise<void> {
181
- await exec.exec("git", ["checkout", "--quiet", ref], { silent: true });
182
- }
183
-
184
- /**
185
- * Checkout previous branch/ref
186
- */
187
- export async function checkoutPrevious(): Promise<void> {
188
- try {
189
- await exec.exec("git", ["checkout", "--quiet", "-"], { silent: true });
190
- } catch {
191
- // Ignore errors
192
- }
193
- }
194
-
195
- /**
196
- * Get a display-friendly name for a ref.
197
- * Returns branch/tag name if available, otherwise the short SHA.
198
- */
199
- export async function getRefDisplayName(ref: string): Promise<string> {
200
- // If it's already a readable name (not a SHA), return it
201
- if (!ref.match(/^[a-f0-9]{40}$/i) && !ref.match(/^[a-f0-9]{7,}$/i)) {
202
- // It's likely already a branch/tag name
203
- return ref;
204
- }
205
-
206
- // Try to find a branch name pointing to this ref
207
- let output = "";
208
- try {
209
- await exec.exec("git", ["branch", "--points-at", ref, "--format=%(refname:short)"], {
210
- silent: true,
211
- listeners: {
212
- stdout: (data) => {
213
- output += data.toString();
214
- },
215
- },
216
- });
217
- const branches = output.trim().split("\n").filter(Boolean);
218
- if (branches.length > 0) {
219
- // Prefer main/master if available
220
- if (branches.includes("main")) return "main";
221
- if (branches.includes("master")) return "master";
222
- return branches[0];
223
- }
224
- } catch {
225
- // Ignore errors
226
- }
227
-
228
- // Try to find a tag pointing to this ref
229
- output = "";
230
- try {
231
- await exec.exec("git", ["tag", "--points-at", ref], {
232
- silent: true,
233
- listeners: {
234
- stdout: (data) => {
235
- output += data.toString();
236
- },
237
- },
238
- });
239
- const tags = output.trim().split("\n").filter(Boolean);
240
- if (tags.length > 0) {
241
- return tags[0];
242
- }
243
- } catch {
244
- // Ignore errors
245
- }
246
-
247
- // Fall back to short SHA
248
- output = "";
249
- try {
250
- await exec.exec("git", ["rev-parse", "--short", ref], {
251
- silent: true,
252
- listeners: {
253
- stdout: (data) => {
254
- output += data.toString();
255
- },
256
- },
257
- });
258
- return output.trim() || ref;
259
- } catch {
260
- return ref.substring(0, 7);
261
- }
262
- }
package/src/index.ts DELETED
@@ -1,284 +0,0 @@
1
- /**
2
- * MCP Server Diff - Main Entry Point
3
- *
4
- * Diffs MCP server public interfaces by comparing
5
- * API responses between the current branch and a reference.
6
- */
7
-
8
- import * as core from "@actions/core";
9
- import * as exec from "@actions/exec";
10
- import { getCurrentBranch, determineCompareRef, getRefDisplayName } from "./git.js";
11
- import { parseConfigurations, parseCustomMessages, parseHeaders, runAllTests } from "./runner.js";
12
- import { generateReport, generateMarkdownReport, saveReport } from "./reporter.js";
13
- import type { ActionInputs } from "./types.js";
14
-
15
- /**
16
- * Get all inputs from the action (composite action style - INPUT_* env vars)
17
- */
18
- function getInputs(): ActionInputs {
19
- // Helper to get input from INPUT_* environment variables
20
- const getInput = (name: string): string => {
21
- const envName = `INPUT_${name.toUpperCase().replace(/-/g, "_")}`;
22
- return process.env[envName] || "";
23
- };
24
-
25
- const getBooleanInput = (name: string): boolean => {
26
- const value = getInput(name);
27
- return value.toLowerCase() === "true";
28
- };
29
-
30
- const transport = (getInput("transport") || "stdio") as "stdio" | "streamable-http";
31
- const startCommand = getInput("start_command");
32
- const serverUrl = getInput("server_url");
33
-
34
- // Parse configurations
35
- const configurationsInput = getInput("configurations");
36
- const configurations = parseConfigurations(
37
- configurationsInput,
38
- transport,
39
- startCommand,
40
- serverUrl
41
- );
42
-
43
- // Parse custom messages
44
- const customMessagesInput = getInput("custom_messages");
45
- const customMessages = parseCustomMessages(customMessagesInput);
46
-
47
- // Parse global headers
48
- const headersInput = getInput("headers");
49
- const headers = parseHeaders(headersInput);
50
-
51
- return {
52
- // Language setup
53
- setupNode: getBooleanInput("setup_node"),
54
- nodeVersion: getInput("node_version") || "20",
55
- setupPython: getBooleanInput("setup_python"),
56
- pythonVersion: getInput("python_version") || "3.11",
57
- setupGo: getBooleanInput("setup_go"),
58
- goVersion: getInput("go_version") || "1.24",
59
- setupRust: getBooleanInput("setup_rust"),
60
- rustToolchain: getInput("rust_toolchain") || "stable",
61
- setupDotnet: getBooleanInput("setup_dotnet"),
62
- dotnetVersion: getInput("dotnet_version") || "9.0.x",
63
-
64
- // Build configuration
65
- installCommand: getInput("install_command"),
66
- buildCommand: getInput("build_command"),
67
- startCommand,
68
-
69
- // Transport configuration
70
- transport,
71
- serverUrl,
72
- headers,
73
- configurations,
74
- customMessages,
75
-
76
- // Shared HTTP server configuration
77
- httpStartCommand: getInput("http_start_command"),
78
- httpStartupWaitMs: parseInt(getInput("http_startup_wait_ms") || "2000", 10),
79
-
80
- // Test configuration
81
- compareRef: getInput("compare_ref"),
82
- failOnError: getBooleanInput("fail_on_error") !== false, // default true
83
- failOnDiff: getBooleanInput("fail_on_diff") === true, // default false
84
- envVars: getInput("env_vars"),
85
- serverTimeout: parseInt(getInput("server_timeout") || "30000", 10),
86
- };
87
- }
88
-
89
- /**
90
- * Set up language runtimes based on inputs
91
- */
92
- async function setupLanguages(inputs: ActionInputs): Promise<void> {
93
- // We rely on composite action setup or assume runtimes are available
94
- // In a pure Node action, we'd need to install these ourselves or
95
- // require the user to set them up in a prior step
96
-
97
- core.info("๐Ÿ“ฆ Verifying language runtimes...");
98
-
99
- if (inputs.setupNode) {
100
- try {
101
- let output = "";
102
- await exec.exec("node", ["--version"], {
103
- silent: true,
104
- listeners: {
105
- stdout: (data) => {
106
- output += data.toString();
107
- },
108
- },
109
- });
110
- core.info(` Node.js: ${output.trim()}`);
111
- } catch {
112
- core.warning("Node.js not available - please set up in a prior step");
113
- }
114
- }
115
-
116
- if (inputs.setupPython) {
117
- try {
118
- let output = "";
119
- await exec.exec("python", ["--version"], {
120
- silent: true,
121
- listeners: {
122
- stdout: (data) => {
123
- output += data.toString();
124
- },
125
- },
126
- });
127
- core.info(` Python: ${output.trim()}`);
128
- } catch {
129
- core.warning("Python not available - please set up in a prior step");
130
- }
131
- }
132
-
133
- if (inputs.setupGo) {
134
- try {
135
- let output = "";
136
- await exec.exec("go", ["version"], {
137
- silent: true,
138
- listeners: {
139
- stdout: (data) => {
140
- output += data.toString();
141
- },
142
- },
143
- });
144
- core.info(` Go: ${output.trim()}`);
145
- } catch {
146
- core.warning("Go not available - please set up in a prior step");
147
- }
148
- }
149
-
150
- if (inputs.setupRust) {
151
- try {
152
- let output = "";
153
- await exec.exec("rustc", ["--version"], {
154
- silent: true,
155
- listeners: {
156
- stdout: (data) => {
157
- output += data.toString();
158
- },
159
- },
160
- });
161
- core.info(` Rust: ${output.trim()}`);
162
- } catch {
163
- core.warning("Rust not available - please set up in a prior step");
164
- }
165
- }
166
-
167
- if (inputs.setupDotnet) {
168
- try {
169
- let output = "";
170
- await exec.exec("dotnet", ["--version"], {
171
- silent: true,
172
- listeners: {
173
- stdout: (data) => {
174
- output += data.toString();
175
- },
176
- },
177
- });
178
- core.info(` .NET: ${output.trim()}`);
179
- } catch {
180
- core.warning(".NET not available - please set up in a prior step");
181
- }
182
- }
183
- }
184
-
185
- /**
186
- * Run initial build for current branch
187
- */
188
- async function runInitialBuild(inputs: ActionInputs): Promise<void> {
189
- core.info("๐Ÿ”จ Running initial build...");
190
-
191
- if (inputs.installCommand) {
192
- core.info(` Install: ${inputs.installCommand}`);
193
- await exec.exec("sh", ["-c", inputs.installCommand]);
194
- }
195
-
196
- if (inputs.buildCommand) {
197
- core.info(` Build: ${inputs.buildCommand}`);
198
- await exec.exec("sh", ["-c", inputs.buildCommand]);
199
- }
200
- }
201
-
202
- /**
203
- * Main action entry point
204
- */
205
- async function run(): Promise<void> {
206
- try {
207
- core.info("๐Ÿš€ MCP Conformance Action");
208
- core.info("");
209
-
210
- // Get inputs
211
- const inputs = getInputs();
212
-
213
- core.info(`๐Ÿ“‹ Configuration:`);
214
- core.info(` Transport: ${inputs.transport}`);
215
- core.info(` Configurations: ${inputs.configurations.length}`);
216
- for (const config of inputs.configurations) {
217
- core.info(` - ${config.name} (${config.transport})`);
218
- }
219
-
220
- // Set up languages
221
- await setupLanguages(inputs);
222
-
223
- // Run initial build
224
- await runInitialBuild(inputs);
225
-
226
- // Determine comparison ref
227
- const currentBranch = await getCurrentBranch();
228
- const compareRef = await determineCompareRef(inputs.compareRef, process.env.GITHUB_REF);
229
- const compareRefDisplay = await getRefDisplayName(compareRef);
230
-
231
- core.info("");
232
- core.info(`๐Ÿ“Š Comparison:`);
233
- core.info(` Current: ${currentBranch}`);
234
- core.info(
235
- ` Compare: ${compareRefDisplay}${compareRefDisplay !== compareRef ? ` (${compareRef.substring(0, 7)})` : ""}`
236
- );
237
-
238
- // Run all tests
239
- core.info("");
240
- core.info("๐Ÿงช Running diff...");
241
-
242
- const workDir = process.cwd();
243
- const results = await runAllTests({
244
- workDir,
245
- inputs,
246
- compareRef,
247
- });
248
-
249
- // Generate and save report
250
- core.info("");
251
- core.info("๐Ÿ“ Generating report...");
252
-
253
- const report = generateReport(results, currentBranch, compareRefDisplay);
254
- const markdown = generateMarkdownReport(report);
255
- saveReport(report, markdown, workDir);
256
-
257
- // Set final status
258
- core.info("");
259
-
260
- // Check for actual probe errors (separate from differences)
261
- const hasErrors = results.some((r) => r.diffs.has("error"));
262
-
263
- if (hasErrors && inputs.failOnError) {
264
- const errorConfigs = results.filter((r) => r.diffs.has("error")).map((r) => r.configName);
265
- core.setFailed(`โŒ Probe errors occurred in: ${errorConfigs.join(", ")}`);
266
- } else if (report.diffCount > 0) {
267
- if (inputs.failOnDiff) {
268
- core.setFailed(`โŒ ${report.diffCount} configuration(s) have API changes`);
269
- } else {
270
- core.info(`๐Ÿ“‹ ${report.diffCount} configuration(s) have API changes`);
271
- }
272
- if (hasErrors) {
273
- core.warning("Some configurations had probe errors (fail_on_error is disabled)");
274
- }
275
- } else {
276
- core.info("โœ… All tests passed - no API changes detected");
277
- }
278
- } catch (error) {
279
- core.setFailed(`Action failed: ${error}`);
280
- }
281
- }
282
-
283
- // Run the action
284
- run();
package/src/logger.ts DELETED
@@ -1,93 +0,0 @@
1
- /**
2
- * Logger abstraction - works in both CLI and GitHub Actions contexts
3
- */
4
-
5
- import * as core from "@actions/core";
6
-
7
- export interface Logger {
8
- info(message: string): void;
9
- warning(message: string): void;
10
- error(message: string): void;
11
- debug(message: string): void;
12
- }
13
-
14
- /**
15
- * GitHub Actions logger - wraps @actions/core
16
- */
17
- export class ActionsLogger implements Logger {
18
- info(message: string): void {
19
- core.info(message);
20
- }
21
-
22
- warning(message: string): void {
23
- core.warning(message);
24
- }
25
-
26
- error(message: string): void {
27
- core.error(message);
28
- }
29
-
30
- debug(message: string): void {
31
- core.debug(message);
32
- }
33
- }
34
-
35
- /**
36
- * Console logger for CLI usage
37
- */
38
- export class ConsoleLogger implements Logger {
39
- private verbose: boolean;
40
-
41
- constructor(verbose: boolean = false) {
42
- this.verbose = verbose;
43
- }
44
-
45
- info(message: string): void {
46
- console.log(message);
47
- }
48
-
49
- warning(message: string): void {
50
- console.log(`โš ๏ธ ${message}`);
51
- }
52
-
53
- error(message: string): void {
54
- console.error(`โŒ ${message}`);
55
- }
56
-
57
- debug(message: string): void {
58
- if (this.verbose) {
59
- console.log(`๐Ÿ” ${message}`);
60
- }
61
- }
62
- }
63
-
64
- /**
65
- * Quiet logger - only outputs errors
66
- */
67
- export class QuietLogger implements Logger {
68
- info(_message: string): void {}
69
- warning(_message: string): void {}
70
- error(message: string): void {
71
- console.error(message);
72
- }
73
- debug(_message: string): void {}
74
- }
75
-
76
- // Global logger instance - defaults to Actions logger for backward compat
77
- let currentLogger: Logger = new ActionsLogger();
78
-
79
- export function setLogger(logger: Logger): void {
80
- currentLogger = logger;
81
- }
82
-
83
- export function getLogger(): Logger {
84
- return currentLogger;
85
- }
86
-
87
- // Convenience exports that use the current logger
88
- export const log = {
89
- info: (message: string) => currentLogger.info(message),
90
- warning: (message: string) => currentLogger.warning(message),
91
- error: (message: string) => currentLogger.error(message),
92
- debug: (message: string) => currentLogger.debug(message),
93
- };