@gramatr/mcp 0.7.3 → 0.7.6
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/dist/bin/add-api-key.d.ts +14 -0
- package/dist/bin/add-api-key.d.ts.map +1 -1
- package/dist/bin/add-api-key.js +11 -12
- package/dist/bin/add-api-key.js.map +1 -1
- package/dist/bin/build-mcpb.d.ts +53 -0
- package/dist/bin/build-mcpb.d.ts.map +1 -1
- package/dist/bin/build-mcpb.js +109 -23
- package/dist/bin/build-mcpb.js.map +1 -1
- package/dist/bin/gramatr-mcp.d.ts +2 -1
- package/dist/bin/gramatr-mcp.d.ts.map +1 -1
- package/dist/bin/gramatr-mcp.js +32 -25
- package/dist/bin/gramatr-mcp.js.map +1 -1
- package/dist/bin/login.d.ts +30 -1
- package/dist/bin/login.d.ts.map +1 -1
- package/dist/bin/login.js +15 -16
- package/dist/bin/login.js.map +1 -1
- package/dist/bin/setup.d.ts +46 -0
- package/dist/bin/setup.d.ts.map +1 -1
- package/dist/bin/setup.js +35 -15
- package/dist/bin/setup.js.map +1 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/lib/feedback.d.ts +0 -2
- package/dist/hooks/lib/feedback.d.ts.map +1 -1
- package/dist/hooks/lib/feedback.js +18 -19
- package/dist/hooks/lib/feedback.js.map +1 -1
- package/dist/hooks/lib/formatting-compat.d.ts.map +1 -1
- package/dist/hooks/lib/formatting-compat.js +82 -55
- package/dist/hooks/lib/formatting-compat.js.map +1 -1
- package/dist/hooks/lib/gramatr-hook-utils.d.ts +0 -59
- package/dist/hooks/lib/gramatr-hook-utils.d.ts.map +1 -1
- package/dist/hooks/lib/gramatr-hook-utils.js +1 -212
- package/dist/hooks/lib/gramatr-hook-utils.js.map +1 -1
- package/dist/hooks/lib/hook-state.d.ts +159 -0
- package/dist/hooks/lib/hook-state.d.ts.map +1 -0
- package/dist/hooks/lib/hook-state.js +430 -0
- package/dist/hooks/lib/hook-state.js.map +1 -0
- package/dist/hooks/lib/intelligence.d.ts.map +1 -1
- package/dist/hooks/lib/intelligence.js +112 -88
- package/dist/hooks/lib/intelligence.js.map +1 -1
- package/dist/hooks/lib/routing.d.ts +2 -2
- package/dist/hooks/lib/routing.d.ts.map +1 -1
- package/dist/hooks/lib/routing.js +71 -25
- package/dist/hooks/lib/routing.js.map +1 -1
- package/dist/hooks/lib/session-end.d.ts +7 -0
- package/dist/hooks/lib/session-end.d.ts.map +1 -1
- package/dist/hooks/lib/session-end.js +33 -16
- package/dist/hooks/lib/session-end.js.map +1 -1
- package/dist/hooks/lib/session.d.ts +7 -34
- package/dist/hooks/lib/session.d.ts.map +1 -1
- package/dist/hooks/lib/session.js +43 -76
- package/dist/hooks/lib/session.js.map +1 -1
- package/dist/hooks/lib/types.d.ts +174 -36
- package/dist/hooks/lib/types.d.ts.map +1 -1
- package/dist/hooks/lib/types.js +1 -0
- package/dist/hooks/lib/types.js.map +1 -1
- package/dist/hooks/session-end.d.ts.map +1 -1
- package/dist/hooks/session-end.js +53 -39
- package/dist/hooks/session-end.js.map +1 -1
- package/dist/hooks/session-start.d.ts.map +1 -1
- package/dist/hooks/session-start.js +87 -79
- package/dist/hooks/session-start.js.map +1 -1
- package/dist/hooks/stop.d.ts.map +1 -1
- package/dist/hooks/stop.js +1 -10
- package/dist/hooks/stop.js.map +1 -1
- package/dist/hooks/user-prompt-submit.d.ts.map +1 -1
- package/dist/hooks/user-prompt-submit.js +57 -67
- package/dist/hooks/user-prompt-submit.js.map +1 -1
- package/dist/intelligence/packet2-fetcher.d.ts +3 -3
- package/dist/intelligence/packet2-fetcher.js +8 -8
- package/dist/intelligence/packet2-fetcher.js.map +1 -1
- package/dist/proxy/local-client.d.ts +49 -0
- package/dist/proxy/local-client.d.ts.map +1 -0
- package/dist/proxy/local-client.js +135 -0
- package/dist/proxy/local-client.js.map +1 -0
- package/dist/server/auth.d.ts +6 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +40 -32
- package/dist/server/auth.js.map +1 -1
- package/dist/server/hooks-listener.d.ts +32 -0
- package/dist/server/hooks-listener.d.ts.map +1 -0
- package/dist/server/hooks-listener.js +144 -0
- package/dist/server/hooks-listener.js.map +1 -0
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +18 -3
- package/dist/server/server.js.map +1 -1
- package/dist/setup/instructions.d.ts +2 -0
- package/dist/setup/instructions.d.ts.map +1 -1
- package/dist/setup/instructions.js +59 -0
- package/dist/setup/instructions.js.map +1 -1
- package/dist/setup/integrations.js +3 -3
- package/dist/setup/integrations.js.map +1 -1
- package/dist/tools/local-tools.d.ts.map +1 -1
- package/dist/tools/local-tools.js +2 -1
- package/dist/tools/local-tools.js.map +1 -1
- package/package.json +5 -2
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Packet 2 Auto-Fetcher — automatically fetches enrichment when route_request
|
|
3
|
-
* returns packet_2_status: "
|
|
3
|
+
* returns packet_2_status: "required".
|
|
4
4
|
*
|
|
5
5
|
* Flow:
|
|
6
6
|
* 1. Agent calls gramatr_route_request
|
|
7
7
|
* 2. Server returns Packet 1 immediately with enrichment_id
|
|
8
|
-
* 3. This module detects
|
|
8
|
+
* 3. This module detects required status, calls gramatr_get_enrichment
|
|
9
9
|
* 4. Merges Packet 2 into the response before returning to agent
|
|
10
10
|
*
|
|
11
11
|
* The agent never has to manually call gramatr_get_enrichment — it just works.
|
|
@@ -14,14 +14,14 @@ import { callRemoteTool } from '../proxy/remote-client.js';
|
|
|
14
14
|
const MAX_RETRIES = 3;
|
|
15
15
|
const RETRY_DELAY_MS = 500;
|
|
16
16
|
/**
|
|
17
|
-
* Check if a tool result is a route_request response with
|
|
17
|
+
* Check if a tool result is a route_request response with required Packet 2.
|
|
18
18
|
*/
|
|
19
19
|
export function hasPendingPacket2(result) {
|
|
20
20
|
if (!result.content?.[0]?.text)
|
|
21
21
|
return { pending: false };
|
|
22
22
|
try {
|
|
23
23
|
const data = JSON.parse(result.content[0].text);
|
|
24
|
-
if (data.packet_2_status === '
|
|
24
|
+
if (data.packet_2_status === 'required' && data.enrichment_id) {
|
|
25
25
|
return { pending: true, enrichmentId: data.enrichment_id };
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -59,9 +59,9 @@ export async function fetchAndMergePacket2(result, enrichmentId) {
|
|
|
59
59
|
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
|
-
// Enrichment not ready after retries — return original Packet 1 as-is
|
|
63
|
-
//
|
|
64
|
-
process.stderr.write(`[gramatr-mcp] Packet 2
|
|
62
|
+
// Enrichment not ready after retries — return original Packet 1 as-is and
|
|
63
|
+
// leave the required status intact so the caller can keep polling.
|
|
64
|
+
process.stderr.write(`[gramatr-mcp] Packet 2 still required after ${MAX_RETRIES} retries; returning Packet 1 and preserving required status\n`);
|
|
65
65
|
return result;
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
@@ -73,7 +73,7 @@ function mergePackets(packet1, packet2) {
|
|
|
73
73
|
const p1Data = JSON.parse(packet1.content[0].text);
|
|
74
74
|
const p2Data = JSON.parse(packet2.content[0].text);
|
|
75
75
|
// Merge Packet 2 into Packet 1
|
|
76
|
-
p1Data.packet_2 = p2Data;
|
|
76
|
+
p1Data.packet_2 = p2Data.packet_2 || p2Data;
|
|
77
77
|
p1Data.packet_2_status = 'merged';
|
|
78
78
|
return {
|
|
79
79
|
content: [{ type: 'text', text: JSON.stringify(p1Data) }],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"packet2-fetcher.js","sourceRoot":"","sources":["../../src/intelligence/packet2-fetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAa3D,MAAM,WAAW,GAAG,CAAC,CAAC;AACtB,MAAM,cAAc,GAAG,GAAG,CAAC;AAE3B;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAsB;IACtD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAE1D,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAwB,CAAC;QACvE,IAAI,IAAI,CAAC,eAAe,KAAK,
|
|
1
|
+
{"version":3,"file":"packet2-fetcher.js","sourceRoot":"","sources":["../../src/intelligence/packet2-fetcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* local-client.ts — Hook-side client for the local MCP hooks listener.
|
|
3
|
+
*
|
|
4
|
+
* Phase IV: Hook subprocesses call this module instead of remote-client.ts.
|
|
5
|
+
* Routes all tool calls and session-context reads/writes through the local
|
|
6
|
+
* MCP server when it is running, falling back to direct remote calls otherwise.
|
|
7
|
+
*
|
|
8
|
+
* Priority:
|
|
9
|
+
* 1. Local hooks server (localhost:PORT from ~/.gramatr/.hooks-port)
|
|
10
|
+
* → auth already loaded in server process, no token resolution needed
|
|
11
|
+
* → data shape validated before any remote call
|
|
12
|
+
* 2. Direct remote (callRemoteTool from remote-client.ts)
|
|
13
|
+
* → hooks resolve their own auth token as before
|
|
14
|
+
*
|
|
15
|
+
* The local server's port file is read once per hook process lifetime and
|
|
16
|
+
* cached. Stale port files (server crashed without cleanup) are caught by a
|
|
17
|
+
* connection error on first use, which clears the cache and falls through to
|
|
18
|
+
* direct remote.
|
|
19
|
+
*/
|
|
20
|
+
/** True when the local hooks server port file exists and contains a valid port. */
|
|
21
|
+
export declare function isLocalHooksServerAvailable(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Call a gramatr tool, routing through the local hooks server when available.
|
|
24
|
+
*
|
|
25
|
+
* The local server injects auth and enforces data shapes before forwarding to
|
|
26
|
+
* the remote API. If the local server is unavailable or returns an error,
|
|
27
|
+
* falls back to a direct remote call via callRemoteTool.
|
|
28
|
+
*/
|
|
29
|
+
export declare function callTool(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
30
|
+
/**
|
|
31
|
+
* Push session context to the local server's in-memory store.
|
|
32
|
+
*
|
|
33
|
+
* Called by the session-start hook after setSessionContext() so later hook
|
|
34
|
+
* processes (user-prompt-submit, session-end) can read the context without
|
|
35
|
+
* hitting the SQLite file or the remote REST API.
|
|
36
|
+
*
|
|
37
|
+
* Returns true if the write was acknowledged. A false return is non-fatal —
|
|
38
|
+
* later hooks fall back to SQLite → remote REST.
|
|
39
|
+
*/
|
|
40
|
+
export declare function pushSessionContextToLocal(ctx: unknown): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Pull session context from the local server's in-memory store.
|
|
43
|
+
*
|
|
44
|
+
* Returns the stored context for the given session_id, or the most recently
|
|
45
|
+
* written context as a fallback. Returns null if the local server is
|
|
46
|
+
* unavailable or has no context for this session.
|
|
47
|
+
*/
|
|
48
|
+
export declare function pullSessionContextFromLocal(sessionId: string): Promise<Record<string, unknown> | null>;
|
|
49
|
+
//# sourceMappingURL=local-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-client.d.ts","sourceRoot":"","sources":["../../src/proxy/local-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA6BH,mFAAmF;AACnF,wBAAgB,2BAA2B,IAAI,OAAO,CAErD;AAID;;;;;;GAMG;AACH,wBAAsB,QAAQ,CAC5B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,OAAO,CAAC,CAuBlB;AAID;;;;;;;;;GASG;AACH,wBAAsB,yBAAyB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAe9E;AAED;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAezC"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* local-client.ts — Hook-side client for the local MCP hooks listener.
|
|
3
|
+
*
|
|
4
|
+
* Phase IV: Hook subprocesses call this module instead of remote-client.ts.
|
|
5
|
+
* Routes all tool calls and session-context reads/writes through the local
|
|
6
|
+
* MCP server when it is running, falling back to direct remote calls otherwise.
|
|
7
|
+
*
|
|
8
|
+
* Priority:
|
|
9
|
+
* 1. Local hooks server (localhost:PORT from ~/.gramatr/.hooks-port)
|
|
10
|
+
* → auth already loaded in server process, no token resolution needed
|
|
11
|
+
* → data shape validated before any remote call
|
|
12
|
+
* 2. Direct remote (callRemoteTool from remote-client.ts)
|
|
13
|
+
* → hooks resolve their own auth token as before
|
|
14
|
+
*
|
|
15
|
+
* The local server's port file is read once per hook process lifetime and
|
|
16
|
+
* cached. Stale port files (server crashed without cleanup) are caught by a
|
|
17
|
+
* connection error on first use, which clears the cache and falls through to
|
|
18
|
+
* direct remote.
|
|
19
|
+
*/
|
|
20
|
+
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { join } from 'node:path';
|
|
22
|
+
import { getGramatrDirFromEnv, getHomeDir } from '../config-runtime.js';
|
|
23
|
+
import { callRemoteTool } from './remote-client.js';
|
|
24
|
+
// ── Port discovery ──
|
|
25
|
+
function getPortFilePath() {
|
|
26
|
+
const dir = getGramatrDirFromEnv() || join(getHomeDir(), '.gramatr');
|
|
27
|
+
return join(dir, '.hooks-port');
|
|
28
|
+
}
|
|
29
|
+
// undefined = not yet read; null = not available; number = available
|
|
30
|
+
let _cachedPort = undefined;
|
|
31
|
+
function getLocalPort() {
|
|
32
|
+
if (_cachedPort !== undefined)
|
|
33
|
+
return _cachedPort;
|
|
34
|
+
try {
|
|
35
|
+
const raw = readFileSync(getPortFilePath(), 'utf8').trim();
|
|
36
|
+
const port = parseInt(raw, 10);
|
|
37
|
+
_cachedPort = Number.isFinite(port) && port > 0 ? port : null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
_cachedPort = null;
|
|
41
|
+
}
|
|
42
|
+
return _cachedPort;
|
|
43
|
+
}
|
|
44
|
+
/** True when the local hooks server port file exists and contains a valid port. */
|
|
45
|
+
export function isLocalHooksServerAvailable() {
|
|
46
|
+
return getLocalPort() !== null;
|
|
47
|
+
}
|
|
48
|
+
// ── Tool call proxy ──
|
|
49
|
+
/**
|
|
50
|
+
* Call a gramatr tool, routing through the local hooks server when available.
|
|
51
|
+
*
|
|
52
|
+
* The local server injects auth and enforces data shapes before forwarding to
|
|
53
|
+
* the remote API. If the local server is unavailable or returns an error,
|
|
54
|
+
* falls back to a direct remote call via callRemoteTool.
|
|
55
|
+
*/
|
|
56
|
+
export async function callTool(name, args) {
|
|
57
|
+
const port = getLocalPort();
|
|
58
|
+
if (port !== null) {
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(`http://127.0.0.1:${port}/hooks/tool`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: { 'Content-Type': 'application/json' },
|
|
63
|
+
body: JSON.stringify({ name, args }),
|
|
64
|
+
signal: AbortSignal.timeout(30000),
|
|
65
|
+
});
|
|
66
|
+
// gramatr-allow: B1 — HTTP transport sentinel, caught immediately in the enclosing try/catch
|
|
67
|
+
if (!res.ok)
|
|
68
|
+
throw new Error(`HTTP ${res.status}`);
|
|
69
|
+
const data = await res.json();
|
|
70
|
+
// gramatr-allow: B1 — error string forwarded from local server, caught immediately in the enclosing try/catch
|
|
71
|
+
if (data.error)
|
|
72
|
+
throw new Error(data.error);
|
|
73
|
+
return data.result;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Local server failed (stale port file, crash, etc.) — invalidate cache
|
|
77
|
+
// and fall through to direct remote.
|
|
78
|
+
_cachedPort = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return callRemoteTool(name, args);
|
|
82
|
+
}
|
|
83
|
+
// ── Session context IPC ──
|
|
84
|
+
/**
|
|
85
|
+
* Push session context to the local server's in-memory store.
|
|
86
|
+
*
|
|
87
|
+
* Called by the session-start hook after setSessionContext() so later hook
|
|
88
|
+
* processes (user-prompt-submit, session-end) can read the context without
|
|
89
|
+
* hitting the SQLite file or the remote REST API.
|
|
90
|
+
*
|
|
91
|
+
* Returns true if the write was acknowledged. A false return is non-fatal —
|
|
92
|
+
* later hooks fall back to SQLite → remote REST.
|
|
93
|
+
*/
|
|
94
|
+
export async function pushSessionContextToLocal(ctx) {
|
|
95
|
+
const port = getLocalPort();
|
|
96
|
+
if (port === null)
|
|
97
|
+
return false;
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(`http://127.0.0.1:${port}/hooks/session`, {
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: { 'Content-Type': 'application/json' },
|
|
102
|
+
body: JSON.stringify(ctx),
|
|
103
|
+
signal: AbortSignal.timeout(3000),
|
|
104
|
+
});
|
|
105
|
+
return res.ok;
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
_cachedPort = null; // Invalidate on connection failure
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Pull session context from the local server's in-memory store.
|
|
114
|
+
*
|
|
115
|
+
* Returns the stored context for the given session_id, or the most recently
|
|
116
|
+
* written context as a fallback. Returns null if the local server is
|
|
117
|
+
* unavailable or has no context for this session.
|
|
118
|
+
*/
|
|
119
|
+
export async function pullSessionContextFromLocal(sessionId) {
|
|
120
|
+
const port = getLocalPort();
|
|
121
|
+
if (port === null)
|
|
122
|
+
return null;
|
|
123
|
+
try {
|
|
124
|
+
const res = await fetch(`http://127.0.0.1:${port}/hooks/session?session_id=${encodeURIComponent(sessionId)}`, { signal: AbortSignal.timeout(3000) });
|
|
125
|
+
if (!res.ok)
|
|
126
|
+
return null;
|
|
127
|
+
const data = await res.json();
|
|
128
|
+
return data.ctx;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
_cachedPort = null;
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=local-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-client.js","sourceRoot":"","sources":["../../src/proxy/local-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEpD,uBAAuB;AAEvB,SAAS,eAAe;IACtB,MAAM,GAAG,GAAG,oBAAoB,EAAE,IAAI,IAAI,CAAC,UAAU,EAAE,EAAE,UAAU,CAAC,CAAC;IACrE,OAAO,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC;AAClC,CAAC;AAED,qEAAqE;AACrE,IAAI,WAAW,GAA8B,SAAS,CAAC;AAEvD,SAAS,YAAY;IACnB,IAAI,WAAW,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,eAAe,EAAE,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3D,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAC/B,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,WAAW,GAAG,IAAI,CAAC;IACrB,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,2BAA2B;IACzC,OAAO,YAAY,EAAE,KAAK,IAAI,CAAC;AACjC,CAAC;AAED,wBAAwB;AAExB;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,IAAY,EACZ,IAA6B;IAE7B,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,oBAAoB,IAAI,aAAa,EAAE;gBAC7D,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;gBACpC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;aACnC,CAAC,CAAC;YACH,6FAA6F;YAC7F,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,MAAM,IAAI,KAAK,CAAC,QAAQ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;YACnD,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA0C,CAAC;YACtE,8GAA8G;YAC9G,IAAI,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC5C,OAAO,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,wEAAwE;YACxE,qCAAqC;YACrC,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;IACD,OAAO,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACpC,CAAC;AAED,4BAA4B;AAE5B;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAAC,GAAY;IAC1D,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,oBAAoB,IAAI,gBAAgB,EAAE;YAChE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC;YACzB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;SAClC,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,EAAE,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,WAAW,GAAG,IAAI,CAAC,CAAC,mCAAmC;QACvD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,2BAA2B,CAC/C,SAAiB;IAEjB,MAAM,IAAI,GAAG,YAAY,EAAE,CAAC;IAC5B,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,KAAK,CACrB,oBAAoB,IAAI,6BAA6B,kBAAkB,CAAC,SAAS,CAAC,EAAE,EACpF,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CACtC,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,IAAI,CAAC;QACzB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAA6C,CAAC;QACzE,OAAO,IAAI,CAAC,GAAG,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,WAAW,GAAG,IAAI,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
package/dist/server/auth.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth token resolution for the local MCP server.
|
|
3
3
|
*
|
|
4
|
-
* Reads the auth token
|
|
4
|
+
* Reads the auth token from the same canonical sources used by the rest of the
|
|
5
|
+
* client stack:
|
|
6
|
+
* 1. GRAMATR_API_KEY
|
|
7
|
+
* 2. GRAMATR_TOKEN / AIOS_MCP_TOKEN
|
|
8
|
+
* 3. ~/.gramatr.json (or $GRAMATR_DIR/../.gramatr.json)
|
|
9
|
+
*
|
|
5
10
|
* Injects as Authorization: Bearer on every proxied request.
|
|
6
11
|
* Re-reads on 401 from remote (token may have been refreshed).
|
|
7
12
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuCH;;;GAGG;AACH,wBAAgB,QAAQ,IAAI,MAAM,GAAG,IAAI,CAGxC;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,GAAG,IAAI,CAgB5C;AAED;;;GAGG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAQrC"}
|
package/dist/server/auth.js
CHANGED
|
@@ -1,16 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auth token resolution for the local MCP server.
|
|
3
3
|
*
|
|
4
|
-
* Reads the auth token
|
|
4
|
+
* Reads the auth token from the same canonical sources used by the rest of the
|
|
5
|
+
* client stack:
|
|
6
|
+
* 1. GRAMATR_API_KEY
|
|
7
|
+
* 2. GRAMATR_TOKEN / AIOS_MCP_TOKEN
|
|
8
|
+
* 3. ~/.gramatr.json (or $GRAMATR_DIR/../.gramatr.json)
|
|
9
|
+
*
|
|
5
10
|
* Injects as Authorization: Bearer on every proxied request.
|
|
6
11
|
* Re-reads on 401 from remote (token may have been refreshed).
|
|
7
12
|
*/
|
|
8
13
|
import { readFileSync } from 'node:fs';
|
|
9
|
-
import { join } from 'node:path';
|
|
10
|
-
|
|
11
|
-
const HOME = process.env.HOME || process.env.USERPROFILE || '';
|
|
12
|
-
const CONFIG_PATH = join(HOME, '.gramatr.json');
|
|
14
|
+
import { dirname, join } from 'node:path';
|
|
15
|
+
import { getGramatrDirFromEnv, getGramatrTokenFromEnv, getGramatrUrlFromEnv, getHomeDir, } from '../config-runtime.js';
|
|
13
16
|
let cachedToken = null;
|
|
17
|
+
function getConfigPath() {
|
|
18
|
+
const gramatrDir = getGramatrDirFromEnv();
|
|
19
|
+
if (gramatrDir) {
|
|
20
|
+
return join(dirname(gramatrDir), '.gramatr.json');
|
|
21
|
+
}
|
|
22
|
+
const home = getHomeDir();
|
|
23
|
+
return join(home, '.gramatr.json');
|
|
24
|
+
}
|
|
25
|
+
function readConfig() {
|
|
26
|
+
try {
|
|
27
|
+
const raw = readFileSync(getConfigPath(), 'utf8');
|
|
28
|
+
return JSON.parse(raw);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
14
34
|
/**
|
|
15
35
|
* Read the auth token from ~/.gramatr.json.
|
|
16
36
|
* Caches in memory — call refreshToken() to force re-read.
|
|
@@ -25,42 +45,30 @@ export function getToken() {
|
|
|
25
45
|
* Called on startup and on 401 from remote.
|
|
26
46
|
*/
|
|
27
47
|
export function refreshToken() {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// API key takes priority (env var)
|
|
32
|
-
// gramatr-allow: C1 — MCP package is standalone config reader
|
|
33
|
-
const envKey = process.env.GRAMATR_API_KEY;
|
|
34
|
-
if (envKey) {
|
|
35
|
-
cachedToken = envKey;
|
|
36
|
-
return cachedToken;
|
|
37
|
-
}
|
|
38
|
-
cachedToken = config.token || null;
|
|
48
|
+
const envKey = process.env.GRAMATR_API_KEY;
|
|
49
|
+
if (envKey) {
|
|
50
|
+
cachedToken = envKey;
|
|
39
51
|
return cachedToken;
|
|
40
52
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
cachedToken =
|
|
44
|
-
return
|
|
53
|
+
const envToken = getGramatrTokenFromEnv();
|
|
54
|
+
if (envToken) {
|
|
55
|
+
cachedToken = envToken;
|
|
56
|
+
return cachedToken;
|
|
45
57
|
}
|
|
58
|
+
const config = readConfig();
|
|
59
|
+
cachedToken = config?.token || null;
|
|
60
|
+
return cachedToken;
|
|
46
61
|
}
|
|
47
62
|
/**
|
|
48
63
|
* Get the remote server base URL.
|
|
49
64
|
* Reads from env or config file.
|
|
50
65
|
*/
|
|
51
66
|
export function getServerUrl() {
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
|
|
55
|
-
return process.env.GRAMATR_URL.replace(/\/mcp\/?$/, '');
|
|
56
|
-
}
|
|
57
|
-
try {
|
|
58
|
-
const raw = readFileSync(CONFIG_PATH, 'utf8');
|
|
59
|
-
const config = JSON.parse(raw);
|
|
60
|
-
return (config.server_url || 'https://api.gramatr.com').replace(/\/mcp\/?$/, '');
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
return 'https://api.gramatr.com';
|
|
67
|
+
const envUrl = getGramatrUrlFromEnv();
|
|
68
|
+
if (envUrl) {
|
|
69
|
+
return envUrl.replace(/\/mcp\/?$/, '');
|
|
64
70
|
}
|
|
71
|
+
const config = readConfig();
|
|
72
|
+
return (config?.server_url || 'https://api.gramatr.com').replace(/\/mcp\/?$/, '');
|
|
65
73
|
}
|
|
66
74
|
//# sourceMappingURL=auth.js.map
|
package/dist/server/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,oBAAoB,EACpB,sBAAsB,EACtB,oBAAoB,EACpB,UAAU,GACX,MAAM,sBAAsB,CAAC;AAS9B,IAAI,WAAW,GAAkB,IAAI,CAAC;AAEtC,SAAS,aAAa;IACpB,MAAM,UAAU,GAAG,oBAAoB,EAAE,CAAC;IAC1C,IAAI,UAAU,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,eAAe,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,OAAO,IAAI,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;AACrC,CAAC;AAED,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,aAAa,EAAE,EAAE,MAAM,CAAC,CAAC;QAClD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAkB,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,QAAQ;IACtB,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IACpC,OAAO,YAAY,EAAE,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IAC3C,IAAI,MAAM,EAAE,CAAC;QACX,WAAW,GAAG,MAAM,CAAC;QACrB,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,QAAQ,GAAG,sBAAsB,EAAE,CAAC;IAC1C,IAAI,QAAQ,EAAE,CAAC;QACb,WAAW,GAAG,QAAQ,CAAC;QACvB,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,WAAW,GAAG,MAAM,EAAE,KAAK,IAAI,IAAI,CAAC;IACpC,OAAO,WAAW,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAC;IACtC,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,OAAO,CAAC,MAAM,EAAE,UAAU,IAAI,yBAAyB,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;AACpF,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hooks-listener.ts — Local HTTP IPC server for hook subprocesses.
|
|
3
|
+
*
|
|
4
|
+
* Phase IV: The local MCP proxy server opens a lightweight HTTP listener on a
|
|
5
|
+
* random localhost port at startup and writes the port to ~/.gramatr/.hooks-port.
|
|
6
|
+
* Hook subprocesses (session-start, user-prompt-submit, session-end) discover
|
|
7
|
+
* this port and route calls through the local server instead of calling the
|
|
8
|
+
* remote server directly.
|
|
9
|
+
*
|
|
10
|
+
* Benefits:
|
|
11
|
+
* - Auth managed in one place: the local server has the token; hooks never
|
|
12
|
+
* need to resolve or refresh it themselves.
|
|
13
|
+
* - Single enforcement point for data shapes and schema validation before
|
|
14
|
+
* any call reaches the remote API.
|
|
15
|
+
* - Cross-hook session context via in-process memory (no SQLite file needed).
|
|
16
|
+
* - Faster: localhost vs. internet round-trip for session context reads.
|
|
17
|
+
*
|
|
18
|
+
* Fallback: if the port file is absent (local server not running), hooks fall
|
|
19
|
+
* back to direct remote calls with their own auth resolution.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Start the hooks IPC listener on a random localhost port.
|
|
23
|
+
* Writes the port to ~/.gramatr/.hooks-port so hook subprocesses can find it.
|
|
24
|
+
* Returns the assigned port number.
|
|
25
|
+
*/
|
|
26
|
+
export declare function startHooksListener(): Promise<number>;
|
|
27
|
+
/**
|
|
28
|
+
* Stop the hooks listener and remove the port file.
|
|
29
|
+
* Called on server shutdown so stale hooks know to fall back to direct remote.
|
|
30
|
+
*/
|
|
31
|
+
export declare function stopHooksListener(): void;
|
|
32
|
+
//# sourceMappingURL=hooks-listener.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-listener.d.ts","sourceRoot":"","sources":["../../src/server/hooks-listener.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA2FH;;;;GAIG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CA4B1D;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAIxC"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hooks-listener.ts — Local HTTP IPC server for hook subprocesses.
|
|
3
|
+
*
|
|
4
|
+
* Phase IV: The local MCP proxy server opens a lightweight HTTP listener on a
|
|
5
|
+
* random localhost port at startup and writes the port to ~/.gramatr/.hooks-port.
|
|
6
|
+
* Hook subprocesses (session-start, user-prompt-submit, session-end) discover
|
|
7
|
+
* this port and route calls through the local server instead of calling the
|
|
8
|
+
* remote server directly.
|
|
9
|
+
*
|
|
10
|
+
* Benefits:
|
|
11
|
+
* - Auth managed in one place: the local server has the token; hooks never
|
|
12
|
+
* need to resolve or refresh it themselves.
|
|
13
|
+
* - Single enforcement point for data shapes and schema validation before
|
|
14
|
+
* any call reaches the remote API.
|
|
15
|
+
* - Cross-hook session context via in-process memory (no SQLite file needed).
|
|
16
|
+
* - Faster: localhost vs. internet round-trip for session context reads.
|
|
17
|
+
*
|
|
18
|
+
* Fallback: if the port file is absent (local server not running), hooks fall
|
|
19
|
+
* back to direct remote calls with their own auth resolution.
|
|
20
|
+
*/
|
|
21
|
+
import { createServer } from 'node:http';
|
|
22
|
+
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
23
|
+
import { join } from 'node:path';
|
|
24
|
+
import { getGramatrDirFromEnv, getHomeDir } from '../config-runtime.js';
|
|
25
|
+
import { callRemoteTool } from '../proxy/remote-client.js';
|
|
26
|
+
// ── In-memory session context store ──
|
|
27
|
+
// Keys: session_id (string) + '__latest__' sentinel for the most recent write.
|
|
28
|
+
// Hooks that don't know the session_id (e.g. a status query) fall back to __latest__.
|
|
29
|
+
const sessionStore = new Map();
|
|
30
|
+
// ── Port file path ──
|
|
31
|
+
function getPortFilePath() {
|
|
32
|
+
const dir = getGramatrDirFromEnv() || join(getHomeDir(), '.gramatr');
|
|
33
|
+
return join(dir, '.hooks-port');
|
|
34
|
+
}
|
|
35
|
+
// ── Request helpers ──
|
|
36
|
+
async function readBody(req) {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
const chunks = [];
|
|
39
|
+
req.on('data', (chunk) => chunks.push(chunk));
|
|
40
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
41
|
+
req.on('error', () => resolve(''));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function json(res, status, body) {
|
|
45
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
46
|
+
res.end(JSON.stringify(body));
|
|
47
|
+
}
|
|
48
|
+
// ── Route handlers ──
|
|
49
|
+
async function handleRequest(req, res) {
|
|
50
|
+
const url = new URL(req.url || '/', 'http://localhost');
|
|
51
|
+
const method = (req.method || 'GET').toUpperCase();
|
|
52
|
+
// ── POST /hooks/tool — proxy a gramatr tool call with server-managed auth ──
|
|
53
|
+
if (url.pathname === '/hooks/tool' && method === 'POST') {
|
|
54
|
+
const raw = await readBody(req);
|
|
55
|
+
try {
|
|
56
|
+
const { name, args } = JSON.parse(raw);
|
|
57
|
+
if (!name || typeof name !== 'string') {
|
|
58
|
+
json(res, 400, { error: 'name is required' });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const result = await callRemoteTool(name, args ?? {});
|
|
62
|
+
json(res, 200, { result });
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
json(res, 502, { error: err instanceof Error ? err.message : String(err) });
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// ── GET /hooks/session?session_id=xxx — retrieve session context ──
|
|
70
|
+
if (url.pathname === '/hooks/session' && method === 'GET') {
|
|
71
|
+
const sessionId = url.searchParams.get('session_id');
|
|
72
|
+
const ctx = (sessionId ? sessionStore.get(sessionId) : null)
|
|
73
|
+
?? sessionStore.get('__latest__')
|
|
74
|
+
?? null;
|
|
75
|
+
json(res, 200, { ctx });
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// ── POST /hooks/session — store session context ──
|
|
79
|
+
if (url.pathname === '/hooks/session' && method === 'POST') {
|
|
80
|
+
const raw = await readBody(req);
|
|
81
|
+
try {
|
|
82
|
+
const ctx = JSON.parse(raw);
|
|
83
|
+
const sessionId = ctx?.session_id;
|
|
84
|
+
if (sessionId)
|
|
85
|
+
sessionStore.set(sessionId, ctx);
|
|
86
|
+
sessionStore.set('__latest__', ctx);
|
|
87
|
+
json(res, 200, { ok: true });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
json(res, 400, { error: 'Invalid JSON' });
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
json(res, 404, { error: 'Not found' });
|
|
95
|
+
}
|
|
96
|
+
// ── Lifecycle ──
|
|
97
|
+
let _server = null;
|
|
98
|
+
/**
|
|
99
|
+
* Start the hooks IPC listener on a random localhost port.
|
|
100
|
+
* Writes the port to ~/.gramatr/.hooks-port so hook subprocesses can find it.
|
|
101
|
+
* Returns the assigned port number.
|
|
102
|
+
*/
|
|
103
|
+
export async function startHooksListener() {
|
|
104
|
+
// Ensure the directory exists for the port file
|
|
105
|
+
const dir = getGramatrDirFromEnv() || join(getHomeDir(), '.gramatr');
|
|
106
|
+
try {
|
|
107
|
+
if (!existsSync(dir))
|
|
108
|
+
mkdirSync(dir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
catch { /* non-critical — port file write will fail gracefully below */ }
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
_server = createServer((req, res) => {
|
|
113
|
+
handleRequest(req, res).catch((err) => {
|
|
114
|
+
json(res, 500, { error: err instanceof Error ? err.message : String(err) });
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
_server.listen(0, '127.0.0.1', () => {
|
|
118
|
+
const addr = _server.address();
|
|
119
|
+
const port = typeof addr === 'object' && addr !== null ? addr.port : 0;
|
|
120
|
+
try {
|
|
121
|
+
// mode 0o600: readable only by the current user
|
|
122
|
+
writeFileSync(getPortFilePath(), String(port), { mode: 0o600 });
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Non-critical — hooks will fall back to SQLite + direct remote
|
|
126
|
+
}
|
|
127
|
+
resolve(port);
|
|
128
|
+
});
|
|
129
|
+
_server.once('error', reject);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Stop the hooks listener and remove the port file.
|
|
134
|
+
* Called on server shutdown so stale hooks know to fall back to direct remote.
|
|
135
|
+
*/
|
|
136
|
+
export function stopHooksListener() {
|
|
137
|
+
_server?.close();
|
|
138
|
+
_server = null;
|
|
139
|
+
try {
|
|
140
|
+
unlinkSync(getPortFilePath());
|
|
141
|
+
}
|
|
142
|
+
catch { /* best-effort cleanup */ }
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=hooks-listener.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks-listener.js","sourceRoot":"","sources":["../../src/server/hooks-listener.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AAyBnE;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAuDrC;AAED;;GAEG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC,CAuEjD"}
|
package/dist/server/server.js
CHANGED
|
@@ -25,16 +25,17 @@ import { loadRemoteResources, getResources } from '../proxy/resource-registry.js
|
|
|
25
25
|
import { proxyToolCall } from '../proxy/tool-proxy.js';
|
|
26
26
|
import { fetchRemotePrompt, fetchRemoteResource } from '../proxy/remote-client.js';
|
|
27
27
|
import { getToken } from './auth.js';
|
|
28
|
+
import { startHooksListener, stopHooksListener } from './hooks-listener.js';
|
|
28
29
|
import { getLocalToolDefinitions } from '../tools/local-tools.js';
|
|
29
30
|
import { replayQueue } from '../queue/replay.js';
|
|
30
31
|
import { queueSize } from '../queue/offline-queue.js';
|
|
32
|
+
import { VERSION } from '../hooks/lib/version.js';
|
|
31
33
|
const SERVER_NAME = 'gramatr-mcp';
|
|
32
|
-
const SERVER_VERSION = '0.0.5';
|
|
33
34
|
/**
|
|
34
35
|
* Create and configure the MCP server.
|
|
35
36
|
*/
|
|
36
37
|
export function createServer() {
|
|
37
|
-
const server = new Server({ name: SERVER_NAME, version:
|
|
38
|
+
const server = new Server({ name: SERVER_NAME, version: VERSION }, { capabilities: { tools: {}, prompts: {}, resources: {} } });
|
|
38
39
|
// ── tools/list handler — remote + local tools ──
|
|
39
40
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
40
41
|
const remoteTools = getTools();
|
|
@@ -124,10 +125,24 @@ export async function startServer() {
|
|
|
124
125
|
process.stderr.write(`[gramatr-mcp] Found ${pending} queued call(s), replaying...\n`);
|
|
125
126
|
void replayQueue();
|
|
126
127
|
}
|
|
127
|
-
// Create and start the server
|
|
128
|
+
// Create and start the MCP stdio server
|
|
128
129
|
const server = createServer();
|
|
129
130
|
const transport = new StdioServerTransport();
|
|
130
131
|
await server.connect(transport);
|
|
131
132
|
process.stderr.write(`[gramatr-mcp] Server started (stdio transport)\n`);
|
|
133
|
+
// Start hooks IPC listener so hook subprocesses can route through this
|
|
134
|
+
// process for auth, schema enforcement, and session context storage.
|
|
135
|
+
try {
|
|
136
|
+
const hooksPort = await startHooksListener();
|
|
137
|
+
process.stderr.write(`[gramatr-mcp] Hooks listener on port ${hooksPort}\n`);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
141
|
+
process.stderr.write(`[gramatr-mcp] Warning: hooks listener failed to start (${msg})\n`);
|
|
142
|
+
}
|
|
143
|
+
// Clean up port file on exit so stale hooks fall back to direct remote.
|
|
144
|
+
process.on('exit', () => stopHooksListener());
|
|
145
|
+
process.on('SIGTERM', () => { stopHooksListener(); process.exit(0); });
|
|
146
|
+
process.on('SIGINT', () => { stopHooksListener(); process.exit(0); });
|
|
132
147
|
}
|
|
133
148
|
//# sourceMappingURL=server.js.map
|