@tmhs/local-ai-mcp 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.
Files changed (60) hide show
  1. package/LICENSE +34 -0
  2. package/README.md +126 -0
  3. package/dist/catalog/models.d.ts +11 -0
  4. package/dist/catalog/models.js +114 -0
  5. package/dist/catalog/models.js.map +1 -0
  6. package/dist/config.d.ts +7 -0
  7. package/dist/config.js +22 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/hardware/index.d.ts +16 -0
  10. package/dist/hardware/index.js +30 -0
  11. package/dist/hardware/index.js.map +1 -0
  12. package/dist/hardware/linux.d.ts +2 -0
  13. package/dist/hardware/linux.js +48 -0
  14. package/dist/hardware/linux.js.map +1 -0
  15. package/dist/hardware/windows.d.ts +2 -0
  16. package/dist/hardware/windows.js +60 -0
  17. package/dist/hardware/windows.js.map +1 -0
  18. package/dist/http.d.ts +14 -0
  19. package/dist/http.js +61 -0
  20. package/dist/http.js.map +1 -0
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +24 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/providers/lmstudio.d.ts +44 -0
  25. package/dist/providers/lmstudio.js +181 -0
  26. package/dist/providers/lmstudio.js.map +1 -0
  27. package/dist/providers/manager.d.ts +9 -0
  28. package/dist/providers/manager.js +31 -0
  29. package/dist/providers/manager.js.map +1 -0
  30. package/dist/providers/ollama.d.ts +34 -0
  31. package/dist/providers/ollama.js +157 -0
  32. package/dist/providers/ollama.js.map +1 -0
  33. package/dist/providers/types.d.ts +102 -0
  34. package/dist/providers/types.js +2 -0
  35. package/dist/providers/types.js.map +1 -0
  36. package/dist/tools/catalog.d.ts +3 -0
  37. package/dist/tools/catalog.js +55 -0
  38. package/dist/tools/catalog.js.map +1 -0
  39. package/dist/tools/context.d.ts +8 -0
  40. package/dist/tools/context.js +2 -0
  41. package/dist/tools/context.js.map +1 -0
  42. package/dist/tools/delegation.d.ts +3 -0
  43. package/dist/tools/delegation.js +54 -0
  44. package/dist/tools/delegation.js.map +1 -0
  45. package/dist/tools/discovery.d.ts +3 -0
  46. package/dist/tools/discovery.js +61 -0
  47. package/dist/tools/discovery.js.map +1 -0
  48. package/dist/tools/helpers.d.ts +42 -0
  49. package/dist/tools/helpers.js +83 -0
  50. package/dist/tools/helpers.js.map +1 -0
  51. package/dist/tools/index.d.ts +3 -0
  52. package/dist/tools/index.js +13 -0
  53. package/dist/tools/index.js.map +1 -0
  54. package/dist/tools/lifecycle.d.ts +3 -0
  55. package/dist/tools/lifecycle.js +66 -0
  56. package/dist/tools/lifecycle.js.map +1 -0
  57. package/dist/tools/ops.d.ts +12 -0
  58. package/dist/tools/ops.js +113 -0
  59. package/dist/tools/ops.js.map +1 -0
  60. package/package.json +54 -0
