@mgcrea/mcp-loki 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +89 -1
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +168 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/server-B2TOeJ3P.js +1593 -0
- package/dist/server-B2TOeJ3P.js.map +1 -0
- package/package.json +70 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Olivier Louvignes
|
|
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
CHANGED
|
@@ -1,3 +1,91 @@
|
|
|
1
1
|
# @mgcrea/mcp-loki
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server for the
|
|
4
|
+
[Grafana Loki](https://grafana.com/oss/loki/) HTTP API: query logs and LogQL metrics, explore
|
|
5
|
+
labels, fields, volume and patterns, with answers shaped to fit a context window.
|
|
6
|
+
|
|
7
|
+
The server is **read-only**. It sends only reads: Loki's push, delete-request and ruler-write
|
|
8
|
+
endpoints are not wrapped, and the `loki_request` escape hatch only sends GET.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **Logs merged across streams, in time order.** Labels every stream shares come once as
|
|
13
|
+
`common_labels`. Each line keeps only the labels that tell its stream apart, plus `level` and
|
|
14
|
+
structured metadata. Timestamps are exact RFC3339 nanoseconds, so the `next` cursor and
|
|
15
|
+
`loki_get_log_context` anchors round-trip without loss.
|
|
16
|
+
- **Cheap before expensive.** `loki_get_stats` and `loki_get_volume` answer from the index
|
|
17
|
+
without reading a line. `loki_list_patterns` summarizes what a service logs as templates.
|
|
18
|
+
`loki_query_metric` counts and ranks without returning lines.
|
|
19
|
+
- **Long lines are cut** at `max_line_chars` (some services log 300 KB lines), ANSI colour
|
|
20
|
+
codes are stripped, and the Kubernetes `filename` label is hidden by default.
|
|
21
|
+
- **Errors carry a remedy.** Loki answers a rejected query with plain text. The common ones
|
|
22
|
+
(empty-compatible matchers, parse errors, range and series limits, timeouts) come back with
|
|
23
|
+
what to change.
|
|
24
|
+
- **Identity and limits up front.** `loki_auth_status` names the tenant, the version,
|
|
25
|
+
retention, max query length, max lines and series per query, and whether volume and patterns
|
|
26
|
+
are enabled. Every data answer carries `tenant`.
|
|
27
|
+
|
|
28
|
+
## Configure
|
|
29
|
+
|
|
30
|
+
| Variable | Required | Description |
|
|
31
|
+
| ------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| `LOKI_URL` | yes | Loki base URL (gateway, query-frontend or single binary), with any path prefix. |
|
|
33
|
+
| `LOKI_USERNAME` | no | Basic auth. Behind the Loki Helm chart's nginx gateway the username **is the tenant**. |
|
|
34
|
+
| `LOKI_PASSWORD` | no | Basic auth password. |
|
|
35
|
+
| `LOKI_TOKEN` | no | Bearer token instead of basic auth (Grafana Cloud, an OAuth proxy). |
|
|
36
|
+
| `LOKI_TENANT_ID` | no | Sent as `X-Scope-OrgID`, for a multi-tenant Loki reached directly. A gateway that maps the basic-auth username overrides it. |
|
|
37
|
+
| `LOKI_HEADERS` | no | Extra headers as a JSON object. |
|
|
38
|
+
| `LOKI_TIMEOUT` | no | HTTP timeout per request, seconds (default 60). |
|
|
39
|
+
| `LOKI_MAX_RETRIES` | no | Retries for 429 and proxy 502/503 (default 2). A 504 is never retried: on Loki it is a slow query, and a retry triples the load. |
|
|
40
|
+
|
|
41
|
+
With nothing configured the server still starts and serves `loki_auth_status`, which says what
|
|
42
|
+
to set.
|
|
43
|
+
|
|
44
|
+
## Quick start
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm install
|
|
48
|
+
pnpm build
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Then copy `.mcp.json.example` to `.mcp.json` and fill it in.
|
|
52
|
+
|
|
53
|
+
## Tools
|
|
54
|
+
|
|
55
|
+
| Tool | What it does |
|
|
56
|
+
| --------------------------- | ---------------------------------------------------------------------------------- |
|
|
57
|
+
| `loki_auth_status` | URL, auth, tenant, version and this tenant's limits; setup steps when unconfigured |
|
|
58
|
+
| `loki_query_logs` | LogQL log query over a range, lines merged across streams, with a `next` cursor |
|
|
59
|
+
| `loki_get_log_context` | The lines just before and after one line, in its own stream |
|
|
60
|
+
| `loki_query_metric` | LogQL metric query at an instant, ranked largest first |
|
|
61
|
+
| `loki_query_metric_range` | LogQL metric query over a range, on a step grid, with per-series stats |
|
|
62
|
+
| `loki_list_labels` | Stream labels with their cardinality |
|
|
63
|
+
| `loki_list_label_values` | Values of one label, optionally within a selector |
|
|
64
|
+
| `loki_list_series` | Streams matching a selector |
|
|
65
|
+
| `loki_list_detected_fields` | JSON / logfmt / structured-metadata fields in the lines, or one field's values |
|
|
66
|
+
| `loki_get_volume` | Bytes per namespace / app / stream from the index, largest first |
|
|
67
|
+
| `loki_get_stats` | Streams, chunks, lines and bytes a selector covers, from the index |
|
|
68
|
+
| `loki_list_patterns` | Line templates with counts, from the pattern ingester |
|
|
69
|
+
| `loki_list_rules` | The Loki ruler's alerting and recording rules |
|
|
70
|
+
| `loki_request` | GET any other read endpoint (`/loki/api/v1/*`, `/prometheus/api/v1/*`, `/config`) |
|
|
71
|
+
|
|
72
|
+
## Develop
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pnpm dev # tsdown --watch
|
|
76
|
+
pnpm test # vitest
|
|
77
|
+
pnpm typecheck
|
|
78
|
+
pnpm lint
|
|
79
|
+
pnpm format
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Publish
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm dlx release-it # bump, commit, tag
|
|
86
|
+
git push --follow-tags # CI publishes to npm from the tag
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { _ as BUILD_INFO, f as authMode, g as setupInstructions, i as createServer, m as loadConfig, p as isConfigured, s as describeError } from "./server-B2TOeJ3P.js";
|
|
3
|
+
import { ZodError } from "zod";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
5
|
+
// src/cli.ts
|
|
6
|
+
const stderrLogger = {
|
|
7
|
+
debug: (...args) => {
|
|
8
|
+
if (process.env.LOKI_DEBUG) console.error("[loki-mcp]", ...args);
|
|
9
|
+
},
|
|
10
|
+
warn: (...args) => console.error("[loki-mcp]", ...args),
|
|
11
|
+
error: (...args) => console.error("[loki-mcp]", ...args)
|
|
12
|
+
};
|
|
13
|
+
/** Show a config mistake as its field messages, not 40 frames of zod internals. */
|
|
14
|
+
const describeFatal = (err) => {
|
|
15
|
+
if (err instanceof ZodError) return err.issues.map((issue) => {
|
|
16
|
+
const path = issue.path.join(".");
|
|
17
|
+
return path ? `${path}: ${issue.message}` : issue.message;
|
|
18
|
+
}).join("\n");
|
|
19
|
+
return err instanceof Error ? err.message : String(err);
|
|
20
|
+
};
|
|
21
|
+
const main = async () => {
|
|
22
|
+
stderrLogger.warn(`${BUILD_INFO.name}@${BUILD_INFO.version} (git ${BUILD_INFO.gitCommit} ${BUILD_INFO.gitCommitDate}, node ${process.version})`);
|
|
23
|
+
const config = loadConfig();
|
|
24
|
+
const { server, ctx } = createServer({
|
|
25
|
+
config,
|
|
26
|
+
logger: stderrLogger
|
|
27
|
+
});
|
|
28
|
+
const transport = new StdioServerTransport();
|
|
29
|
+
await server.connect(transport);
|
|
30
|
+
stderrLogger.warn(`loki-mcp connected (url=${config.url ?? "MISSING"}, auth=${authMode(config)}, tenant=${ctx.tenant?.tenant ?? "none"}, read-only)`);
|
|
31
|
+
for (const line of setupInstructions(config)) stderrLogger.warn(line);
|
|
32
|
+
if (isConfigured(config)) ctx.identity().then((id) => {
|
|
33
|
+
const l = id.limits ?? {};
|
|
34
|
+
stderrLogger.warn(`loki ${id.version ?? "?"} at ${new URL(config.url).host}` + (ctx.tenant ? `, tenant ${ctx.tenant.tenant}` : "") + (l.retention ? `, retention ${l.retention}` : "") + (l.maxQueryLength ? `, max range ${l.maxQueryLength}` : ""));
|
|
35
|
+
}).catch((err) => {
|
|
36
|
+
const { error, remedy } = describeError(err);
|
|
37
|
+
stderrLogger.warn(`loki unreachable: ${error}${remedy ? ` — ${String(remedy)}` : ""}`);
|
|
38
|
+
});
|
|
39
|
+
const shutdown = (signal) => {
|
|
40
|
+
stderrLogger.warn(`received ${signal}, shutting down`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
};
|
|
43
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
44
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
45
|
+
};
|
|
46
|
+
main().catch((err) => {
|
|
47
|
+
console.error(`[loki-mcp] fatal: ${describeFatal(err)}`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
50
|
+
export {};
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { StdioServerTransport } from \"@modelcontextprotocol/server/stdio\";\nimport { ZodError } from \"zod\";\n\nimport { BUILD_INFO } from \"#/build-info\";\nimport { authMode, isConfigured, loadConfig, setupInstructions } from \"#/config\";\nimport { createServer } from \"#/server\";\nimport { describeError } from \"#/tools/util\";\n\n// Everything goes to stderr: stdout is the MCP protocol channel, and a stray\n// log line there corrupts the stream.\nconst stderrLogger = {\n debug: (...args: unknown[]) => {\n if (process.env.LOKI_DEBUG) console.error(\"[loki-mcp]\", ...args);\n },\n warn: (...args: unknown[]) => console.error(\"[loki-mcp]\", ...args),\n error: (...args: unknown[]) => console.error(\"[loki-mcp]\", ...args),\n};\n\n/** Show a config mistake as its field messages, not 40 frames of zod internals. */\nconst describeFatal = (err: unknown): string => {\n if (err instanceof ZodError) {\n return err.issues\n .map((issue) => {\n const path = issue.path.join(\".\");\n return path ? `${path}: ${issue.message}` : issue.message;\n })\n .join(\"\\n\");\n }\n return err instanceof Error ? err.message : String(err);\n};\n\nconst main = async (): Promise<void> => {\n stderrLogger.warn(\n `${BUILD_INFO.name}@${BUILD_INFO.version} (git ${BUILD_INFO.gitCommit} ${BUILD_INFO.gitCommitDate}, node ${process.version})`,\n );\n const config = loadConfig();\n const { server, ctx } = createServer({ config, logger: stderrLogger });\n const transport = new StdioServerTransport();\n await server.connect(transport);\n stderrLogger.warn(\n `loki-mcp connected (url=${config.url ?? \"MISSING\"}, auth=${authMode(config)}, ` +\n `tenant=${ctx.tenant?.tenant ?? \"none\"}, read-only)`,\n );\n for (const line of setupInstructions(config)) stderrLogger.warn(line);\n\n // Which Loki answered, after connecting so a slow one never delays the\n // handshake. The tenant line scrolling past is the cheapest chance to notice\n // a credential copied from the wrong project.\n if (isConfigured(config)) {\n ctx\n .identity()\n .then((id) => {\n const l = id.limits ?? {};\n stderrLogger.warn(\n `loki ${id.version ?? \"?\"} at ${new URL(config.url as string).host}` +\n (ctx.tenant ? `, tenant ${ctx.tenant.tenant}` : \"\") +\n (l.retention ? `, retention ${l.retention}` : \"\") +\n (l.maxQueryLength ? `, max range ${l.maxQueryLength}` : \"\"),\n );\n })\n .catch((err: unknown) => {\n const { error, remedy } = describeError(err);\n stderrLogger.warn(`loki unreachable: ${error}${remedy ? ` — ${String(remedy)}` : \"\"}`);\n });\n }\n\n const shutdown = (signal: string): void => {\n stderrLogger.warn(`received ${signal}, shutting down`);\n process.exit(0);\n };\n process.on(\"SIGINT\", () => shutdown(\"SIGINT\"));\n process.on(\"SIGTERM\", () => shutdown(\"SIGTERM\"));\n};\n\nmain().catch((err: unknown) => {\n console.error(`[loki-mcp] fatal: ${describeFatal(err)}`);\n process.exit(1);\n});\n"],"mappings":";;;;;AAWA,MAAM,eAAe;CACnB,QAAQ,GAAG,SAAoB;EAC7B,IAAI,QAAQ,IAAI,YAAY,QAAQ,MAAM,cAAc,GAAG,IAAI;CACjE;CACA,OAAO,GAAG,SAAoB,QAAQ,MAAM,cAAc,GAAG,IAAI;CACjE,QAAQ,GAAG,SAAoB,QAAQ,MAAM,cAAc,GAAG,IAAI;AACpE;;AAGA,MAAM,iBAAiB,QAAyB;CAC9C,IAAI,eAAe,UACjB,OAAO,IAAI,OACR,KAAK,UAAU;EACd,MAAM,OAAO,MAAM,KAAK,KAAK,GAAG;EAChC,OAAO,OAAO,GAAG,KAAK,IAAI,MAAM,YAAY,MAAM;CACpD,CAAC,CAAC,CACD,KAAK,IAAI;CAEd,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;AAEA,MAAM,OAAO,YAA2B;CACtC,aAAa,KACX,GAAG,WAAW,KAAK,GAAG,WAAW,QAAQ,QAAQ,WAAW,UAAU,GAAG,WAAW,cAAc,SAAS,QAAQ,QAAQ,EAC7H;CACA,MAAM,SAAS,WAAW;CAC1B,MAAM,EAAE,QAAQ,QAAQ,aAAa;EAAE;EAAQ,QAAQ;CAAa,CAAC;CACrE,MAAM,YAAY,IAAI,qBAAqB;CAC3C,MAAM,OAAO,QAAQ,SAAS;CAC9B,aAAa,KACX,2BAA2B,OAAO,OAAO,UAAU,SAAS,SAAS,MAAM,EAAE,WACjE,IAAI,QAAQ,UAAU,OAAO,aAC3C;CACA,KAAK,MAAM,QAAQ,kBAAkB,MAAM,GAAG,aAAa,KAAK,IAAI;CAKpE,IAAI,aAAa,MAAM,GACrB,IACG,SAAS,CAAC,CACV,MAAM,OAAO;EACZ,MAAM,IAAI,GAAG,UAAU,CAAC;EACxB,aAAa,KACX,QAAQ,GAAG,WAAW,IAAI,MAAM,IAAI,IAAI,OAAO,GAAa,CAAC,CAAC,UAC3D,IAAI,SAAS,YAAY,IAAI,OAAO,WAAW,OAC/C,EAAE,YAAY,eAAe,EAAE,cAAc,OAC7C,EAAE,iBAAiB,eAAe,EAAE,mBAAmB,GAC5D;CACF,CAAC,CAAC,CACD,OAAO,QAAiB;EACvB,MAAM,EAAE,OAAO,WAAW,cAAc,GAAG;EAC3C,aAAa,KAAK,qBAAqB,QAAQ,SAAS,MAAM,OAAO,MAAM,MAAM,IAAI;CACvF,CAAC;CAGL,MAAM,YAAY,WAAyB;EACzC,aAAa,KAAK,YAAY,OAAO,gBAAgB;EACrD,QAAQ,KAAK,CAAC;CAChB;CACA,QAAQ,GAAG,gBAAgB,SAAS,QAAQ,CAAC;CAC7C,QAAQ,GAAG,iBAAiB,SAAS,SAAS,CAAC;AACjD;AAEA,KAAK,CAAC,CAAC,OAAO,QAAiB;CAC7B,QAAQ,MAAM,qBAAqB,cAAc,GAAG,GAAG;CACvD,QAAQ,KAAK,CAAC;AAChB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
declare const ConfigSchema: z.ZodObject<{
|
|
4
|
+
url: z.ZodOptional<z.ZodURL>;
|
|
5
|
+
username: z.ZodOptional<z.ZodString>;
|
|
6
|
+
password: z.ZodOptional<z.ZodString>;
|
|
7
|
+
token: z.ZodOptional<z.ZodString>;
|
|
8
|
+
tenantId: z.ZodOptional<z.ZodString>;
|
|
9
|
+
headers: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
10
|
+
timeoutSeconds: z.ZodDefault<z.ZodNumber>;
|
|
11
|
+
maxRetries: z.ZodDefault<z.ZodNumber>;
|
|
12
|
+
problems: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
13
|
+
}, z.core.$strict>;
|
|
14
|
+
type Config = z.infer<typeof ConfigSchema>;
|
|
15
|
+
type AuthMode = "basic" | "bearer" | "none";
|
|
16
|
+
/**
|
|
17
|
+
* Environment only: the server persists nothing of its own, so there is no
|
|
18
|
+
* config file to merge with.
|
|
19
|
+
*/
|
|
20
|
+
export declare const loadConfig: (env?: NodeJS.ProcessEnv) => Config;
|
|
21
|
+
export declare const authMode: (config: Config) => AuthMode;
|
|
22
|
+
/** A URL and no contradictions. A local Loki needs no auth, so none is required. */
|
|
23
|
+
export declare const isConfigured: (config: Config) => boolean;
|
|
24
|
+
type Tenant = {
|
|
25
|
+
tenant: string;
|
|
26
|
+
source: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Which tenant's logs every answer comes from. Loki never echoes it back — no
|
|
30
|
+
* endpoint says "you are tenant X" — so this is what the server sends or what
|
|
31
|
+
* the gateway is known to derive, and it is labelled as such.
|
|
32
|
+
*
|
|
33
|
+
* The Loki chart's nginx gateway with basicAuth sets `X-Scope-OrgID` from the
|
|
34
|
+
* basic-auth username and overwrites any header the client sent (observed on
|
|
35
|
+
* Loki 3.7.9 / chart 20.2.1: a bogus X-Scope-OrgID still answered with the
|
|
36
|
+
* user's tenant). So with basic auth the username is the tenant, and a
|
|
37
|
+
* LOKI_TENANT_ID beside it is probably ignored.
|
|
38
|
+
*/
|
|
39
|
+
export declare const resolveTenant: (config: Config) => Tenant | undefined;
|
|
40
|
+
/** Returned by loki_auth_status and printed to stderr at startup. */
|
|
41
|
+
export declare const setupInstructions: (config: Config) => string[];
|
|
42
|
+
type Logger = {
|
|
43
|
+
debug?(...args: unknown[]): void;
|
|
44
|
+
warn?(...args: unknown[]): void;
|
|
45
|
+
error?(...args: unknown[]): void;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The headers every request carries. Loki has no login of its own: basic or
|
|
49
|
+
* bearer auth satisfies whatever sits in front of it (the chart's nginx
|
|
50
|
+
* gateway, an ingress, Grafana Cloud), and `X-Scope-OrgID` picks the tenant
|
|
51
|
+
* when Loki runs with `auth_enabled: true`. Nothing to refresh, so a 401 is
|
|
52
|
+
* always final.
|
|
53
|
+
*/
|
|
54
|
+
export declare const authHeaders: (config: Config) => Record<string, string>;
|
|
55
|
+
/** Repeated keys (`match[]`) are arrays; undefined values are dropped. */
|
|
56
|
+
type Params = Record<string, string | number | bigint | boolean | string[] | undefined>;
|
|
57
|
+
type LokiResult<T> = {
|
|
58
|
+
data: T;
|
|
59
|
+
warnings?: string[];
|
|
60
|
+
};
|
|
61
|
+
type LokiClientOptions = {
|
|
62
|
+
baseUrl: string;
|
|
63
|
+
headers: Record<string, string>;
|
|
64
|
+
timeoutSeconds: number;
|
|
65
|
+
maxRetries: number;
|
|
66
|
+
userAgent: string;
|
|
67
|
+
fetch?: typeof fetch;
|
|
68
|
+
logger?: Logger;
|
|
69
|
+
};
|
|
70
|
+
export declare class LokiClient {
|
|
71
|
+
readonly baseUrl: string;
|
|
72
|
+
readonly timeoutSeconds: number;
|
|
73
|
+
private readonly headers;
|
|
74
|
+
private readonly maxRetries;
|
|
75
|
+
private readonly userAgent;
|
|
76
|
+
private readonly fetchImpl;
|
|
77
|
+
private readonly logger;
|
|
78
|
+
constructor(opts: LokiClientOptions);
|
|
79
|
+
get<T = unknown>(path: string, params?: Params, headers?: Record<string, string>): Promise<LokiResult<T>>;
|
|
80
|
+
/** A read on an endpoint that also takes a form POST: GET unless the URL would be too long. */
|
|
81
|
+
read<T = unknown>(path: string, params: Params, headers?: Record<string, string>): Promise<LokiResult<T>>;
|
|
82
|
+
request<T = unknown>(method: "GET" | "POST", path: string, opts?: {
|
|
83
|
+
params?: Params;
|
|
84
|
+
form?: Params;
|
|
85
|
+
headers?: Record<string, string>;
|
|
86
|
+
}): Promise<LokiResult<T>>;
|
|
87
|
+
}
|
|
88
|
+
type Limits = {
|
|
89
|
+
retention?: string;
|
|
90
|
+
maxQueryLength?: string;
|
|
91
|
+
maxQueryRange?: string;
|
|
92
|
+
maxQueryLookback?: string;
|
|
93
|
+
maxEntriesPerQuery?: number;
|
|
94
|
+
maxQuerySeries?: number;
|
|
95
|
+
maxQueryBytesRead?: string;
|
|
96
|
+
queryTimeout?: string;
|
|
97
|
+
volumeEnabled?: boolean;
|
|
98
|
+
patternIngesterEnabled?: boolean;
|
|
99
|
+
discoverLogLevels?: boolean;
|
|
100
|
+
};
|
|
101
|
+
type Identity = {
|
|
102
|
+
version?: string;
|
|
103
|
+
revision?: string;
|
|
104
|
+
limits?: Limits;
|
|
105
|
+
/** Endpoints that did not answer: normal on older Loki or a gateway that does not route them. */
|
|
106
|
+
unavailable?: string[];
|
|
107
|
+
};
|
|
108
|
+
export declare const fetchIdentity: (client: LokiClient) => Promise<Identity>;
|
|
109
|
+
type ToolContext = {
|
|
110
|
+
config: Config;
|
|
111
|
+
hasCredentials: boolean;
|
|
112
|
+
/** The tenant every answer is read from, as far as the configuration says. */
|
|
113
|
+
tenant: Tenant | undefined;
|
|
114
|
+
/**
|
|
115
|
+
* Spread first into every data answer. A credential copied from another
|
|
116
|
+
* tenant answers every query with plausible logs from that tenant, so each
|
|
117
|
+
* answer says whose logs they are.
|
|
118
|
+
*/
|
|
119
|
+
tag: () => {
|
|
120
|
+
tenant?: string;
|
|
121
|
+
};
|
|
122
|
+
/** Who answered and what it allows, memoized after the first success. */
|
|
123
|
+
identity: () => Promise<Identity>;
|
|
124
|
+
/** Seconds since the epoch; injectable so tests can pin relative times. */
|
|
125
|
+
now: () => number;
|
|
126
|
+
};
|
|
127
|
+
export declare const createToolContext: (client: LokiClient, config: Config, hasCredentials: boolean, now?: () => number) => ToolContext;
|
|
128
|
+
/**
|
|
129
|
+
* Register the tools. Every tool is a read: Loki's mutating endpoints (push,
|
|
130
|
+
* the compactor's delete requests, ruler writes) are deliberately not wrapped,
|
|
131
|
+
* so there is no write flag. `loki_request` is GET-only.
|
|
132
|
+
*/
|
|
133
|
+
export declare const registerTools: (server: McpServer, client: LokiClient, ctx: ToolContext) => void;
|
|
134
|
+
export declare const SERVER_NAME: string;
|
|
135
|
+
export declare const SERVER_VERSION: string;
|
|
136
|
+
export declare const USER_AGENT: string;
|
|
137
|
+
type CreateServerOptions = {
|
|
138
|
+
config: Config;
|
|
139
|
+
fetch?: typeof fetch;
|
|
140
|
+
logger?: Logger;
|
|
141
|
+
/** Seconds since the epoch, for relative times; injectable for tests. */
|
|
142
|
+
now?: () => number;
|
|
143
|
+
};
|
|
144
|
+
type CreatedServer = {
|
|
145
|
+
server: McpServer;
|
|
146
|
+
client: LokiClient;
|
|
147
|
+
ctx: ToolContext;
|
|
148
|
+
};
|
|
149
|
+
export declare const createServer: (opts: CreateServerOptions) => CreatedServer;
|
|
150
|
+
export declare class LokiApiError extends Error {
|
|
151
|
+
readonly name: string;
|
|
152
|
+
/** HTTP status, or 0 when no response arrived at all (DNS, TLS, timeout). */
|
|
153
|
+
readonly status: number;
|
|
154
|
+
/**
|
|
155
|
+
* What to do about it, kept separate from `message`: the message says what
|
|
156
|
+
* happened, the remedy is the half a model should act on, and fail() lifts it
|
|
157
|
+
* to a top-level field so it is not buried inside the upstream text.
|
|
158
|
+
*/
|
|
159
|
+
readonly remedy: string | undefined;
|
|
160
|
+
readonly errors: unknown;
|
|
161
|
+
constructor(message: string, opts: {
|
|
162
|
+
status: number;
|
|
163
|
+
remedy?: string;
|
|
164
|
+
errors?: unknown;
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
export type { Config, CreateServerOptions, CreatedServer, Identity, Limits, Logger, LokiClientOptions, LokiResult, Params, Tenant, ToolContext };
|
|
168
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/config.ts","../src/client/auth.ts","../src/client/loki.ts","../src/client/identity.ts","../src/tools/index.ts","../src/server.ts","../src/client/errors.ts"],"mappings":";;cAEM,cAAY,EAAA;;;;;;;;;;GA+BP,EAAA,KAAA;KAEC,SAAS,EAAE,aAAa;KAExB;;;;;qBA4CC,aAAU,MAAS,OAAO,eAA2B;qBA6DrD,WAAQ,QAAY,WAAS;;qBAI7B,eAAY,QAAY;KAGzB;EAAW;EAAgB;;;;;;;;;;;;;qBAa1B,gBAAa,QAAY,WAAS;;qBAkBlC,oBAAiB,QAAY;KClL9B;EACV,UAAU;EACV,SAAS;EACT,UAAU;;;;;;;;;qBAUC,cAAW,QAAY,WAAS;;KCSjC,SAAS;KAET,WAAW;EAAO,MAAM;EAAG;;KAuG3B;EACV;EACA,SAAS;EACT;EACA;EACA;EACA,eAAe;EACf,SAAS;;qBAGE;WACF;WACA;mBACQ;mBACA;mBACA;mBACA;mBACA;EAEjB,YAAY,MAAM;EAUZ,IAAI,aACR,cACA,SAAS,QACT,UAAU,yBACT,QAAQ,WAAW;;EAKhB,KAAK,aACT,cACA,QAAQ,QACR,UAAU,yBACT,QAAQ,WAAW;EAQhB,QAAQ,aACZ,wBACA,cACA;IAAQ,SAAS;IAAQ,OAAO;IAAQ,UAAU;MACjD,QAAQ,WAAW;;KC7KZ;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;KAGU;EACV;EACA;EACA,SAAS;;EAET;;qBAmCW,gBAAa,QAAkB,eAAa,QAAQ;KCnDrD;EACV,QAAQ;EACR;;EAEA,QAAQ;;;;;;EAMR;IAAa;;;EAEb,gBAAgB,QAAQ;;EAExB;;qBAGW,oBAAiB,QACpB,YAAU,QACV,QAAM,yBACS,uBAEtB;;;;;;qBAyBU,gBAAa,QAAY,WAAS,QAAU,YAAU,KAAO;qBCpD7D;qBACA;qBACA;KAED;EACV,QAAQ;EACR,eAAe;EACf,SAAS;;EAET;;KAGU;EACV,QAAQ;EACR,QAAQ;EACR,KAAK;;qBAGM,eAAY,MAAU,wBAAsB;qBC1B5C,qBAAqB;WAEd;;WAET;;;;;;WAMA;WACA;EAET,YAAY,iBAAiB;IAAQ;IAAgB;IAAiB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { u as LokiApiError, l as LokiClient, t as SERVER_NAME, n as SERVER_VERSION, r as USER_AGENT, d as authHeaders, f as authMode, i as createServer, a as createToolContext, c as fetchIdentity, p as isConfigured, m as loadConfig, o as registerTools, h as resolveTenant, g as setupInstructions } from "./server-B2TOeJ3P.js";
|