graylog-mcp-fix 1.0.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/.claude/settings.local.json +9 -0
- package/AGENTS.md +61 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +284 -0
- package/dist/index.js.map +1 -0
- package/index.js +293 -0
- package/package.json +19 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# graylog-mcp-fix
|
|
2
|
+
|
|
3
|
+
## What this is
|
|
4
|
+
|
|
5
|
+
A local MCP proxy for Graylog. It fetches tools from a remote Graylog MCP endpoint at startup, fixes their schemas and response data, then serves them to local MCP clients over stdio.
|
|
6
|
+
|
|
7
|
+
## Run
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
node index.js
|
|
11
|
+
# or
|
|
12
|
+
npm start
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Architecture
|
|
16
|
+
|
|
17
|
+
Single source file: `index.js` (plain JavaScript, ES modules, no build step)
|
|
18
|
+
|
|
19
|
+
**Startup flow:**
|
|
20
|
+
1. Load `.env` via `process.loadEnvFile()`
|
|
21
|
+
2. Initialize session with remote Graylog MCP (`initialize` method)
|
|
22
|
+
3. Fetch all tools (`tools/list`)
|
|
23
|
+
4. Fix tool schemas: `resolveLocalRefs` → `fixOutputSchema`
|
|
24
|
+
5. Start low-level `Server` (stdio transport) and register handlers
|
|
25
|
+
|
|
26
|
+
**Request flow:**
|
|
27
|
+
- `tools/list` → return fixed `remoteTools`
|
|
28
|
+
- `tools/call` → proxy to Graylog, then fix response via `fixToolResponseContent` (text content) and `fixStructuredContent` (structured content)
|
|
29
|
+
|
|
30
|
+
## Schema fixes applied
|
|
31
|
+
|
|
32
|
+
### `resolveLocalRefs(schema)`
|
|
33
|
+
- Replaces `$ref: "#/$defs/..."` nodes with `{}` (accept-any)
|
|
34
|
+
- Strips all `$defs` sections
|
|
35
|
+
- Collapses `allOf/anyOf/oneOf` where all items are now `{}` (were refs): removes `allOf`, `type`, and `required` from the node, making optional typed fields (like `unit`) accept null
|
|
36
|
+
|
|
37
|
+
### `fixOutputSchema(schema, propName)`
|
|
38
|
+
- `datarows` array properties: forces `items: { type: "object", additionalProperties: true }`
|
|
39
|
+
- Non-root property schemas with `type: "object"`: removes `type` and `required` to allow null values (Graylog returns null for many optional object fields)
|
|
40
|
+
|
|
41
|
+
## Response fixes applied
|
|
42
|
+
|
|
43
|
+
### `fixToolResponseContent(content)` — text content
|
|
44
|
+
- Converts `datarows` arrays-of-arrays to arrays-of-objects (keyed by field name)
|
|
45
|
+
- Wraps `effective_timerange.from`/`.to` primitives into `{ type: "absolute", timestamp }` objects
|
|
46
|
+
|
|
47
|
+
### `fixStructuredContent(obj, schema)` — structured content
|
|
48
|
+
- Same datarows conversion, schema-aware (uses `field ?? name` from the schema array)
|
|
49
|
+
- Same `effective_timerange` fix
|
|
50
|
+
- Schema-aware: passes output schema through recursion so null replacements are guided by the schema
|
|
51
|
+
|
|
52
|
+
## Why `Server` not `McpServer`
|
|
53
|
+
|
|
54
|
+
`McpServer` requires Zod schemas for tool registration. This proxy receives raw JSON Schema from a remote server and can't convert at runtime. The SDK explicitly designates `Server` for "advanced use cases" like this proxy pattern.
|
|
55
|
+
|
|
56
|
+
## Environment variables
|
|
57
|
+
|
|
58
|
+
| Variable | Description |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `GRAYLOG_TOKEN` | Raw API token — encoded as `base64(token:token)` for Basic Auth |
|
|
61
|
+
| `GRAYLOG_MCP_URL` | Remote Graylog MCP endpoint |
|
package/CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@AGENTS.md
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lauris Vavere
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# graylog-mcp-fix
|
|
2
|
+
|
|
3
|
+
A local MCP proxy that sits between your AI client and a Graylog MCP server, fixing schema and response compatibility issues that prevent standard MCP clients from working with Graylog's API.
|
|
4
|
+
|
|
5
|
+
## Why
|
|
6
|
+
|
|
7
|
+
Graylog's MCP implementation has several issues that cause MCP clients to reject its responses:
|
|
8
|
+
|
|
9
|
+
- Output schemas contain `$ref`/`$defs` references that clients cannot resolve
|
|
10
|
+
- `datarows` are returned as arrays-of-arrays instead of arrays-of-objects
|
|
11
|
+
- `effective_timerange.from`/`.to` are returned as primitives but schemas require objects
|
|
12
|
+
- Optional object fields (e.g. `unit`) are returned as `null` but schemas lack null support
|
|
13
|
+
|
|
14
|
+
This proxy transparently fixes all of these on the fly.
|
|
15
|
+
|
|
16
|
+
## Setup
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Create a `.env` file in the project root:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
GRAYLOG_TOKEN=your_api_token_here
|
|
26
|
+
GRAYLOG_MCP_URL=https://your-graylog-instance/api/mcp
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The token is used as HTTP Basic Auth: `Authorization: Basic base64(token:token)`.
|
|
30
|
+
|
|
31
|
+
## Running
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
node index.js
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## MCP client configuration
|
|
38
|
+
|
|
39
|
+
For opencode (`~/.config/opencode/opencode.json`):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"mcp": {
|
|
44
|
+
"graylog": {
|
|
45
|
+
"type": "local",
|
|
46
|
+
"command": "node /path/to/graylog-mcp-fix/index.js",
|
|
47
|
+
"enabled": true,
|
|
48
|
+
"environment": {
|
|
49
|
+
"GRAYLOG_MCP_URL": "Graylog MCP endpoint URL",
|
|
50
|
+
"GRAYLOG_TOKEN": "Graylog API token"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Environment variables
|
|
58
|
+
|
|
59
|
+
| Variable | Default | Description |
|
|
60
|
+
|---|---|---|
|
|
61
|
+
| `GRAYLOG_TOKEN` | _(empty)_ | Graylog API token |
|
|
62
|
+
| `GRAYLOG_MCP_URL` | _(built-in URL)_ | Graylog MCP endpoint URL |
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
// Server (low-level) is used intentionally: McpServer requires Zod schemas but this
|
|
2
|
+
// proxy receives raw JSON Schema from a remote server. The SDK docs explicitly say
|
|
3
|
+
// "Only use Server for advanced use cases" — a dynamic proxy qualifies.
|
|
4
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
+
// Load .env file if present (Node 20.6+ built-in, no extra dependency needed)
|
|
8
|
+
try {
|
|
9
|
+
process.loadEnvFile();
|
|
10
|
+
}
|
|
11
|
+
catch { /* .env not found, continue with process.env */ }
|
|
12
|
+
const GRAYLOG_MCP_URL = process.env.GRAYLOG_MCP_URL ?? "";
|
|
13
|
+
const GRAYLOG_TOKEN = process.env.GRAYLOG_TOKEN ?? "";
|
|
14
|
+
const GRAYLOG_AUTH = "Basic " + Buffer.from(`${GRAYLOG_TOKEN}:token`).toString("base64");
|
|
15
|
+
let sessionId;
|
|
16
|
+
let remoteTools = [];
|
|
17
|
+
async function graylogRequest(method, params = {}, skipError = false) {
|
|
18
|
+
const resp = await fetch(GRAYLOG_MCP_URL, {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
Authorization: GRAYLOG_AUTH,
|
|
23
|
+
...(sessionId ? { "Mcp-Session-Id": sessionId } : {}),
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
jsonrpc: "2.0",
|
|
27
|
+
id: Date.now(),
|
|
28
|
+
method,
|
|
29
|
+
params,
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
const sid = resp.headers.get("Mcp-Session-Id");
|
|
33
|
+
if (sid)
|
|
34
|
+
sessionId = sid;
|
|
35
|
+
const data = await resp.json();
|
|
36
|
+
if (data.error) {
|
|
37
|
+
if (skipError)
|
|
38
|
+
return null;
|
|
39
|
+
throw new Error(`Graylog MCP error: ${data.error.message ?? JSON.stringify(data.error)}`);
|
|
40
|
+
}
|
|
41
|
+
if (!resp.ok) {
|
|
42
|
+
throw new Error(`Graylog MCP HTTP ${resp.status}: ${resp.statusText}`);
|
|
43
|
+
}
|
|
44
|
+
return data.result;
|
|
45
|
+
}
|
|
46
|
+
// Recursively replace any $ref pointing to a local $defs entry with an open schema ({})
|
|
47
|
+
// and strip all $defs sections. Graylog emits $defs references (DateTime, Unit, etc.)
|
|
48
|
+
// that MCP clients cannot resolve. Using {} (accepts any value) is safer than guessing
|
|
49
|
+
// the concrete type — the actual type may be string, null, number, or object.
|
|
50
|
+
function resolveLocalRefs(schema) {
|
|
51
|
+
if (!schema || typeof schema !== "object")
|
|
52
|
+
return schema;
|
|
53
|
+
if (Array.isArray(schema))
|
|
54
|
+
return schema.map(resolveLocalRefs);
|
|
55
|
+
if (typeof schema.$ref === "string" && schema.$ref.startsWith("#/$defs/")) {
|
|
56
|
+
return {};
|
|
57
|
+
}
|
|
58
|
+
const result = {};
|
|
59
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
60
|
+
if (key === "$defs")
|
|
61
|
+
continue;
|
|
62
|
+
result[key] = resolveLocalRefs(value);
|
|
63
|
+
}
|
|
64
|
+
// If allOf/anyOf/oneOf now contains only open schemas (all items were $refs → {}),
|
|
65
|
+
// the type definition came entirely from external $defs. Strip allOf, type, and
|
|
66
|
+
// required so the field accepts null (which Graylog actually returns for optional
|
|
67
|
+
// typed fields). Any inline properties become optional hints rather than constraints.
|
|
68
|
+
for (const kw of ["allOf", "anyOf", "oneOf"]) {
|
|
69
|
+
if (Array.isArray(result[kw])) {
|
|
70
|
+
const allOpen = result[kw].every((s) => typeof s === "object" && Object.keys(s).length === 0);
|
|
71
|
+
if (allOpen) {
|
|
72
|
+
delete result[kw];
|
|
73
|
+
delete result.type;
|
|
74
|
+
delete result.required;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
// Recursively walk schema by property name so we find datarows/from/to regardless
|
|
82
|
+
// of how deeply they are nested or whether intermediate nodes lost their `properties`
|
|
83
|
+
// via resolveLocalRefs $ref replacement.
|
|
84
|
+
function fixOutputSchema(schema, propName) {
|
|
85
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema))
|
|
86
|
+
return schema;
|
|
87
|
+
// Any property named "datarows" that is an array → make items be plain objects.
|
|
88
|
+
// Handles both 1D (items = $ref→object) and 2D (items = array of $ref→object) cases.
|
|
89
|
+
if (propName === "datarows" && schema.type === "array") {
|
|
90
|
+
return { ...schema, items: { type: "object", additionalProperties: true } };
|
|
91
|
+
}
|
|
92
|
+
const result = {};
|
|
93
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
94
|
+
if (key === "properties" && value && typeof value === "object" && !Array.isArray(value)) {
|
|
95
|
+
// Recurse into each named property with its name
|
|
96
|
+
const newProps = {};
|
|
97
|
+
for (const [pName, pSchema] of Object.entries(value)) {
|
|
98
|
+
newProps[pName] = fixOutputSchema(pSchema, pName);
|
|
99
|
+
}
|
|
100
|
+
result[key] = newProps;
|
|
101
|
+
}
|
|
102
|
+
else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
103
|
+
result[key] = fixOutputSchema(value, key);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
result[key] = value;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// For non-root property schemas, remove type:"object" so null values pass validation.
|
|
110
|
+
// Graylog marks many fields as type:object but returns null for optional ones.
|
|
111
|
+
// Removing the constraint (not changing to an array type) is the safest approach.
|
|
112
|
+
if (propName !== undefined && result.type === "object") {
|
|
113
|
+
delete result.type;
|
|
114
|
+
delete result.required;
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
// Returns true if the JSON Schema node requires an object (not null).
|
|
119
|
+
function schemaRequiresObject(schema) {
|
|
120
|
+
if (!schema || typeof schema !== "object")
|
|
121
|
+
return false;
|
|
122
|
+
if (schema.type === "object")
|
|
123
|
+
return true;
|
|
124
|
+
if (Array.isArray(schema.allOf))
|
|
125
|
+
return schema.allOf.some(schemaRequiresObject);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
// Collect the merged properties map from a schema node (including allOf members).
|
|
129
|
+
function schemaProperties(schema) {
|
|
130
|
+
if (!schema || typeof schema !== "object")
|
|
131
|
+
return {};
|
|
132
|
+
const props = { ...(schema.properties ?? {}) };
|
|
133
|
+
for (const s of schema.allOf ?? []) {
|
|
134
|
+
Object.assign(props, schemaProperties(s));
|
|
135
|
+
}
|
|
136
|
+
return props;
|
|
137
|
+
}
|
|
138
|
+
// Recursively fix structured content to match the output schema:
|
|
139
|
+
// - datarows arrays-of-arrays → arrays-of-objects
|
|
140
|
+
// - effective_timerange.from/.to primitives → objects
|
|
141
|
+
// - null values where the schema requires an object → {}
|
|
142
|
+
function fixStructuredContent(obj, schema) {
|
|
143
|
+
if (obj === null || obj === undefined)
|
|
144
|
+
return obj;
|
|
145
|
+
if (typeof obj !== "object")
|
|
146
|
+
return obj;
|
|
147
|
+
if (Array.isArray(obj)) {
|
|
148
|
+
const itemSchema = schema?.items;
|
|
149
|
+
return obj.map((item) => fixStructuredContent(item, itemSchema));
|
|
150
|
+
}
|
|
151
|
+
const result = { ...obj };
|
|
152
|
+
const props = schemaProperties(schema);
|
|
153
|
+
// Fix effective_timerange.from/.to: Graylog returns these as timestamps (number/string)
|
|
154
|
+
// but the output schema requires objects.
|
|
155
|
+
if (result.effective_timerange && typeof result.effective_timerange === "object" &&
|
|
156
|
+
!Array.isArray(result.effective_timerange)) {
|
|
157
|
+
const tr = result.effective_timerange;
|
|
158
|
+
if (typeof tr.from === "number" || typeof tr.from === "string") {
|
|
159
|
+
tr.from = { type: "absolute", timestamp: tr.from };
|
|
160
|
+
}
|
|
161
|
+
if (typeof tr.to === "number" || typeof tr.to === "string") {
|
|
162
|
+
tr.to = { type: "absolute", timestamp: tr.to };
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
// If this level has both schema and datarows (array of arrays), transform in place
|
|
166
|
+
if (Array.isArray(result.schema) && Array.isArray(result.datarows) &&
|
|
167
|
+
result.datarows.length > 0 && Array.isArray(result.datarows[0])) {
|
|
168
|
+
const fieldNames = result.schema.map((s) => s.field ?? s.name ?? String(s));
|
|
169
|
+
result.datarows = result.datarows.map((row) => {
|
|
170
|
+
const o = {};
|
|
171
|
+
fieldNames.forEach((name, i) => { o[name] = row[i] ?? null; });
|
|
172
|
+
return o;
|
|
173
|
+
});
|
|
174
|
+
for (const key of Object.keys(result)) {
|
|
175
|
+
if (key !== "datarows" && key !== "schema") {
|
|
176
|
+
result[key] = fixStructuredContent(result[key], props[key]);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
for (const key of Object.keys(result)) {
|
|
182
|
+
result[key] = fixStructuredContent(result[key], props[key]);
|
|
183
|
+
}
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
function fixToolResponseContent(content) {
|
|
187
|
+
for (const item of content) {
|
|
188
|
+
if (item?.type !== "text")
|
|
189
|
+
continue;
|
|
190
|
+
const textItem = item;
|
|
191
|
+
try {
|
|
192
|
+
const parsed = JSON.parse(textItem.text);
|
|
193
|
+
// Graylog returns {schema: [...], datarows: [[...], [...]]}
|
|
194
|
+
// Convert to {schema: [...], datarows: [{...}, {...}]}
|
|
195
|
+
if (parsed.datarows && Array.isArray(parsed.datarows) && parsed.schema && Array.isArray(parsed.schema)) {
|
|
196
|
+
const fieldNames = parsed.schema.map((s) => s.name);
|
|
197
|
+
parsed.datarows = parsed.datarows.map((row) => {
|
|
198
|
+
const obj = {};
|
|
199
|
+
fieldNames.forEach((name, i) => {
|
|
200
|
+
obj[name] = row[i] ?? null;
|
|
201
|
+
});
|
|
202
|
+
return obj;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// Also handle nested data.datarows format
|
|
206
|
+
if (parsed.data?.datarows && parsed.data.metadata?.schema) {
|
|
207
|
+
const schema = parsed.data.metadata.schema;
|
|
208
|
+
if (Array.isArray(schema)) {
|
|
209
|
+
const fieldNames = schema.map((s) => s.name);
|
|
210
|
+
parsed.data.datarows = parsed.data.datarows.map((row) => {
|
|
211
|
+
const obj = {};
|
|
212
|
+
fieldNames.forEach((name, i) => {
|
|
213
|
+
obj[name] = row[i] ?? null;
|
|
214
|
+
});
|
|
215
|
+
return obj;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Fix effective_timerange primitives -> objects
|
|
220
|
+
if (parsed.data?.metadata?.effective_timerange) {
|
|
221
|
+
const tr = parsed.data.metadata.effective_timerange;
|
|
222
|
+
if (typeof tr.from === "number" || typeof tr.from === "string") {
|
|
223
|
+
tr.from = { type: "absolute", timestamp: tr.from };
|
|
224
|
+
}
|
|
225
|
+
if (typeof tr.to === "number" || typeof tr.to === "string") {
|
|
226
|
+
tr.to = { type: "absolute", timestamp: tr.to };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
textItem.text = JSON.stringify(parsed);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// not JSON, leave as-is
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return content;
|
|
236
|
+
}
|
|
237
|
+
// Initialize session with remote server
|
|
238
|
+
const initResult = await graylogRequest("initialize", {
|
|
239
|
+
protocolVersion: "2025-06-18",
|
|
240
|
+
capabilities: {},
|
|
241
|
+
clientInfo: { name: "graylog", version: "1.0.0" },
|
|
242
|
+
});
|
|
243
|
+
// Fetch remote tools
|
|
244
|
+
const toolsResult = await graylogRequest("tools/list");
|
|
245
|
+
remoteTools = toolsResult.tools ?? [];
|
|
246
|
+
// Fix schemas on all tools: resolve local $ref/$defs that clients can't follow,
|
|
247
|
+
// then apply Graylog-specific structural fixes.
|
|
248
|
+
for (const tool of remoteTools) {
|
|
249
|
+
if (tool.inputSchema) {
|
|
250
|
+
tool.inputSchema = resolveLocalRefs(tool.inputSchema);
|
|
251
|
+
}
|
|
252
|
+
if (tool.outputSchema) {
|
|
253
|
+
tool.outputSchema = resolveLocalRefs(tool.outputSchema);
|
|
254
|
+
tool.outputSchema = fixOutputSchema(tool.outputSchema);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Notify remote server we're initialized (may not be supported, ignore errors)
|
|
258
|
+
try {
|
|
259
|
+
await graylogRequest("notifications/initialized", {}, true);
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Graylog MCP doesn't support this notification, skip
|
|
263
|
+
}
|
|
264
|
+
console.error(`graylog-mcp-fix: proxied ${remoteTools.length} tools from Graylog MCP`);
|
|
265
|
+
const server = new Server({ name: "graylog", version: "1.0.0" }, { capabilities: { tools: { listChanged: false } } });
|
|
266
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
267
|
+
tools: remoteTools,
|
|
268
|
+
}));
|
|
269
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
270
|
+
const { name, arguments: args } = request.params;
|
|
271
|
+
const result = await graylogRequest("tools/call", { name, arguments: args });
|
|
272
|
+
if (result.content && Array.isArray(result.content)) {
|
|
273
|
+
result.content = fixToolResponseContent(result.content);
|
|
274
|
+
}
|
|
275
|
+
if (result.structuredContent) {
|
|
276
|
+
const toolSchema = remoteTools.find((t) => t.name === name)?.outputSchema;
|
|
277
|
+
result.structuredContent = fixStructuredContent(result.structuredContent, toolSchema);
|
|
278
|
+
}
|
|
279
|
+
return result;
|
|
280
|
+
});
|
|
281
|
+
const transport = new StdioServerTransport();
|
|
282
|
+
await server.connect(transport);
|
|
283
|
+
console.error("graylog-mcp-fix running on stdio");
|
|
284
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/index.js
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Server (low-level) is used intentionally: McpServer requires Zod schemas but this
|
|
3
|
+
// proxy receives raw JSON Schema from a remote server. The SDK docs explicitly say
|
|
4
|
+
// "Only use Server for advanced use cases" — a dynamic proxy qualifies.
|
|
5
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
8
|
+
|
|
9
|
+
// Load .env file if present (Node 20.6+ built-in, no extra dependency needed)
|
|
10
|
+
try { process.loadEnvFile(); } catch { /* .env not found, continue with process.env */ }
|
|
11
|
+
|
|
12
|
+
const GRAYLOG_MCP_URL = process.env.GRAYLOG_MCP_URL ?? "";
|
|
13
|
+
const GRAYLOG_TOKEN = process.env.GRAYLOG_TOKEN ?? "";
|
|
14
|
+
const GRAYLOG_AUTH = "Basic " + Buffer.from(`${GRAYLOG_TOKEN}:token`).toString("base64");
|
|
15
|
+
|
|
16
|
+
let sessionId;
|
|
17
|
+
let remoteTools = [];
|
|
18
|
+
|
|
19
|
+
async function graylogRequest(method, params = {}, skipError = false) {
|
|
20
|
+
const resp = await fetch(GRAYLOG_MCP_URL, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: {
|
|
23
|
+
"Content-Type": "application/json",
|
|
24
|
+
Authorization: GRAYLOG_AUTH,
|
|
25
|
+
...(sessionId ? { "Mcp-Session-Id": sessionId } : {}),
|
|
26
|
+
},
|
|
27
|
+
body: JSON.stringify({
|
|
28
|
+
jsonrpc: "2.0",
|
|
29
|
+
id: Date.now(),
|
|
30
|
+
method,
|
|
31
|
+
params,
|
|
32
|
+
}),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const sid = resp.headers.get("Mcp-Session-Id");
|
|
36
|
+
if (sid) sessionId = sid;
|
|
37
|
+
|
|
38
|
+
const data = await resp.json();
|
|
39
|
+
if (data.error) {
|
|
40
|
+
if (skipError) return null;
|
|
41
|
+
throw new Error(`Graylog MCP error: ${data.error.message ?? JSON.stringify(data.error)}`);
|
|
42
|
+
}
|
|
43
|
+
if (!resp.ok) {
|
|
44
|
+
throw new Error(`Graylog MCP HTTP ${resp.status}: ${resp.statusText}`);
|
|
45
|
+
}
|
|
46
|
+
return data.result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Recursively replace any $ref pointing to a local $defs entry with an open schema ({})
|
|
50
|
+
// and strip all $defs sections. Graylog emits $defs references (DateTime, Unit, etc.)
|
|
51
|
+
// that MCP clients cannot resolve. Using {} (accepts any value) is safer than guessing
|
|
52
|
+
// the concrete type — the actual type may be string, null, number, or object.
|
|
53
|
+
function resolveLocalRefs(schema) {
|
|
54
|
+
if (!schema || typeof schema !== "object") return schema;
|
|
55
|
+
if (Array.isArray(schema)) return schema.map(resolveLocalRefs);
|
|
56
|
+
if (typeof schema.$ref === "string" && schema.$ref.startsWith("#/$defs/")) {
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
const result = {};
|
|
60
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
61
|
+
if (key === "$defs") continue;
|
|
62
|
+
result[key] = resolveLocalRefs(value);
|
|
63
|
+
}
|
|
64
|
+
// If allOf/anyOf/oneOf now contains only open schemas (all items were $refs → {}),
|
|
65
|
+
// the type definition came entirely from external $defs. Strip allOf, type, and
|
|
66
|
+
// required so the field accepts null (which Graylog actually returns for optional
|
|
67
|
+
// typed fields). Any inline properties become optional hints rather than constraints.
|
|
68
|
+
for (const kw of ["allOf", "anyOf", "oneOf"]) {
|
|
69
|
+
if (Array.isArray(result[kw])) {
|
|
70
|
+
const allOpen = result[kw].every((s) => typeof s === "object" && Object.keys(s).length === 0);
|
|
71
|
+
if (allOpen) {
|
|
72
|
+
delete result[kw];
|
|
73
|
+
delete result.type;
|
|
74
|
+
delete result.required;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Recursively walk schema by property name so we find datarows/from/to regardless
|
|
83
|
+
// of how deeply they are nested or whether intermediate nodes lost their `properties`
|
|
84
|
+
// via resolveLocalRefs $ref replacement.
|
|
85
|
+
function fixOutputSchema(schema, propName) {
|
|
86
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return schema;
|
|
87
|
+
|
|
88
|
+
// Any property named "datarows" that is an array → make items be plain objects.
|
|
89
|
+
// Handles both 1D (items = $ref→object) and 2D (items = array of $ref→object) cases.
|
|
90
|
+
if (propName === "datarows" && schema.type === "array") {
|
|
91
|
+
return { ...schema, items: { type: "object", additionalProperties: true } };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const result = {};
|
|
95
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
96
|
+
if (key === "properties" && value && typeof value === "object" && !Array.isArray(value)) {
|
|
97
|
+
// Recurse into each named property with its name
|
|
98
|
+
const newProps = {};
|
|
99
|
+
for (const [pName, pSchema] of Object.entries(value)) {
|
|
100
|
+
newProps[pName] = fixOutputSchema(pSchema, pName);
|
|
101
|
+
}
|
|
102
|
+
result[key] = newProps;
|
|
103
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
104
|
+
result[key] = fixOutputSchema(value, key);
|
|
105
|
+
} else {
|
|
106
|
+
result[key] = value;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// For non-root property schemas, remove type:"object" so null values pass validation.
|
|
110
|
+
// Graylog marks many fields as type:object but returns null for optional ones.
|
|
111
|
+
// Removing the constraint (not changing to an array type) is the safest approach.
|
|
112
|
+
if (propName !== undefined && result.type === "object") {
|
|
113
|
+
delete result.type;
|
|
114
|
+
delete result.required;
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Returns true if the JSON Schema node requires an object (not null).
|
|
120
|
+
function schemaRequiresObject(schema) {
|
|
121
|
+
if (!schema || typeof schema !== "object") return false;
|
|
122
|
+
if (schema.type === "object") return true;
|
|
123
|
+
if (Array.isArray(schema.allOf)) return schema.allOf.some(schemaRequiresObject);
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Collect the merged properties map from a schema node (including allOf members).
|
|
128
|
+
function schemaProperties(schema) {
|
|
129
|
+
if (!schema || typeof schema !== "object") return {};
|
|
130
|
+
const props = { ...(schema.properties ?? {}) };
|
|
131
|
+
for (const s of schema.allOf ?? []) {
|
|
132
|
+
Object.assign(props, schemaProperties(s));
|
|
133
|
+
}
|
|
134
|
+
return props;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Recursively fix structured content to match the output schema:
|
|
138
|
+
// - datarows arrays-of-arrays → arrays-of-objects
|
|
139
|
+
// - effective_timerange.from/.to primitives → objects
|
|
140
|
+
// - null values where the schema requires an object → {}
|
|
141
|
+
function fixStructuredContent(obj, schema) {
|
|
142
|
+
if (obj === null || obj === undefined) return obj;
|
|
143
|
+
if (typeof obj !== "object") return obj;
|
|
144
|
+
if (Array.isArray(obj)) {
|
|
145
|
+
const itemSchema = schema?.items;
|
|
146
|
+
return obj.map((item) => fixStructuredContent(item, itemSchema));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const result = { ...obj };
|
|
150
|
+
const props = schemaProperties(schema);
|
|
151
|
+
|
|
152
|
+
// Fix effective_timerange.from/.to: Graylog returns these as timestamps (number/string)
|
|
153
|
+
// but the output schema requires objects.
|
|
154
|
+
if (result.effective_timerange && typeof result.effective_timerange === "object" &&
|
|
155
|
+
!Array.isArray(result.effective_timerange)) {
|
|
156
|
+
const tr = result.effective_timerange;
|
|
157
|
+
if (typeof tr.from === "number" || typeof tr.from === "string") {
|
|
158
|
+
tr.from = { type: "absolute", timestamp: tr.from };
|
|
159
|
+
}
|
|
160
|
+
if (typeof tr.to === "number" || typeof tr.to === "string") {
|
|
161
|
+
tr.to = { type: "absolute", timestamp: tr.to };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If this level has both schema and datarows (array of arrays), transform in place
|
|
166
|
+
if (Array.isArray(result.schema) && Array.isArray(result.datarows) &&
|
|
167
|
+
result.datarows.length > 0 && Array.isArray(result.datarows[0])) {
|
|
168
|
+
const fieldNames = result.schema.map((s) => s.field ?? s.name ?? String(s));
|
|
169
|
+
result.datarows = result.datarows.map((row) => {
|
|
170
|
+
const o = {};
|
|
171
|
+
fieldNames.forEach((name, i) => { o[name] = row[i] ?? null; });
|
|
172
|
+
return o;
|
|
173
|
+
});
|
|
174
|
+
for (const key of Object.keys(result)) {
|
|
175
|
+
if (key !== "datarows" && key !== "schema") {
|
|
176
|
+
result[key] = fixStructuredContent(result[key], props[key]);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return result;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (const key of Object.keys(result)) {
|
|
183
|
+
result[key] = fixStructuredContent(result[key], props[key]);
|
|
184
|
+
}
|
|
185
|
+
return result;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function fixToolResponseContent(content) {
|
|
189
|
+
for (const item of content) {
|
|
190
|
+
if (item?.type !== "text") continue;
|
|
191
|
+
try {
|
|
192
|
+
const parsed = JSON.parse(item.text);
|
|
193
|
+
// Graylog returns {schema: [...], datarows: [[...], [...]]}
|
|
194
|
+
// Convert to {schema: [...], datarows: [{...}, {...}]}
|
|
195
|
+
if (parsed.datarows && Array.isArray(parsed.datarows) && parsed.schema && Array.isArray(parsed.schema)) {
|
|
196
|
+
const fieldNames = parsed.schema.map((s) => s.name);
|
|
197
|
+
parsed.datarows = parsed.datarows.map((row) => {
|
|
198
|
+
const obj = {};
|
|
199
|
+
fieldNames.forEach((name, i) => {
|
|
200
|
+
obj[name] = row[i] ?? null;
|
|
201
|
+
});
|
|
202
|
+
return obj;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
// Also handle nested data.datarows format
|
|
206
|
+
if (parsed.data?.datarows && parsed.data.metadata?.schema) {
|
|
207
|
+
const schema = parsed.data.metadata.schema;
|
|
208
|
+
if (Array.isArray(schema)) {
|
|
209
|
+
const fieldNames = schema.map((s) => s.name);
|
|
210
|
+
parsed.data.datarows = parsed.data.datarows.map((row) => {
|
|
211
|
+
const obj = {};
|
|
212
|
+
fieldNames.forEach((name, i) => {
|
|
213
|
+
obj[name] = row[i] ?? null;
|
|
214
|
+
});
|
|
215
|
+
return obj;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
// Fix effective_timerange primitives -> objects
|
|
220
|
+
if (parsed.data?.metadata?.effective_timerange) {
|
|
221
|
+
const tr = parsed.data.metadata.effective_timerange;
|
|
222
|
+
if (typeof tr.from === "number" || typeof tr.from === "string") {
|
|
223
|
+
tr.from = { type: "absolute", timestamp: tr.from };
|
|
224
|
+
}
|
|
225
|
+
if (typeof tr.to === "number" || typeof tr.to === "string") {
|
|
226
|
+
tr.to = { type: "absolute", timestamp: tr.to };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
item.text = JSON.stringify(parsed);
|
|
230
|
+
} catch {
|
|
231
|
+
// not JSON, leave as-is
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return content;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Initialize session with remote server
|
|
238
|
+
const initResult = await graylogRequest("initialize", {
|
|
239
|
+
protocolVersion: "2025-06-18",
|
|
240
|
+
capabilities: {},
|
|
241
|
+
clientInfo: { name: "graylog", version: "1.0.0" },
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Fetch remote tools
|
|
245
|
+
const toolsResult = await graylogRequest("tools/list");
|
|
246
|
+
remoteTools = toolsResult.tools ?? [];
|
|
247
|
+
|
|
248
|
+
// Fix schemas on all tools: resolve local $ref/$defs that clients can't follow,
|
|
249
|
+
// then apply Graylog-specific structural fixes.
|
|
250
|
+
for (const tool of remoteTools) {
|
|
251
|
+
if (tool.inputSchema) {
|
|
252
|
+
tool.inputSchema = resolveLocalRefs(tool.inputSchema);
|
|
253
|
+
}
|
|
254
|
+
if (tool.outputSchema) {
|
|
255
|
+
tool.outputSchema = resolveLocalRefs(tool.outputSchema);
|
|
256
|
+
tool.outputSchema = fixOutputSchema(tool.outputSchema);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Notify remote server we're initialized (may not be supported, ignore errors)
|
|
261
|
+
try {
|
|
262
|
+
await graylogRequest("notifications/initialized", {}, true);
|
|
263
|
+
} catch {
|
|
264
|
+
// Graylog MCP doesn't support this notification, skip
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
console.error(`graylog-mcp-fix: proxied ${remoteTools.length} tools from Graylog MCP`);
|
|
268
|
+
|
|
269
|
+
const server = new Server(
|
|
270
|
+
{ name: "graylog", version: "1.0.0" },
|
|
271
|
+
{ capabilities: { tools: { listChanged: false } } },
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
275
|
+
tools: remoteTools,
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
279
|
+
const { name, arguments: args } = request.params;
|
|
280
|
+
const result = await graylogRequest("tools/call", { name, arguments: args });
|
|
281
|
+
if (result.content && Array.isArray(result.content)) {
|
|
282
|
+
result.content = fixToolResponseContent(result.content);
|
|
283
|
+
}
|
|
284
|
+
if (result.structuredContent) {
|
|
285
|
+
const toolSchema = remoteTools.find((t) => t.name === name)?.outputSchema;
|
|
286
|
+
result.structuredContent = fixStructuredContent(result.structuredContent, toolSchema);
|
|
287
|
+
}
|
|
288
|
+
return result;
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const transport = new StdioServerTransport();
|
|
292
|
+
await server.connect(transport);
|
|
293
|
+
console.error("graylog-mcp-fix running on stdio");
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "graylog-mcp-fix",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Local MCP proxy for Graylog — fixes schemas and response data for standard MCP client compatibility",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"graylog-mcp-fix": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node index.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": ["mcp", "graylog", "proxy"],
|
|
13
|
+
"author": "Lauris Vavere <lauris.vavere@gmail.com>",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
18
|
+
}
|
|
19
|
+
}
|