@gjsify/mcp 0.53.0

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/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # @gjsify/mcp
2
+
3
+ Server-side runtime for [MCP](https://modelcontextprotocol.io) servers that run on GJS and Node. It wraps `@modelcontextprotocol/sdk` (a peer dependency) with the lifecycle pieces every stdio server ends up re-writing.
4
+
5
+ ```ts
6
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7
+ import { applyReadOnlyGate, serveStdio, mcpSuccess } from '@gjsify/mcp';
8
+
9
+ const server = new McpServer({ name: 'demo', version: '1.0.0' });
10
+ applyReadOnlyGate(server, process.env.ALLOW_WRITE === '1'); // BEFORE any registerTool
11
+ server.registerTool('ping', { annotations: { readOnlyHint: true } }, async () => mcpSuccess({ pong: true }));
12
+ await serveStdio(server, 'demo');
13
+ ```
14
+
15
+ ## API
16
+
17
+ | Export | Purpose |
18
+ |---|---|
19
+ | `applyReadOnlyGate(server, allowWrite)` | Default-deny: without `allowWrite`, only tools with `annotations.readOnlyHint === true` are registered. A forgotten annotation drops the tool (visible), it never exposes a mutation (silent). Call it before registering tools. |
20
+ | `serveUntilClosed(server, { label?, stdin?, stdout? })` | Connect over stdio and resolve when the client goes away (stdin `end`/`close` or transport close). The SDK does not report stdin EOF itself, so a server without this outlives its client. Streams are injectable for tests. |
21
+ | `serveStdio(server, label?)` | `serveUntilClosed`, then `process.exit(0)` — needed under GJS, where the GLib main loop keeps an idle process alive. |
22
+ | `mcpSuccess(data)` / `mcpError(msg)` / `mcpErrorFrom(err)` | One result shape: a single JSON text block. Errors carry the message only, never a stack. |
23
+
24
+ Logs go to stderr; stdout is the protocol channel.
25
+
26
+ ## License
27
+
28
+ MIT
@@ -0,0 +1 @@
1
+ var e=Object.defineProperty,__name=(t,n)=>e(t,`name`,{value:n,configurable:!0});export{__name};
@@ -0,0 +1 @@
1
+ import{applyReadOnlyGate as e}from"./read-only-gate.js";import{serveStdio as t,serveUntilClosed as n}from"./serve-stdio.js";import{mcpError as r,mcpErrorFrom as i,mcpSuccess as a}from"./tool-result.js";export{e as applyReadOnlyGate,r as mcpError,i as mcpErrorFrom,a as mcpSuccess,t as serveStdio,n as serveUntilClosed};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";function applyReadOnlyGate(e,t){if(t)return;let n=e.registerTool.bind(e);e.registerTool=((e,t,...r)=>t?.annotations?.readOnlyHint===!0?n(e,t,...r):void 0)}export{applyReadOnlyGate};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";import{StdioServerTransport as e}from"@modelcontextprotocol/sdk/server/stdio.js";async function serveUntilClosed(t,n={}){let r=n.stdin??process.stdin,i=new e(r,n.stdout??process.stdout);await t.connect(i),await new Promise(e=>{let t=i.onclose;i.onclose=()=>{t?.(),e()},r.once(`end`,e),r.once(`close`,e)}),console.error(`[${n.label??`mcp`}] client disconnected (stdin closed) — exiting`)}async function serveStdio(e,t=`mcp`){return await serveUntilClosed(e,{label:t}),process.exit(0)}export{serveStdio,serveUntilClosed};
@@ -0,0 +1 @@
1
+ import"./_virtual/_rolldown/runtime.js";function mcpSuccess(e){return{content:[{type:`text`,text:JSON.stringify(e,null,2)}]}}function mcpError(e){return{content:[{type:`text`,text:JSON.stringify({error:e})}],isError:!0}}function mcpErrorFrom(e){return mcpError(e instanceof Error?e.message:String(e))}export{mcpError,mcpErrorFrom,mcpSuccess};
@@ -0,0 +1,3 @@
1
+ export { applyReadOnlyGate } from './read-only-gate.js';
2
+ export { serveStdio, serveUntilClosed, type ServeOptions } from './serve-stdio.js';
3
+ export { mcpSuccess, mcpError, mcpErrorFrom, type McpTextResult } from './tool-result.js';
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,18 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ /**
3
+ * Gate mutating tools, DEFAULT-DENY.
4
+ *
5
+ * Unless writes are explicitly allowed, a tool is registered ONLY if it proves it is read-only
6
+ * (`annotations.readOnlyHint === true`). A tool that omits the annotation, or mis-sets it, is
7
+ * DROPPED.
8
+ *
9
+ * The direction is the whole point: the obvious spelling — drop only when `readOnlyHint === false`
10
+ * — fails OPEN, silently exposing every tool whose author forgot the annotation. This one fails
11
+ * CLOSED: the failure mode is a tool missing from `tools/list`, which gets noticed and fixed,
12
+ * instead of a mutation quietly reachable. Tools identify themselves by their own annotation, so
13
+ * there is no second name list to drift.
14
+ *
15
+ * Call this BEFORE registering anything — it wraps `registerTool`, so tools registered earlier
16
+ * are already through.
17
+ */
18
+ export declare function applyReadOnlyGate(server: McpServer, allowWrite: boolean): void;
@@ -0,0 +1,34 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { Readable, Writable } from 'node:stream';
3
+ export interface ServeOptions {
4
+ /** Prefix of the stderr line printed on disconnect. Default `mcp`. */
5
+ label?: string;
6
+ /** Input stream. Default `process.stdin`. */
7
+ stdin?: Readable;
8
+ /** Output stream. Default `process.stdout`. */
9
+ stdout?: Writable;
10
+ }
11
+ /**
12
+ * Serve over stdio until the client goes away, then resolve (the process is left alone — see
13
+ * {@link serveStdio} for the variant that exits).
14
+ *
15
+ * Park until EOF — NOT forever. `await new Promise<never>(() => {})` is unsettleable, so a server
16
+ * whose client died keeps running; such processes have been found REPARENTED TO `systemd --user`,
17
+ * which only happens once the spawning process is gone. stdin EOF is the signal: for a stdio
18
+ * child, the parent closing the pipe IS "you are done". The MCP SDK will not report it — its
19
+ * `StdioServerTransport` attaches only `data` and `error` to stdin and calls `close()` solely on
20
+ * explicit shutdown, so EOF is left to the server author.
21
+ *
22
+ * Several concurrently running servers are NOT leaks: each editor session legitimately owns one.
23
+ * The liveness test is a live client ancestor, not membership in the session doing the counting.
24
+ */
25
+ export declare function serveUntilClosed(server: McpServer, options?: ServeOptions): Promise<void>;
26
+ /**
27
+ * {@link serveUntilClosed}, then `process.exit(0)`.
28
+ *
29
+ * The explicit exit is needed because returning is not enough: the GLib main loop a gjsify CLI arms
30
+ * keeps the process parked at 0 % CPU with nothing left to serve, and only `process.exit()` tears
31
+ * it down. `return` in front is the house rule — a bare `process.exit()` under GJS schedules the
32
+ * exit and keeps running, which double-exits.
33
+ */
34
+ export declare function serveStdio(server: McpServer, label?: string): Promise<never>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Every tool answers with a single JSON text block, so a client never has to guess. Errors carry
3
+ * the message only: the underlying failures are protocol errors whose stack traces contain
4
+ * hostnames and paths, and an MCP error string ends up in a transcript that outlives the request.
5
+ */
6
+ export interface McpTextResult {
7
+ content: Array<{
8
+ type: 'text';
9
+ text: string;
10
+ }>;
11
+ isError?: boolean;
12
+ [key: string]: unknown;
13
+ }
14
+ export declare function mcpSuccess(data: unknown): McpTextResult;
15
+ export declare function mcpError(message: string): McpTextResult;
16
+ /** Error result from a caught value (the common `catch` handler). */
17
+ export declare function mcpErrorFrom(err: unknown): McpTextResult;
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@gjsify/mcp",
3
+ "version": "0.53.0",
4
+ "description": "MCP server runtime for GJS and Node — default-deny read-only gate, stdio serve loop that ends on client EOF, uniform tool results",
5
+ "type": "module",
6
+ "module": "lib/esm/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/esm/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "lib"
16
+ ],
17
+ "scripts": {
18
+ "clear": "gjsify clear lib tmp tsconfig.tsbuildinfo test.gjs.mjs test.node.mjs",
19
+ "check": "gjsify tsc --noEmit",
20
+ "build": "gjsify run build:gjsify && gjsify run build:types",
21
+ "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
22
+ "build:types": "gjsify tsc",
23
+ "build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
24
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
25
+ "test": "gjsify run build:gjsify && gjsify run build:test && gjsify run test:node && gjsify run test:gjs",
26
+ "test:gjs": "gjsify run test.gjs.mjs",
27
+ "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
28
+ "test:node": "node test.node.mjs"
29
+ },
30
+ "keywords": [
31
+ "gjs",
32
+ "mcp",
33
+ "stdio",
34
+ "model-context-protocol",
35
+ "agent"
36
+ ],
37
+ "dependencies": {},
38
+ "peerDependencies": {
39
+ "@modelcontextprotocol/sdk": "^1.29.0"
40
+ },
41
+ "devDependencies": {
42
+ "@gjsify/cli": "^0.53.0",
43
+ "@gjsify/stream": "^0.53.0",
44
+ "@gjsify/unit": "^0.53.0",
45
+ "@modelcontextprotocol/sdk": "^1.29.0",
46
+ "@types/node": "^25.9.2",
47
+ "typescript": "^6.0.3"
48
+ },
49
+ "gjsify": {
50
+ "runtimes": {
51
+ "gjs": "polyfill",
52
+ "node": "none",
53
+ "browser": "none",
54
+ "nativescript": "none"
55
+ },
56
+ "tier": 2
57
+ },
58
+ "license": "MIT",
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/gjsify/gjsify.git",
62
+ "directory": "packages/framework/mcp"
63
+ },
64
+ "bugs": {
65
+ "url": "https://github.com/gjsify/gjsify/issues"
66
+ },
67
+ "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/framework/mcp#readme"
68
+ }