Skip to content
Open
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
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: CI

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
node-version: [18.x, 20.x, 22.x]

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'

- name: Install dependencies
run: npm install

- name: Run tests
run: npm test
6 changes: 3 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ function getComponentDocs(componentName) {

function getSelectedComponentsDocs(componentNames) {
const docsObject = {};
console.log(
console.error(
`✅ Getting documentation for components: ${componentNames.join(", ")}`
);

Expand Down Expand Up @@ -215,7 +215,7 @@ server.tool(
.describe("The names of the components"),
},
(input) => {
console.log(
console.error(
`✅ Selected components: ${input.selectedComponents.join(", ")}`
);

Expand Down Expand Up @@ -247,7 +247,7 @@ server.tool(
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.log("Use Gluestack Components MCP Server running on stdio");
console.error("Use Gluestack Components MCP Server running on stdio");
}

main().catch((error) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "node --test"
},
"keywords": [],
"author": "",
Expand Down
59 changes: 59 additions & 0 deletions test/no-stdout-on-start.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { spawn } from "child_process";
import { test } from "node:test";
import assert from "node:assert";
import { fileURLToPath } from "url";
import path from "path";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

test("MCP server should not write to stdout on startup", async () => {
const indexPath = path.join(__dirname, "..", "index.js");

return new Promise((resolve, reject) => {
const child = spawn("node", [indexPath], {
stdio: ["pipe", "pipe", "pipe"],
});

let stdoutData = "";
let stderrData = "";

child.stdout.on("data", (data) => {
stdoutData += data.toString();
});

child.stderr.on("data", (data) => {
stderrData += data.toString();
});

// Wait 500ms then kill the process and check stdout
setTimeout(() => {
child.kill("SIGTERM");

// Allow time for process to clean up
setTimeout(() => {
try {
// Assert that stdout is empty (no plain text logging)
assert.strictEqual(
stdoutData.trim(),
"",
"stdout should be empty on startup - MCP protocol uses stdout for JSON-RPC messages only"
);

// Verify that stderr contains our log message (optional, but good to confirm logs are going somewhere)
assert.ok(
stderrData.includes("Gluestack") && stderrData.includes("MCP Server"),
"stderr should contain startup message with 'Gluestack' and 'MCP Server'"
);

resolve();
} catch (err) {
reject(err);
}
}, 100);
}, 500);

child.on("error", (err) => {
reject(err);
});
});
});