Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,12 +147,25 @@ tools:
- `required`: 必須かどうか(デフォルト: `true`)
- `default`: デフォルト値(オプション)

入力パラメータはシェルスクリプトに環境変数として渡されます。変数名は `INPUTS__` プレフィックスが付き、大文字に変換されます(ハイフンはアンダースコアに変換)。
入力パラメータはシェルスクリプトに2つの方法で環境変数として渡されます:

#### 個別の環境変数
変数名は `INPUTS__` プレフィックスが付き、大文字に変換されます(ハイフンはアンダースコアに変換)。

例:
- `message` → `$INPUTS__MESSAGE`
- `branch-name` → `$INPUTS__BRANCH_NAME`

#### JSON形式(INPUTS_JSON)
すべての入力は `INPUTS_JSON` 環境変数に単一のJSONオブジェクトとしても利用できます。これにより型情報が保持され、シェル以外のインタープリタでの作業が容易になります。

使用例:
```javascript
// Node.js
const inputs = JSON.parse(process.env.INPUTS_JSON);
console.log(inputs.num * 2); // numは文字列ではなく数値
```

### Shellオプション

`shell` オプションを使用すると、スクリプトを実行するためのカスタムシェルまたはインタープリタを指定できます。`{0}` プレースホルダーは一時スクリプトファイルのパスに置き換えられます。
Expand Down Expand Up @@ -193,6 +206,18 @@ tools:
const endpoint = Deno.env.get("INPUTS__ENDPOINT");
const response = await fetch(endpoint);
console.log(await response.json());

# INPUTS_JSONを使用した型保持の例
- name: add_2
description: add 2 to a number
shell: "node {0}"
inputs:
num:
type: number
description: a number to add 2 to
run: |
const inputs = JSON.parse(process.env.INPUTS_JSON);
console.log(inputs.num + 2); // numは文字列ではなく数値
```

#### 高度な例 - AIエージェントとWeb検索
Expand All @@ -201,19 +226,20 @@ tools:
# yaml-language-server: $schema=https://raw.githubusercontent.com/izumin5210/any-script-mcp/main/config.schema.json
tools:
- name: gemini-search
description: Gemini 2.5 FlashとWeb検索を使用したAIエージェント
description: AI agent with web search using Gemini 2.5 Flash
shell: "deno run -N -E {0}"
inputs:
input:
query:
type: string
description: AI検索のクエリ
description: Query for AI search
required: true
run: |
import { GoogleGenAI } from "npm:@google/genai@^1";
const inputs = JSON.parse(Deno.env.get("INPUTS_JSON"));
const ai = new GoogleGenAI({ apiKey: Deno.env.get("GEMINI_API_KEY") });
const res = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: Deno.env.get("INPUTS__INPUT")!,
contents: inputs.query,
config: {
tools: [{ googleSearch: {} }],
systemInstruction: "...",
Expand All @@ -224,20 +250,21 @@ tools:
);

- name: gpt-5-search
description: GPT-5とWeb検索を使用したAIエージェント
description: AI agent with web search using GPT-5
shell: "deno run -N -E {0}"
inputs:
input:
query:
type: string
description: AI検索のクエリ
description: Query for AI search
required: true
run: |
import OpenAI from "jsr:@openai/openai";
const inputs = JSON.parse(Deno.env.get("INPUTS_JSON"));
const client = new OpenAI({ apiKey: Deno.env.get("OPENAI_API_KEY") });
const res = await client.responses.create({
model: "gpt-5",
tools: [{ type: "web_search_preview" }],
input: Deno.env.get("INPUTS__INPUT"),
input: inputs.input,
instructions: "...",
});
console.log(res.output_text);
Expand Down
37 changes: 32 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,25 @@ Each input parameter has the following fields:
- `required`: Whether the parameter is required (default: `true`)
- `default`: Default value (optional)

Input parameters are passed as environment variables to shell scripts. Variable names have the `INPUTS__` prefix and are converted to uppercase (hyphens are converted to underscores).
Input parameters are passed as environment variables to shell scripts in two ways:

#### Individual Environment Variables
Variable names have the `INPUTS__` prefix and are converted to uppercase (hyphens are converted to underscores).

Examples:
- `message` → `$INPUTS__MESSAGE`
- `branch-name` → `$INPUTS__BRANCH_NAME`

#### JSON Format (INPUTS_JSON)
All inputs are also available as a single JSON object in the `INPUTS_JSON` environment variable. This preserves type information, making it easier to work with non-shell interpreters.

Example usage:
```javascript
// Node.js
const inputs = JSON.parse(process.env.INPUTS_JSON);
console.log(inputs.num * 2); // count is a number, not a string
```

### Shell Option

The `shell` option allows you to specify a custom shell or interpreter for executing scripts. The `{0}` placeholder is replaced with the path to the temporary script file.
Expand Down Expand Up @@ -197,6 +210,18 @@ tools:
const endpoint = Deno.env.get("INPUTS__ENDPOINT");
const response = await fetch(endpoint);
console.log(await response.json());

# Using INPUTS_JSON for type preservation
- name: add_2
description: add 2 to a number
shell: "node {0}"
inputs:
num:
type: number
description: a number to add 2 to
run: |
const inputs = JSON.parse(process.env.INPUTS_JSON);
console.log(inputs.num + 2); // number is a number, not a string
```

