Welcome to MCP Toolbox UI
+This is the main content area. Click a tab on the left to navigate.
+diff --git a/cmd/root.go b/cmd/root.go index c931d1dd2c71..e5aebd89648b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -226,6 +226,7 @@ func NewCommand(opts ...Option) *Command { flags.StringVar(&cmd.prebuiltConfig, "prebuilt", "", "Use a prebuilt tool configuration by source type. Cannot be used with --tools-file. Allowed: 'alloydb-postgres-admin', alloydb-postgres', 'bigquery', 'cloud-sql-mysql', 'cloud-sql-postgres', 'cloud-sql-mssql', 'dataplex', 'firestore', 'looker', 'mssql', 'mysql', 'postgres', 'spanner', 'spanner-postgres'.") flags.BoolVar(&cmd.cfg.Stdio, "stdio", false, "Listens via MCP STDIO instead of acting as a remote HTTP server.") flags.BoolVar(&cmd.cfg.DisableReload, "disable-reload", false, "Disables dynamic reloading of tools file.") + flags.BoolVar(&cmd.cfg.UI, "ui", false, "Launches the Toolbox UI web server.") // wrap RunE command so that we have access to original Command object cmd.RunE = func(*cobra.Command, []string) error { return run(cmd) } @@ -802,6 +803,9 @@ func run(cmd *Command) error { return errMsg } cmd.logger.InfoContext(ctx, "Server ready to serve!") + if cmd.cfg.UI { + cmd.logger.InfoContext(ctx, "Toolbox UI is up and running at: http://localhost:5000/ui") + } go func() { defer close(srvErr) diff --git a/docs/en/getting-started/introduction/_index.md b/docs/en/getting-started/introduction/_index.md index 85e7ba09dbc6..5765401ff4a8 100644 --- a/docs/en/getting-started/introduction/_index.md +++ b/docs/en/getting-started/introduction/_index.md @@ -136,6 +136,15 @@ Toolbox enables dynamic reloading by default. To disable, use the `--disable-reload` flag. {{< /notice >}} +#### Launching Toolbox UI + +To launch Toolbox's interactive UI, use the `--ui` flag. This allows you to test tools and toolsets +with features such as authorized parameters. To learn more, visit [Toolbox UI](../../how-to/use-toolbox-ui/index.md). + +```sh +./toolbox --ui +``` + #### Homebrew Users If you installed Toolbox using Homebrew, the `toolbox` binary is available in your system path. You can start the server with the same command: diff --git a/docs/en/how-to/use-toolbox-ui/edit-headers.gif b/docs/en/how-to/use-toolbox-ui/edit-headers.gif new file mode 100644 index 000000000000..b63860dca005 Binary files /dev/null and b/docs/en/how-to/use-toolbox-ui/edit-headers.gif differ diff --git a/docs/en/how-to/use-toolbox-ui/edit-headers.png b/docs/en/how-to/use-toolbox-ui/edit-headers.png new file mode 100644 index 000000000000..07aebc07d20f Binary files /dev/null and b/docs/en/how-to/use-toolbox-ui/edit-headers.png differ diff --git a/docs/en/how-to/use-toolbox-ui/index.md b/docs/en/how-to/use-toolbox-ui/index.md new file mode 100644 index 000000000000..aa3fd6688af8 --- /dev/null +++ b/docs/en/how-to/use-toolbox-ui/index.md @@ -0,0 +1,106 @@ +--- +title: "Toolbox UI" +type: docs +weight: 1 +description: > + How to effectively use Toolbox UI. +--- + +Toolbox UI is a built-in web interface that allows users to visually inspect and test out configured resources such as tools and toolsets. + +## Launching Toolbox UI + +To launch Toolbox's interactive UI, use the `--ui` flag. + +```sh +./toolbox --ui +``` + +Toolbox UI will be served from the same host and port as the Toolbox Server, with the `/ui` suffix. Once Toolbox +is launched, the following INFO log with Toolbox UI's url will be shown: + +```bash +INFO "Toolbox UI is up and running at: http://localhost:5000/ui" +``` + +## Navigating the Tools Page + +The tools page shows all tools loaded from your configuration file. This corresponds to the default toolset (represented by an empty string). Each tool's name on this page will exactly match its name in the configuration +file. + +To view details for a specific tool, click on the tool name. The main content area will be populated +with the tool name, description, and available parameters. + + + +### Invoking a Tool + +1. Click on a Tool +2. Enter appropriate parameters in each parameter field +3. Click "Run Tool" +4. Done! Your results will appear in the response field +5. (Optional) Uncheck "Prettify JSON" to format the response as plain text + + + +### Optional Parameters + +Toolbox allows users to add [optional parameters](../../resources/tools/#basic-parameters) with or without a default value. + +To exclude a parameter, uncheck the box to the right of an associated parameter, and that parameter will not be +included in the request body. If the parameter is not sent, Toolbox will either use it as `nil` value or the `default` value, if configured. If the parameter is required, Toolbox will throw an error. + +When the box is checked, parameter will be sent exactly as entered in the response field (e.g. empty string). + + + + + +### Editing Headers + +To edit headers, press the "Edit Headers" button to display the header modal. Within this modal, +users can make direct edits by typing into the header's text area. + +Toolbox UI validates that the headers are in correct JSON format. Other header-related errors (e.g., +incorrect header names or values required by the tool) will be reported in the Response section +after running the tool. + + + +#### Google OAuth + +Currently, Toolbox supports Google OAuth 2.0 as an AuthService, which allows tools to utilize +authorized parameters. When a tool uses an authorized parameter, the parameter will be displayed +but not editable, as it will be populated from the authentication token. + +To provide the token, add your Google OAuth ID Token to the request header using the "Edit Headers" +button and modal described above. The key should be the name of your AuthService as defined in +your tool configuration file, suffixed with `_token`. The value should be your ID token as a string. + +1. Select a tool that requires [authenticated parameters]() +2. The auth parameter's text field is greyed out. This is because it cannot be entered manually and will +be parsed from the resolved auth token +3. To update request headers with the token, select "Edit Headers" +4. Checkout the dropdown "How to extract Google OAuth ID Token manually" for guidance on retrieving ID token +5. Paste the request header +6. Click "Save" +7. Click "Run Tool" + +```json +{ + "Content-Type": "application/json", + "my-google-auth_token": "YOUR_ID_TOKEN_HERE" +} +``` + + + +## Navigating the Toolsets Page + +Through the toolsets page, users can search for a specific toolset to retrieve tools from. Simply +enter the toolset name in the search bar, and press "Enter" to retrieve the associated tools. + +If the toolset name is not defined within the tools configuration file, an error message will be +displayed. + + diff --git a/docs/en/how-to/use-toolbox-ui/optional-param-checked.png b/docs/en/how-to/use-toolbox-ui/optional-param-checked.png new file mode 100644 index 000000000000..54eff95936ab Binary files /dev/null and b/docs/en/how-to/use-toolbox-ui/optional-param-checked.png differ diff --git a/docs/en/how-to/use-toolbox-ui/optional-param-unchecked.png b/docs/en/how-to/use-toolbox-ui/optional-param-unchecked.png new file mode 100644 index 000000000000..3265adc9bb1b Binary files /dev/null and b/docs/en/how-to/use-toolbox-ui/optional-param-unchecked.png differ diff --git a/docs/en/how-to/use-toolbox-ui/run-tool.gif b/docs/en/how-to/use-toolbox-ui/run-tool.gif new file mode 100644 index 000000000000..58fbc933a794 Binary files /dev/null and b/docs/en/how-to/use-toolbox-ui/run-tool.gif differ diff --git a/docs/en/how-to/use-toolbox-ui/tools.png b/docs/en/how-to/use-toolbox-ui/tools.png new file mode 100644 index 000000000000..3d2934d9f462 Binary files /dev/null and b/docs/en/how-to/use-toolbox-ui/tools.png differ diff --git a/docs/en/how-to/use-toolbox-ui/toolsets.png b/docs/en/how-to/use-toolbox-ui/toolsets.png new file mode 100644 index 000000000000..9eb7a60c4c5b Binary files /dev/null and b/docs/en/how-to/use-toolbox-ui/toolsets.png differ diff --git a/go.mod b/go.mod index 395617145f46..448f552c1f5e 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( github.com/go-chi/chi/v5 v5.2.2 github.com/go-chi/httplog/v2 v2.1.1 github.com/go-chi/render v1.0.3 + github.com/go-goquery/goquery v1.0.1 github.com/go-playground/validator/v10 v10.27.0 github.com/go-sql-driver/mysql v1.9.3 github.com/goccy/go-yaml v1.18.0 @@ -49,6 +50,7 @@ require ( ) require ( + github.com/andybalholm/cascadia v1.3.3 // indirect github.com/duckdb/duckdb-go-bindings v0.1.17 // indirect github.com/duckdb/duckdb-go-bindings/darwin-amd64 v0.1.12 // indirect github.com/duckdb/duckdb-go-bindings/darwin-arm64 v0.1.12 // indirect @@ -75,6 +77,7 @@ require ( github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect + github.com/PuerkitoBio/goquery v1.10.3 // indirect github.com/ajg/form v1.5.1 // indirect github.com/apache/arrow-go/v18 v18.4.0 // indirect github.com/apache/arrow/go/v15 v15.0.2 // indirect diff --git a/go.sum b/go.sum index 64898b922127..b388a5be50ab 100644 --- a/go.sum +++ b/go.sum @@ -665,6 +665,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapp github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0/go.mod h1:cSgYe11MCNYunTnRXrKiR/tHc0eoKjICUuWpNZoVCOo= github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c/go.mod h1:X0CRv0ky0k6m906ixxpzmDRLvX58TFUKS2eePweuyxk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo= +github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY= @@ -674,6 +676,8 @@ github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGW github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM= +github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/arrow-go/v18 v18.4.0 h1:/RvkGqH517iY8bZKc4FD5/kkdwXJGjxf28JIXbJ/oB0= github.com/apache/arrow-go/v18 v18.4.0/go.mod h1:Aawvwhj8x2jURIzD9Moy72cF0FyJXOpkYpdmGRHcw14= @@ -806,6 +810,8 @@ github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmn github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-goquery/goquery v1.0.1 h1:kpchVA1LdOFWdRpkDPESVdlb1JQI6ixsJ5MiNUITO7U= +github.com/go-goquery/goquery v1.0.1/go.mod h1:W5s8OWbqWf6lG0LkXWBeh7U1Y/X5XTI0Br65MHF8uJk= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -910,6 +916,7 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -1226,6 +1233,10 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1287,6 +1298,9 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91 golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1346,6 +1360,11 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -1395,6 +1414,10 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1477,8 +1500,14 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= @@ -1487,6 +1516,11 @@ golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1503,6 +1537,10 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1577,6 +1615,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/server/config.go b/internal/server/config.go index 2a9e09d36eb0..506d2bd784a1 100644 --- a/internal/server/config.go +++ b/internal/server/config.go @@ -55,6 +55,8 @@ type ServerConfig struct { Stdio bool // DisableReload indicates if the user has disabled dynamic reloading for Toolbox. DisableReload bool + // UI indicates if Toolbox UI endpoints (/ui) are available + UI bool } type logFormat string diff --git a/internal/server/server.go b/internal/server/server.go index 015cee33666d..6e3572ced877 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -330,6 +330,13 @@ func NewServer(ctx context.Context, cfg ServerConfig) (*Server, error) { return nil, err } r.Mount("/mcp", mcpR) + if cfg.UI { + webR, err := webRouter() + if err != nil { + return nil, err + } + r.Mount("/ui", webR) + } // default endpoint for validating server is running r.Get("/", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("🧰 Hello, World! 🧰")) diff --git a/internal/server/static/assets/mcptoolboxlogo.png b/internal/server/static/assets/mcptoolboxlogo.png new file mode 100644 index 000000000000..afeebd59a58a Binary files /dev/null and b/internal/server/static/assets/mcptoolboxlogo.png differ diff --git a/internal/server/static/css/style.css b/internal/server/static/css/style.css new file mode 100644 index 000000000000..8d2c9ebac49c --- /dev/null +++ b/internal/server/static/css/style.css @@ -0,0 +1,580 @@ +:root { + --toolbox-blue: #4285f4; + --text-primary-gray: #444444; + --text-secondary-gray: #6e6e6e; + --button-primary: var(--toolbox-blue); + --button-secondary: #616161; +} + +body { + display: flex; + height: 100vh; + margin: 0; + font-family: 'Trebuchet MS'; + background-color: #f8f9fa; + box-sizing: border-box; +} + +*, *:before, *:after { + box-sizing: inherit; +} + +#navbar-container { + flex: 0 0 250px; + height: 100%; + position: relative; + z-index: 10; +} + +#main-content-container { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + overflow-x: hidden; +} + +.left-nav { + background-color: #fff; + box-shadow: 4px 0px 12px rgba(0, 0, 0, 0.15); + display: flex; + flex-direction: column; + padding: 15px; + align-items: center; + width: 100%; + height: 100%; + z-index: 3; + + ul { + font-family: 'Verdana'; + list-style: none; + padding: 0; + margin: 0; + width: 100%; + + li { + margin-bottom: 5px; + + a { + display: flex; + align-items: center; + padding: 12px; + text-decoration: none; + color: #333; + border-radius: 0; + + &:hover { + background-color: #e9e9e9; + border-radius: 35px; + } + + &.active { + background-color: #d0d0d0; + font-weight: bold; + border-radius: 35px; + } + } + } + } +} + +.second-nav { + flex: 0 0 250px; + background-color: #fff; + box-shadow: 4px 0px 12px rgba(0, 0, 0, 0.15); + z-index: 2; + display: flex; + flex-direction: column; + padding: 15px; + align-items: center; + position: relative; +} + +.nav-logo { + width: 90%; + margin-bottom: 40px; + flex-shrink: 0; + + img { + max-width: 100%; + height: auto; + display: block; + } +} + +.main-content-area { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + overflow-x: hidden; +} + +.top-bar { + background-color: #fff; + padding: 30px 30px; + display: flex; + justify-content: flex-end; + align-items: center; + border-bottom: 1px solid #eee; +} + +.content { + padding: 20px; + flex-grow: 1; + overflow-y: auto; + display: flex; + flex-direction: column; +} + +.btn { + display: flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + color: white; + border: none; + border-radius: 30px; + font: inherit; + font-size: 1em; + font-weight: bolder; + cursor: pointer; + + &:hover { + opacity: 0.8; + } +} + +.btn--run { + background-color: var(--button-primary); +} + +.btn--editHeaders { + background-color: var(--button-secondary) +} + +.btn--saveHeaders { + background-color: var(--button-primary) +} + +.btn--closeHeaders { + background-color: var(--button-secondary) +} + +.tool-button { + display: flex; + align-items: center; + padding: 12px; + text-decoration: none; + color: #333; + background-color: transparent; + border: none; + border-radius: 0; + width: 100%; + text-align: left; + cursor: pointer; + font-family: inherit; + font-size: inherit; + + transition: background-color 0.1s ease-in-out, border-radius 0.1s ease-in-out; + + &:hover { + background-color: #e9e9e9; + border-radius: 35px; + } + + &:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(208, 208, 208, 0.5); + } + + &.active { + background-color: #d0d0d0; + font-weight: bold; + border-radius: 35px; + + &:hover { + background-color: #d0d0d0; + } + } +} + +#secondary-panel-content { + flex: 1; + overflow-y: auto; + width: 100%; + min-height: 0; + + ul { + list-style: none; + padding: 0; + margin: 0; + width: 100%; + } +} + +.tool-details-grid { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 20px; + margin: 0 0 20px 0; + align-items: start; + flex-shrink: 0; +} + +.tool-info { + display: flex; + flex-direction: column; + gap: 15px; +} + +.tool-execution-area { + display: flex; + flex-direction: column; + gap: 12px; +} + +.tool-params { + background-color: #ffffff; + padding: 15px; + border-radius: 4px; + border: 1px solid #ddd; + + h5 { + margin-bottom: 0; + } +} + +.tool-box { + background-color: #ffffff; + padding: 15px; + border-radius: 4px; + border: 1px solid #eee; + + h5 { + color: var(--toolbox-blue); + margin-top: 0; + font-weight: bold; + } +} + +.params-header { + display: flex; + justify-content: flex-end; + margin-bottom: 8px; + padding-right: 6px; + font-weight: bold; + font-size: 0.9em; + color: var(--text-secondary-gray); +} + +.params-disclaimer { + font-style: italic; + color: var(--text-secondary-gray); + font-size: 0.8em; + margin-bottom: 10px; + width: 100%; + word-wrap: break-word; +} + +.param-item { + margin-bottom: 12px; + + label { + display: block; + margin-bottom: 4px; + font-family: inherit; + } + + &.disabled-param { + > label { + color: #888; + text-decoration: line-through; + } + + .param-input-element { + background-color: #f5f5f5; + opacity: 0.6; + } + } + + input[type="text"], + input[type="number"], + select, + textarea { + width: calc(100% - 12px); + padding: 6px; + border: 1px solid #ccc; + border-radius: 4px; + font-family: inherit; + } + + input[type="checkbox"].param-input-element { + width: auto; + padding: 0; + border: initial; + border-radius: initial; + vertical-align: middle; + margin-right: 4px; + accent-color: var(--toolbox-blue); + flex-grow: 0; + } +} + +.input-checkbox-wrapper { + display: flex; + align-items: center; + gap: 10px; +} + +.param-input-element-container { + flex-grow: 1; +} + +.param-input-element { + box-sizing: border-box; +} + +.include-param-container { + display: flex; + align-items: center; + white-space: nowrap; + + input[type="checkbox"] { + width: auto; + padding: 0; + border: initial; + border-radius: initial; + vertical-align: middle; + margin-right: 0; + accent-color: var(--toolbox-blue); + } +} + +.include-param-container input[type="checkbox"] { + width: auto; + padding: 0; + border: initial; + border-radius: initial; + vertical-align: middle; + margin: 0; + accent-color: var(--toolbox-blue); +} + +.checkbox-bool-label { + margin-left: 5px; + font-style: italic; + color: var(--text-primary-gray); +} + +.checkbox-bool-label.disabled { + color: #aaa; + cursor: not-allowed; +} + +.param-label-extras { + font-style: italic; + font-weight: lighter; + color: var(--text-secondary-gray); +} + +.auth-param-input { + background-color: #e0e0e0; + cursor: not-allowed; +} + +.run-button-container { + display: flex; + justify-content: flex-end; + gap: 20px; +} + +.header-modal { + display: none; + position: fixed; + z-index: 1000; + left: 0; + top: 0; + width: 100%; + height: 100%; + overflow: auto; + background-color: rgba(0,0,0,0.4); + + li { + margin-bottom: 10px; + } + + .header-modal-content { + background-color: #fefefe; + margin: 10% auto; + padding: 20px; + border: 1px solid #888; + width: 80%; + max-width: 50%; + border-radius: 8px; + display: flex; + flex-direction: column; + gap: 15px; + align-items: center; + + h5 { + margin-top: 0; + font-size: 1.2em; + } + + .headers-textarea { + width: calc(100% - 16px); + padding: 8px; + font-family: monospace; + border: 1px solid #ccc; + border-radius: 4px; + min-height: 150px; + } + + .header-modal-actions { + display: flex; + justify-content: center; + gap: 30px; + width: 100%; + } + + .auth-token-details { + width: 100%; + max-width: calc(100% - 16px); + margin-left: 8px; + margin-right: 8px; + + summary { + cursor: pointer; + text-align: left; + padding: 5px 0; + } + + .auth-token-content { + padding: 10px; + border: 1px solid #eee; + margin-top: 5px; + background-color: #f9f9f9; + text-align: left; + max-width: 100%; + overflow-wrap: break-word; + + .auth-tab-group { + display: flex; + border-bottom: 1px solid #ccc; + margin-bottom: 10px; + } + + .auth-tab-picker { + padding: 8px 12px; + cursor: pointer; + border: 1px solid transparent; + border-bottom: 1px solid transparent; + margin-bottom: -1px; + background-color: #f0f0f0; + + &.active { + background-color: #fff; + border-color: #ccc; + border-bottom-color: #fff; + font-weight: bold; + } + } + + .auth-tab-content { + display: none; + overflow-wrap: break-word; + word-wrap: break-word; + max-width: 100%; + + &.active { + display: block; + } + + pre { + white-space: pre-wrap; + word-wrap: break-word; + overflow-x: auto; + background-color: #f5f5f5; + padding: 10px; + border: 1px solid #ccc; + border-radius: 4px; + max-width: 100%; + + code { + display: block; + word-wrap: break-word; + color: inherit; + } + } + } + } + } + } +} + +.tool-response { + margin: 20px 0 0 0; + + textarea { + width: 100%; + min-height: 150px; + padding: 12px; + border: 1px solid #ddd; + border-radius: 4px; + font-family: monospace; + } +} + +.search-container { + display: flex; + width: 100%; + margin-bottom: 15px; + + #toolset-search-input { + flex-grow: 1; + padding: 10px 12px; + border: 1px solid #ccc; + border-radius: 20px 0 0 20px; + border-right: none; + font-family: inherit; + font-size: 0.9em; + color: var(--text-primary-gray); + + &:focus { + outline: none; + border-color: var(--toolbox-blue); + box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.3); + } + + &::placeholder { + color: var(--text-secondary-gray); + } + } + + #toolset-search-button { + padding: 10px 15px; + border: 1px solid var(--button-primary); + background-color: var(--button-primary); + color: white; + border-radius: 0 20px 20px 0; + cursor: pointer; + font-family: inherit; + font-size: 0.9em; + font-weight: bold; + transition: opacity 0.2s ease-in-out; + flex-shrink: 0; + line-height: 1; + + &:hover { + opacity: 0.8; + } + + &:focus { + outline: none; + box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.3); + } + } +} + + diff --git a/internal/server/static/index.html b/internal/server/static/index.html new file mode 100644 index 000000000000..b152f69d6a1f --- /dev/null +++ b/internal/server/static/index.html @@ -0,0 +1,24 @@ + + +
+ + +Fetching tools...
'; + try { + const response = await fetch(`/api/toolset/${toolsetName}`); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const apiResponse = await response.json(); + renderToolList(apiResponse, secondNavContent, toolDisplayArea); + } catch (error) { + console.error('Failed to load tools:', error); + secondNavContent.innerHTML = `Failed to load tools:
${error}`;
+ }
+}
+
+/**
+ * Renders the list of tools as buttons within the provided HTML element.
+ * @param {?{tools: ?ObjectLoading tool details...
'; + + try { + const response = await fetch(`/api/tool/${encodeURIComponent(toolName)}`, { signal }); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + const apiResponse = await response.json(); + + if (!apiResponse.tools || !apiResponse.tools[toolName]) { + throw new Error(`Tool "${toolName}" data not found in API response.`); + } + const toolObject = apiResponse.tools[toolName]; + console.debug("Received tool object: ", toolObject) + + const toolInterfaceData = { + id: toolName, + name: toolName, + description: toolObject.description || "No description provided.", + parameters: (toolObject.parameters || []).map(param => { + let inputType = 'text'; + const apiType = param.type ? param.type.toLowerCase() : 'string'; + let valueType = 'string'; + let label = param.description || param.name; + + if (apiType === 'integer' || apiType === 'float') { + inputType = 'number'; + valueType = 'number'; + } else if (apiType === 'boolean') { + inputType = 'checkbox'; + valueType = 'boolean'; + } else if (apiType === 'array') { + inputType = 'textarea'; + const itemType = param.items && param.items.type ? param.items.type.toLowerCase() : 'string'; + valueType = `array<${itemType}>`; + label += ' (Array)'; + } + + return { + name: param.name, + type: inputType, + valueType: valueType, + label: label, + authServices: param.authSources, + required: param.required || false, + // defaultValue: param.default, can't do this yet bc tool manifest doesn't have default + }; + }) + }; + + console.debug("Transformed toolInterfaceData:", toolInterfaceData); + + renderToolInterface(toolInterfaceData, toolDisplayArea); + } catch (error) { + if (error.name === 'AbortError') { + console.debug("Previous fetch was aborted, expected behavior."); + } else { + console.error(`Failed to load details for tool "${toolName}":`, error); + toolDisplayArea.innerHTML = `Failed to load details for ${toolName}. ${error.message}
`; + } + } +} \ No newline at end of file diff --git a/internal/server/static/js/mainContent.js b/internal/server/static/js/mainContent.js new file mode 100644 index 000000000000..e8542968bcb1 --- /dev/null +++ b/internal/server/static/js/mainContent.js @@ -0,0 +1,40 @@ +// Copyright 2025 Google LLC +// +// Licensed 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. + +/** + * Renders the main content area into the HTML. + * @param {string} containerId The ID of the DOM element to inject the content into. + * @param {string} idString The id of the item inside the main content area. + */ +function renderMainContent(containerId, idString) { + const mainContentContainer = document.getElementById(containerId); + if (!mainContentContainer) { + console.error(`Content container with ID "${containerId}" not found.`); + return; + } + + const idAttribute = idString ? `id="${idString}"` : ''; + const contentHTML = ` +This is the main content area. Click a tab on the left to navigate.
+${tool.name}
`; + descBox.className = 'tool-box tool-description'; + descBox.innerHTML = `${tool.description}
`; + + toolInfoContainer.className = 'tool-info'; + toolInfoContainer.appendChild(nameBox); + toolInfoContainer.appendChild(descBox); + gridContainer.appendChild(toolInfoContainer); + + const DISLCAIMER_INFO = "*Checked parameters are sent with the value from their text field. Empty fields will be sent as an empty string. To exclude a parameter, uncheck it." + const paramsContainer = document.createElement('div'); + const form = document.createElement('form'); + const paramsHeader = document.createElement('div'); + const disclaimerText = document.createElement('div'); + + paramsContainer.className = 'tool-params tool-box'; + paramsContainer.innerHTML = 'To obtain a Google OAuth ID token using a service account:
+gcloud auth list
+ gcloud auth print-identity-token --audiences=YOUR_CLIENT_ID_HERE
+ _token
+ {
+ "Content-Type": "application/json",
+ "my-google-auth_token": "YOUR_ID_TOKEN_HERE"
+}
+ This token is typically short-lived.
`; + +const AUTH_TOKEN_INSTRUCTIONS_STANDARD = ` +To obtain a Google OAuth ID token using a standard account:
+gcloud auth list
+ https://developers.google.com/oauthplayground
+ _token
+ {
+ "Content-Type": "application/json",
+ "my-google-auth_token": "YOUR_ID_TOKEN_HERE"
+}
+ This token is typically short-lived.
`; \ No newline at end of file diff --git a/internal/server/static/js/tools.js b/internal/server/static/js/tools.js new file mode 100644 index 000000000000..1928ec15228d --- /dev/null +++ b/internal/server/static/js/tools.js @@ -0,0 +1,32 @@ +// Copyright 2025 Google LLC +// +// Licensed 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 { loadTools } from "./loadTools.js"; + +/** + * These functions runs after the browser finishes loading and parsing HTML structure. + * This ensures that elements can be safely accessed. + */ +document.addEventListener('DOMContentLoaded', () => { + const toolDisplayArea = document.getElementById('tool-display-area'); + const secondaryPanelContent = document.getElementById('secondary-panel-content'); + const DEFAULT_TOOLSET = ""; // will return all toolsets + + if (!secondaryPanelContent || !toolDisplayArea) { + console.error('Required DOM elements not found.'); + return; + } + + loadTools(secondaryPanelContent, toolDisplayArea, DEFAULT_TOOLSET); +}); diff --git a/internal/server/static/js/toolsets.js b/internal/server/static/js/toolsets.js new file mode 100644 index 000000000000..f49afbf5f3ef --- /dev/null +++ b/internal/server/static/js/toolsets.js @@ -0,0 +1,51 @@ +// Copyright 2025 Google LLC +// +// Licensed 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 { loadTools } from "./loadTools.js"; + +document.addEventListener('DOMContentLoaded', () => { + const searchInput = document.getElementById('toolset-search-input'); + const searchButton = document.getElementById('toolset-search-button'); + const secondNavContent = document.getElementById('secondary-panel-content'); + const toolDisplayArea = document.getElementById('tool-display-area'); + + if (!searchInput || !searchButton || !secondNavContent || !toolDisplayArea) { + console.error('Required DOM elements not found.'); + return; + } + + // Event listener for search button click + searchButton.addEventListener('click', () => { + toolDisplayArea.innerHTML = ''; + const toolsetName = searchInput.value.trim(); + if (toolsetName) { + loadTools(secondNavContent, toolDisplayArea, toolsetName) + } else { + secondNavContent.innerHTML = 'Please enter a toolset name to see available tools.
To view the default toolset that consists of all tools, please select the "Tools" tab.
Please enter a toolset name to see available tools.
To view the default toolset that consists of all tools, please select the "Tools" tab.