gigai-tools-mcp 0.1.0 → 0.2.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 (3) hide show
  1. package/README.md +5 -3
  2. package/package.json +1 -1
  3. package/server.mjs +73 -3
package/README.md CHANGED
@@ -15,7 +15,7 @@ Claude Desktop (`claude_desktop_config.json`) or any MCP client:
15
15
  "mcpServers": {
16
16
  "gigai-tools": {
17
17
  "command": "npx",
18
- "args": ["-y", "github:cbsshekhawat18-lab/gigai-tools-mcp"],
18
+ "args": ["-y", "gigai-tools-mcp"],
19
19
  "env": {
20
20
  "GIGAI_API_KEY": "gk_live_...",
21
21
  "GIGAI_MCP_OUT_DIR": "/path/where/results/go"
@@ -28,12 +28,14 @@ Claude Desktop (`claude_desktop_config.json`) or any MCP client:
28
28
  Claude Code:
29
29
 
30
30
  ```
31
- claude mcp add gigai-tools -e GIGAI_API_KEY=gk_live_... -- npx -y github:cbsshekhawat18-lab/gigai-tools-mcp
31
+ claude mcp add gigai-tools -e GIGAI_API_KEY=gk_live_... -- npx -y gigai-tools-mcp
32
32
  ```
33
33
 
34
34
  ## What the agent can do
35
35
 
36
- Every engine becomes a tool: `merge_pdf`, `split_pdf`, `rotate_pdf`, `pdf_to_text`, `convert_image`, `resize_image`, `compress_image`, `optimize_svg`, `markdown_to_html`, `generate_qr`. Tools take local file paths (or URLs the server fetches), and results are saved to `GIGAI_MCP_OUT_DIR` - file bytes never pass through the model's context, only the saved paths do.
36
+ Every catalog engine becomes a tool - all of them, from `merge_pdf` and `ocr_pdf` to `detect_pii` and `generate_qr`, fetched from the live catalog at startup so new engines appear without updating this package. Tools take local file paths (or URLs the server fetches), and results are saved to `GIGAI_MCP_OUT_DIR` - file bytes never pass through the model's context, only the saved paths do.
37
+
38
+ Since 0.2.0 there is also `agent_plan`: give it a goal sentence ("shrink this photo to under 150 KB as WebP") and it returns ordered steps naming this server's own tools with ready-to-use options. It sends only file metadata (basename, type, size) - never contents - and is limited to 10 plans per day per account. When planning capacity is unavailable it says so and the agent simply picks tools from the list itself.
37
39
 
38
40
  ## Environment
39
41
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gigai-tools-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for the GigAI Tools API - lets AI agents merge PDFs, convert images, generate QR codes and more. Free plan.",
5
5
  "type": "module",
6
6
  "bin": {
package/server.mjs CHANGED
@@ -16,7 +16,7 @@
16
16
  */
17
17
 
18
18
  import path from "node:path";
19
- import { mkdir } from "node:fs/promises";
19
+ import { mkdir, stat } from "node:fs/promises";
20
20
 
21
21
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
22
22
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -68,16 +68,86 @@ function toMcpTool(engine) {
68
68
 
69
69
  const bySlug = new Map(catalog.map((e) => [e.slug.replaceAll("-", "_"), e]));
70
70
 
71
+ /**
72
+ * agent_plan: goal sentence + optional local file paths -> a tool pipeline,
73
+ * planned by the API's free-tier model chain. Only file METADATA (basename,
74
+ * type, size) is sent - never contents. The returned steps reference the
75
+ * engine tools this server already exposes; the API may be capacity-limited,
76
+ * in which case the honest answer is "plan it yourself from the tool list".
77
+ */
78
+ const AGENT_PLAN_TOOL = {
79
+ name: "agent_plan",
80
+ description:
81
+ "Plan a multi-step file-processing pipeline from a goal sentence " +
82
+ "(e.g. 'shrink this photo to under 150 KB as WebP'). Returns ordered steps " +
83
+ "naming this server's own tools with ready-to-use options. Sends only file " +
84
+ "metadata, never contents. Rate-limited (10 plans/day per account).",
85
+ inputSchema: {
86
+ type: "object",
87
+ properties: {
88
+ goal: { type: "string", description: "What should happen, in one sentence." },
89
+ files: { type: "array", items: { type: "string" }, description: "Local file path(s) the goal is about (optional; only name/size/type are sent)." },
90
+ },
91
+ required: ["goal"],
92
+ },
93
+ };
94
+
95
+ const EXT_MIME = {
96
+ ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".png": "image/png", ".webp": "image/webp",
97
+ ".gif": "image/gif", ".avif": "image/avif", ".pdf": "application/pdf", ".csv": "text/csv",
98
+ ".json": "application/json", ".txt": "text/plain", ".md": "text/markdown",
99
+ ".html": "text/html", ".yaml": "application/yaml", ".yml": "application/yaml",
100
+ ".js": "text/javascript", ".css": "text/css", ".svg": "image/svg+xml",
101
+ ".srt": "application/x-subrip", ".vtt": "text/vtt",
102
+ };
103
+
104
+ async function agentPlan(goal, files = []) {
105
+ const metas = [];
106
+ for (const p of files.slice(0, 10)) {
107
+ let size = 0;
108
+ try { size = (await stat(p)).size; } catch { /* metadata only; missing file stays size 0 */ }
109
+ metas.push({ name: path.basename(p), type: EXT_MIME[path.extname(p).toLowerCase()] ?? "application/octet-stream", size });
110
+ }
111
+ const compact = catalog.map((e) => ({ id: e.slug, desc: e.description, inputs: e.options_schema }));
112
+ const resp = await fetch(`${client.baseUrl}/agent/plan`, {
113
+ method: "POST",
114
+ headers: { "content-type": "application/json", authorization: `Bearer ${process.env.GIGAI_API_KEY}` },
115
+ body: JSON.stringify({ goal, files: metas, catalogue: compact }),
116
+ });
117
+ const data = await resp.json().catch(() => null);
118
+ if (!resp.ok) {
119
+ const message = data?.detail?.error?.message ?? `HTTP ${resp.status}`;
120
+ return { content: [{ type: "text", text: `No plan - ${message}\nYou can plan manually: list_tools shows every engine and its options.` }], isError: true };
121
+ }
122
+ const steps = (data.plan?.steps ?? []).filter((s) => bySlug.has(String(s.toolId ?? "").replaceAll("-", "_")));
123
+ if (!steps.length) {
124
+ return { content: [{ type: "text", text: "The model returned no usable steps for this server's tools. Plan manually from the tool list." }], isError: true };
125
+ }
126
+ const lines = steps.map((s, i) =>
127
+ `${i + 1}. ${String(s.toolId).replaceAll("-", "_")} ${JSON.stringify(s.input ?? {})} - ${s.why ?? ""}`);
128
+ return {
129
+ content: [{
130
+ type: "text",
131
+ text: `Plan (via ${data.provider}):\n${lines.join("\n")}\n\nCall the listed tools in order, passing each step's options; feed each result into the next step's files.`,
132
+ }],
133
+ };
134
+ }
135
+
71
136
  const server = new Server(
72
- { name: "gigai-tools", version: "0.1.0" },
137
+ { name: "gigai-tools", version: "0.2.0" },
73
138
  { capabilities: { tools: {} } },
74
139
  );
75
140
 
76
141
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
77
- tools: catalog.map(toMcpTool),
142
+ tools: [AGENT_PLAN_TOOL, ...catalog.map(toMcpTool)],
78
143
  }));
79
144
 
80
145
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
146
+ if (request.params.name === "agent_plan") {
147
+ const { goal, files } = request.params.arguments ?? {};
148
+ if (!goal) return { content: [{ type: "text", text: "agent_plan needs a goal." }], isError: true };
149
+ return agentPlan(String(goal), Array.isArray(files) ? files : []);
150
+ }
81
151
  const engine = bySlug.get(request.params.name);
82
152
  if (!engine) {
83
153
  return { content: [{ type: "text", text: `Unknown tool '${request.params.name}'.` }], isError: true };