#### Advanced Examples - AI Agents with Web Search
Expand All @@ -208,16 +233,17 @@ tools:
description: AI agent with web search using Gemini 2.5 Flash
shell: "deno run -N -E {0}"
inputs:
input:
query:
type: string
description: Query for AI search
required: true
run: |
import { GoogleGenAI } from "npm:@google/genai@^1";
const inputs = JSON.parse(Deno.env.get("INPUTS_JSON"));
const ai = new GoogleGenAI({ apiKey: Deno.env.get("GEMINI_API_KEY") });
const res = await ai.models.generateContent({
model: "gemini-2.5-flash",
contents: Deno.env.get("INPUTS__INPUT")!,
contents: inputs.query,
config: {
tools: [{ googleSearch: {} }],
systemInstruction: "...",
Expand All @@ -231,17 +257,18 @@ tools:
description: AI agent with web search using GPT-5
shell: "deno run -N -E {0}"
inputs:
input:
query:
type: string
description: Query for AI search
required: true
run: |
import OpenAI from "jsr:@openai/openai";
const inputs = JSON.parse(Deno.env.get("INPUTS_JSON"));
const client = new OpenAI({ apiKey: Deno.env.get("OPENAI_API_KEY") });
const res = await client.responses.create({
model: "gpt-5",
tools: [{ type: "web_search_preview" }],
input: Deno.env.get("INPUTS__INPUT"),
input: inputs.query,
instructions: "...",
});
console.log(res.output_text);
Expand Down
188 changes: 188 additions & 0 deletions src/executor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,4 +235,192 @@ echo "Success"`,
expect(lines[0]).toMatch(/^Script path: .*script-.*$/);
expect(lines[1]).toBe("Hello");
});

it("should pass inputs as INPUTS_JSON environment variable", async () => {
const config = buildToolConfig({
name: "json_test",
description: "Test INPUTS_JSON environment variable",
inputs: {
message: {
type: "string",
description: "Message",
required: true,
},
count: {
type: "number",
description: "Count",
required: true,
},
},
run: `echo "$INPUTS_JSON"`,
});

const inputs = { message: "Hello", count: 42 };
const result = await executeCommand(config, inputs);
const parsed = JSON.parse(result.trim());
expect(parsed).toEqual(inputs);
});

it("should preserve types in INPUTS_JSON for Node.js", async () => {
const config = buildToolConfig({
name: "node_json_test",
description: "Test INPUTS_JSON with Node.js",
shell: "node {0}",
inputs: {
text: {
type: "string",
description: "Text value",
required: true,
},
number: {
type: "number",
description: "Numeric value",
required: true,
},
flag: {
type: "boolean",
description: "Boolean flag",
required: true,
},
},
run: `
const inputs = JSON.parse(process.env.INPUTS_JSON);
console.log(JSON.stringify({
textType: typeof inputs.text,
numberType: typeof inputs.number,
flagType: typeof inputs.flag,
values: inputs
}));`,
});

const result = await executeCommand(config, {
text: "test",
number: 123,
flag: true,
});
const parsed = JSON.parse(result.trim());
expect(parsed.textType).toBe("string");
expect(parsed.numberType).toBe("number");
expect(parsed.flagType).toBe("boolean");
expect(parsed.values).toEqual({
text: "test",
number: 123,
flag: true,
});
});

it("should preserve types in INPUTS_JSON for Python", async () => {
const config = buildToolConfig({
name: "python_json_test",
description: "Test INPUTS_JSON with Python",
shell: "python3 {0}",
inputs: {
text: {
type: "string",
description: "Text value",
required: true,
},
number: {
type: "number",
description: "Numeric value",
required: true,
},
flag: {
type: "boolean",
description: "Boolean flag",
required: true,
},
},
run: `
import os
import json

inputs = json.loads(os.environ['INPUTS_JSON'])
result = {
'textType': type(inputs['text']).__name__,
'numberType': type(inputs['number']).__name__,
'flagType': type(inputs['flag']).__name__,
'values': inputs
}
print(json.dumps(result))`,
});

const result = await executeCommand(config, {
text: "test",
number: 456,
flag: false,
});
const parsed = JSON.parse(result.trim());
expect(parsed.textType).toBe("str");
// Python3 では number は int または float として解釈される
expect(["int", "float"]).toContain(parsed.numberType);
expect(parsed.flagType).toBe("bool");
expect(parsed.values).toEqual({
text: "test",
number: 456,
flag: false,
});
});

it("should handle complex objects in INPUTS_JSON", async () => {
const config = buildToolConfig({
name: "complex_json_test",
description: "Test INPUTS_JSON with complex data",
shell: "node {0}",
inputs: {
"user-name": {
type: "string",
description: "User name with hyphen",
required: true,
},
age: {
type: "number",
description: "User age",
required: false,
},
active: {
type: "boolean",
description: "Active status",
default: true,
},
},
run: `
const inputs = JSON.parse(process.env.INPUTS_JSON);
console.log(JSON.stringify(inputs));`,
});

const inputs = { "user-name": "Alice", age: 30, active: false };
const result = await executeCommand(config, inputs);
const parsed = JSON.parse(result.trim());
expect(parsed).toEqual(inputs);
});

it("should maintain backward compatibility with individual env vars", async () => {
const config = buildToolConfig({
name: "compatibility_test",
description: "Test backward compatibility",
inputs: {
message: {
type: "string",
description: "Message",
required: true,
},
count: {
type: "number",
description: "Count",
required: true,
},
},
run: `
# Both individual env vars and INPUTS_JSON should be available
echo "Individual: $INPUTS__MESSAGE - $INPUTS__COUNT"
echo "JSON: $INPUTS_JSON"`,
});

const result = await executeCommand(config, { message: "Test", count: 99 });
const lines = result.trim().split("\n");
expect(lines[0]).toBe("Individual: Test - 99");
const parsed = JSON.parse(lines[1].replace("JSON: ", ""));
expect(parsed).toEqual({ message: "Test", count: 99 });
});
});
3 changes: 3 additions & 0 deletions src/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export async function executeCommand(
env[envKey] = String(value);
}

// Also provide inputs as JSON to preserve type information
env["INPUTS_JSON"] = JSON.stringify(inputs);

// Replace all {0} placeholders with the script file path
const command = config.shell.replaceAll("{0}", tmpFile);

Expand Down
Loading