Compare commits

...

13 Commits

Author SHA1 Message Date
duwenxin99
8c6f290481 remove manifest dups 2025-07-24 15:45:12 -04:00
duwenxin99
768cbdc07c refactor manifest 2025-07-24 15:45:12 -04:00
duwenxin99
6a7edab6e6 mcp manifest change 2025-07-24 15:45:12 -04:00
duwenxin99
71b751c400 update comments 2025-07-24 15:45:12 -04:00
duwenxin99
7815dce6a0 update docs table 2025-07-24 15:45:12 -04:00
duwenxin
6b4dd50a20 fix links 2025-07-24 15:45:12 -04:00
duwenxin
7e814804aa add docs 2025-07-24 15:45:12 -04:00
Author: Dennis Geurts
296a7a7aa8 feat: Add MongoDB insert Tools 2025-07-24 15:45:12 -04:00
duwenxin
b37aedd880 move verify code 2025-07-24 15:38:36 -04:00
duwenxin
3a19e46d63 dedup with http 2025-07-24 15:35:34 -04:00
Author: Dennis Geurts
2c6eb4f47f add commons 2025-07-24 15:34:25 -04:00
duwenxin
8a8649b789 remove test config 2025-07-24 15:33:18 -04:00
Author: Venkatesh Shanbhag
9159550089 feat: Add MongoDB Source 2025-07-24 15:33:05 -04:00
10 changed files with 686 additions and 4 deletions

View File