@@ -0,0 +1,8 @@
1
+ import type { Config } from "../config.js";
2
+ import type { HardwareProbe } from "../hardware/index.js";
3
+ import type { ProviderManager } from "../providers/manager.js";
4
+ export interface ToolContext {
5
+ manager: ProviderManager;
6
+ hardware: HardwareProbe;
7
+ config: Config;
8
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/tools/context.ts"],"names":[],"mappings":""}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ToolContext } from "./context.js";
3
+ export declare function register(server: McpServer, ctx: ToolContext): void;
@@ -0,0 +1,54 @@
1
+ import { z } from "zod";
2
+ import { errMsg, fail, ok } from "./helpers.js";
3
+ export function register(server, ctx) {
4
+ const { manager, config } = ctx;
5
+ server.tool("complete", "DELEGATED INFERENCE: Offload a text/chat completion to a local model runtime for cost savings and privacy (data never leaves the machine). This is NOT a chat feature for the user; it delegates work to a local LLM. Provide either prompt or messages. Without a provider arg, uses the first detected provider.", {
6
+ model: z.string().describe("Model id/name to run the completion on"),
7
+ prompt: z.string().optional().describe("Plain prompt text (alternative to messages)"),
8
+ messages: z
9
+ .array(z.object({ role: z.string(), content: z.string() }))
10
+ .optional()
11
+ .describe("Chat-style messages (alternative to prompt)"),
12
+ maxTokens: z.number().optional().describe("Maximum tokens to generate"),
13
+ temperature: z.number().optional().describe("Sampling temperature"),
14
+ stop: z.array(z.string()).optional().describe("Stop sequences"),
15
+ provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id"),
16
+ }, async ({ model, prompt, messages, maxTokens, temperature, stop, provider }) => {
17
+ try {
18
+ if (!prompt && (!messages || messages.length === 0)) {
19
+ return fail("Provide either 'prompt' or non-empty 'messages'.");
20
+ }
21
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
22
+ if (providers.length === 0) {
23
+ return fail("No live providers detected to run the completion.");
24
+ }
25
+ const p = providers[0];
26
+ const result = await p.complete({ model, prompt, messages, maxTokens, temperature, stop }, config.requestTimeoutMs);
27
+ return ok(result);
28
+ }
29
+ catch (err) {
30
+ return fail(errMsg(err));
31
+ }
32
+ });
33
+ server.tool("embed", "DELEGATED EMBEDDINGS: Offload embedding generation to a local model runtime for cost savings and privacy. Accepts a single string or an array of strings. Without a provider arg, uses the first detected provider.", {
34
+ model: z.string().describe("Embedding model id/name"),
35
+ input: z
36
+ .union([z.string(), z.array(z.string())])
37
+ .describe("Text or array of texts to embed"),
38
+ provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id"),
39
+ }, async ({ model, input, provider }) => {
40
+ try {
41
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
42
+ if (providers.length === 0) {
43
+ return fail("No live providers detected to generate embeddings.");
44
+ }
45
+ const p = providers[0];
46
+ const result = await p.embed({ model, input }, config.requestTimeoutMs);
47
+ return ok(result);
48
+ }
49
+ catch (err) {
50
+ return fail(errMsg(err));
51
+ }
52
+ });
53
+ }
54
+ //# sourceMappingURL=delegation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delegation.js","sourceRoot":"","sources":["../../src/tools/delegation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ToolContext } from "./context.js";
3
+ export declare function register(server: McpServer, ctx: ToolContext): void;
@@ -0,0 +1,61 @@
1
+ import { z } from "zod";
2
+ import { aggregate, errMsg, fail, ok } from "./helpers.js";
3
+ export function register(server, ctx) {
4
+ const { manager, config } = ctx;
5
+ server.tool("list_providers", "List the configured local model runtime providers (Ollama, LM Studio) with their host, whether they are detected/live, and their capabilities. Optionally filter to a single provider.", { provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id to filter to") }, async ({ provider }) => {
6
+ try {
7
+ const targets = provider ? [manager.get(provider)].filter(Boolean) : manager.providers;
8
+ if (provider && targets.length === 0) {
9
+ return fail(`Unknown provider: ${provider}`);
10
+ }
11
+ const results = await Promise.all(targets.map(async (p) => {
12
+ const live = await p.detect(config.detectTimeoutMs).catch(() => false);
13
+ return {
14
+ provider: p.id,
15
+ host: p.host,
16
+ detected: live,
17
+ live,
18
+ capabilities: p.capabilities(),
19
+ };
20
+ }));
21
+ return ok({ providers: results });
22
+ }
23
+ catch (err) {
24
+ return fail(errMsg(err));
25
+ }
26
+ });
27
+ server.tool("list_models", "List models installed/available on each detected provider. Without a provider arg, aggregates across all detected providers keyed by provider.", { provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id") }, async ({ provider }) => {
28
+ try {
29
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
30
+ const byProvider = await aggregate(providers, (p) => p.listModels(config.requestTimeoutMs));
31
+ return ok({ models: byProvider });
32
+ }
33
+ catch (err) {
34
+ return fail(errMsg(err));
35
+ }
36
+ });
37
+ server.tool("list_loaded", "List models currently loaded into memory on each detected provider. Without a provider arg, aggregates across all detected providers keyed by provider.", { provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id") }, async ({ provider }) => {
38
+ try {
39
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
40
+ const byProvider = await aggregate(providers, (p) => p.listLoaded(config.requestTimeoutMs));
41
+ return ok({ loaded: byProvider });
42
+ }
43
+ catch (err) {
44
+ return fail(errMsg(err));
45
+ }
46
+ });
47
+ server.tool("model_info", "Show detailed metadata for a specific model (family, parameter size, quantization, context length). Without a provider arg, queries all detected providers.", {
48
+ model: z.string().describe("Model id/name to inspect"),
49
+ provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id"),
50
+ }, async ({ model, provider }) => {
51
+ try {
52
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
53
+ const byProvider = await aggregate(providers, (p) => p.modelInfo(model, config.requestTimeoutMs));
54
+ return ok({ model, info: byProvider });
55
+ }
56
+ catch (err) {
57
+ return fail(errMsg(err));
58
+ }
59
+ });
60
+ }
61
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../src/tools/discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { SystemResources } from "../hardware/index.js";
2
+ export type ToolResult = {
3
+ content: Array<{
4
+ type: "text";
5
+ text: string;
6
+ }>;
7
+ isError?: boolean;
8
+ };
9
+ export declare function ok(data: unknown): ToolResult;
10
+ export declare function fail(message: string): ToolResult;
11
+ export declare function errMsg(err: unknown): string;
12
+ /**
13
+ * Aggregate per-provider results, capturing errors per provider rather than
14
+ * failing the whole call.
15
+ */
16
+ export declare function aggregate<T>(providers: Array<{
17
+ id: string;
18
+ }>, fn: (p: any) => Promise<T>): Promise<Record<string, T | {
19
+ error: string;
20
+ }>>;
21
+ export interface FitOutcome {
22
+ fits: boolean;
23
+ target: "gpu" | "cpu" | "none";
24
+ requiredBytes: number;
25
+ availableBytes: number;
26
+ note: string;
27
+ }
28
+ /**
29
+ * Pure fit computation. Prefers GPU VRAM (free) when a GPU reports free VRAM;
30
+ * otherwise falls back to system RAM free. Requires requiredBytes * 1.2 to fit.
31
+ */
32
+ export declare function computeFit(resources: SystemResources, requiredBytes: number): FitOutcome;
33
+ /**
34
+ * Destructive-action confirm gate for remove_model. Refuses unless confirm === true.
35
+ */
36
+ export declare function removeModelGuarded(confirm: boolean, model: string, remove: () => Promise<unknown>): Promise<{
37
+ refused: true;
38
+ message: string;
39
+ } | {
40
+ refused: false;
41
+ result: unknown;
42
+ }>;
@@ -0,0 +1,83 @@
1
+ export function ok(data) {
2
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
3
+ }
4
+ export function fail(message) {
5
+ return {
6
+ content: [{ type: "text", text: JSON.stringify({ error: message }, null, 2) }],
7
+ isError: true,
8
+ };
9
+ }
10
+ export function errMsg(err) {
11
+ return err instanceof Error ? err.message : String(err);
12
+ }
13
+ /**
14
+ * Aggregate per-provider results, capturing errors per provider rather than
15
+ * failing the whole call.
16
+ */
17
+ export async function aggregate(
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ providers, fn) {
20
+ const entries = await Promise.all(providers.map(async (p) => {
21
+ try {
22
+ return [p.id, await fn(p)];
23
+ }
24
+ catch (err) {
25
+ return [p.id, { error: errMsg(err) }];
26
+ }
27
+ }));
28
+ return Object.fromEntries(entries);
29
+ }
30
+ /**
31
+ * Pure fit computation. Prefers GPU VRAM (free) when a GPU reports free VRAM;
32
+ * otherwise falls back to system RAM free. Requires requiredBytes * 1.2 to fit.
33
+ */
34
+ export function computeFit(resources, requiredBytes) {
35
+ const headroom = requiredBytes * 1.2;
36
+ const gpuFree = resources.gpus
37
+ .map((g) => g.vramFreeBytes)
38
+ .filter((v) => typeof v === "number" && v > 0);
39
+ const bestGpuFree = gpuFree.length > 0 ? Math.max(...gpuFree) : undefined;
40
+ if (bestGpuFree !== undefined) {
41
+ if (headroom <= bestGpuFree) {
42
+ return {
43
+ fits: true,
44
+ target: "gpu",
45
+ requiredBytes,
46
+ availableBytes: bestGpuFree,
47
+ note: "Fits in free GPU VRAM (with 20% headroom).",
48
+ };
49
+ }
50
+ }
51
+ const ramFree = resources.ramFreeBytes;
52
+ if (headroom <= ramFree) {
53
+ return {
54
+ fits: true,
55
+ target: "cpu",
56
+ requiredBytes,
57
+ availableBytes: ramFree,
58
+ note: bestGpuFree !== undefined
59
+ ? "Does not fit in free GPU VRAM; fits in system RAM (CPU inference, slower)."
60
+ : "No GPU VRAM info available; fits in system RAM (CPU inference, slower).",
61
+ };
62
+ }
63
+ return {
64
+ fits: false,
65
+ target: "none",
66
+ requiredBytes,
67
+ availableBytes: bestGpuFree !== undefined ? Math.max(bestGpuFree, ramFree) : ramFree,
68
+ note: "Does not fit in free GPU VRAM or system RAM (with 20% headroom).",
69
+ };
70
+ }
71
+ /**
72
+ * Destructive-action confirm gate for remove_model. Refuses unless confirm === true.
73
+ */
74
+ export async function removeModelGuarded(confirm, model, remove) {
75
+ if (confirm !== true) {
76
+ return {
77
+ refused: true,
78
+ message: `Refusing to remove "${model}": this is a destructive action. Pass confirm:true to proceed.`,
79
+ };
80
+ }
81
+ return { refused: false, result: await remove() };
82
+ }
83
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/tools/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ToolContext } from "./context.js";
3
+ export declare function registerAll(server: McpServer, ctx: ToolContext): void;
@@ -0,0 +1,13 @@
1
+ import { register as registerCatalog } from "./catalog.js";
2
+ import { register as registerDelegation } from "./delegation.js";
3
+ import { register as registerDiscovery } from "./discovery.js";
4
+ import { register as registerLifecycle } from "./lifecycle.js";
5
+ import { register as registerOps } from "./ops.js";
6
+ export function registerAll(server, ctx) {
7
+ registerDiscovery(server, ctx);
8
+ registerLifecycle(server, ctx);
9
+ registerOps(server, ctx);
10
+ registerCatalog(server, ctx);
11
+ registerDelegation(server, ctx);
12
+ }
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,IAAI,eAAe,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,QAAQ,IAAI,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,QAAQ,IAAI,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,QAAQ,IAAI,WAAW,EAAE,MAAM,UAAU,CAAC;AAEnD,MAAM,UAAU,WAAW,CAAC,MAAiB,EAAE,GAAgB;IAC7D,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,iBAAiB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzB,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ToolContext } from "./context.js";
3
+ export declare function register(server: McpServer, ctx: ToolContext): void;
@@ -0,0 +1,66 @@
1
+ import { z } from "zod";
2
+ import { aggregate, errMsg, fail, ok, removeModelGuarded } from "./helpers.js";
3
+ export function register(server, ctx) {
4
+ const { manager, config } = ctx;
5
+ server.tool("pull_model", "HEAVY: Download/pull a model onto a provider. WARNING: this may download multiple gigabytes and can take a long time. Without a provider arg, attempts the pull on every detected provider.", {
6
+ model: z.string().describe("Model id/name to pull (download)"),
7
+ provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id"),
8
+ }, async ({ model, provider }) => {
9
+ try {
10
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
11
+ const byProvider = await aggregate(providers, (p) => p.pull(model, config.requestTimeoutMs));
12
+ return ok({ pull: byProvider });
13
+ }
14
+ catch (err) {
15
+ return fail(errMsg(err));
16
+ }
17
+ });
18
+ server.tool("remove_model", "DESTRUCTIVE: Permanently delete a model from a provider. Requires confirm:true; without it the action is refused. Without a provider arg, removes from every detected provider.", {
19
+ model: z.string().describe("Model id/name to remove"),
20
+ confirm: z.boolean().describe("Must be true to perform this destructive deletion"),
21
+ provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id"),
22
+ }, async ({ model, confirm, provider }) => {
23
+ try {
24
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
25
+ const byProvider = await aggregate(providers, async (p) => {
26
+ const guarded = await removeModelGuarded(confirm, model, () => p.remove(model, config.requestTimeoutMs));
27
+ if (guarded.refused) {
28
+ return { removed: false, refused: true, message: guarded.message };
29
+ }
30
+ return guarded.result;
31
+ });
32
+ return ok({ remove: byProvider });
33
+ }
34
+ catch (err) {
35
+ return fail(errMsg(err));
36
+ }
37
+ });
38
+ server.tool("load_model", "Load a model into memory so it is ready for inference. Optionally set keepAlive (e.g. '5m', '1h'). Without a provider arg, loads on every detected provider.", {
39
+ model: z.string().describe("Model id/name to load"),
40
+ keepAlive: z.string().optional().describe("How long to keep the model resident, e.g. '5m'"),
41
+ provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id"),
42
+ }, async ({ model, keepAlive, provider }) => {
43
+ try {
44
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
45
+ const byProvider = await aggregate(providers, (p) => p.load(model, config.requestTimeoutMs, keepAlive));
46
+ return ok({ load: byProvider });
47
+ }
48
+ catch (err) {
49
+ return fail(errMsg(err));
50
+ }
51
+ });
52
+ server.tool("unload_model", "Unload a model from memory to free VRAM/RAM. Without a provider arg, unloads from every detected provider.", {
53
+ model: z.string().describe("Model id/name to unload"),
54
+ provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id"),
55
+ }, async ({ model, provider }) => {
56
+ try {
57
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
58
+ const byProvider = await aggregate(providers, (p) => p.unload(model, config.requestTimeoutMs));
59
+ return ok({ unload: byProvider });
60
+ }
61
+ catch (err) {
62
+ return fail(errMsg(err));
63
+ }
64
+ });
65
+ }
66
+ //# sourceMappingURL=lifecycle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/tools/lifecycle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { ToolContext } from "./context.js";
3
+ /**
4
+ * Resolve the on-disk byte size for a model: explicit sizeBytes wins, then a
5
+ * provider's listModels entry, then the static catalog. Returns undefined if
6
+ * unknown.
7
+ */
8
+ export declare function resolveModelSizeBytes(ctx: ToolContext, model: string, explicitSizeBytes: number | undefined, providerArg: string | undefined): Promise<{
9
+ requiredBytes: number | undefined;
10
+ source: string;
11
+ }>;
12
+ export declare function register(server: McpServer, ctx: ToolContext): void;
@@ -0,0 +1,113 @@
1
+ import { z } from "zod";
2
+ import { CATALOG } from "../catalog/models.js";
3
+ import { aggregate, computeFit, errMsg, fail, ok } from "./helpers.js";
4
+ /**
5
+ * Resolve the on-disk byte size for a model: explicit sizeBytes wins, then a
6
+ * provider's listModels entry, then the static catalog. Returns undefined if
7
+ * unknown.
8
+ */
9
+ export async function resolveModelSizeBytes(ctx, model, explicitSizeBytes, providerArg) {
10
+ if (typeof explicitSizeBytes === "number" && explicitSizeBytes > 0) {
11
+ return { requiredBytes: explicitSizeBytes, source: "explicit" };
12
+ }
13
+ try {
14
+ const providers = await ctx.manager.resolve(providerArg, ctx.config.detectTimeoutMs);
15
+ for (const p of providers) {
16
+ const models = await p.listModels(ctx.config.requestTimeoutMs).catch(() => []);
17
+ const match = models.find((m) => m.id === model);
18
+ if (match && typeof match.sizeBytes === "number" && match.sizeBytes > 0) {
19
+ return { requiredBytes: match.sizeBytes, source: `provider:${p.id}` };
20
+ }
21
+ }
22
+ }
23
+ catch {
24
+ // ignore detection/listing failures and fall back to catalog
25
+ }
26
+ const cat = CATALOG.find((c) => c.name === model);
27
+ if (cat) {
28
+ return { requiredBytes: cat.approxSizeBytes, source: "catalog" };
29
+ }
30
+ return { requiredBytes: undefined, source: "unknown" };
31
+ }
32
+ export function register(server, ctx) {
33
+ const { manager, hardware, config } = ctx;
34
+ server.tool("health_check", "Check whether each provider's local runtime is reachable and report its version. Without a provider arg, checks all configured providers.", { provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id") }, async ({ provider }) => {
35
+ try {
36
+ const targets = provider ? [manager.get(provider)].filter(Boolean) : manager.providers;
37
+ if (provider && targets.length === 0) {
38
+ return fail(`Unknown provider: ${provider}`);
39
+ }
40
+ const byProvider = await aggregate(targets, (p) => p.health(config.detectTimeoutMs));
41
+ return ok({ health: byProvider });
42
+ }
43
+ catch (err) {
44
+ return fail(errMsg(err));
45
+ }
46
+ });
47
+ server.tool("system_resources", "Report local hardware resources: platform, total/free RAM, CPU count, and detected GPUs with VRAM. Used to reason about which models can run locally.", {}, async () => {
48
+ try {
49
+ const resources = await hardware.getSystemResources();
50
+ return ok(resources);
51
+ }
52
+ catch (err) {
53
+ return fail(errMsg(err));
54
+ }
55
+ });
56
+ server.tool("fit_check", "Determine whether a model fits on the local hardware. Resolves the model size from the provider or the static catalog (or an explicit sizeBytes), then compares against free GPU VRAM, falling back to system RAM. Returns fits, target (gpu/cpu/none), required and available bytes.", {
57
+ model: z.string().describe("Model id/name to check"),
58
+ sizeBytes: z
59
+ .number()
60
+ .optional()
61
+ .describe("Optional explicit model size in bytes (overrides lookup)"),
62
+ provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id"),
63
+ }, async ({ model, sizeBytes, provider }) => {
64
+ try {
65
+ const { requiredBytes, source } = await resolveModelSizeBytes(ctx, model, sizeBytes, provider);
66
+ if (requiredBytes === undefined) {
67
+ return fail(`Could not determine size for "${model}" from provider or catalog. Pass sizeBytes to check fit explicitly.`);
68
+ }
69
+ const resources = await hardware.getSystemResources();
70
+ const outcome = computeFit(resources, requiredBytes);
71
+ return ok({ model, sizeSource: source, ...outcome });
72
+ }
73
+ catch (err) {
74
+ return fail(errMsg(err));
75
+ }
76
+ });
77
+ server.tool("benchmark", "HEAVY: Runs REAL inference. Executes one small completion against a loaded/loadable model and measures latency (ms) and throughput (tokens/sec). This consumes compute and may load the model. Without a provider arg, runs on the first detected provider.", {
78
+ model: z.string().describe("Model id/name to benchmark"),
79
+ prompt: z.string().optional().describe("Optional prompt; a short default is used otherwise"),
80
+ maxTokens: z.number().optional().describe("Max tokens to generate (default 64)"),
81
+ provider: z.enum(["ollama", "lmstudio"]).optional().describe("Optional provider id"),
82
+ }, async ({ model, prompt, maxTokens, provider }) => {
83
+ try {
84
+ const providers = await manager.resolve(provider, config.detectTimeoutMs);
85
+ if (providers.length === 0) {
86
+ return fail("No live providers detected to benchmark against.");
87
+ }
88
+ const p = providers[0];
89
+ const max = typeof maxTokens === "number" && maxTokens > 0 ? maxTokens : 64;
90
+ const start = Date.now();
91
+ const result = await p.complete({
92
+ model,
93
+ prompt: prompt ?? "Write one short sentence about local AI.",
94
+ maxTokens: max,
95
+ }, config.requestTimeoutMs);
96
+ const latencyMs = Date.now() - start;
97
+ const completionTokens = result.completionTokens ?? Math.max(1, Math.round(result.text.length / 4));
98
+ const tokensPerSec = latencyMs > 0 ? (completionTokens / latencyMs) * 1000 : 0;
99
+ return ok({
100
+ provider: p.id,
101
+ model,
102
+ latencyMs,
103
+ completionTokens,
104
+ tokensPerSec: Number(tokensPerSec.toFixed(2)),
105
+ maxTokens: max,
106
+ });
107
+ }
108
+ catch (err) {
109
+ return fail(errMsg(err));
110
+ }
111
+ });
112
+ }
113
+ //# sourceMappingURL=ops.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ops.js","sourceRoot":"","sources":["../../src/tools/ops.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@tmhs/local-ai-mcp",
3
+ "version": "0.1.0",
4
+ "description": "Unified MCP server for managing local model runtimes (Ollama, LM Studio, and more): provider-agnostic discovery, lifecycle, hardware-fit, and delegated inference.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "local-ai-mcp": "dist/index.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc -p tsconfig.json",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest",
19
+ "typecheck": "tsc -p tsconfig.json --noEmit",
20
+ "prepublishOnly": "npm run build"
21
+ },
22
+ "author": "TMHSDigital",
23
+ "license": "CC-BY-NC-ND-4.0",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/TMHSDigital/local-ai-mcp.git"
27
+ },
28
+ "homepage": "https://tmhsdigital.github.io/local-ai-mcp/",
29
+ "bugs": {
30
+ "url": "https://github.com/TMHSDigital/local-ai-mcp/issues"
31
+ },
32
+ "keywords": [
33
+ "mcp",
34
+ "model-context-protocol",
35
+ "ollama",
36
+ "lm-studio",
37
+ "local-llm",
38
+ "local-ai",
39
+ "inference",
40
+ "developer-tools"
41
+ ],
42
+ "engines": {
43
+ "node": ">=20.0.0"
44
+ },
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.29.0",
47
+ "zod": "^3.25.0"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^22.10.0",
51
+ "typescript": "^5.6.0",
52
+ "vitest": "^2.1.9"
53
+ }
54
+ }