terramend 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.
- package/LICENSE +661 -0
- package/README.md +145 -0
- package/dist/agents/claude.d.ts +73 -0
- package/dist/agents/claudePretoolGate.d.ts +99 -0
- package/dist/agents/gateServer.d.ts +7 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/nativeFsDenies.d.ts +28 -0
- package/dist/agents/opencode.d.ts +231 -0
- package/dist/agents/opencodePlugin.d.ts +85 -0
- package/dist/agents/opencodeShared.d.ts +40 -0
- package/dist/agents/postRun.d.ts +132 -0
- package/dist/agents/reviewer.d.ts +38 -0
- package/dist/agents/sessionLabeler.d.ts +97 -0
- package/dist/agents/shared.d.ts +189 -0
- package/dist/agents/subagentModels.d.ts +19 -0
- package/dist/agents/subagentToolGates.d.ts +55 -0
- package/dist/cli.mjs +197426 -0
- package/dist/external.d.ts +227 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +196783 -0
- package/dist/internal/index.d.ts +18 -0
- package/dist/internal.js +1714 -0
- package/dist/lifecycle.d.ts +2 -0
- package/dist/main.d.ts +8 -0
- package/dist/mcp/arkConfig.d.ts +1 -0
- package/dist/mcp/checkSuite.d.ts +25 -0
- package/dist/mcp/checkout.d.ts +77 -0
- package/dist/mcp/comment.d.ts +119 -0
- package/dist/mcp/commitInfo.d.ts +9 -0
- package/dist/mcp/crosswalk.d.ts +105 -0
- package/dist/mcp/dependencies.d.ts +8 -0
- package/dist/mcp/geminiSanitizer.d.ts +28 -0
- package/dist/mcp/git.d.ts +46 -0
- package/dist/mcp/guardrails.d.ts +104 -0
- package/dist/mcp/issue.d.ts +18 -0
- package/dist/mcp/issueComments.d.ts +9 -0
- package/dist/mcp/issueEvents.d.ts +9 -0
- package/dist/mcp/issueInfo.d.ts +9 -0
- package/dist/mcp/labels.d.ts +12 -0
- package/dist/mcp/localContext.d.ts +19 -0
- package/dist/mcp/moduleExtraction.d.ts +71 -0
- package/dist/mcp/moduleTests.d.ts +104 -0
- package/dist/mcp/modules.d.ts +179 -0
- package/dist/mcp/output.d.ts +12 -0
- package/dist/mcp/pathSafety.d.ts +14 -0
- package/dist/mcp/policy.d.ts +48 -0
- package/dist/mcp/pr.d.ts +49 -0
- package/dist/mcp/prInfo.d.ts +9 -0
- package/dist/mcp/providerSchema.d.ts +50 -0
- package/dist/mcp/review.d.ts +199 -0
- package/dist/mcp/reviewComments.d.ts +178 -0
- package/dist/mcp/roots.d.ts +58 -0
- package/dist/mcp/scope.d.ts +15 -0
- package/dist/mcp/selectMode.d.ts +18 -0
- package/dist/mcp/server.d.ts +48 -0
- package/dist/mcp/shared.d.ts +47 -0
- package/dist/mcp/shell.d.ts +37 -0
- package/dist/mcp/staleFix.d.ts +51 -0
- package/dist/mcp/terraform/cost.d.ts +55 -0
- package/dist/mcp/terraform/currency.d.ts +94 -0
- package/dist/mcp/terraform/decisions.d.ts +178 -0
- package/dist/mcp/terraform/findings.d.ts +75 -0
- package/dist/mcp/terraform/plan.d.ts +157 -0
- package/dist/mcp/terraform/scanners.d.ts +131 -0
- package/dist/mcp/terraform/tools.d.ts +63 -0
- package/dist/mcp/terraform/types.d.ts +172 -0
- package/dist/mcp/terraform.d.ts +22 -0
- package/dist/mcp/terratest.d.ts +83 -0
- package/dist/mcp/upload.d.ts +6 -0
- package/dist/models.d.ts +171 -0
- package/dist/modes.d.ts +26 -0
- package/dist/prep/index.d.ts +7 -0
- package/dist/prep/installNodeDependencies.d.ts +2 -0
- package/dist/prep/installPythonDependencies.d.ts +2 -0
- package/dist/prep/types.d.ts +31 -0
- package/dist/reviewQuality.d.ts +64 -0
- package/dist/skills/terraform-best-practices/SKILL.md +369 -0
- package/dist/toolState.d.ts +135 -0
- package/dist/utils/activity.d.ts +40 -0
- package/dist/utils/agent.d.ts +20 -0
- package/dist/utils/agentHangReport.d.ts +38 -0
- package/dist/utils/apiFetch.d.ts +19 -0
- package/dist/utils/apiKeys.d.ts +41 -0
- package/dist/utils/apiUrl.d.ts +20 -0
- package/dist/utils/assets.d.ts +8 -0
- package/dist/utils/billingErrors.d.ts +85 -0
- package/dist/utils/body.d.ts +34 -0
- package/dist/utils/buildTerramendFooter.d.ts +25 -0
- package/dist/utils/byokFallback.d.ts +85 -0
- package/dist/utils/claudeSubscription.d.ts +30 -0
- package/dist/utils/cli.d.ts +10 -0
- package/dist/utils/codexHome.d.ts +29 -0
- package/dist/utils/codexOAuth.d.ts +60 -0
- package/dist/utils/diffCoverage.d.ts +63 -0
- package/dist/utils/errorReport.d.ts +17 -0
- package/dist/utils/exitHandler.d.ts +8 -0
- package/dist/utils/fixDoubleEscapedString.d.ts +1 -0
- package/dist/utils/gitAuth.d.ts +84 -0
- package/dist/utils/gitAuthServer.d.ts +24 -0
- package/dist/utils/github.d.ts +78 -0
- package/dist/utils/globals.d.ts +3 -0
- package/dist/utils/install.d.ts +60 -0
- package/dist/utils/instructions.d.ts +48 -0
- package/dist/utils/leapingComment.d.ts +11 -0
- package/dist/utils/learnings.d.ts +62 -0
- package/dist/utils/learningsTruncate.d.ts +25 -0
- package/dist/utils/lifecycle.d.ts +57 -0
- package/dist/utils/log.d.ts +111 -0
- package/dist/utils/normalizeEnv.d.ts +30 -0
- package/dist/utils/openCodeModels.d.ts +11 -0
- package/dist/utils/overrides.d.ts +40 -0
- package/dist/utils/packageManager.d.ts +49 -0
- package/dist/utils/patchWorkflowRunFields.d.ts +29 -0
- package/dist/utils/payload.d.ts +105 -0
- package/dist/utils/prSummary.d.ts +61 -0
- package/dist/utils/progressComment.d.ts +146 -0
- package/dist/utils/providerErrors.d.ts +31 -0
- package/dist/utils/rangeDiff.d.ts +51 -0
- package/dist/utils/remediationCommand.d.ts +55 -0
- package/dist/utils/retry.d.ts +13 -0
- package/dist/utils/reviewCleanup.d.ts +14 -0
- package/dist/utils/run.d.ts +9 -0
- package/dist/utils/runContext.d.ts +60 -0
- package/dist/utils/runContextData.d.ts +23 -0
- package/dist/utils/runErrorRenderer.d.ts +64 -0
- package/dist/utils/runLifecycle.d.ts +86 -0
- package/dist/utils/runStartupLog.d.ts +15 -0
- package/dist/utils/secrets.d.ts +22 -0
- package/dist/utils/setup.d.ts +90 -0
- package/dist/utils/shell.d.ts +32 -0
- package/dist/utils/skills.d.ts +10 -0
- package/dist/utils/subprocess.d.ts +80 -0
- package/dist/utils/terraformMcp.d.ts +42 -0
- package/dist/utils/time.d.ts +15 -0
- package/dist/utils/timer.d.ts +23 -0
- package/dist/utils/todoTracking.d.ts +16 -0
- package/dist/utils/token.d.ts +39 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/versioning.d.ts +7 -0
- package/dist/utils/vertex.d.ts +16 -0
- package/dist/utils/workflow.d.ts +13 -0
- package/package.json +119 -0
- package/src/agents/claude.test.ts +1016 -0
- package/src/agents/claude.ts +1246 -0
- package/src/agents/claudePretoolGate.test.ts +28 -0
- package/src/agents/claudePretoolGate.ts +173 -0
- package/src/agents/gateServer.test.ts +204 -0
- package/src/agents/gateServer.ts +124 -0
- package/src/agents/index.ts +10 -0
- package/src/agents/nativeFsDenies.ts +82 -0
- package/src/agents/opencode.test.ts +1440 -0
- package/src/agents/opencode.ts +1312 -0
- package/src/agents/opencodePlugin.ts +222 -0
- package/src/agents/opencodeShared.test.ts +34 -0
- package/src/agents/opencodeShared.ts +121 -0
- package/src/agents/postRun.test.ts +549 -0
- package/src/agents/postRun.ts +535 -0
- package/src/agents/reviewer.ts +104 -0
- package/src/agents/sessionLabeler.test.ts +247 -0
- package/src/agents/sessionLabeler.ts +178 -0
- package/src/agents/shared.test.ts +76 -0
- package/src/agents/shared.ts +292 -0
- package/src/agents/subagentModels.test.ts +113 -0
- package/src/agents/subagentModels.ts +40 -0
- package/src/agents/subagentRegistration.test.ts +41 -0
- package/src/agents/subagentToolGates.ts +114 -0
- package/src/cli.test.ts +129 -0
- package/src/cli.ts +105 -0
- package/src/commands/gha.test.ts +192 -0
- package/src/commands/gha.ts +188 -0
- package/src/commands/mcp.ts +122 -0
- package/src/config.ts +1 -0
- package/src/entry.ts +7 -0
- package/src/entryPost.stdlibOnly.test.ts +109 -0
- package/src/entryPost.ts +99 -0
- package/src/external.test.ts +16 -0
- package/src/external.ts +302 -0
- package/src/index.ts +11 -0
- package/src/internal/index.ts +71 -0
- package/src/lifecycle.ts +2 -0
- package/src/main.test.ts +873 -0
- package/src/main.ts +712 -0
- package/src/mcp/__fixtures__/terramend-scratch-pr-49-review-3485940013.json +110 -0
- package/src/mcp/__fixtures__/terramend-scratch-pr-64-review-3531000326.json +14 -0
- package/src/mcp/__fixtures__/terramend-test-repo-pr-1.diff.json +67 -0
- package/src/mcp/__snapshots__/checkout.test.ts.snap +109 -0
- package/src/mcp/__snapshots__/reviewComments.test.ts.snap +71 -0
- package/src/mcp/arkConfig.ts +7 -0
- package/src/mcp/checkSuite.test.ts +245 -0
- package/src/mcp/checkSuite.ts +255 -0
- package/src/mcp/checkout.test.ts +752 -0
- package/src/mcp/checkout.ts +886 -0
- package/src/mcp/comment.test.ts +772 -0
- package/src/mcp/comment.ts +582 -0
- package/src/mcp/commitInfo.test.ts +127 -0
- package/src/mcp/commitInfo.ts +61 -0
- package/src/mcp/crosswalk.test.ts +106 -0
- package/src/mcp/crosswalk.ts +339 -0
- package/src/mcp/dependencies.test.ts +309 -0
- package/src/mcp/dependencies.ts +189 -0
- package/src/mcp/geminiSanitizer.test.ts +287 -0
- package/src/mcp/geminiSanitizer.ts +207 -0
- package/src/mcp/git.test.ts +1083 -0
- package/src/mcp/git.ts +890 -0
- package/src/mcp/guardrails.test.ts +705 -0
- package/src/mcp/guardrails.ts +465 -0
- package/src/mcp/issue.test.ts +113 -0
- package/src/mcp/issue.ts +73 -0
- package/src/mcp/issueComments.test.ts +69 -0
- package/src/mcp/issueComments.ts +48 -0
- package/src/mcp/issueEvents.test.ts +134 -0
- package/src/mcp/issueEvents.ts +100 -0
- package/src/mcp/issueInfo.test.ts +104 -0
- package/src/mcp/issueInfo.ts +72 -0
- package/src/mcp/labels.test.ts +52 -0
- package/src/mcp/labels.ts +34 -0
- package/src/mcp/localContext.ts +28 -0
- package/src/mcp/localServer.test.ts +75 -0
- package/src/mcp/localServer.ts +131 -0
- package/src/mcp/moduleExtraction.test.ts +261 -0
- package/src/mcp/moduleExtraction.ts +313 -0
- package/src/mcp/moduleTests.test.ts +269 -0
- package/src/mcp/moduleTests.ts +421 -0
- package/src/mcp/modules.test.ts +640 -0
- package/src/mcp/modules.ts +696 -0
- package/src/mcp/output.test.ts +96 -0
- package/src/mcp/output.ts +70 -0
- package/src/mcp/pathSafety.test.ts +44 -0
- package/src/mcp/pathSafety.ts +28 -0
- package/src/mcp/policy.test.ts +282 -0
- package/src/mcp/policy.ts +199 -0
- package/src/mcp/pr.test.ts +387 -0
- package/src/mcp/pr.ts +194 -0
- package/src/mcp/prInfo.test.ts +96 -0
- package/src/mcp/prInfo.ts +91 -0
- package/src/mcp/providerSchema.test.ts +85 -0
- package/src/mcp/providerSchema.ts +175 -0
- package/src/mcp/review.test.ts +936 -0
- package/src/mcp/review.ts +923 -0
- package/src/mcp/reviewComments.test.ts +549 -0
- package/src/mcp/reviewComments.ts +896 -0
- package/src/mcp/roots.test.ts +175 -0
- package/src/mcp/roots.ts +217 -0
- package/src/mcp/scope.test.ts +59 -0
- package/src/mcp/scope.ts +65 -0
- package/src/mcp/security.test.ts +720 -0
- package/src/mcp/selectMode.test.ts +210 -0
- package/src/mcp/selectMode.ts +181 -0
- package/src/mcp/server.test.ts +292 -0
- package/src/mcp/server.ts +403 -0
- package/src/mcp/shared.ts +100 -0
- package/src/mcp/shell.test.ts +520 -0
- package/src/mcp/shell.ts +505 -0
- package/src/mcp/staleFix.test.ts +237 -0
- package/src/mcp/staleFix.ts +277 -0
- package/src/mcp/terraform/cost.ts +163 -0
- package/src/mcp/terraform/currency.test.ts +338 -0
- package/src/mcp/terraform/currency.ts +336 -0
- package/src/mcp/terraform/decisions.ts +527 -0
- package/src/mcp/terraform/findings.ts +333 -0
- package/src/mcp/terraform/plan.ts +348 -0
- package/src/mcp/terraform/scanners.ts +809 -0
- package/src/mcp/terraform/tools.test.ts +1071 -0
- package/src/mcp/terraform/tools.ts +908 -0
- package/src/mcp/terraform/types.ts +305 -0
- package/src/mcp/terraform.test.ts +1957 -0
- package/src/mcp/terraform.ts +23 -0
- package/src/mcp/terratest.test.ts +105 -0
- package/src/mcp/terratest.ts +196 -0
- package/src/mcp/toolFiltering.test.ts +85 -0
- package/src/mcp/upload.test.ts +180 -0
- package/src/mcp/upload.ts +112 -0
- package/src/models.test.ts +300 -0
- package/src/models.ts +708 -0
- package/src/modes.test.ts +107 -0
- package/src/modes.ts +880 -0
- package/src/prep/index.ts +43 -0
- package/src/prep/installNodeDependencies.test.ts +298 -0
- package/src/prep/installNodeDependencies.ts +196 -0
- package/src/prep/installPythonDependencies.test.ts +268 -0
- package/src/prep/installPythonDependencies.ts +199 -0
- package/src/prep/types.ts +38 -0
- package/src/reviewQuality.test.ts +63 -0
- package/src/reviewQuality.ts +134 -0
- package/src/runCli.test.ts +214 -0
- package/src/runCli.ts +282 -0
- package/src/skills/terraform-best-practices/SKILL.md +369 -0
- package/src/toolState.test.ts +45 -0
- package/src/toolState.ts +252 -0
- package/src/utils/activity.test.ts +188 -0
- package/src/utils/activity.ts +210 -0
- package/src/utils/agent.test.ts +251 -0
- package/src/utils/agent.ts +139 -0
- package/src/utils/agentHangReport.test.ts +203 -0
- package/src/utils/agentHangReport.ts +170 -0
- package/src/utils/apiFetch.test.ts +115 -0
- package/src/utils/apiFetch.ts +62 -0
- package/src/utils/apiKeys.test.ts +344 -0
- package/src/utils/apiKeys.ts +206 -0
- package/src/utils/apiUrl.test.ts +30 -0
- package/src/utils/apiUrl.ts +59 -0
- package/src/utils/assets.test.ts +153 -0
- package/src/utils/assets.ts +107 -0
- package/src/utils/billingErrors.test.ts +121 -0
- package/src/utils/billingErrors.ts +189 -0
- package/src/utils/body.test.ts +217 -0
- package/src/utils/body.ts +168 -0
- package/src/utils/buildTerramendFooter.test.ts +38 -0
- package/src/utils/buildTerramendFooter.ts +82 -0
- package/src/utils/byokFallback.test.ts +205 -0
- package/src/utils/byokFallback.ts +128 -0
- package/src/utils/claudeSubscription.test.ts +179 -0
- package/src/utils/claudeSubscription.ts +93 -0
- package/src/utils/cli.ts +31 -0
- package/src/utils/codexHome.test.ts +190 -0
- package/src/utils/codexHome.ts +191 -0
- package/src/utils/codexOAuth.ts +147 -0
- package/src/utils/codexRefreshDetect.test.ts +85 -0
- package/src/utils/codexRefreshDetect.ts +35 -0
- package/src/utils/diffCoverage.test.ts +468 -0
- package/src/utils/diffCoverage.ts +404 -0
- package/src/utils/errorReport.test.ts +135 -0
- package/src/utils/errorReport.ts +83 -0
- package/src/utils/exitHandler.ts +35 -0
- package/src/utils/fixDoubleEscapedString.ts +9 -0
- package/src/utils/ghaCore.ts +13 -0
- package/src/utils/gitAuth.test.ts +322 -0
- package/src/utils/gitAuth.ts +263 -0
- package/src/utils/gitAuthServer.test.ts +260 -0
- package/src/utils/gitAuthServer.ts +182 -0
- package/src/utils/github.test.ts +615 -0
- package/src/utils/github.ts +538 -0
- package/src/utils/globals.ts +9 -0
- package/src/utils/humanEditCapture.test.ts +100 -0
- package/src/utils/humanEditCapture.ts +193 -0
- package/src/utils/install.test.ts +768 -0
- package/src/utils/install.ts +492 -0
- package/src/utils/instructions.test.ts +240 -0
- package/src/utils/instructions.ts +543 -0
- package/src/utils/leapingComment.test.ts +51 -0
- package/src/utils/leapingComment.ts +18 -0
- package/src/utils/learnings.test.ts +87 -0
- package/src/utils/learnings.ts +138 -0
- package/src/utils/learningsTocRender.test.ts +116 -0
- package/src/utils/learningsTruncate.test.ts +39 -0
- package/src/utils/learningsTruncate.ts +42 -0
- package/src/utils/lifecycle.test.ts +195 -0
- package/src/utils/lifecycle.ts +198 -0
- package/src/utils/log.test.ts +402 -0
- package/src/utils/log.ts +432 -0
- package/src/utils/normalizeEnv.test.ts +91 -0
- package/src/utils/normalizeEnv.ts +106 -0
- package/src/utils/openCodeModels.ts +82 -0
- package/src/utils/overrides.test.ts +89 -0
- package/src/utils/overrides.ts +98 -0
- package/src/utils/packageManager.test.ts +321 -0
- package/src/utils/packageManager.ts +257 -0
- package/src/utils/patchWorkflowRunFields.test.ts +92 -0
- package/src/utils/patchWorkflowRunFields.ts +150 -0
- package/src/utils/payload.test.ts +497 -0
- package/src/utils/payload.ts +371 -0
- package/src/utils/postApiFetch.ts +51 -0
- package/src/utils/prSummary.test.ts +224 -0
- package/src/utils/prSummary.ts +147 -0
- package/src/utils/progressComment.ts +261 -0
- package/src/utils/providerErrors.test.ts +315 -0
- package/src/utils/providerErrors.ts +172 -0
- package/src/utils/rangeDiff.test.ts +236 -0
- package/src/utils/rangeDiff.ts +182 -0
- package/src/utils/remediationCommand.test.ts +163 -0
- package/src/utils/remediationCommand.ts +119 -0
- package/src/utils/retry.test.ts +153 -0
- package/src/utils/retry.ts +58 -0
- package/src/utils/reviewCleanup.ts +106 -0
- package/src/utils/run.ts +99 -0
- package/src/utils/runContext.ts +145 -0
- package/src/utils/runContextData.ts +58 -0
- package/src/utils/runErrorRenderer.test.ts +95 -0
- package/src/utils/runErrorRenderer.ts +259 -0
- package/src/utils/runFixture.ts +76 -0
- package/src/utils/runLifecycle.ts +237 -0
- package/src/utils/runStartupLog.ts +60 -0
- package/src/utils/secrets.test.ts +103 -0
- package/src/utils/secrets.ts +177 -0
- package/src/utils/setup.test.ts +509 -0
- package/src/utils/setup.ts +352 -0
- package/src/utils/shell.ts +103 -0
- package/src/utils/skills.test.ts +46 -0
- package/src/utils/skills.ts +67 -0
- package/src/utils/subprocess.test.ts +170 -0
- package/src/utils/subprocess.ts +438 -0
- package/src/utils/terraformMcp.test.ts +63 -0
- package/src/utils/terraformMcp.ts +83 -0
- package/src/utils/time.test.ts +105 -0
- package/src/utils/time.ts +59 -0
- package/src/utils/timer.test.ts +91 -0
- package/src/utils/timer.ts +72 -0
- package/src/utils/todoTracking.test.ts +223 -0
- package/src/utils/todoTracking.ts +167 -0
- package/src/utils/token.test.ts +239 -0
- package/src/utils/token.ts +186 -0
- package/src/utils/version.ts +10 -0
- package/src/utils/versioning.test.ts +34 -0
- package/src/utils/versioning.ts +44 -0
- package/src/utils/vertex.ts +85 -0
- package/src/utils/workflow.ts +25 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// this must be imported first
|
|
2
|
+
import "#app/mcp/arkConfig";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { createServer } from "node:net";
|
|
5
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
6
|
+
import { FastMCP, type Tool } from "fastmcp";
|
|
7
|
+
import { type AgentId, terramendMcpName } from "#app/external";
|
|
8
|
+
import { CheckoutPrTool } from "#app/mcp/checkout";
|
|
9
|
+
import { GetCheckSuiteLogsTool } from "#app/mcp/checkSuite";
|
|
10
|
+
import {
|
|
11
|
+
CreateCommentTool,
|
|
12
|
+
EditCommentTool,
|
|
13
|
+
ReplyToReviewCommentTool,
|
|
14
|
+
ReportProgressTool,
|
|
15
|
+
} from "#app/mcp/comment";
|
|
16
|
+
import { CommitInfoTool } from "#app/mcp/commitInfo";
|
|
17
|
+
import { ComplianceCrosswalkTool } from "#app/mcp/crosswalk";
|
|
18
|
+
import {
|
|
19
|
+
AwaitDependencyInstallationTool,
|
|
20
|
+
StartDependencyInstallationTool,
|
|
21
|
+
} from "#app/mcp/dependencies";
|
|
22
|
+
import {
|
|
23
|
+
DeleteBranchTool,
|
|
24
|
+
GitFetchTool,
|
|
25
|
+
GitTool,
|
|
26
|
+
PushBranchTool,
|
|
27
|
+
PushTagsTool,
|
|
28
|
+
} from "#app/mcp/git";
|
|
29
|
+
import { IssueTool } from "#app/mcp/issue";
|
|
30
|
+
import { GetIssueCommentsTool } from "#app/mcp/issueComments";
|
|
31
|
+
import { GetIssueEventsTool } from "#app/mcp/issueEvents";
|
|
32
|
+
import { IssueInfoTool } from "#app/mcp/issueInfo";
|
|
33
|
+
import { AddLabelsTool } from "#app/mcp/labels";
|
|
34
|
+
import { ModuleExtractionCandidatesTool } from "#app/mcp/moduleExtraction";
|
|
35
|
+
import {
|
|
36
|
+
ListModulesTool,
|
|
37
|
+
TerraformModuleGraphTool,
|
|
38
|
+
TerraformModuleInterfaceTool,
|
|
39
|
+
} from "#app/mcp/modules";
|
|
40
|
+
import { TerraformModuleTestsTool } from "#app/mcp/moduleTests";
|
|
41
|
+
import { SetOutputTool } from "#app/mcp/output";
|
|
42
|
+
import { PolicyCheckTool } from "#app/mcp/policy";
|
|
43
|
+
import { CreatePullRequestTool, UpdatePullRequestBodyTool } from "#app/mcp/pr";
|
|
44
|
+
import { PullRequestInfoTool } from "#app/mcp/prInfo";
|
|
45
|
+
import { TerraformProviderSchemaTool } from "#app/mcp/providerSchema";
|
|
46
|
+
import { CreatePullRequestReviewTool } from "#app/mcp/review";
|
|
47
|
+
import {
|
|
48
|
+
GetReviewCommentsTool,
|
|
49
|
+
ListPullRequestReviewsTool,
|
|
50
|
+
ResolveReviewThreadTool,
|
|
51
|
+
} from "#app/mcp/reviewComments";
|
|
52
|
+
import { TerraformRootsTool } from "#app/mcp/roots";
|
|
53
|
+
import { SelectModeTool } from "#app/mcp/selectMode";
|
|
54
|
+
import { addTools } from "#app/mcp/shared";
|
|
55
|
+
import { KillBackgroundTool, ShellTool } from "#app/mcp/shell";
|
|
56
|
+
import { ClosePullRequestTool, ListRemediationPrsTool } from "#app/mcp/staleFix";
|
|
57
|
+
import {
|
|
58
|
+
InfracostDiffTool,
|
|
59
|
+
ReadFindingsTool,
|
|
60
|
+
TerraformEmitSarifTool,
|
|
61
|
+
TerraformPlanTool,
|
|
62
|
+
TerraformScanTool,
|
|
63
|
+
TerraformValidateTool,
|
|
64
|
+
TerraformVerifyRemediationTool,
|
|
65
|
+
TerraformVersionCurrencyTool,
|
|
66
|
+
} from "#app/mcp/terraform";
|
|
67
|
+
import { ScaffoldTerratestTool } from "#app/mcp/terratest";
|
|
68
|
+
import { UploadFileTool } from "#app/mcp/upload";
|
|
69
|
+
import type { Mode } from "#app/modes";
|
|
70
|
+
import type { ToolState } from "#app/toolState";
|
|
71
|
+
import type { OctokitWithPlugins } from "#app/utils/github";
|
|
72
|
+
import type { ResolvedPayload } from "#app/utils/payload";
|
|
73
|
+
import type { AccountPlan } from "#app/utils/runContext";
|
|
74
|
+
import type { RunContextData } from "#app/utils/runContextData";
|
|
75
|
+
import packageJson from "#package.json" with { type: "json" };
|
|
76
|
+
|
|
77
|
+
export interface ToolContext {
|
|
78
|
+
agentId: AgentId;
|
|
79
|
+
repo: RunContextData["repo"];
|
|
80
|
+
payload: ResolvedPayload;
|
|
81
|
+
octokit: OctokitWithPlugins;
|
|
82
|
+
githubInstallationToken: string;
|
|
83
|
+
gitToken: string;
|
|
84
|
+
apiToken: string;
|
|
85
|
+
modes: Mode[];
|
|
86
|
+
postCheckoutScript: string | null;
|
|
87
|
+
prepushScript: string | null;
|
|
88
|
+
prApproveEnabled: boolean;
|
|
89
|
+
modeInstructions: Record<string, string>;
|
|
90
|
+
toolState: ToolState;
|
|
91
|
+
runId: number | undefined;
|
|
92
|
+
mcpServerUrl: string;
|
|
93
|
+
// per-run bearer token the agent's MCP client must present (Authorization:
|
|
94
|
+
// Bearer <token>) to reach this server. Closes the unauthenticated-localhost
|
|
95
|
+
// side door: a co-located process on the runner (e.g. a malicious dependency
|
|
96
|
+
// postinstall) that scans the loopback port range can no longer drive
|
|
97
|
+
// privileged tools without the token. Minted in startMcpHttpServer; delivered
|
|
98
|
+
// to each agent's MCP config out-of-band (env-expanded header for Claude,
|
|
99
|
+
// OPENCODE_CONFIG_CONTENT for opencode) so it never lands in a readable file.
|
|
100
|
+
mcpServerToken: string;
|
|
101
|
+
tmpdir: string;
|
|
102
|
+
// repo-level OSS flag + account-level billing plan. together they decide
|
|
103
|
+
// whether terramend is paying for marginal infra — see `isInfraCovered` in
|
|
104
|
+
// the server's `utils/billing.ts`. plan gating for endpoints like the
|
|
105
|
+
// learnings PATCH is enforced server-side via 402, so we pass plan along
|
|
106
|
+
// mostly for future use / observability. see wiki/pricing.md.
|
|
107
|
+
oss: boolean;
|
|
108
|
+
plan: AccountPlan;
|
|
109
|
+
// resolved upstream model specifier (e.g. "google/gemini-3.1-pro-preview").
|
|
110
|
+
// undefined when the alias is unresolvable (agent auto-selects).
|
|
111
|
+
// used by the schema sanitizer to detect Gemini-routed traffic.
|
|
112
|
+
resolvedModel: string | undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const mcpPortStart = 3764;
|
|
116
|
+
const mcpPortAttempts = 100;
|
|
117
|
+
const mcpHost = "127.0.0.1";
|
|
118
|
+
const mcpEndpoint = "/mcp";
|
|
119
|
+
|
|
120
|
+
function readEnvPort(): number | null {
|
|
121
|
+
const rawPort = process.env.TERRAMEND_MCP_PORT;
|
|
122
|
+
if (!rawPort) return null;
|
|
123
|
+
const parsed = Number.parseInt(rawPort, 10);
|
|
124
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
125
|
+
throw new Error(`invalid TERRAMEND_MCP_PORT: ${rawPort}`);
|
|
126
|
+
}
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isPortAvailable(port: number): Promise<boolean> {
|
|
131
|
+
return new Promise((resolve) => {
|
|
132
|
+
const server = createServer();
|
|
133
|
+
server.unref();
|
|
134
|
+
server.once("error", () => resolve(false));
|
|
135
|
+
server.once("listening", () => {
|
|
136
|
+
server.close(() => resolve(true));
|
|
137
|
+
});
|
|
138
|
+
server.listen(port, mcpHost);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function getErrorMessage(error: unknown): string {
|
|
143
|
+
if (error instanceof Error) return error.message;
|
|
144
|
+
return String(error);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isAddressInUse(error: unknown): boolean {
|
|
148
|
+
const message = getErrorMessage(error).toLowerCase();
|
|
149
|
+
return message.includes("eaddrinuse") || message.includes("address already in use");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
type JsonSchema = Record<string, unknown>;
|
|
153
|
+
|
|
154
|
+
function buildCommonTools(ctx: ToolContext, outputSchema?: JsonSchema): Tool<any, any>[] {
|
|
155
|
+
const tools: Tool<any, any>[] = [
|
|
156
|
+
StartDependencyInstallationTool(ctx),
|
|
157
|
+
AwaitDependencyInstallationTool(ctx),
|
|
158
|
+
CreateCommentTool(ctx),
|
|
159
|
+
EditCommentTool(ctx),
|
|
160
|
+
ReplyToReviewCommentTool(ctx),
|
|
161
|
+
IssueTool(ctx),
|
|
162
|
+
IssueInfoTool(ctx),
|
|
163
|
+
GetIssueCommentsTool(ctx),
|
|
164
|
+
GetIssueEventsTool(ctx),
|
|
165
|
+
CreatePullRequestReviewTool(ctx),
|
|
166
|
+
PullRequestInfoTool(ctx),
|
|
167
|
+
CommitInfoTool(ctx),
|
|
168
|
+
CheckoutPrTool(ctx),
|
|
169
|
+
GetReviewCommentsTool(ctx),
|
|
170
|
+
ListPullRequestReviewsTool(ctx),
|
|
171
|
+
ResolveReviewThreadTool(ctx),
|
|
172
|
+
GetCheckSuiteLogsTool(ctx),
|
|
173
|
+
AddLabelsTool(ctx),
|
|
174
|
+
GitTool(ctx),
|
|
175
|
+
GitFetchTool(ctx),
|
|
176
|
+
UploadFileTool(ctx),
|
|
177
|
+
// Terraform best-practice check tools (read-only). Always available so the
|
|
178
|
+
// Remediate / GenerateTerraform modes can scan + gate without extra perms.
|
|
179
|
+
TerraformScanTool(ctx),
|
|
180
|
+
TerraformValidateTool(ctx),
|
|
181
|
+
TerraformVerifyRemediationTool(ctx),
|
|
182
|
+
InfracostDiffTool(ctx),
|
|
183
|
+
ReadFindingsTool(ctx),
|
|
184
|
+
TerraformPlanTool(ctx),
|
|
185
|
+
TerraformVersionCurrencyTool(ctx),
|
|
186
|
+
ListModulesTool(ctx),
|
|
187
|
+
TerraformModuleGraphTool(ctx),
|
|
188
|
+
TerraformModuleInterfaceTool(ctx),
|
|
189
|
+
TerraformModuleTestsTool(ctx),
|
|
190
|
+
ModuleExtractionCandidatesTool(ctx),
|
|
191
|
+
TerraformProviderSchemaTool(ctx),
|
|
192
|
+
TerraformRootsTool(ctx),
|
|
193
|
+
ScaffoldTerratestTool(ctx),
|
|
194
|
+
TerraformEmitSarifTool(ctx),
|
|
195
|
+
PolicyCheckTool(ctx),
|
|
196
|
+
ComplianceCrosswalkTool(ctx),
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const isStandalone = ctx.payload.event.trigger === "unknown";
|
|
200
|
+
if (isStandalone || outputSchema) {
|
|
201
|
+
tools.push(SetOutputTool(ctx, outputSchema));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// MCP shell with filtered env (no secrets leaked to child processes)
|
|
205
|
+
if (ctx.payload.shell === "restricted") {
|
|
206
|
+
tools.push(ShellTool(ctx));
|
|
207
|
+
tools.push(KillBackgroundTool(ctx));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return tools;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function buildOrchestratorTools(ctx: ToolContext, outputSchema?: JsonSchema): Tool<any, any>[] {
|
|
214
|
+
return [
|
|
215
|
+
...buildCommonTools(ctx, outputSchema),
|
|
216
|
+
ReportProgressTool(ctx),
|
|
217
|
+
SelectModeTool(ctx),
|
|
218
|
+
PushBranchTool(ctx),
|
|
219
|
+
PushTagsTool(ctx),
|
|
220
|
+
DeleteBranchTool(ctx),
|
|
221
|
+
CreatePullRequestTool(ctx),
|
|
222
|
+
UpdatePullRequestBodyTool(ctx),
|
|
223
|
+
// §27 stale-fix self-healing — list open remediation PRs + close a redundant
|
|
224
|
+
// one. Orchestrator-only (close is a write; the sweep is an orchestrator job).
|
|
225
|
+
ListRemediationPrsTool(ctx),
|
|
226
|
+
ClosePullRequestTool(ctx),
|
|
227
|
+
];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
type McpStartResult = {
|
|
231
|
+
server: FastMCP;
|
|
232
|
+
url: string;
|
|
233
|
+
port: number;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Build the FastMCP `authenticate` hook that enforces the per-run bearer token.
|
|
238
|
+
*
|
|
239
|
+
* The streamable-HTTP transport invokes this at session creation, so a caller
|
|
240
|
+
* without the token can't even open a session (let alone call a tool). On a
|
|
241
|
+
* valid token it returns a truthy session object; otherwise it throws and the
|
|
242
|
+
* transport rejects the connection. The token is compared with a
|
|
243
|
+
* length-then-constant-time check to avoid leaking it via timing.
|
|
244
|
+
*/
|
|
245
|
+
function buildAuthenticate(token: string) {
|
|
246
|
+
const expected = `Bearer ${token}`;
|
|
247
|
+
return async (request: import("node:http").IncomingMessage): Promise<{ authorized: true }> => {
|
|
248
|
+
const header = request.headers.authorization;
|
|
249
|
+
const provided = Array.isArray(header) ? header[0] : header;
|
|
250
|
+
if (!provided || !timingSafeEqualStr(provided, expected)) {
|
|
251
|
+
throw new Error("Unauthorized: missing or invalid MCP bearer token");
|
|
252
|
+
}
|
|
253
|
+
return { authorized: true };
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** constant-time string compare (length-guarded) so a wrong token can't be
|
|
258
|
+
* recovered byte-by-byte via response timing. */
|
|
259
|
+
function timingSafeEqualStr(a: string, b: string): boolean {
|
|
260
|
+
if (a.length !== b.length) return false;
|
|
261
|
+
let diff = 0;
|
|
262
|
+
for (let i = 0; i < a.length; i++) {
|
|
263
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
264
|
+
}
|
|
265
|
+
return diff === 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function tryStartMcpServer(
|
|
269
|
+
ctx: ToolContext,
|
|
270
|
+
tools: Tool<any, any>[],
|
|
271
|
+
port: number,
|
|
272
|
+
token: string,
|
|
273
|
+
): Promise<McpStartResult | null> {
|
|
274
|
+
const server = new FastMCP({
|
|
275
|
+
name: terramendMcpName,
|
|
276
|
+
version: packageJson.version as `${number}.${number}.${number}`,
|
|
277
|
+
authenticate: buildAuthenticate(token),
|
|
278
|
+
});
|
|
279
|
+
addTools(ctx, server, tools);
|
|
280
|
+
|
|
281
|
+
try {
|
|
282
|
+
await server.start({
|
|
283
|
+
transportType: "httpStream",
|
|
284
|
+
httpStream: {
|
|
285
|
+
port,
|
|
286
|
+
host: mcpHost,
|
|
287
|
+
endpoint: mcpEndpoint,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
const url = `http://${mcpHost}:${port}${mcpEndpoint}`;
|
|
291
|
+
return { server, url, port };
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (!isAddressInUse(error)) {
|
|
294
|
+
throw error;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
await server.stop();
|
|
298
|
+
} catch {
|
|
299
|
+
// ignore cleanup errors on failed start
|
|
300
|
+
}
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function selectMcpPort(
|
|
306
|
+
ctx: ToolContext,
|
|
307
|
+
tools: Tool<any, any>[],
|
|
308
|
+
token: string,
|
|
309
|
+
): Promise<McpStartResult> {
|
|
310
|
+
let lastError: unknown = null;
|
|
311
|
+
|
|
312
|
+
const requestedPort = readEnvPort();
|
|
313
|
+
if (requestedPort !== null) {
|
|
314
|
+
if (await isPortAvailable(requestedPort)) {
|
|
315
|
+
const requestedResult = await tryStartMcpServer(ctx, tools, requestedPort, token);
|
|
316
|
+
if (requestedResult) {
|
|
317
|
+
return requestedResult;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// randomize start offset to reduce collision chance in parallel runs
|
|
323
|
+
const randomOffset = Math.floor(Math.random() * 50);
|
|
324
|
+
|
|
325
|
+
for (let offset = 0; offset < mcpPortAttempts; offset++) {
|
|
326
|
+
const port = mcpPortStart + randomOffset + offset;
|
|
327
|
+
try {
|
|
328
|
+
if (!(await isPortAvailable(port))) {
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
const result = await tryStartMcpServer(ctx, tools, port, token);
|
|
332
|
+
if (result) {
|
|
333
|
+
return result;
|
|
334
|
+
}
|
|
335
|
+
} catch (error) {
|
|
336
|
+
lastError = error;
|
|
337
|
+
if (!isAddressInUse(error)) {
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const message = getErrorMessage(lastError);
|
|
344
|
+
throw new Error(
|
|
345
|
+
`could not find available mcp port starting at ${mcpPortStart} (last error: ${message})`,
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
async function killBackgroundProcesses(toolState: ToolState): Promise<void> {
|
|
350
|
+
const backgroundProcesses = toolState.backgroundProcesses;
|
|
351
|
+
if (backgroundProcesses.size === 0) return;
|
|
352
|
+
for (const proc of backgroundProcesses.values()) {
|
|
353
|
+
try {
|
|
354
|
+
process.kill(-proc.pid, "SIGTERM");
|
|
355
|
+
} catch {
|
|
356
|
+
// already dead
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
await sleep(200);
|
|
360
|
+
for (const proc of backgroundProcesses.values()) {
|
|
361
|
+
try {
|
|
362
|
+
process.kill(-proc.pid, "SIGKILL");
|
|
363
|
+
} catch {
|
|
364
|
+
// already dead
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
backgroundProcesses.clear();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
type McpHttpServerOptions = {
|
|
371
|
+
outputSchema?: JsonSchema | undefined;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Start the MCP HTTP server.
|
|
376
|
+
*
|
|
377
|
+
* The returned disposer is idempotent — safe to call multiple times.
|
|
378
|
+
* Callers (e.g. the inner activity-timeout handler in main.ts) may need to
|
|
379
|
+
* stop the server before the `await using` block exits; a subsequent
|
|
380
|
+
* automatic dispose is then a no-op.
|
|
381
|
+
*/
|
|
382
|
+
export async function startMcpHttpServer(
|
|
383
|
+
ctx: ToolContext,
|
|
384
|
+
options?: McpHttpServerOptions,
|
|
385
|
+
): Promise<{ url: string; token: string; [Symbol.asyncDispose]: () => Promise<void> }> {
|
|
386
|
+
// per-run bearer token. minted here (not in process.env) and returned so the
|
|
387
|
+
// caller can hand it to the agent's MCP client out-of-band; see ToolContext.
|
|
388
|
+
const token = randomUUID();
|
|
389
|
+
const tools = buildOrchestratorTools(ctx, options?.outputSchema);
|
|
390
|
+
const startResult = await selectMcpPort(ctx, tools, token);
|
|
391
|
+
|
|
392
|
+
let disposed = false;
|
|
393
|
+
return {
|
|
394
|
+
url: startResult.url,
|
|
395
|
+
token,
|
|
396
|
+
[Symbol.asyncDispose]: async () => {
|
|
397
|
+
if (disposed) return;
|
|
398
|
+
disposed = true;
|
|
399
|
+
await killBackgroundProcesses(ctx.toolState);
|
|
400
|
+
await startResult.server.stop();
|
|
401
|
+
},
|
|
402
|
+
};
|
|
403
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from "@standard-schema/spec";
|
|
2
|
+
import { encode as toonEncode } from "@toon-format/toon";
|
|
3
|
+
import type { FastMCP, Tool } from "fastmcp";
|
|
4
|
+
import { isGeminiRouted, sanitizeToolForGemini } from "#app/mcp/geminiSanitizer";
|
|
5
|
+
import type { ToolContext } from "#app/mcp/server";
|
|
6
|
+
import { formatJsonValue, log } from "#app/utils/cli";
|
|
7
|
+
|
|
8
|
+
export const tool = <const params>(
|
|
9
|
+
toolDef: Tool<any, StandardSchemaV1<params>>,
|
|
10
|
+
): Tool<any, StandardSchemaV1<params>> => toolDef;
|
|
11
|
+
|
|
12
|
+
export interface ToolResult {
|
|
13
|
+
content: {
|
|
14
|
+
type: "text";
|
|
15
|
+
text: string;
|
|
16
|
+
}[];
|
|
17
|
+
isError?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Structured tool-outcome envelope (§3.5 "Structured tool errors"). Every tool
|
|
22
|
+
* that can be unavailable, skipped, or degrade green returns the SAME shape so a
|
|
23
|
+
* caller (the agent, or a downstream parser) can branch deterministically:
|
|
24
|
+
* - success: `{ ok: true, … }`
|
|
25
|
+
* - skip / unavailable / soft failure: `{ ok: false, code, detail }`
|
|
26
|
+
* where `code` is a stable machine token (snake_case) and `detail` a human
|
|
27
|
+
* sentence. The newer tools (terraform_roots / terraform_module_interface /
|
|
28
|
+
* terraform_provider_schema / policy_check) emit this natively; the older
|
|
29
|
+
* degrade-green tools historically returned `{ ran|found: false,
|
|
30
|
+
* skipped_reason|reason }`. Those keep their legacy aliases ALONGSIDE the new
|
|
31
|
+
* fields (the call site spreads `toolSkip(...)` into its existing object) so
|
|
32
|
+
* prompt + test contracts keep working while the surface converges — additive,
|
|
33
|
+
* never breaking.
|
|
34
|
+
*/
|
|
35
|
+
export type ToolOk<T extends Record<string, any>> = T & { ok: true };
|
|
36
|
+
|
|
37
|
+
/** wrap a success payload with `ok: true`. */
|
|
38
|
+
export const toolOk = <T extends Record<string, any>>(data: T): ToolOk<T> => ({
|
|
39
|
+
ok: true,
|
|
40
|
+
...data,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
/** the structured skip/unavailable envelope: `{ ok: false, code, detail }`. */
|
|
44
|
+
export const toolSkip = (
|
|
45
|
+
code: string,
|
|
46
|
+
detail: string,
|
|
47
|
+
): { ok: false; code: string; detail: string } => ({ ok: false, code, detail });
|
|
48
|
+
|
|
49
|
+
export const handleToolSuccess = (data: Record<string, any> | string): ToolResult => {
|
|
50
|
+
const text = typeof data === "string" ? data : toonEncode(data);
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text }],
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const handleToolError = (error: unknown): ToolResult => {
|
|
57
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: `Error: ${errorMessage}`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
isError: true,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Helper to wrap a tool execute function with error handling.
|
|
71
|
+
* Captures ctx in closure so tools don't need to handle try/catch.
|
|
72
|
+
* @param fn - the function to execute
|
|
73
|
+
* @param toolName - optional tool name for error logging
|
|
74
|
+
*/
|
|
75
|
+
export const execute = <T, R extends Record<string, any> | string>(
|
|
76
|
+
fn: (params: T) => Promise<R>,
|
|
77
|
+
toolName?: string,
|
|
78
|
+
) => {
|
|
79
|
+
const _fn = async (params: T): Promise<ToolResult> => {
|
|
80
|
+
try {
|
|
81
|
+
const result = await fn(params);
|
|
82
|
+
return handleToolSuccess(result);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
85
|
+
const prefix = toolName ? `[${toolName}]` : "tool";
|
|
86
|
+
log.info(`${prefix} error: ${errorMessage}`);
|
|
87
|
+
log.debug(`${prefix} params: ${formatJsonValue(params)}`);
|
|
88
|
+
return handleToolError(error);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
return _fn;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const addTools = (ctx: ToolContext, server: FastMCP<any>, tools: Tool<any, any>[]) => {
|
|
95
|
+
const shouldSanitize = isGeminiRouted(ctx);
|
|
96
|
+
for (const tool of tools) {
|
|
97
|
+
server.addTool(shouldSanitize ? sanitizeToolForGemini(tool) : tool);
|
|
98
|
+
}
|
|
99
|
+
return server;
|
|
100
|
+
};
|