@@ -73,6 +73,8 @@ import (
_ "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbdeleteone"
_ "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbfind"
_ "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbfindone"
_ "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbinsertmany"
_ "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbinsertone"
_ "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbupdatemany"
_ "github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbupdateone"
_ "github.com/googleapis/genai-toolbox/internal/tools/mssql/mssqlexecutesql"

View File

@@ -0,0 +1,52 @@
---
title: "mongodb-insert-many"
type: docs
weight: 1
description: >
A "mongodb-insert-many" tool inserts multiple new documents into a MongoDB collection.
aliases:
- /resources/tools/mongodb-insert-many
---
## About
The `mongodb-insert-many` tool inserts **multiple new documents** into a specified MongoDB collection in a single bulk operation. This is highly efficient for adding large amounts of data at once.
This tool takes one required parameter named `data`. This `data` parameter must be a string containing a **JSON array of document objects**. Upon successful insertion, the tool returns a JSON array containing the unique `_id` of **each** new document that was created.
This tool is compatible with the following source kind:
* [`mongodb`](../../sources/mongodb.md)
---
## Example
Here is an example configuration for a tool that logs multiple events at once.
```yaml
tools:
log_batch_events:
kind: mongodb-insert-many
source: my-mongo-source
description: Inserts a batch of event logs into the database.
database: logging
collection: events
canonical: true
```
An LLM would call this tool by providing an array of documents as a JSON string in the `data` parameter, like this:
`tool_code: log_batch_events(data='[{"event": "login", "user": "user1"}, {"event": "click", "user": "user2"}, {"event": "logout", "user": "user1"}]')`
---
## Reference
| **field** | **type** | **required** | **description** |
|:------------|:---------|:-------------|:---------------------------------------------------------------------------------------------------|
| kind | string | true | Must be `mongodb-insert-many`. |
| source | string | true | The name of the `mongodb` source to use. |
| description | string | true | A description of the tool that is passed to the LLM. |
| database | string | true | The name of the MongoDB database containing the collection. |
| collection | string | true | The name of the MongoDB collection into which the documents will be inserted. |
| canonical | bool | true | Determines if the data string is parsed using MongoDB's Canonical or Relaxed Extended JSON format. |

View File

@@ -0,0 +1,48 @@
---
title: "mongodb-insert-one"
type: docs
weight: 1
description: >
A "mongodb-insert-one" tool inserts a single new document into a MongoDB collection.
aliases:
- /resources/tools/mongodb-insert-one
---
## About
The `mongodb-insert-one` tool inserts a **single new document** into a specified MongoDB collection.
This tool takes one required parameter named `data`, which must be a string containing the JSON object you want to insert. Upon successful insertion, the tool returns the unique `_id` of the newly created document.
This tool is compatible with the following source kind:
* [`mongodb`](../../sources/mongodb.md)
## Example
Here is an example configuration for a tool that adds a new user to a `users` collection.
```yaml
tools:
create_new_user:
kind: mongodb-insert-one
source: my-mongo-source
description: Creates a new user record in the database.
database: user_data
collection: users
canonical: false
```
An LLM would call this tool by providing the document as a JSON string in the `data` parameter, like this:
`tool_code: create_new_user(data='{"email": "new.user@example.com", "name": "Jane Doe", "status": "active"}')`
## Reference
| **field** | **type** | **required** | **description** |
|:------------|:---------|:-------------|:---------------------------------------------------------------------------------------------------|
| kind | string | true | Must be `mongodb-insert-one`. |
| source | string | true | The name of the `mongodb` source to use. |
| description | string | true | A description of the tool that is passed to the LLM. |
| database | string | true | The name of the MongoDB database containing the collection. |
| collection | string | true | The name of the MongoDB collection into which the document will be inserted. |
| canonical | bool | true | Determines if the data string is parsed using MongoDB's Canonical or Relaxed Extended JSON format. |

View File

@@ -104,7 +104,7 @@ func PopulateTemplateWithJSON(templateName, templateString string, data map[stri
return result.String(), nil
}
// Verify there are no duplicate parameter names
// CheckDuplicateParameters verify there are no duplicate parameter names
func CheckDuplicateParameters(ps Parameters) error {
seenNames := make(map[string]bool)
for _, p := range ps {

View File

@@ -0,0 +1,167 @@
// 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.
package mongodbinsertmany
import (
"context"
"errors"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
mongosrc "github.com/googleapis/genai-toolbox/internal/sources/mongodb"
"github.com/googleapis/genai-toolbox/internal/tools"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
const kind string = "mongodb-insert-many"
const paramDataKey = "data"
func init() {
if !tools.Register(kind, newConfig) {
panic(fmt.Sprintf("tool kind %q already registered", kind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
AuthRequired []string `yaml:"authRequired" validate:"required"`
Description string `yaml:"description" validate:"required"`
Database string `yaml:"database" validate:"required"`
Collection string `yaml:"collection" validate:"required"`
Canonical bool `yaml:"canonical" validate:"required"` //i want to force the user to choose
}
// validate interface
var _ tools.ToolConfig = Config{}
func (cfg Config) ToolConfigKind() string {
return kind
}
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
// verify source exists
rawS, ok := srcs[cfg.Source]
if !ok {
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
}
// verify the source is compatible
s, ok := rawS.(*mongosrc.Source)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind)
}
dataParam := tools.NewStringParameterWithRequired(paramDataKey, "the JSON payload to insert, should be a JSON array of documents", true)
allParameters := tools.Parameters{dataParam}
// Create Toolbox manifest
paramManifest := allParameters.Manifest()
if paramManifest == nil {
paramManifest = make([]tools.ParameterManifest, 0)
}
// Create MCP manifest
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
InputSchema: allParameters.McpManifest(),
}
// finish tool setup
return Tool{
Name: cfg.Name,
Kind: kind,
AuthRequired: cfg.AuthRequired,
Collection: cfg.Collection,
Canonical: cfg.Canonical,
PayloadParams: allParameters,
database: s.Client.Database(cfg.Database),
manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Description string `yaml:"description"`
Collection string `yaml:"collection"`
Canonical bool `yaml:"canonical" validation:"required"` //i want to force the user to choose
PayloadParams tools.Parameters
database *mongo.Database
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
if len(params) == 0 {
return nil, errors.New("no input found")
}
paramsMap := params.AsMap()
var jsonData, ok = paramsMap[paramDataKey].(string)
if !ok {
return nil, errors.New("no input found")
}
var data = []any{}
err := bson.UnmarshalExtJSON([]byte(jsonData), t.Canonical, &data)
if err != nil {
return nil, err
}
res, err := t.database.Collection(t.Collection).InsertMany(ctx, data, options.InsertMany())
if err != nil {
return nil, err
}
return res.InsertedIDs, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.PayloadParams, data, claims)
}
func (t Tool) Manifest() tools.Manifest {
return t.manifest
}
func (t Tool) McpManifest() tools.McpManifest {
return t.mcpManifest
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -0,0 +1,123 @@
// 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.
package mongodbinsertmany_test
import (
"strings"
"testing"
"github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbinsertmany"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
)
func TestParseFromYamlMongoQuery(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
example_tool:
kind: mongodb-insert-many
source: my-instance
description: some description
database: test_db
collection: test_coll
canonical: true
`,
want: server.ToolConfigs{
"example_tool": mongodbinsertmany.Config{
Name: "example_tool",
Kind: "mongodb-insert-many",
Source: "my-instance",
AuthRequired: []string{},
Database: "test_db",
Collection: "test_coll",
Description: "some description",
Canonical: true,
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}
func TestFailParseFromYamlMongoQuery(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
err string
}{
{
desc: "Invalid method",
in: `
tools:
example_tool:
kind: mongodb-insert-many
source: my-instance
description: some description
collection: test_coll
`,
err: `unable to parse tool "example_tool" as kind "mongodb-insert-many"`,
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if !strings.Contains(errStr, tc.err) {
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
}
})
}
}

View File

@@ -0,0 +1,166 @@
// 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.
package mongodbinsertone
import (
"context"
"errors"
"fmt"
"github.com/goccy/go-yaml"
"github.com/googleapis/genai-toolbox/internal/sources"
mongosrc "github.com/googleapis/genai-toolbox/internal/sources/mongodb"
"github.com/googleapis/genai-toolbox/internal/tools"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
const kind string = "mongodb-insert-one"
const dataParamsKey = "data"
func init() {
if !tools.Register(kind, newConfig) {
panic(fmt.Sprintf("tool kind %q already registered", kind))
}
}
func newConfig(ctx context.Context, name string, decoder *yaml.Decoder) (tools.ToolConfig, error) {
actual := Config{Name: name}
if err := decoder.DecodeContext(ctx, &actual); err != nil {
return nil, err
}
return actual, nil
}
type Config struct {
Name string `yaml:"name" validate:"required"`
Kind string `yaml:"kind" validate:"required"`
Source string `yaml:"source" validate:"required"`
AuthRequired []string `yaml:"authRequired" validate:"required"`
Description string `yaml:"description" validate:"required"`
Database string `yaml:"database" validate:"required"`
Collection string `yaml:"collection" validate:"required"`
Canonical bool `yaml:"canonical" validate:"required"` //i want to force the user to choose
}
// validate interface
var _ tools.ToolConfig = Config{}
func (cfg Config) ToolConfigKind() string {
return kind
}
func (cfg Config) Initialize(srcs map[string]sources.Source) (tools.Tool, error) {
// verify source exists
rawS, ok := srcs[cfg.Source]
if !ok {
return nil, fmt.Errorf("no source named %q configured", cfg.Source)
}
// verify the source is compatible
s, ok := rawS.(*mongosrc.Source)
if !ok {
return nil, fmt.Errorf("invalid source for %q tool: source kind must be `mongodb`", kind)
}
payloadParams := tools.NewStringParameterWithRequired(dataParamsKey, "the JSON payload to insert, should be a JSON object", true)
allParameters := tools.Parameters{payloadParams}
// Create Toolbox manifest
paramManifest := allParameters.Manifest()
if paramManifest == nil {
paramManifest = make([]tools.ParameterManifest, 0)
}
// Create MCP manifest
mcpManifest := tools.McpManifest{
Name: cfg.Name,
Description: cfg.Description,
InputSchema: allParameters.McpManifest(),
}
// finish tool setup
return Tool{
Name: cfg.Name,
Kind: kind,
AuthRequired: cfg.AuthRequired,
Collection: cfg.Collection,
Canonical: cfg.Canonical,
PayloadParams: allParameters,
database: s.Client.Database(cfg.Database),
manifest: tools.Manifest{Description: cfg.Description, Parameters: paramManifest, AuthRequired: cfg.AuthRequired},
mcpManifest: mcpManifest,
}, nil
}
// validate interface
var _ tools.Tool = Tool{}
type Tool struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
AuthRequired []string `yaml:"authRequired"`
Description string `yaml:"description"`
Collection string `yaml:"collection"`
Canonical bool `yaml:"canonical" validation:"required"`
PayloadParams tools.Parameters `yaml:"payloadParams" validate:"required"`
database *mongo.Database
manifest tools.Manifest
mcpManifest tools.McpManifest
}
func (t Tool) Invoke(ctx context.Context, params tools.ParamValues) (any, error) {
if len(params) == 0 {
return nil, errors.New("no input found")
}
// use the first, assume it's a string
var jsonData, ok = params[0].Value.(string)
if !ok {
return nil, errors.New("no input found")
}
var data any
err := bson.UnmarshalExtJSON([]byte(jsonData), t.Canonical, &data)
if err != nil {
return nil, err
}
res, err := t.database.Collection(t.Collection).InsertOne(ctx, data, options.InsertOne())
if err != nil {
return nil, err
}
return res.InsertedID, nil
}
func (t Tool) ParseParams(data map[string]any, claims map[string]map[string]any) (tools.ParamValues, error) {
return tools.ParseParams(t.PayloadParams, data, claims)
}
func (t Tool) Manifest() tools.Manifest {
return t.manifest
}
func (t Tool) McpManifest() tools.McpManifest {
return t.mcpManifest
}
func (t Tool) Authorized(verifiedAuthServices []string) bool {
return tools.IsAuthorized(t.AuthRequired, verifiedAuthServices)
}

View File

@@ -0,0 +1,124 @@
// 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.
package mongodbinsertone_test
import (
"strings"
"testing"
"github.com/googleapis/genai-toolbox/internal/tools/mongodb/mongodbinsertone"
yaml "github.com/goccy/go-yaml"
"github.com/google/go-cmp/cmp"
"github.com/googleapis/genai-toolbox/internal/server"
"github.com/googleapis/genai-toolbox/internal/testutils"
)
func TestParseFromYamlMongoQuery(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
want server.ToolConfigs
}{
{
desc: "basic example",
in: `
tools:
example_tool:
kind: mongodb-insert-one
source: my-instance
description: some description
database: test_db
collection: test_coll
canonical: true
`,
want: server.ToolConfigs{
"example_tool": mongodbinsertone.Config{
Name: "example_tool",
Kind: "mongodb-insert-one",
Source: "my-instance",
AuthRequired: []string{},
Database: "test_db",
Collection: "test_coll",
Canonical: true,
Description: "some description",
},
},
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err != nil {
t.Fatalf("unable to unmarshal: %s", err)
}
if diff := cmp.Diff(tc.want, got.Tools); diff != "" {
t.Fatalf("incorrect parse: diff %v", diff)
}
})
}
}
func TestFailParseFromYamlMongoQuery(t *testing.T) {
ctx, err := testutils.ContextWithNewLogger()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
tcs := []struct {
desc string
in string
err string
}{
{
desc: "Invalid method",
in: `
tools:
example_tool:
kind: mongodb-insert-one
source: my-instance
description: some description
collection: test_coll
canonical: true
`,
err: `unable to parse tool "example_tool" as kind "mongodb-insert-one"`,
},
}
for _, tc := range tcs {
t.Run(tc.desc, func(t *testing.T) {
got := struct {
Tools server.ToolConfigs `yaml:"tools"`
}{}
// Parse contents
err := yaml.UnmarshalContext(ctx, testutils.FormatYaml(tc.in), &got)
if err == nil {
t.Fatalf("expect parsing to fail")
}
errStr := err.Error()
if !strings.Contains(errStr, tc.err) {
t.Fatalf("unexpected error string: got %q, want substring %q", errStr, tc.err)
}
})
}
}

View File

@@ -55,7 +55,7 @@ type Config struct {
FilterParams tools.Parameters `yaml:"filterParams" validate:"required"`
UpdatePayload string `yaml:"updatePayload" validate:"required"`
UpdateParams tools.Parameters `yaml:"updateParams" validate:"required"`
Canonical bool `yaml:"canonical" validate:"required"` //i want to force the user to choose
Canonical bool `yaml:"canonical" validate:"required"`
Upsert bool `yaml:"upsert"`
}
@@ -135,7 +135,7 @@ type Tool struct {
UpdatePayload string `yaml:"updatePayload" validate:"required"`
UpdateParams tools.Parameters `yaml:"updateParams" validate:"required"`
AllParams tools.Parameters `yaml:"allParams"`
Canonical bool `yaml:"canonical" validation:"required"` //i want to force the user to choose
Canonical bool `yaml:"canonical" validation:"required"`
Upsert bool `yaml:"upsert"`
database *mongo.Database

View File

@@ -56,7 +56,7 @@ type Config struct {
UpdatePayload string `yaml:"updatePayload" validate:"required"`
UpdateParams tools.Parameters `yaml:"updateParams" validate:"required"`
Canonical bool `yaml:"canonical" validate:"required"` //i want to force the user to choose
Canonical bool `yaml:"canonical" validate:"required"`
Upsert bool `yaml:"upsert"`
}