From 17ce2d1e8d6bf993717b784077935438dd4ac601 Mon Sep 17 00:00:00 2001 From: Adkid-Zephyr <169631528+Adkid-Zephyr@users.noreply.github.com> Date: Wed, 19 Aug 2026 01:32:07 +0800 Subject: [PATCH] fix: return action results from action_invoke Generated-by: OpenAI Codex --- README.md | 2 +- spec.md | 2 +- src/tools/invoke.ts | 6 +++--- tests/invoke.test.ts | 51 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 tests/invoke.test.ts diff --git a/README.md b/README.md index 3364276..5223f9a 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Model Context Protocol so any MCP-capable agent can drive them. | Tool | Description | |---|---| | `action_new` | Idempotently create an API endpoint (`__main__.py` + module file). `public` defaults to true. | -| `action_invoke` | Run `ops action invoke ` with `key=value` params. | +| `action_invoke` | Run `ops invoke ` with `key=value` params and return the JSON result. | | `action_requirements` | Add a library to an endpoint's `requirements.txt` (skips preinstalled libs). | | `action_add_secret` | Wire a `.env` secret into an endpoint's context as `ctx.`. | | `secret_status` | Check secret presence and endpoint bindings without reading its value. | diff --git a/spec.md b/spec.md index b05237c..2f1c6df 100644 --- a/spec.md +++ b/spec.md @@ -95,7 +95,7 @@ Receive an (`package/action`) and a series of key=value pairs. ## executes -the command `ops action invoke -p "" "" ... -p "" ""` +the command `ops invoke -p "" "" ... -p "" ""` ## returns diff --git a/src/tools/invoke.ts b/src/tools/invoke.ts index 75790cc..9cdc5a7 100644 --- a/src/tools/invoke.ts +++ b/src/tools/invoke.ts @@ -22,7 +22,7 @@ import { error, text, defineTool } from "../lib.ts" export default defineTool({ name: "action_invoke", config: { - description: "Invoke an API action. Executes `ops action invoke` with the given endpoint and key=value parameters.", + description: "Invoke an API action and return its JSON result. Executes `ops invoke` with the given endpoint and key=value parameters.", inputSchema: { endpoint: z.string().describe("The endpoint path: 'package/action'"), params: z @@ -43,7 +43,7 @@ export default defineTool({ } } - const argv = ["action", "invoke", endpoint.trim(), ...paramArgs] + const argv = ["invoke", endpoint.trim(), ...paramArgs] const { stdout, stderr, exitCode } = await new Promise<{ stdout: string @@ -62,6 +62,6 @@ export default defineTool({ if (exitCode !== 0) result += `\nExit code: ${exitCode}` return exitCode === 0 ? text(result || "(no output)") - : error(result || `ops action invoke failed with exit code ${exitCode}`) + : error(result || `ops invoke failed with exit code ${exitCode}`) }, }) diff --git a/tests/invoke.test.ts b/tests/invoke.test.ts new file mode 100644 index 0000000..42d7a31 --- /dev/null +++ b/tests/invoke.test.ts @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import assert from "node:assert/strict" +import { chmodSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs" +import { tmpdir } from "node:os" +import { delimiter, join } from "node:path" +import test from "node:test" +import actionInvoke from "../src/tools/invoke.ts" + +function resultText(result: { content: { text: string }[] }): string { + return result.content.map((part) => part.text).join("\n") +} + +test("action_invoke returns the action result instead of an activation id", async () => { + const directory = mkdtempSync(join(tmpdir(), "openserverless-mcp-invoke-test-")) + const bin = join(directory, "bin") + const ops = join(bin, "ops") + const previousPath = process.env.PATH + mkdirSync(bin) + writeFileSync(ops, `#!/usr/bin/env node +const args = process.argv.slice(2) +const resultOnly = args[0] === "invoke" || args.includes("-r") +process.stdout.write(resultOnly ? '{"answer":42}\\n' : "ok: invoked /_/v1/demo with id activation-123\\n") +`) + chmodSync(ops, 0o755) + + try { + process.env.PATH = `${bin}${delimiter}${previousPath ?? ""}` + const result = await actionInvoke.handler({ endpoint: "v1/demo", params: ["key=value"] }) + assert.equal(result.isError, undefined) + assert.equal(resultText(result), '{"answer":42}\n') + } finally { + process.env.PATH = previousPath + rmSync(directory, { recursive: true, force: true }) + } +})