diff --git a/.github/workflows/proofs/formatting.txt b/.github/workflows/proofs/formatting.txt index abef03d6513..ad3562b837f 100644 --- a/.github/workflows/proofs/formatting.txt +++ b/.github/workflows/proofs/formatting.txt @@ -1 +1 @@ -6beb1be8064da30b4f87469f52846b0068bad6d736c0da0205153641723d03ed ab5370655049c955d122d644eb26598258e7998bf91ebc7433f068470a966a9e pass +d3571a0bf0f29900e33ca925391598e5dbfc6dbfcd77a37cd1ae905c4fb920f0 ab5370655049c955d122d644eb26598258e7998bf91ebc7433f068470a966a9e pass diff --git a/.github/workflows/proofs/tests.txt b/.github/workflows/proofs/tests.txt index 9f196c91346..3e224a54d32 100644 --- a/.github/workflows/proofs/tests.txt +++ b/.github/workflows/proofs/tests.txt @@ -1 +1 @@ -16576458607acdc2215ac80de3c245fc8979e8dd046af802cf9352b193deee13 7a39b8df88ef160e852c6aac30ee2a8bfdb8074b060d5afc8c72bf5f03d1f0a7 pass +6b3af2ea0e22d965075f5678144a56927a6c5d9f550c2053b4a1ac46bff3404c 7a39b8df88ef160e852c6aac30ee2a8bfdb8074b060d5afc8c72bf5f03d1f0a7 pass diff --git a/.github/workflows/proofs/transcripts.txt b/.github/workflows/proofs/transcripts.txt index 598e88db5af..2a4f492038c 100644 --- a/.github/workflows/proofs/transcripts.txt +++ b/.github/workflows/proofs/transcripts.txt @@ -1 +1 @@ -f467012b1820e6b37674b199955b991946843b73acdce1b7d1bc41338e7adf9e 72c0b9bfd651515ecb3d3f4981a6bdb8c6d52eba2308cd11fea004d31941aeef pass +8389db50ad367587e05badcf09152052c3980d7884ec5b4d335594e9b3407e41 72c0b9bfd651515ecb3d3f4981a6bdb8c6d52eba2308cd11fea004d31941aeef pass diff --git a/nix/unison-project.nix b/nix/unison-project.nix index 546df960795..6cc53430e19 100644 --- a/nix/unison-project.nix +++ b/nix/unison-project.nix @@ -23,6 +23,7 @@ in conduit-extra = pkgs.haskell.lib.dontCheck hprev.conduit-extra; data-clist = pkgs.haskell.lib.dontCheck hprev.data-clist; fsnotify = pkgs.haskell.lib.dontCheck hprev.fsnotify; + filelock = pkgs.haskell.lib.dontCheck hprev.filelock; doctest-discover = pkgs.haskell.lib.dontCheck hprev.doctest-discover; haskeline = pkgs.haskell.lib.dontCheck hprev.haskeline; hs-mcp = pkgs.haskell.lib.dontCheck hprev.hs-mcp; diff --git a/unison-cli/src/Unison/MCP/Tools.hs b/unison-cli/src/Unison/MCP/Tools.hs index 245622e92fb..ece64c940ce 100644 --- a/unison-cli/src/Unison/MCP/Tools.hs +++ b/unison-cli/src/Unison/MCP/Tools.hs @@ -79,7 +79,9 @@ tools = deleteNamespaceTool, reflogTool, historyTool, - createBranchTool + createBranchTool, + compileTool, + libUpgradeTool ] currentProjectContext :: (MonadIO m, MonadReader Env m) => m ProjectContext @@ -258,7 +260,7 @@ runTool :: Tool MCP runTool = Tool { toolName = toToolName RunTool, - toolDescription = "Execute/Run a given definition.", + toolDescription = "Execute/Run a given definition. If `code` is provided, it will be typechecked first and the definition will be run from the typechecked file without updating the codebase.", toolAnnotations = ToolAnnotations { title = Just "Run", @@ -268,11 +270,15 @@ runTool = openWorldHint = Just False }, toolArgType = Proxy, - toolHandler = \(RunToolArguments {mainFunctionName, projectContext, args}) -> handleToolError $ do + toolHandler = \(RunToolArguments {mainFunctionName, projectContext, args, code}) -> handleToolError $ do let input = ExecuteI NoProf (HQ.NameOnly mainFunctionName) (Text.unpack <$> args) - output <- handleInputMCP projectContext [Right input] - let outputJSON = Text.decodeUtf8 . BL.toStrict $ Aeson.encode output - pure $ textToolResult outputJSON + case code of + Nothing -> do + output <- handleInputMCP projectContext [Right input] + let outputJSON = Text.decodeUtf8 . BL.toStrict $ Aeson.encode output + pure $ textToolResult outputJSON + Just source -> + withCode source [input] projectContext } shareProjectReadmeTool :: Tool MCP @@ -842,3 +848,46 @@ createBranchTool = let outputJSON = Text.decodeUtf8 . BL.toStrict $ Aeson.encode output pure $ textToolResult outputJSON } + +compileTool :: Tool MCP +compileTool = + Tool + { toolName = toToolName CompileTool, + toolDescription = "Compile a Unison definition to a standalone .uc file. The file is written relative to the codebase directory. Run it with: ucm run.compiled .uc", + toolAnnotations = + ToolAnnotations + { title = Just "Compile", + readOnlyHint = Just False, + destructiveHint = Just False, + idempotentHint = Just True, + openWorldHint = Just False + }, + toolArgType = Proxy, + toolHandler = \(CompileToolArguments {projectContext, mainFunctionName, outputPath}) -> handleToolError $ do + let input = MakeStandaloneI (Text.unpack outputPath) (HQ.NameOnly mainFunctionName) + output <- handleInputMCP projectContext [Right input] + let outputJSON = Text.decodeUtf8 . BL.toStrict $ Aeson.encode output + pure $ textToolResult outputJSON + } + +libUpgradeTool :: Tool MCP +libUpgradeTool = + Tool + { toolName = toToolName LibUpgradeTool, + toolDescription = "Upgrade a library dependency from one version to another. Equivalent to `lib.upgrade old new` in UCM.", + toolAnnotations = + ToolAnnotations + { title = Just "Lib Upgrade", + readOnlyHint = Just False, + destructiveHint = Just True, + idempotentHint = Just False, + openWorldHint = Just False + }, + toolArgType = Proxy, + toolHandler = \(LibUpgradeToolArguments {projectContext, oldLibName, newLibName}) -> handleToolError $ do + let segs = map NameSegment.unsafeParseText [oldLibName, newLibName] + input = UpgradeI segs + output <- handleInputMCP projectContext [Right input] + let outputJSON = Text.decodeUtf8 . BL.toStrict $ Aeson.encode output + pure $ textToolResult outputJSON + } diff --git a/unison-cli/src/Unison/MCP/Types.hs b/unison-cli/src/Unison/MCP/Types.hs index bf5e22df9e9..df8cebe7bb4 100644 --- a/unison-cli/src/Unison/MCP/Types.hs +++ b/unison-cli/src/Unison/MCP/Types.hs @@ -32,6 +32,8 @@ module Unison.MCP.Types ReflogToolArguments (..), HistoryToolArguments (..), CreateBranchToolArguments (..), + CompileToolArguments (..), + LibUpgradeToolArguments (..), toToolName, fromToolName, ) @@ -106,6 +108,8 @@ data ToolKind | ReflogTool | HistoryTool | CreateBranchTool + | CompileTool + | LibUpgradeTool deriving (Eq, Ord, Show, Bounded, Enum) kindNameMapping :: Map ToolKind Text @@ -140,7 +144,9 @@ kindNameMapping = (DiffUpdateTool, "diff-update"), (ReflogTool, "reflog"), (HistoryTool, "history"), - (CreateBranchTool, "create-branch") + (CreateBranchTool, "create-branch"), + (CompileTool, "compile"), + (LibUpgradeTool, "lib-upgrade") ] data ProjectDefinitionNameArgument = ProjectDefinitionNameArgument @@ -568,7 +574,8 @@ instance FromJSON DocsToolArguments where data RunToolArguments = RunToolArguments { projectContext :: ProjectContext, mainFunctionName :: Name, - args :: [Text] + args :: [Text], + code :: Maybe (Either FilePath Text) } deriving (Eq, Show) @@ -593,6 +600,27 @@ instance HasInputSchema RunToolArguments where "description" .= ("An argument to pass to the main function." :: Text) ], "description" .= ("The arguments to pass to the main function." :: Text) + ], + "code" + .= object + [ "description" .= ("Optional source code to typecheck before running. Allows running definitions without updating the codebase. Either the `sourceCode` key or the `filePath`, but not both." :: Text), + "type" .= ("object" :: Text), + "properties" + .= object + [ "sourceCode" + .= object + [ "type" .= ("string" :: Text), + "description" .= ("The source code to typecheck." :: Text) + ], + "filePath" + .= object + [ "type" .= ("string" :: Text), + "description" .= ("An absolute file path to the source code." :: Text) + ] + ], + "additionalProperties" .= False, + "minProperties" .= (1 :: Int), + "maxProperties" .= (1 :: Int) ] ], "required" .= ["projectContext", "mainFunctionName", "args" :: Text] @@ -606,7 +634,16 @@ instance FromJSON RunToolArguments where Left err -> fail $ "Invalid main function name: " ++ show err Right name -> pure name args <- o .: "args" - pure $ RunToolArguments {projectContext, mainFunctionName, args} + code <- + o .:? "code" >>= \case + Nothing -> pure Nothing + Just source -> + source .:? "filePath" >>= \case + Just filePath -> pure $ Just (Left filePath) + Nothing -> do + text <- source .: "sourceCode" + pure $ Just (Right text) + pure $ RunToolArguments {projectContext, mainFunctionName, args, code} data ProjectCodeToolArguments = ProjectCodeToolArguments { projectContext :: ProjectContext @@ -1101,6 +1138,77 @@ instance FromJSON CreateBranchToolArguments where sourceBranchName <- fmap UnsafeProjectBranchName <$> o .:? "sourceBranchName" pure $ CreateBranchToolArguments {projectName, newBranchName, sourceType, sourceBranchProject, sourceBranchName} +data CompileToolArguments = CompileToolArguments + { projectContext :: ProjectContext, + mainFunctionName :: Name, + outputPath :: Text + } + deriving (Eq, Show) + +instance HasInputSchema CompileToolArguments where + toInputSchema _ = + object + [ "type" .= ("object" :: Text), + "properties" + .= object + [ "projectContext" .= toInputSchema (Proxy :: Proxy ProjectContext), + "mainFunctionName" + .= object + [ "type" .= ("string" :: Text), + "description" .= ("The main function to compile, e.g. `myMain` or `mynamespace.myprogram`." :: Text) + ], + "outputPath" + .= object + [ "type" .= ("string" :: Text), + "description" .= ("Output file path (without .uc extension). UCM writes the .uc file relative to the codebase directory." :: Text) + ] + ], + "required" .= ["projectContext", "mainFunctionName", "outputPath" :: Text] + ] + +instance FromJSON CompileToolArguments where + parseJSON = withObject "CompileToolArguments" $ \o -> do + projectContext <- o .: "projectContext" + mainFunctionName <- Name.unsafeParseText <$> o .: "mainFunctionName" + outputPath <- o .: "outputPath" + pure $ CompileToolArguments {projectContext, mainFunctionName, outputPath} + +-- | Each element is a pair (old, new); the list is flattened: [old1, new1, old2, new2, ...] +data LibUpgradeToolArguments = LibUpgradeToolArguments + { projectContext :: ProjectContext, + oldLibName :: Text, + newLibName :: Text + } + deriving (Eq, Show) + +instance HasInputSchema LibUpgradeToolArguments where + toInputSchema _ = + object + [ "type" .= ("object" :: Text), + "properties" + .= object + [ "projectContext" .= toInputSchema (Proxy :: Proxy ProjectContext), + "oldLibName" + .= object + [ "type" .= ("string" :: Text), + "description" .= ("The current library name segment to upgrade from, e.g. `unison_base_1_0_0`." :: Text) + ], + "newLibName" + .= object + [ "type" .= ("string" :: Text), + "description" .= ("The new library name segment to upgrade to, e.g. `unison_base_2_0_0`." :: Text) + ] + ], + "required" .= ["projectContext", "oldLibName", "newLibName" :: Text] + ] + +instance FromJSON LibUpgradeToolArguments where + parseJSON = withObject "LibUpgradeToolArguments" $ \o -> do + projectContext <- o .: "projectContext" + oldLibName <- o .: "oldLibName" + newLibName <- o .: "newLibName" + pure $ LibUpgradeToolArguments {projectContext, oldLibName, newLibName} + nameKindMapping :: Map Text ToolKind nameKindMapping = (Map.toList kindNameMapping) diff --git a/unison-src/transcripts/idempotent/mcp-compile-upgrade.md b/unison-src/transcripts/idempotent/mcp-compile-upgrade.md new file mode 100644 index 00000000000..4ccd5e816d9 --- /dev/null +++ b/unison-src/transcripts/idempotent/mcp-compile-upgrade.md @@ -0,0 +1,144 @@ +## compile and lib-upgrade MCP tools + +Tests for the `compile` and `lib-upgrade` MCP tools. + +``` ucm +scratch/main> builtins.mergeio lib.builtins + + Done. +``` + +``` unison :hide +myMain : '{IO, Exception} () +myMain = do + _ = putBytes.impl (io2.IO.stdHandle StdOut) (Text.toUtf8 "compiled!\n") + () +``` + +``` ucm +scratch/main> update + + Okay, I'm searching the branch for code that needs to be + updated... + + Done. +``` + +### compile — success + +Compile `myMain` to a standalone `.uc` file. + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "compile", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "myMain", + "outputPath": "/tmp/mcp-compile-test-out" + } + } + } + +RESPONSE: + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[],\"outputMessages\":[],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### compile — missing definition + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "compile", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "doesNotExist", + "outputPath": "/tmp/mcp-compile-test-missing" + } + } + } + +RESPONSE: + { + "id": 2, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[\"😶\\n\\nI looked for a function `doesNotExist` in the most recently typechecked file and codebase but couldn't find one. It has to have the type:\\n\\n doesNotExist : '{IO, Exception} result\"],\"outputMessages\":[\"😶\\n\\nI looked for a function `doesNotExist` in the most recently typechecked file and codebase but couldn't find one. It has to have the type:\\n\\n doesNotExist : '{IO, Exception} result\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### lib-upgrade — old lib not found + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "lib-upgrade", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "oldLibName": "no_such_lib_1_0_0", + "newLibName": "no_such_lib_2_0_0" + } + } + } + +RESPONSE: + { + "id": 3, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[\"⚠️\\n\\nThe namespace .lib.no_such_lib_1_0_0 doesn't exist.\"],\"outputMessages\":[\"⚠️\\n\\nThe namespace .lib.no_such_lib_1_0_0 doesn't exist.\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` diff --git a/unison-src/transcripts/idempotent/mcp-run-with-code.md b/unison-src/transcripts/idempotent/mcp-run-with-code.md new file mode 100644 index 00000000000..35f3b90204c --- /dev/null +++ b/unison-src/transcripts/idempotent/mcp-run-with-code.md @@ -0,0 +1,233 @@ +## run with code parameter + +This tests the `run` tool's optional `code` parameter, which typechecks source +before running a definition from it — without updating the codebase. + +``` ucm +scratch/main> builtins.mergeio lib.builtins + + Done. +``` + +``` unison :hide +printLn : Text ->{IO} () +printLn msg = + _ = putBytes.impl (io2.IO.stdHandle StdOut) (Text.toUtf8 (msg ++ "\n")) + () + +existingMain : '{IO, Exception} () +existingMain = do printLn "from codebase" +``` + +``` ucm +scratch/main> update + + Okay, I'm searching the branch for code that needs to be + updated... + + Done. +``` + +### Run without code — definition not in codebase (should fail) + +`newMain` doesn't exist in the codebase, so running it without `code` should error. + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "run", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "newMain", + "args": [] + } + } + } + +RESPONSE: + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[\"😶\\n\\nI looked for a function `newMain` in the most recently typechecked file and codebase but couldn't find one. It has to have the type:\\n\\n newMain : '{IO, Exception} result\"],\"outputMessages\":[\"😶\\n\\nI looked for a function `newMain` in the most recently typechecked file and codebase but couldn't find one. It has to have the type:\\n\\n newMain : '{IO, Exception} result\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### Run with code (inline sourceCode) — same definition succeeds + +Now provide `newMain` via the `code` parameter. It should typecheck and run. + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "run", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "newMain", + "args": [], + "code": {"sourceCode": "newMain : '{IO, Exception} ()\nnewMain = do\n _ = putBytes.impl (io2.IO.stdHandle StdOut) (Text.toUtf8 \"hello from code param\\n\")\n ()"} + } + } + } + +RESPONSE: + { + "id": 2, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[],\"outputMessages\":[\"Loading changes detected in .\",\"+ newMain : '{IO, Exception} ()\\n\\nRun `update` to apply these changes to your codebase.\",\"()\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"hello from code param\\n\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### Run with code (filePath) — loads from a .u file + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "run", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "fileMain", + "args": [], + "code": {"filePath": "unison-src/transcripts/idempotent/mcp-run-with-code.u"} + } + } + } + +RESPONSE: + { + "id": 3, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[],\"outputMessages\":[\"Loading changes detected in unison-src/transcripts/idempotent/mcp-run-with-code.u.\",\"+ fileMain : '{IO, Exception} ()\\n\\nRun `update` to apply these changes to your codebase.\",\"()\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"hello from file\\n\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### Multi-term code — run one of several definitions + +Provide multiple definitions in `code` and run just one of them. + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 4, + "method": "tools/call", + "params": { + "name": "run", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "greet", + "args": [], + "code": {"sourceCode": "helper : Text\nhelper = \"world\"\n\ngreet : '{IO, Exception} ()\ngreet = do\n _ = putBytes.impl (io2.IO.stdHandle StdOut) (Text.toUtf8 (\"hello \" ++ helper ++ \"\\n\"))\n ()"} + } + } + } + +RESPONSE: + { + "id": 4, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[],\"outputMessages\":[\"Loading changes detected in .\",\"+ greet : '{IO, Exception} ()\\n+ helper : Text\\n\\nRun `update` to apply these changes to your codebase.\",\"()\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"hello world\\n\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### Confirm codebase is unchanged + +The definitions provided via `code` should NOT have been added to the codebase. + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 5, + "method": "tools/call", + "params": { + "name": "list-project-definitions", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + } + } + } + } + +RESPONSE: + { + "id": 5, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"content\":[{\"text\":\"{\\\"errorMessages\\\":[],\\\"outputMessages\\\":[\\\"1. existingMain : '{IO, Exception} ()\\\\n2. printLn : Text ->{IO} ()\\\\n\\\"],\\\"sourceCodeUpdates\\\":[],\\\"stderr\\\":\\\"\\\",\\\"stdout\\\":\\\"\\\"}\",\"type\":\"text\"}],\"isError\":false}", + "type": "text" + } + ], + "isError": false + } + } + +``` diff --git a/unison-src/transcripts/idempotent/mcp-run-with-code.output.md b/unison-src/transcripts/idempotent/mcp-run-with-code.output.md new file mode 100644 index 00000000000..b94813acacd --- /dev/null +++ b/unison-src/transcripts/idempotent/mcp-run-with-code.output.md @@ -0,0 +1,230 @@ +## run with code parameter + +This tests the `run` tool's optional `code` parameter, which typechecks source +before running a definition from it — without updating the codebase. + +``` ucm +scratch/main> builtins.mergeio lib.builtins + + Done. +``` + +``` unison :hide +printLn : Text ->{IO} () +printLn msg = + _ = putBytes.impl (io2.IO.stdHandle StdOut) (Text.toUtf8 (msg ++ "\n")) + () + +existingMain : '{IO, Exception} () +existingMain = do printLn "from codebase" +``` + +``` ucm +scratch/main> update + + Done. +``` + +### Run without code — definition not in codebase (should fail) + +`newMain` doesn't exist in the codebase, so running it without `code` should error. + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "name": "run", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "newMain", + "args": [] + } + } + } + +RESPONSE: + { + "id": 1, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[\"😶\\n\\nI looked for a function `newMain` in the most recently typechecked file and codebase but couldn't find one. It has to have the type:\\n\\n newMain : '{IO, Exception} result\"],\"outputMessages\":[\"😶\\n\\nI looked for a function `newMain` in the most recently typechecked file and codebase but couldn't find one. It has to have the type:\\n\\n newMain : '{IO, Exception} result\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### Run with code (inline sourceCode) — same definition succeeds + +Now provide `newMain` via the `code` parameter. It should typecheck and run. + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 2, + "method": "tools/call", + "params": { + "name": "run", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "newMain", + "args": [], + "code": {"sourceCode": "newMain : '{IO, Exception} ()\nnewMain = do\n _ = putBytes.impl (io2.IO.stdHandle StdOut) (Text.toUtf8 \"hello from code param\\n\")\n ()"} + } + } + } + +RESPONSE: + { + "id": 2, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[],\"outputMessages\":[\"Loading changes detected in .\",\"+ newMain : '{IO, Exception} ()\\n\\nRun `update` to apply these changes to your codebase.\",\"()\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"hello from code param\\n\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### Run with code (filePath) — loads from a .u file + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 3, + "method": "tools/call", + "params": { + "name": "run", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "fileMain", + "args": [], + "code": {"filePath": "unison-src/transcripts/idempotent/mcp-run-with-code.u"} + } + } + } + +RESPONSE: + { + "id": 3, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[],\"outputMessages\":[\"Loading changes detected in unison-src/transcripts/idempotent/mcp-run-with-code.u.\",\"+ fileMain : '{IO, Exception} ()\\n\\nRun `update` to apply these changes to your codebase.\",\"()\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"hello from file\\n\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### Multi-term code — run one of several definitions + +Provide multiple definitions in `code` and run just one of them. + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 4, + "method": "tools/call", + "params": { + "name": "run", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + }, + "mainFunctionName": "greet", + "args": [], + "code": {"sourceCode": "helper : Text\nhelper = \"world\"\n\ngreet : '{IO, Exception} ()\ngreet = do\n _ = putBytes.impl (io2.IO.stdHandle StdOut) (Text.toUtf8 (\"hello \" ++ helper ++ \"\\n\"))\n ()"} + } + } + } + +RESPONSE: + { + "id": 4, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"errorMessages\":[],\"outputMessages\":[\"Loading changes detected in .\",\"+ greet : '{IO, Exception} ()\\n+ helper : Text\\n\\nRun `update` to apply these changes to your codebase.\",\"()\"],\"sourceCodeUpdates\":[],\"stderr\":\"\",\"stdout\":\"hello world\\n\"}", + "type": "text" + } + ], + "isError": false + } + } + +``` + +### Confirm codebase is unchanged + +The definitions provided via `code` should NOT have been added to the codebase. + +``` api +POST /mcp +BODY: + { + "jsonrpc": "2.0", + "id": 5, + "method": "tools/call", + "params": { + "name": "list-project-definitions", + "arguments": { + "projectContext": { + "projectName": "scratch", + "branchName": "main" + } + } + } + } + +RESPONSE: + { + "id": 5, + "jsonrpc": "2.0", + "result": { + "content": [ + { + "text": "{\"content\":[{\"text\":\"{\\\"errorMessages\\\":[],\\\"outputMessages\\\":[\\\"1. existingMain : '{IO, Exception} ()\\\\n2. printLn : Text ->{IO} ()\\\\n\\\"],\\\"sourceCodeUpdates\\\":[],\\\"stderr\\\":\\\"\\\",\\\"stdout\\\":\\\"\\\"}\",\"type\":\"text\"}],\"isError\":false}", + "type": "text" + } + ], + "isError": false + } + } + +``` diff --git a/unison-src/transcripts/idempotent/mcp-run-with-code.u b/unison-src/transcripts/idempotent/mcp-run-with-code.u new file mode 100644 index 00000000000..80b824cb1c4 --- /dev/null +++ b/unison-src/transcripts/idempotent/mcp-run-with-code.u @@ -0,0 +1,4 @@ +fileMain : '{IO, Exception} () +fileMain = do + _ = putBytes.impl (io2.IO.stdHandle StdOut) (Text.toUtf8 "hello from file\n") + ()