Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ jobs:

- uses: actions/setup-node@v4
with:
node-version: '20'
# >=22 required: the `test` script uses --test-coverage-exclude,
# which Node added in 22.5.0. On Node 20 it errors with
# `bad option: --test-coverage-exclude` and the build job hard-fails.
node-version: '22'

- run: npm ci
- run: npm run build
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
# >=22 for --test-coverage-exclude support (added 22.5.0).
node-version: 22
cache: npm
- run: npm ci
- run: npm test -- --coverage
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@
"dev": "tsc --watch",
"start": "node dist/index.js",
"pretest": "tsc && tsc -p tsconfig.test.json",
"test": "node --test dist-test/test/integration.test.js dist-test/test/live-smoke.test.js",
"test": "node --test --experimental-test-coverage --test-coverage-exclude='dist-test/test/**' --test-coverage-exclude='dist/**' --test-coverage-exclude='node_modules/**' dist-test/test/integration.test.js dist-test/test/live-smoke.test.js dist-test/test/client-unit.test.js dist-test/test/index-unit.test.js dist-test/test/tools-unit.test.js",
"test:nocov": "node --test dist-test/test/integration.test.js dist-test/test/live-smoke.test.js dist-test/test/client-unit.test.js dist-test/test/index-unit.test.js dist-test/test/tools-unit.test.js",
"test:smoke": "bash test.sh",
"prepublishOnly": "npm run build"
},
Expand Down
46 changes: 38 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,26 @@ import { nameSchema } from "./name_schema.js";

const client = new InstantClient();

const pkgPath = resolve(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
const pkgVersion = JSON.parse(readFileSync(pkgPath, "utf8")).version as string;
/**
* Resolve the package.json version once at module-init. Falls back to "dev"
* if the file isn't where we expect — same defensive pattern as the
* User-Agent resolver in client.ts, so unit tests that import this module
* from a non-canonical build path (e.g. `dist-test/src/index.js`, two dirs
* removed from the package.json instead of one) don't crash before any
* test code runs. The production binary path (`dist/index.js`) is one
* level under the repo root, so the resolve always finds the file.
*/
function resolvePkgVersion(): string {
try {
const here = dirname(fileURLToPath(import.meta.url));
const pkgPath = resolve(here, "..", "package.json");
return (JSON.parse(readFileSync(pkgPath, "utf8")).version as string) ?? "dev";
} catch {
return "dev";
}
}

const pkgVersion = resolvePkgVersion();

const server = new McpServer({
name: "instanode.dev",
Expand All @@ -85,7 +103,7 @@ const server = new McpServer({
* Upgrade: {upgrade_url, when present}
* Claim: {claim_url, when present}
*/
function formatError(err: unknown): string {
export function formatError(err: unknown): string {
if (err instanceof AuthRequiredError) {
return err.message;
}
Expand Down Expand Up @@ -152,11 +170,11 @@ function formatError(err: unknown): string {
return `instanode.dev error: ${msg}`;
}

function textResult(text: string) {
export function textResult(text: string) {
return { content: [{ type: "text" as const, text }] };
}

function formatLimits(limits: ProvisionLimits | undefined): string[] {
export function formatLimits(limits: ProvisionLimits | undefined): string[] {
const lines: string[] = [];
if (!limits) return lines;
if (typeof limits.storage_mb === "number") lines.push(`Storage: ${limits.storage_mb} MB`);
Expand All @@ -172,7 +190,7 @@ function formatLimits(limits: ProvisionLimits | undefined): string[] {
* so the end user sees the exact CTA + claim URL. Structurally typed so
* it accepts both ProvisionResultBase and DeployResult.
*/
function appendUpgradeBlock(
export function appendUpgradeBlock(
lines: string[],
result: { note?: string; upgrade?: string }
): void {
Expand Down Expand Up @@ -1136,5 +1154,17 @@ Requires INSTANODE_TOKEN.`,

// ── Start server ──────────────────────────────────────────────────────────────

const transport = new StdioServerTransport();
await server.connect(transport);
// Unit tests import this module purely to reach the exported helpers
// (formatError / formatLimits / appendUpgradeBlock) without binding to a real
// stdio transport — set INSTANODE_MCP_NO_LISTEN=1 in that case. The CLI binary
// path (and integration tests that spawn `node dist/index.js`) never set this
// var, so the production behavior is unchanged.
if (!process.env["INSTANODE_MCP_NO_LISTEN"]) {
const transport = new StdioServerTransport();
await server.connect(transport);
}

// Re-export the MCP server so unit tests can introspect the tool registry
// without spawning a subprocess. Production callers ignore this — the binary
// entrypoint only depends on the `await server.connect(...)` above.
export { server };
Loading
Loading